diff --git a/cassandane/Cassandane/Address.pm b/cassandane/Cassandane/Address.pm index 407c5a529a..47fffee2e8 100644 --- a/cassandane/Cassandane/Address.pm +++ b/cassandane/Cassandane/Address.pm @@ -42,60 +42,53 @@ use strict; use warnings; use overload qw("") => \&as_string; -sub new -{ - my $class = shift; - my %params = @_; - my $self = { - name => undef, - localpart => undef, - domain => undef, - }; +sub new { + my $class = shift; + my %params = @_; + my $self = { + name => undef, + localpart => undef, + domain => undef, + }; - $self->{name} = $params{name} - if defined $params{name}; - $self->{localpart} = $params{localpart} - if defined $params{localpart}; - $self->{domain} = $params{domain} - if defined $params{domain}; + $self->{name} = $params{name} + if defined $params{name}; + $self->{localpart} = $params{localpart} + if defined $params{localpart}; + $self->{domain} = $params{domain} + if defined $params{domain}; - bless $self, $class; - return $self; + bless $self, $class; + return $self; } -sub name -{ - my ($self) = @_; - return $self->{name}; +sub name { + my ($self) = @_; + return $self->{name}; } -sub localpart -{ - my ($self) = @_; - return ($self->{localpart} || 'unknown-user'); +sub localpart { + my ($self) = @_; + return ($self->{localpart} || 'unknown-user'); } -sub domain -{ - my ($self) = @_; - return ($self->{domain} || 'unspecified-domain'); +sub domain { + my ($self) = @_; + return ($self->{domain} || 'unspecified-domain'); } -sub address -{ - my ($self) = @_; - return $self->localpart() . '@' . $self->domain(); +sub address { + my ($self) = @_; + return $self->localpart() . '@' . $self->domain(); } -sub as_string -{ - my ($self) = @_; - my $s = ''; - $s .= $self->{name} . ' ' - if defined $self->{name}; - $s .= '<' . $self->address() . '>'; - return $s; +sub as_string { + my ($self) = @_; + my $s = ''; + $s .= $self->{name} . ' ' + if defined $self->{name}; + $s .= '<' . $self->address() . '>'; + return $s; } - 1; diff --git a/cassandane/Cassandane/BuildInfo.pm b/cassandane/Cassandane/BuildInfo.pm index 2d3cc747dd..ff73bf14da 100644 --- a/cassandane/Cassandane/BuildInfo.pm +++ b/cassandane/Cassandane/BuildInfo.pm @@ -45,56 +45,54 @@ use Cassandane::Cassini; use Cassandane::Util::Log; sub new { - my $class = shift; - my %params = @_; - my $self = {}; + my $class = shift; + my %params = @_; + my $self = {}; - my $cassini = Cassandane::Cassini->instance(); + my $cassini = Cassandane::Cassini->instance(); - my $prefix = $cassini->val("cyrus default", 'prefix', '/usr/cyrus'); - $prefix = $params{cyrus_prefix} - if defined $params{cyrus_prefix}; + my $prefix = $cassini->val("cyrus default", 'prefix', '/usr/cyrus'); + $prefix = $params{cyrus_prefix} + if defined $params{cyrus_prefix}; - my $destdir = $cassini->val("cyrus default", 'destdir', ''); - $destdir = $params{cyrus_destdir} - if defined $params{cyrus_destdir}; + my $destdir = $cassini->val("cyrus default", 'destdir', ''); + $destdir = $params{cyrus_destdir} + if defined $params{cyrus_destdir}; - $self->{data} = _read_buildinfo($destdir, $prefix); + $self->{data} = _read_buildinfo($destdir, $prefix); - return bless $self, $class; + return bless $self, $class; } -sub _read_buildinfo -{ - my ($destdir, $prefix) = @_; - - my $cyr_buildinfo; - foreach my $bindir (qw(sbin cyrus/bin)) { - my $p = "$destdir$prefix/$bindir/cyr_buildinfo"; - if (-x $p) { - $cyr_buildinfo = $p; - last; - } - } +sub _read_buildinfo { + my ($destdir, $prefix) = @_; - if (not defined $cyr_buildinfo) { - xlog "Couldn't find cyr_buildinfo: ". - "don't know what features Cyrus supports"; - return; + my $cyr_buildinfo; + foreach my $bindir (qw(sbin cyrus/bin)) { + my $p = "$destdir$prefix/$bindir/cyr_buildinfo"; + if (-x $p) { + $cyr_buildinfo = $p; + last; } + } + + if (not defined $cyr_buildinfo) { + xlog "Couldn't find cyr_buildinfo: " + . "don't know what features Cyrus supports"; + return; + } - my $jsondata = qx($cyr_buildinfo); - return if not $jsondata; + my $jsondata = qx($cyr_buildinfo); + return if not $jsondata; - return JSON::decode_json($jsondata); + return JSON::decode_json($jsondata); } -sub get -{ - my ($self, $category, $key) = @_; +sub get { + my ($self, $category, $key) = @_; - return if not exists $self->{data}->{$category}->{$key}; - return $self->{data}->{$category}->{$key}; + return if not exists $self->{data}->{$category}->{$key}; + return $self->{data}->{$category}->{$key}; } 1; diff --git a/cassandane/Cassandane/Cassini.pm b/cassandane/Cassandane/Cassini.pm index b12c1dac32..b8bd9d88cd 100644 --- a/cassandane/Cassandane/Cassini.pm +++ b/cassandane/Cassandane/Cassini.pm @@ -51,205 +51,187 @@ use Cassandane::Util::Log; my $instance; sub homedir { - my ($uid) = @_; + my ($uid) = @_; - return undef if not $uid; + return undef if not $uid; - my @pw = getpwuid($uid); - return $pw[7]; # dir field + my @pw = getpwuid($uid); + return $pw[7]; # dir field } -sub new -{ - my ($class, %params) = @_; - - my $filename; - - if (defined $params{filename}) { - # explicitly requested filename: just use it - $filename = $params{filename}; - } - elsif (defined $ENV{CASSINI_FILENAME}) { - xlog "Using ini file from environment:" - . " filename=\"$ENV{CASSINI_FILENAME}\""; - $filename = $ENV{CASSINI_FILENAME}; +sub new { + my ($class, %params) = @_; + + my $filename; + + if (defined $params{filename}) { + # explicitly requested filename: just use it + $filename = $params{filename}; + } elsif (defined $ENV{CASSINI_FILENAME}) { + xlog "Using ini file from environment:" + . " filename=\"$ENV{CASSINI_FILENAME}\""; + $filename = $ENV{CASSINI_FILENAME}; + } else { + # check some likely places, in order + foreach + my $dir (q{.}, q{..}, homedir($>), homedir($<), homedir($ENV{SUDO_UID})) + { + next if not $dir; + + # might be called "cassandane.ini" + if (-e "$dir/cassandane.ini") { + $filename = "$dir/cassandane.ini"; + last; + } + + # might be called ".cassandane.ini" + if (-e "$dir/.cassandane.ini") { + $filename = "$dir/.cassandane.ini"; + last; + } } - else { - # check some likely places, in order - foreach my $dir (q{.}, - q{..}, - homedir($>), - homedir($<), - homedir($ENV{SUDO_UID}) - ) { - next if not $dir; - - # might be called "cassandane.ini" - if (-e "$dir/cassandane.ini") { - $filename = "$dir/cassandane.ini"; - last; - } - - # might be called ".cassandane.ini" - if (-e "$dir/.cassandane.ini") { - $filename = "$dir/.cassandane.ini"; - last; - } - } + } + + $filename = abs_path($filename) if $filename; + + my $inifile = new Config::IniFiles(); + if (-f $filename) { + xlog "Reading $filename" if get_verbose; + $inifile->SetFileName($filename); + if (!$inifile->ReadConfig()) { + # Config::IniFiles seems to include the filename in + # error messages, so we don't. However it tends to + # emit multiline-messages which confuses our logs. + set_verbose(1); + map { s/[\n\r]\s*/ /g; xlog $_; } @Config::IniFiles::errors; + die "Failed reading $filename"; } + } - $filename = abs_path($filename) if $filename; + my $self = { + filename => $filename, + inifile => $inifile + }; - my $inifile = new Config::IniFiles(); - if ( -f $filename) - { - xlog "Reading $filename" if get_verbose; - $inifile->SetFileName($filename); - if (!$inifile->ReadConfig()) - { - # Config::IniFiles seems to include the filename in - # error messages, so we don't. However it tends to - # emit multiline-messages which confuses our logs. - set_verbose(1); - map { s/[\n\r]\s*/ /g; xlog $_; } @Config::IniFiles::errors; - die "Failed reading $filename"; - } - } + bless $self, $class; - my $self = { - filename => $filename, - inifile => $inifile - }; + if ((not $filename or not -f $filename) + and not $self->bool_val('cassandane', 'allow_noinifile', 'no')) + { + die "couldn't find a cassandane.ini file"; + } - bless $self, $class; + # pre-validate cassandane.core_pattern early -- if the configured + # pattern is invalid the qr// will crash out + my $core_pattern = $self->val('cassandane', 'core_pattern'); + $core_pattern = qr{$core_pattern} if $core_pattern; - if ((not $filename or not -f $filename) - and not $self->bool_val('cassandane', 'allow_noinifile', 'no')) - { - die "couldn't find a cassandane.ini file"; - } + $instance = $self + unless defined $instance; + return $self; +} - # pre-validate cassandane.core_pattern early -- if the configured - # pattern is invalid the qr// will crash out - my $core_pattern = $self->val('cassandane', 'core_pattern'); - $core_pattern = qr{$core_pattern} if $core_pattern; +sub instance { + my ($class) = @_; - $instance = $self - unless defined $instance; - return $self; + if (!defined $instance) { + $instance = Cassandane::Cassini->new(); + die "Singleton broken in Cassini ctor!" + unless defined $instance; + } + return $instance; } -sub instance -{ - my ($class) = @_; - - if (!defined $instance) - { - $instance = Cassandane::Cassini->new(); - die "Singleton broken in Cassini ctor!" - unless defined $instance; - } - return $instance; +sub val { + my ($self, $section, $name, $default) = @_; + + # Allow overrides from specially-named environment variables. + # + # Examples: + # + # to override the "rootdir" option from the "[cassandane]" section, + # set: CASSINI_CASSANDANE_ROOTDIR=/some/different/value + # + # to override the "prefix" option from the "[cyrus default]" section, + # set: CASSINI_CYRUS_DEFAULT_PREFIX=/some/different/value + # + my $envname = "\UCASSINI $section $name\E"; + $envname =~ s{[^A-Z0-9]+}{_}g; + if (defined $ENV{$envname}) { + xlog "Using configuration from environment:" + . " \$$envname=\"$ENV{$envname}\""; + return $ENV{$envname}; + } + + # see the Config::IniFiles documentation for ->val() + return $self->{inifile}->val($section, $name, $default); } -sub val -{ - my ($self, $section, $name, $default) = @_; - - # Allow overrides from specially-named environment variables. - # - # Examples: - # - # to override the "rootdir" option from the "[cassandane]" section, - # set: CASSINI_CASSANDANE_ROOTDIR=/some/different/value - # - # to override the "prefix" option from the "[cyrus default]" section, - # set: CASSINI_CYRUS_DEFAULT_PREFIX=/some/different/value - # - my $envname = "\UCASSINI $section $name\E"; - $envname =~ s{[^A-Z0-9]+}{_}g; - if (defined $ENV{$envname}) { - xlog "Using configuration from environment:" - . " \$$envname=\"$ENV{$envname}\""; - return $ENV{$envname}; - } +sub bool_val { + # Args are: section, name, default + # returns a boolean 1 or 0 + my ($self, $section, $parameter, $default) = @_; + $default = 'no' if !defined $default; + my $v = $self->val($section, $parameter, $default); - # see the Config::IniFiles documentation for ->val() - return $self->{inifile}->val($section, $name, $default); -} + return 1 if ($v =~ m/^yes$/i); + return 1 if ($v =~ m/^true$/i); + return 1 if ($v =~ m/^on$/i); + return 1 if ($v =~ m/^1$/); + + return 0 if ($v =~ m/^no$/i); + return 0 if ($v =~ m/^false$/i); + return 0 if ($v =~ m/^off$/i); + return 0 if ($v =~ m/^0$/); -sub bool_val -{ - # Args are: section, name, default - # returns a boolean 1 or 0 - my ($self, $section, $parameter, $default) = @_; - $default = 'no' if !defined $default; - my $v = $self->val($section, $parameter, $default); - - return 1 if ($v =~ m/^yes$/i); - return 1 if ($v =~ m/^true$/i); - return 1 if ($v =~ m/^on$/i); - return 1 if ($v =~ m/^1$/); - - return 0 if ($v =~ m/^no$/i); - return 0 if ($v =~ m/^false$/i); - return 0 if ($v =~ m/^off$/i); - return 0 if ($v =~ m/^0$/); - - die "Bad boolean \"$v\""; + die "Bad boolean \"$v\""; } -sub override -{ - my ($self, $section, $parameter, $value) = @_; - my $ii = $self->{inifile}; +sub override { + my ($self, $section, $parameter, $value) = @_; + my $ii = $self->{inifile}; - if (defined $ii->val($section, $parameter)) - { - $ii->setval($section, $parameter, $value); - } - else - { - $ii->newval($section, $parameter, $value); - } + if (defined $ii->val($section, $parameter)) { + $ii->setval($section, $parameter, $value); + } else { + $ii->newval($section, $parameter, $value); + } } -sub get_section -{ - my ($self, $section) = @_; - my $inifile = $self->{inifile}; - my %params; - my $filename = $self->{filename} || 'inifile'; - if ($inifile->SectionExists($section)) { - foreach my $key ($inifile->Parameters($section)) { - # n.b. if there are multiple values for this section.key, - # val() in scalar context returns them joined by $/, which is - # nasty. So call it in list context instead, even though we - # don't support multiple values, and use the last one... - my @values = $inifile->val($section, $key); - - if (scalar @values > 1) { - # ... and whinge if there were multiple! - xlog "$filename: multiple values for $section.$key," - . " using last ($values[-1])"; - if (get_verbose()) { - xlog "$filename: $section.$key=<$_>" for @values; - } - } - - $params{$key} = $values[-1]; +sub get_section { + my ($self, $section) = @_; + my $inifile = $self->{inifile}; + my %params; + my $filename = $self->{filename} || 'inifile'; + if ($inifile->SectionExists($section)) { + foreach my $key ($inifile->Parameters($section)) { + # n.b. if there are multiple values for this section.key, + # val() in scalar context returns them joined by $/, which is + # nasty. So call it in list context instead, even though we + # don't support multiple values, and use the last one... + my @values = $inifile->val($section, $key); + + if (scalar @values > 1) { + # ... and whinge if there were multiple! + xlog "$filename: multiple values for $section.$key," + . " using last ($values[-1])"; + if (get_verbose()) { + xlog "$filename: $section.$key=<$_>" for @values; } + } + + $params{$key} = $values[-1]; } - return \%params; + } + return \%params; } -sub get_core_pattern -{ - my ($self) = @_; +sub get_core_pattern { + my ($self) = @_; - my $core_pattern = $self->val('cassandane', 'core_pattern', - '^core.*?(?:\.(\d+))?$'); - return qr{$core_pattern}; + my $core_pattern + = $self->val('cassandane', 'core_pattern', '^core.*?(?:\.(\d+))?$'); + return qr{$core_pattern}; } 1; diff --git a/cassandane/Cassandane/Config.pm b/cassandane/Cassandane/Config.pm index 86cc1a8fdd..d2e5f180c6 100644 --- a/cassandane/Cassandane/Config.pm +++ b/cassandane/Cassandane/Config.pm @@ -51,352 +51,321 @@ my $default; # XXX Once these repositories are merged, we'll be able to automate keeping # XXX this synchronised... my %bitfields = ( - 'calendar_component_set' => 'VEVENT VTODO VJOURNAL VFREEBUSY VAVAILABILITY VPOLL', - 'event_extra_params' => 'bodyStructure clientAddress diskUsed flagNames messageContent messageSize messages modseq service timestamp uidnext vnd.cmu.midset vnd.cmu.unseenMessages vnd.cmu.envelope vnd.cmu.sessionId vnd.cmu.mailboxACL vnd.cmu.mbtype vnd.cmu.davFilename vnd.cmu.davUid vnd.fastmail.clientId vnd.fastmail.sessionId vnd.fastmail.convExists vnd.fastmail.convUnseen vnd.fastmail.cid vnd.fastmail.counters vnd.fastmail.jmapEmail vnd.fastmail.jmapStates vnd.cmu.emailid vnd.cmu.threadid vnd.cmu.visibleUsers', - 'event_groups' => 'message quota flags access mailbox subscription calendar applepushservice jmap', - 'httpmodules' => 'admin caldav carddav cgi domainkey freebusy ischedule jmap prometheus rss tzdist webdav', - 'metapartition_files' => 'header index cache expunge squat annotations lock dav archivecache', - 'newsaddheaders' => 'to replyto', - 'sieve_extensions' => 'fileinto reject vacation vacation-seconds notify include envelope environment body relational regex subaddress copy date index imap4flags imapflags mailbox mboxmetadata servermetadata variables editheader extlists duplicate ihave fcc special-use redirect-dsn redirect-deliverby mailboxid vnd.cyrus.log x-cyrus-log vnd.cyrus.jmapquery x-cyrus-jmapquery vnd.cyrus.imip snooze vnd.cyrus.snooze x-cyrus-snooze vnd.cyrus.implicit_keep_target', + 'calendar_component_set' => + 'VEVENT VTODO VJOURNAL VFREEBUSY VAVAILABILITY VPOLL', + 'event_extra_params' => + 'bodyStructure clientAddress diskUsed flagNames messageContent messageSize messages modseq service timestamp uidnext vnd.cmu.midset vnd.cmu.unseenMessages vnd.cmu.envelope vnd.cmu.sessionId vnd.cmu.mailboxACL vnd.cmu.mbtype vnd.cmu.davFilename vnd.cmu.davUid vnd.fastmail.clientId vnd.fastmail.sessionId vnd.fastmail.convExists vnd.fastmail.convUnseen vnd.fastmail.cid vnd.fastmail.counters vnd.fastmail.jmapEmail vnd.fastmail.jmapStates vnd.cmu.emailid vnd.cmu.threadid vnd.cmu.visibleUsers', + 'event_groups' => + 'message quota flags access mailbox subscription calendar applepushservice jmap', + 'httpmodules' => + 'admin caldav carddav cgi domainkey freebusy ischedule jmap prometheus rss tzdist webdav', + 'metapartition_files' => + 'header index cache expunge squat annotations lock dav archivecache', + 'newsaddheaders' => 'to replyto', + 'sieve_extensions' => + 'fileinto reject vacation vacation-seconds notify include envelope environment body relational regex subaddress copy date index imap4flags imapflags mailbox mboxmetadata servermetadata variables editheader extlists duplicate ihave fcc special-use redirect-dsn redirect-deliverby mailboxid vnd.cyrus.log x-cyrus-log vnd.cyrus.jmapquery x-cyrus-jmapquery vnd.cyrus.imip snooze vnd.cyrus.snooze x-cyrus-snooze vnd.cyrus.implicit_keep_target', ); my $bitfields_fixed = 0; -sub init_bitfields -{ - if (!$bitfields_fixed) { - while (my ($key, $allvalues) = each %bitfields) { - $bitfields{$key} = {}; - foreach my $v (split /\s/, $allvalues) { - $bitfields{$key}->{$v} = 1; - } - } - $bitfields_fixed = 1; +sub init_bitfields { + if (!$bitfields_fixed) { + while (my ($key, $allvalues) = each %bitfields) { + $bitfields{$key} = {}; + foreach my $v (split /\s/, $allvalues) { + $bitfields{$key}->{$v} = 1; + } } + $bitfields_fixed = 1; + } } -sub new -{ - my $class = shift; +sub new { + my $class = shift; - init_bitfields(); + init_bitfields(); - my $self = { - parent => undef, - variables => {}, - params => {}, - }; + my $self = { + parent => undef, + variables => {}, + params => {}, + }; - bless $self, $class; + bless $self, $class; - # any arguments are initial params, process them properly - $self->set(@_); + # any arguments are initial params, process them properly + $self->set(@_); - return $self; + return $self; } -sub default -{ - if (!defined($default)) { - $default = Cassandane::Config->new( - admins => 'admin mailproxy mupduser repluser', - rfc3028_strict => 'no', - configdirectory => '@basedir@/conf', - syslog_prefix => '@name@', - sievedir => '@basedir@/conf/sieve', - defaultpartition => 'default', - defaultdomain => 'defdomain', - 'partition-default' => '@basedir@/data', - sasl_mech_list => 'PLAIN LOGIN', - allowplaintext => 'yes', - # for debugging - see cassandane.ini.example - debug_command => '@prefix@/utils/gdbtramp %s %d', - # everyone should be running this - improved_mboxlist_sort => 'yes', - # default changed, we want to be explicit about it - unixhierarchysep => 'no', - # let's hear all about it - auditlog => 'yes', - chatty => 'yes', - debug => 'yes', - httpprettytelemetry => 'yes', - ); - my $defs = Cassandane::Cassini->instance()->get_section('config'); - $default->set(%$defs); - } - - return $default; +sub default { + if (!defined($default)) { + $default = Cassandane::Config->new( + admins => 'admin mailproxy mupduser repluser', + rfc3028_strict => 'no', + configdirectory => '@basedir@/conf', + syslog_prefix => '@name@', + sievedir => '@basedir@/conf/sieve', + defaultpartition => 'default', + defaultdomain => 'defdomain', + 'partition-default' => '@basedir@/data', + sasl_mech_list => 'PLAIN LOGIN', + allowplaintext => 'yes', + # for debugging - see cassandane.ini.example + debug_command => '@prefix@/utils/gdbtramp %s %d', + # everyone should be running this + improved_mboxlist_sort => 'yes', + # default changed, we want to be explicit about it + unixhierarchysep => 'no', + # let's hear all about it + auditlog => 'yes', + chatty => 'yes', + debug => 'yes', + httpprettytelemetry => 'yes', + ); + my $defs = Cassandane::Cassini->instance()->get_section('config'); + $default->set(%$defs); + } + + return $default; } -sub clone -{ - my ($self) = @_; +sub clone { + my ($self) = @_; - my $child = Cassandane::Config->new(); - $child->{parent} = $self; - return $child; + my $child = Cassandane::Config->new(); + $child->{parent} = $self; + return $child; } -sub _explode_bit_string -{ - my ($s) = @_; - return split / /, $s; +sub _explode_bit_string { + my ($s) = @_; + return split / /, $s; } -sub set -{ - my ($self, %nv) = @_; - while (my ($n, $v) = each %nv) - { - if (exists $bitfields{$n}) { - # it's a bitfield, set exactly what's given (clearing others) - if (ref $v eq 'ARRAY') { - $self->clear_all_bits($n); - $self->set_bits($n, @{$v}); - } - elsif (ref $v eq q{}) { - $self->clear_all_bits($n); - $self->set_bits($n, _explode_bit_string($v)); - } - else { - die "don't know what to do with value '$v'"; - } - } - else { - $self->{params}->{$n} = $v; - } +sub set { + my ($self, %nv) = @_; + while (my ($n, $v) = each %nv) { + if (exists $bitfields{$n}) { + # it's a bitfield, set exactly what's given (clearing others) + if (ref $v eq 'ARRAY') { + $self->clear_all_bits($n); + $self->set_bits($n, @{$v}); + } elsif (ref $v eq q{}) { + $self->clear_all_bits($n); + $self->set_bits($n, _explode_bit_string($v)); + } else { + die "don't know what to do with value '$v'"; + } + } else { + $self->{params}->{$n} = $v; } + } } -sub set_bits -{ - my ($self, $name, @bits) = @_; +sub set_bits { + my ($self, $name, @bits) = @_; - die "$name is not a bitfield option" if not exists $bitfields{$name}; + die "$name is not a bitfield option" if not exists $bitfields{$name}; - # explode space-delimited list as only bit - if (scalar @bits == 1 && $bits[0] =~ m/ /) { - @bits = _explode_bit_string($bits[0]); - } + # explode space-delimited list as only bit + if (scalar @bits == 1 && $bits[0] =~ m/ /) { + @bits = _explode_bit_string($bits[0]); + } - foreach my $bit (@bits) { - die "$bit is not a $name value" - if not exists $bitfields{$name}->{$bit}; + foreach my $bit (@bits) { + die "$bit is not a $name value" + if not exists $bitfields{$name}->{$bit}; - $self->{params}->{$name}->{$bit} = 1; - } + $self->{params}->{$name}->{$bit} = 1; + } } -sub clear_bits -{ - my ($self, $name, @bits) = @_; +sub clear_bits { + my ($self, $name, @bits) = @_; - die "$name is not a bitfield option" if not exists $bitfields{$name}; + die "$name is not a bitfield option" if not exists $bitfields{$name}; - # explode space-delimited list as only bit - if (scalar @bits == 1 && $bits[0] =~ m/ /) { - @bits = _explode_bit_string($bits[0]); - } + # explode space-delimited list as only bit + if (scalar @bits == 1 && $bits[0] =~ m/ /) { + @bits = _explode_bit_string($bits[0]); + } - foreach my $bit (@bits) { - die "$bit is not a $name value" - if not exists $bitfields{$name}->{$bit}; + foreach my $bit (@bits) { + die "$bit is not a $name value" + if not exists $bitfields{$name}->{$bit}; - $self->{params}->{$name}->{$bit} = 0; - } + $self->{params}->{$name}->{$bit} = 0; + } } -sub clear_all_bits -{ - my ($self, $name) = @_; +sub clear_all_bits { + my ($self, $name) = @_; - die "$name is not a bitfield option" if not exists $bitfields{$name}; + die "$name is not a bitfield option" if not exists $bitfields{$name}; - $self->{params}->{$name}->{$_} = 0 for keys %{$bitfields{$name}}; + $self->{params}->{$name}->{$_} = 0 for keys %{ $bitfields{$name} }; } -sub get -{ - my ($self, $n) = @_; - if (exists $bitfields{$n}) { - my %bits; - while (defined $self) { - if (exists $self->{params}->{$n}) { - while (my ($bit, $val) = each %{$self->{params}->{$n}}) { - $bits{$bit} //= $val; - } - } - $self = $self->{parent}; +sub get { + my ($self, $n) = @_; + if (exists $bitfields{$n}) { + my %bits; + while (defined $self) { + if (exists $self->{params}->{$n}) { + while (my ($bit, $val) = each %{ $self->{params}->{$n} }) { + $bits{$bit} //= $val; } - my @v = grep { $bits{$_} } sort keys %bits; - return wantarray ? @v : join q{ }, @v; + } + $self = $self->{parent}; } - else { - while (defined $self) - { - return $self->{params}->{$n} - if exists $self->{params}->{$n}; - $self = $self->{parent}; - } + my @v = grep { $bits{$_} } sort keys %bits; + return wantarray ? @v : join q{ }, @v; + } else { + while (defined $self) { + return $self->{params}->{$n} + if exists $self->{params}->{$n}; + $self = $self->{parent}; } - return undef; + } + return undef; } -sub get_bit -{ - my ($self, $name, $bit) = @_; +sub get_bit { + my ($self, $name, $bit) = @_; - die "$name is not a bitfield option" if not exists $bitfields{$name}; - die "$bit is not a $name value" if not exists $bitfields{$name}->{$bit}; + die "$name is not a bitfield option" if not exists $bitfields{$name}; + die "$bit is not a $name value" if not exists $bitfields{$name}->{$bit}; - while (defined $self) { - return $self->{params}->{$name}->{$bit} - if exists $self->{params}->{$name}->{$bit}; - $self = $self->{parent}; - } - return undef; + while (defined $self) { + return $self->{params}->{$name}->{$bit} + if exists $self->{params}->{$name}->{$bit}; + $self = $self->{parent}; + } + return undef; } -sub get_bool -{ - my ($self, $n, $def) = @_; +sub get_bool { + my ($self, $n, $def) = @_; - die "bitfield $n cannot be boolean" if exists $bitfields{$n}; + die "bitfield $n cannot be boolean" if exists $bitfields{$n}; - $def = 'no' if !defined $def; - my $v = $self->get($n); - $v = $def if !defined $v; + $def = 'no' if !defined $def; + my $v = $self->get($n); + $v = $def if !defined $v; - return 1 if ($v =~ m/^yes$/i); - return 1 if ($v =~ m/^true$/i); - return 1 if ($v =~ m/^on$/i); - return 1 if ($v =~ m/^1$/); + return 1 if ($v =~ m/^yes$/i); + return 1 if ($v =~ m/^true$/i); + return 1 if ($v =~ m/^on$/i); + return 1 if ($v =~ m/^1$/); - return 0 if ($v =~ m/^no$/i); - return 0 if ($v =~ m/^false$/i); - return 0 if ($v =~ m/^off$/i); - return 0 if ($v =~ m/^0$/); + return 0 if ($v =~ m/^no$/i); + return 0 if ($v =~ m/^false$/i); + return 0 if ($v =~ m/^off$/i); + return 0 if ($v =~ m/^0$/); - die "Bad boolean \"$v\""; + die "Bad boolean \"$v\""; } -sub set_variables -{ - my ($self, %nv) = @_; - while (my ($n, $v) = each %nv) - { - $self->{variables}->{$n} = $v; - } +sub set_variables { + my ($self, %nv) = @_; + while (my ($n, $v) = each %nv) { + $self->{variables}->{$n} = $v; + } } -sub _get_variable -{ - my ($self, $n) = @_; - $n =~ s/@//g; - while (defined $self) - { - return $self->{variables}->{$n} - if exists $self->{variables}->{$n}; - $self = $self->{parent}; - } - die "Variable $n not defined"; +sub _get_variable { + my ($self, $n) = @_; + $n =~ s/@//g; + while (defined $self) { + return $self->{variables}->{$n} + if exists $self->{variables}->{$n}; + $self = $self->{parent}; + } + die "Variable $n not defined"; } -sub substitute -{ - my ($self, $s) = @_; - - return unless defined $s; - my $r = ''; - while (defined $s) - { - my ($pre, $ref, $post) = ($s =~ m/(.*)(@[a-z]+@)(.*)/); - if (defined $ref) - { - $r .= $pre . $self->_get_variable($ref); - $s = $post; - } - else - { - $r .= $s; - last; - } +sub substitute { + my ($self, $s) = @_; + + return unless defined $s; + my $r = ''; + while (defined $s) { + my ($pre, $ref, $post) = ($s =~ m/(.*)(@[a-z]+@)(.*)/); + if (defined $ref) { + $r .= $pre . $self->_get_variable($ref); + $s = $post; + } else { + $r .= $s; + last; } - return $r; + } + return $r; } -sub _flatten -{ - my ($self) = @_; - my %nv; - for (my $conf = $self ; defined $conf ; $conf = $conf->{parent}) - { - foreach my $n (keys %{$conf->{params}}) - { - if (exists $bitfields{$n}) { - # no variable substitution on bitfields - while (my ($bit, $val) = each %{$conf->{params}->{$n}}) { - $nv{$n}->{$bit} //= $val; - } - } - else { - $nv{$n} = $self->substitute($conf->{params}->{$n}) - unless exists $nv{$n}; - } +sub _flatten { + my ($self) = @_; + my %nv; + for (my $conf = $self; defined $conf; $conf = $conf->{parent}) { + foreach my $n (keys %{ $conf->{params} }) { + if (exists $bitfields{$n}) { + # no variable substitution on bitfields + while (my ($bit, $val) = each %{ $conf->{params}->{$n} }) { + $nv{$n}->{$bit} //= $val; } + } else { + $nv{$n} = $self->substitute($conf->{params}->{$n}) + unless exists $nv{$n}; + } } - return \%nv; + } + return \%nv; } -sub generate -{ - my ($self, $filename) = @_; - my $nv = $self->_flatten(); - - open CONF,'>',$filename - or die "Cannot open $filename for writing: $!"; - while (my ($n, $v) = each %$nv) - { - next unless defined $v; - if (exists $bitfields{$n}) { - my @bits = grep { $nv->{$n}->{$_} } sort keys %{$nv->{$n}}; - print CONF "$n: " . join(q{ }, @bits) . "\n"; - } - else { - print CONF "$n: $v\n"; - } +sub generate { + my ($self, $filename) = @_; + my $nv = $self->_flatten(); + + open CONF, '>', $filename + or die "Cannot open $filename for writing: $!"; + while (my ($n, $v) = each %$nv) { + next unless defined $v; + if (exists $bitfields{$n}) { + my @bits = grep { $nv->{$n}->{$_} } sort keys %{ $nv->{$n} }; + print CONF "$n: " . join(q{ }, @bits) . "\n"; + } else { + print CONF "$n: $v\n"; } - close CONF; + } + close CONF; } -sub is_bitfield -{ - my ($name) = @_; +sub is_bitfield { + my ($name) = @_; - init_bitfields(); + init_bitfields(); - return defined $bitfields{$name}; + return defined $bitfields{$name}; } -sub is_bitfield_bit -{ - my ($name, $value) = @_; +sub is_bitfield_bit { + my ($name, $value) = @_; - init_bitfields(); + init_bitfields(); - die "$name is not a bitfield option" if not exists $bitfields{$name}; + die "$name is not a bitfield option" if not exists $bitfields{$name}; - return defined $bitfields{$name}->{$value}; + return defined $bitfields{$name}->{$value}; } -sub get_bitfield_bits -{ - my ($name) = @_; +sub get_bitfield_bits { + my ($name) = @_; - init_bitfields(); + init_bitfields(); - die "$name is not a bitfield option" if not exists $bitfields{$name}; + die "$name is not a bitfield option" if not exists $bitfields{$name}; - return sort keys %{$bitfields{$name}}; + return sort keys %{ $bitfields{$name} }; } 1; diff --git a/cassandane/Cassandane/Cyrus/ACL.pm b/cassandane/Cassandane/Cyrus/ACL.pm index 3c0f096026..ad82602001 100644 --- a/cassandane/Cassandane/Cyrus/ACL.pm +++ b/cassandane/Cassandane/Cyrus/ACL.pm @@ -50,318 +50,298 @@ use Cassandane::Generator; use Cassandane::MessageStoreFactory; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({adminstore => 1}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; +sub set_up { + my ($self) = @_; - $self->SUPER::set_up(); + $self->SUPER::set_up(); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # let's create ourselves an archive user - # sub folders of another user - one is subscribable - $self->{instance}->create_user("archive", - subdirs => [ 'cassandane', ['cassandane', 'sent'] ]); - $admintalk->setacl("user.archive.cassandane.sent", "cassandane", "lrswp"); + # let's create ourselves an archive user + # sub folders of another user - one is subscribable + $self->{instance}->create_user("archive", + subdirs => [ 'cassandane', [ 'cassandane', 'sent' ] ]); + $admintalk->setacl("user.archive.cassandane.sent", "cassandane", "lrswp"); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test regular delete # sub test_delete - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $self->{adminstore}->set_folder('user.archive.cassandane.sent'); - $self->make_message("Message A", store => $self->{adminstore}); + $self->{adminstore}->set_folder('user.archive.cassandane.sent'); + $self->make_message("Message A", store => $self->{adminstore}); - $self->{store}->set_folder('user.archive.cassandane.sent'); - $self->{store}->_select(); + $self->{store}->set_folder('user.archive.cassandane.sent'); + $self->{store}->_select(); - my $res = $talk->store('1', '+flags', '(\\deleted)'); - $self->assert_null($res); # means it failed - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/permission denied/i); + my $res = $talk->store('1', '+flags', '(\\deleted)'); + $self->assert_null($res); # means it failed + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/permission denied/i); } sub test_many_users - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); - $self->make_message("Message A"); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); + $self->make_message("Message A"); - $talk->create("INBOX.multi"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create("INBOX.multi"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - for (1..100) { - $admintalk->setacl("user.cassandane.multi", "test$_", "lrswipcd"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - } + for (1 .. 100) { + $admintalk->setacl("user.cassandane.multi", "test$_", "lrswipcd"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } - my $res = $talk->select("INBOX.multi"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + my $res = $talk->select("INBOX.multi"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); } sub test_move - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $self->{adminstore}->set_folder('user.archive.cassandane.sent'); - $self->make_message("Message A", store => $self->{adminstore}); + $self->{adminstore}->set_folder('user.archive.cassandane.sent'); + $self->make_message("Message A", store => $self->{adminstore}); - $self->{store}->set_folder('user.archive.cassandane.sent'); - $self->{store}->_select(); + $self->{store}->set_folder('user.archive.cassandane.sent'); + $self->{store}->_select(); - my $res = $talk->move('1', "INBOX"); - $self->assert_null($res); # means it failed - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/permission denied/i); + my $res = $talk->move('1', "INBOX"); + $self->assert_null($res); # means it failed + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/permission denied/i); } sub test_setacl_badacl - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.badacl"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create("INBOX.badacl"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.badacl", "foo", "ylrswipcd"); - $self->assert_str_equals('bad', $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.badacl", "foo", "ylrswipcd"); + $self->assert_str_equals('bad', $admintalk->get_last_completion_response()); } sub test_setacl_addacl - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.addacl"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create("INBOX.addacl"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.addacl", "foo", "lrswipkxtecdn"); - $admintalk->setacl("user.cassandane.addacl", "foo", "+a"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.addacl", "foo", "lrswipkxtecdn"); + $admintalk->setacl("user.cassandane.addacl", "foo", "+a"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } sub test_setacl_rmacl - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.rmacl"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create("INBOX.rmacl"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.rmacl", "foo", "lrswipkxtecdan"); - $admintalk->setacl("user.cassandane.rmacl", "foo", "-a"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.rmacl", "foo", "lrswipkxtecdan"); + $admintalk->setacl("user.cassandane.rmacl", "foo", "-a"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } sub test_setacl_addacl_exists - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.exists"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create("INBOX.exists"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.exists", "foo", "lrswipkxtecdan"); - $admintalk->setacl("user.cassandane.exists", "foo", "+a"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.exists", "foo", "lrswipkxtecdan"); + $admintalk->setacl("user.cassandane.exists", "foo", "+a"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } sub test_setacl_rmacl_unexists - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.rmunexists"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create("INBOX.rmunexists"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.rmunexists", "foo", "lrswipkxtecdn"); - $admintalk->setacl("user.cassandane.rmunexists", "foo", "-a"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.rmunexists", "foo", "lrswipkxtecdn"); + $admintalk->setacl("user.cassandane.rmunexists", "foo", "-a"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } -sub test_reconstruct -{ - my ($self) = @_; +sub test_reconstruct { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - my $oldacl = $admintalk->getacl("user.archive.cassandane.sent"); + my $oldacl = $admintalk->getacl("user.archive.cassandane.sent"); - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); + $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); - my $newacl = $admintalk->getacl("user.archive.cassandane.sent"); - $self->assert_deep_equals($oldacl, $newacl); + my $newacl = $admintalk->getacl("user.archive.cassandane.sent"); + $self->assert_deep_equals($oldacl, $newacl); } -sub test_setacl_emptyid -{ - my ($self) = @_; +sub test_setacl_emptyid { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.emptyid"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create("INBOX.emptyid"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - # send an empty identifier for SETACL - $admintalk->setacl("user.cassandane.emptyid", "", "lrswipcd"); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + # send an empty identifier for SETACL + $admintalk->setacl("user.cassandane.emptyid", "", "lrswipcd"); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); } sub test_setacl_badrights - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.badrights"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create("INBOX.badrights"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - my $origacl = $admintalk->getacl("user.cassandane.badrights"); + my $origacl = $admintalk->getacl("user.cassandane.badrights"); - $admintalk->setacl("user.cassandane.badrights", "cassandane", "b"); - $self->assert_str_equals('bad', $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.badrights", "cassandane", "b"); + $self->assert_str_equals('bad', $admintalk->get_last_completion_response()); - my $newacl = $admintalk->getacl("user.cassandane.badrights"); - $self->assert_deep_equals($origacl, $newacl); + my $newacl = $admintalk->getacl("user.cassandane.badrights"); + $self->assert_deep_equals($origacl, $newacl); } #Magic word virtdomains in name sets config virtdomains = userid sub test_virtdomains_noinherit1 - :NoAltNamespace :CrossDomains -{ - my ($self) = @_; - - my $defaultdomain = $self->{instance}->{config}->get('defaultdomain') - // 'internal'; - my $cass_defdom = "cassandane\@$defaultdomain"; - my $cass_dom = 'cassandane@uhoh.org'; - - # get stores that authenticate as each username - $self->{instance}->create_user($cass_dom); - my $imap = $self->{instance}->get_service('imap'); - my $defdom_store = $imap->create_store(username => $cass_defdom); - my $dom_store = $imap->create_store(username => $cass_dom); - - # set up a target folder and some permissions - $self->{instance}->create_user('banana'); - my $folder = 'user.banana'; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->setacl($folder, cassandane => 'lrswip'); - $admintalk->setacl($folder, $cass_dom => 'lrs'); - $admintalk->getacl($folder); - - # make the stores all look at the same folder - $self->{store}->set_folder($folder); - $defdom_store->set_folder($folder); - $dom_store->set_folder($folder); - - # 'cassandane' should be able to make a message - $self->make_message("message from cassandane", store => $self->{store}); - - # 'cassandane@{defaultdomain}' should be able to make a message - $self->make_message("message from $cass_defdom", store => $defdom_store); - - # 'cassandane@somedomain' should NOT be able to make a message - eval { - $self->make_message("message from $cass_dom", store => $dom_store); - }; - my $err = q{} . $@; - $self->assert_matches(qr/permission denied/i, $err); + : NoAltNamespace : CrossDomains { + my ($self) = @_; + + my $defaultdomain = $self->{instance}->{config}->get('defaultdomain') + // 'internal'; + my $cass_defdom = "cassandane\@$defaultdomain"; + my $cass_dom = 'cassandane@uhoh.org'; + + # get stores that authenticate as each username + $self->{instance}->create_user($cass_dom); + my $imap = $self->{instance}->get_service('imap'); + my $defdom_store = $imap->create_store(username => $cass_defdom); + my $dom_store = $imap->create_store(username => $cass_dom); + + # set up a target folder and some permissions + $self->{instance}->create_user('banana'); + my $folder = 'user.banana'; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->setacl($folder, cassandane => 'lrswip'); + $admintalk->setacl($folder, $cass_dom => 'lrs'); + $admintalk->getacl($folder); + + # make the stores all look at the same folder + $self->{store}->set_folder($folder); + $defdom_store->set_folder($folder); + $dom_store->set_folder($folder); + + # 'cassandane' should be able to make a message + $self->make_message("message from cassandane", store => $self->{store}); + + # 'cassandane@{defaultdomain}' should be able to make a message + $self->make_message("message from $cass_defdom", store => $defdom_store); + + # 'cassandane@somedomain' should NOT be able to make a message + eval { $self->make_message("message from $cass_dom", store => $dom_store); }; + my $err = q{} . $@; + $self->assert_matches(qr/permission denied/i, $err); } #Magic word virtdomains in name sets config virtdomains = userid sub test_virtdomains_noinherit2 - :NoAltNamespace :CrossDomains -{ - my ($self) = @_; - - my $defaultdomain = $self->{instance}->{config}->get('defaultdomain') - // 'internal'; - my $cass_defdom = "cassandane\@$defaultdomain"; - my $cass_dom = 'cassandane@uhoh.org'; - - # get stores that authenticate as each username - $self->{instance}->create_user($cass_dom); - my $imap = $self->{instance}->get_service('imap'); - my $defdom_store = $imap->create_store(username => $cass_defdom); - my $dom_store = $imap->create_store(username => $cass_dom); - - # set up a target folder and some permissions - $self->{instance}->create_user('banana'); - my $folder = 'user.banana'; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->setacl($folder, cassandane => 'lrs'); - $admintalk->setacl($folder, $cass_dom => 'lrswip'); - $admintalk->getacl($folder); - - # make the stores all look at the same folder - $self->{store}->set_folder($folder); - $defdom_store->set_folder($folder); - $dom_store->set_folder($folder); - - # 'cassandane' should NOT be able to make a message - eval { - $self->make_message("message from cassandane", - store => $self->{store}); - }; - my $err = q{} . $@; - $self->assert_matches(qr/permission denied/i, $err); - - # 'cassandane@{defaultdomain}' should NOT be able to make a message - eval { - $self->make_message("message from $cass_defdom", - store => $defdom_store); - }; - $err = q{} . $@; - $self->assert_matches(qr/permission denied/i, $err); - - # 'cassandane@somedomain' should be able to make a message - $self->make_message("message from $cass_dom", store => $dom_store); + : NoAltNamespace : CrossDomains { + my ($self) = @_; + + my $defaultdomain = $self->{instance}->{config}->get('defaultdomain') + // 'internal'; + my $cass_defdom = "cassandane\@$defaultdomain"; + my $cass_dom = 'cassandane@uhoh.org'; + + # get stores that authenticate as each username + $self->{instance}->create_user($cass_dom); + my $imap = $self->{instance}->get_service('imap'); + my $defdom_store = $imap->create_store(username => $cass_defdom); + my $dom_store = $imap->create_store(username => $cass_dom); + + # set up a target folder and some permissions + $self->{instance}->create_user('banana'); + my $folder = 'user.banana'; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->setacl($folder, cassandane => 'lrs'); + $admintalk->setacl($folder, $cass_dom => 'lrswip'); + $admintalk->getacl($folder); + + # make the stores all look at the same folder + $self->{store}->set_folder($folder); + $defdom_store->set_folder($folder); + $dom_store->set_folder($folder); + + # 'cassandane' should NOT be able to make a message + eval { + $self->make_message("message from cassandane", store => $self->{store}); + }; + my $err = q{} . $@; + $self->assert_matches(qr/permission denied/i, $err); + + # 'cassandane@{defaultdomain}' should NOT be able to make a message + eval { + $self->make_message("message from $cass_defdom", store => $defdom_store); + }; + $err = q{} . $@; + $self->assert_matches(qr/permission denied/i, $err); + + # 'cassandane@somedomain' should be able to make a message + $self->make_message("message from $cass_dom", store => $dom_store); } # see also LDAP.pm for groupid tests diff --git a/cassandane/Cassandane/Cyrus/Admin.pm b/cassandane/Cassandane/Cyrus/Admin.pm index 9d934edc54..666a4a440f 100644 --- a/cassandane/Cassandane/Cyrus/Admin.pm +++ b/cassandane/Cassandane/Cyrus/Admin.pm @@ -47,113 +47,109 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -sub new -{ - my $class = shift; - my $config = Cassandane::Config::default()->clone(); - $config->set( imap_admins => 'admin imapadmin' ); - return $class->SUPER::new({ config => $config, adminstore => 1 }, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config::default()->clone(); + $config->set(imap_admins => 'admin imapadmin'); + return $class->SUPER::new({ config => $config, adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - my $imap = $self->{instance}->get_service('imap'); - $self->{imapadminstore} = $imap->create_store(username => 'imapadmin'); + my $imap = $self->{instance}->get_service('imap'); + $self->{imapadminstore} = $imap->create_store(username => 'imapadmin'); } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - $self->{imapadminstore}->disconnect(); - delete $self->{imapadminstore}; + $self->{imapadminstore}->disconnect(); + delete $self->{imapadminstore}; - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } -sub test_imap_admins -{ - # test whether the imap_admins setting works correctly - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - my $imapadmintalk = $self->{imapadminstore}->get_client(); - my $talk = $self->{store}->get_client(); - - # we should be able to reconstruct as 'admin', because although - # imap_admins overrides admins, we have 'admin' in imap_admins too - # (it MUST be there for Cassandane itself to work) - my $res = $admintalk->_imap_cmd("reconstruct" , 0, {}, "user.cassandane"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - # we should not be able to reconstruct as 'cassandane', because - # reconstruct is an admin-only command - $res = $talk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/permission denied/i, $talk->get_last_error()); - - # we should be able to reconstruct as 'imapadmin', because this user - # is in imap_admins - $res = $imapadmintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); - $self->assert_str_equals('ok', $imapadmintalk->get_last_completion_response()); +sub test_imap_admins { + # test whether the imap_admins setting works correctly + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + my $imapadmintalk = $self->{imapadminstore}->get_client(); + my $talk = $self->{store}->get_client(); + + # we should be able to reconstruct as 'admin', because although + # imap_admins overrides admins, we have 'admin' in imap_admins too + # (it MUST be there for Cassandane itself to work) + my $res = $admintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # we should not be able to reconstruct as 'cassandane', because + # reconstruct is an admin-only command + $res = $talk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/permission denied/i, $talk->get_last_error()); + + # we should be able to reconstruct as 'imapadmin', because this user + # is in imap_admins + $res = $imapadmintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); + $self->assert_str_equals('ok', + $imapadmintalk->get_last_completion_response()); } #Magic word virtdomains in name sets config virtdomains = userid -sub test_imap_admins_virtdomains -{ - # test whether the imap_admins setting works correctly under virtdomains - my ($self) = @_; - - my $domainadmin = 'admin@uhoh.org'; - my $defaultdomain = $self->{instance}->{config}->get('defaultdomain') - // 'internal'; - my $defdomadmin = "admin\@$defaultdomain"; - - $self->{instance}->create_user($domainadmin); - my $imap = $self->{instance}->get_service('imap'); - my $domainadminstore = $imap->create_store(username => $domainadmin); - my $defdomadminstore = $imap->create_store(username => $defdomadmin); - - my $admintalk = $self->{adminstore}->get_client(); - my $imapadmintalk = $self->{imapadminstore}->get_client(); - my $domainadmintalk = $domainadminstore->get_client(); - my $defdomadmintalk = $defdomadminstore->get_client(); - my $talk = $self->{store}->get_client(); - - # we should be able to reconstruct as 'admin', because although - # imap_admins overrides admins, we have 'admin' in imap_admins too - # (it MUST be there for Cassandane itself to work) - my $res = $admintalk->_imap_cmd("reconstruct" , 0, {}, "user.cassandane"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - # we should not be able to reconstruct as 'cassandane', because - # reconstruct is an admin-only command - $res = $talk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/permission denied/i, $talk->get_last_error()); - - # we should be able to reconstruct as 'imapadmin', because this user - # is in imap_admins - $res = $imapadmintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); - $self->assert_str_equals('ok', - $imapadmintalk->get_last_completion_response()); - - # we MUST NOT be able to reconstruct as 'admin@uhoh.org', because - # this user is not in imap_admins, even though bare 'admin' is - $res = $domainadmintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); - $self->assert_str_equals('no', - $domainadmintalk->get_last_completion_response()); - $self->assert_matches(qr/permission denied/i, - $domainadmintalk->get_last_error()); - - # we should be able to reconstruct as admin@$defaultdomain, because - # we treat bare username and username@defaultdomain as equivalent - $res = $defdomadmintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); - $self->assert_str_equals('ok', - $defdomadmintalk->get_last_completion_response()); +sub test_imap_admins_virtdomains { + # test whether the imap_admins setting works correctly under virtdomains + my ($self) = @_; + + my $domainadmin = 'admin@uhoh.org'; + my $defaultdomain = $self->{instance}->{config}->get('defaultdomain') + // 'internal'; + my $defdomadmin = "admin\@$defaultdomain"; + + $self->{instance}->create_user($domainadmin); + my $imap = $self->{instance}->get_service('imap'); + my $domainadminstore = $imap->create_store(username => $domainadmin); + my $defdomadminstore = $imap->create_store(username => $defdomadmin); + + my $admintalk = $self->{adminstore}->get_client(); + my $imapadmintalk = $self->{imapadminstore}->get_client(); + my $domainadmintalk = $domainadminstore->get_client(); + my $defdomadmintalk = $defdomadminstore->get_client(); + my $talk = $self->{store}->get_client(); + + # we should be able to reconstruct as 'admin', because although + # imap_admins overrides admins, we have 'admin' in imap_admins too + # (it MUST be there for Cassandane itself to work) + my $res = $admintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # we should not be able to reconstruct as 'cassandane', because + # reconstruct is an admin-only command + $res = $talk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/permission denied/i, $talk->get_last_error()); + + # we should be able to reconstruct as 'imapadmin', because this user + # is in imap_admins + $res = $imapadmintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); + $self->assert_str_equals('ok', + $imapadmintalk->get_last_completion_response()); + + # we MUST NOT be able to reconstruct as 'admin@uhoh.org', because + # this user is not in imap_admins, even though bare 'admin' is + $res = $domainadmintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); + $self->assert_str_equals('no', + $domainadmintalk->get_last_completion_response()); + $self->assert_matches(qr/permission denied/i, + $domainadmintalk->get_last_error()); + + # we should be able to reconstruct as admin@$defaultdomain, because + # we treat bare username and username@defaultdomain as equivalent + $res = $defdomadmintalk->_imap_cmd("reconstruct", 0, {}, "user.cassandane"); + $self->assert_str_equals('ok', + $defdomadmintalk->get_last_completion_response()); } 1; diff --git a/cassandane/Cassandane/Cyrus/Annotator.pm b/cassandane/Cassandane/Cyrus/Annotator.pm index c8fd414542..6fe29ebbe7 100644 --- a/cassandane/Cassandane/Cyrus/Annotator.pm +++ b/cassandane/Cassandane/Cyrus/Annotator.pm @@ -48,454 +48,478 @@ use Cassandane::Util::Log; use Cassandane::Util::Slurp; use Cassandane::Util::Wait; -sub new -{ - my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set( - annotation_callout => '@basedir@/conf/socket/annotator.sock', - ); - return $class->SUPER::new({ - config => $config, - deliver => 1, - start_instances => 0, - adminstore => 1, - }, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config->default()->clone(); + $config->set(annotation_callout => '@basedir@/conf/socket/annotator.sock',); + return $class->SUPER::new( + { + config => $config, + deliver => 1, + start_instances => 0, + adminstore => 1, + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub start_my_instances -{ - my ($self) = @_; - - $self->{instance}->add_generic_listener( - name => 'annotator', - port => $self->{instance}->{config}->get('annotation_callout'), - argv => sub { - my ($listener) = @_; - return ( - abs_path('utils/annotator.pl'), - '--port', $listener->port(), - '--pidfile', '@basedir@/run/annotator.pid', - ); - }); - - $self->_start_instances(); +sub start_my_instances { + my ($self) = @_; + + $self->{instance}->add_generic_listener( + name => 'annotator', + port => $self->{instance}->{config}->get('annotation_callout'), + argv => sub { + my ($listener) = @_; + return ( + abs_path('utils/annotator.pl'), '--port', + $listener->port(), '--pidfile', + '@basedir@/run/annotator.pid', + ); + } + ); + + $self->_start_instances(); } -sub test_add_annot_deliver -{ - my ($self) = @_; +sub test_add_annot_deliver { + my ($self) = @_; - $self->start_my_instances(); + $self->start_my_instances(); - my $entry = '/comment'; - my $attrib = 'value.shared'; - # Data thanks to http://hipsteripsum.me - my $value1 = 'you_probably_havent_heard_of_them'; + my $entry = '/comment'; + my $attrib = 'value.shared'; + # Data thanks to http://hipsteripsum.me + my $value1 = 'you_probably_havent_heard_of_them'; - my %exp; - $exp{A} = $self->{gen}->generate(subject => "Message A"); - $exp{A}->set_body("set_shared_annotation $entry $value1\r\n"); - $self->{instance}->deliver($exp{A}); - $exp{A}->set_annotation($entry, $attrib, $value1); + my %exp; + $exp{A} = $self->{gen}->generate(subject => "Message A"); + $exp{A}->set_body("set_shared_annotation $entry $value1\r\n"); + $self->{instance}->deliver($exp{A}); + $exp{A}->set_annotation($entry, $attrib, $value1); - # Local delivery adds headers we can't predict or control, - # which change the SHA1 of delivered messages, so we can't - # be checking the GUIDs here. - $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $self->check_messages(\%exp, check_guid => 0); + # Local delivery adds headers we can't predict or control, + # which change the SHA1 of delivered messages, so we can't + # be checking the GUIDs here. + $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $self->check_messages(\%exp, check_guid => 0); } sub test_add_annot_deliver_tomailbox - :NoAltNamespace -{ - my ($self) = @_; - - $self->start_my_instances(); - - xlog $self, "Testing adding an annotation from the Annotator"; - xlog $self, "when delivering to a non-INBOX mailbox [IRIS-955]"; - - my $entry = '/comment'; - my $attrib = 'value.shared'; - # Data thanks to http://hipsteripsum.me - my $value1 = 'before_they_sold_out'; - - my $subfolder = 'target'; - my $talk = $self->{store}->get_client(); - $talk->create("INBOX.$subfolder") - or die "Failed to create INBOX.$subfolder"; - - my %exp; - $exp{A} = $self->{gen}->generate(subject => "Message A"); - $exp{A}->set_body("set_shared_annotation $entry $value1\r\n"); - $self->{instance}->deliver($exp{A}, folder => $subfolder); - $exp{A}->set_annotation($entry, $attrib, $value1); - - # Local delivery adds headers we can't predict or control, - # which change the SHA1 of delivered messages, so we can't - # be checking the GUIDs here. - $self->{store}->set_folder("INBOX.$subfolder"); - $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $self->check_messages(\%exp, check_guid => 0); + : NoAltNamespace { + my ($self) = @_; + + $self->start_my_instances(); + + xlog $self, "Testing adding an annotation from the Annotator"; + xlog $self, "when delivering to a non-INBOX mailbox [IRIS-955]"; + + my $entry = '/comment'; + my $attrib = 'value.shared'; + # Data thanks to http://hipsteripsum.me + my $value1 = 'before_they_sold_out'; + + my $subfolder = 'target'; + my $talk = $self->{store}->get_client(); + $talk->create("INBOX.$subfolder") + or die "Failed to create INBOX.$subfolder"; + + my %exp; + $exp{A} = $self->{gen}->generate(subject => "Message A"); + $exp{A}->set_body("set_shared_annotation $entry $value1\r\n"); + $self->{instance}->deliver($exp{A}, folder => $subfolder); + $exp{A}->set_annotation($entry, $attrib, $value1); + + # Local delivery adds headers we can't predict or control, + # which change the SHA1 of delivered messages, so we can't + # be checking the GUIDs here. + $self->{store}->set_folder("INBOX.$subfolder"); + $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $self->check_messages(\%exp, check_guid => 0); } -sub test_set_system_flag_deliver -{ - my ($self) = @_; +sub test_set_system_flag_deliver { + my ($self) = @_; - $self->start_my_instances(); + $self->start_my_instances(); - my $flag = '\\Flagged'; + my $flag = '\\Flagged'; - my %exp; - $exp{A} = $self->{gen}->generate(subject => "Message A"); - $exp{A}->set_body("set_flag $flag\r\n"); - $self->{instance}->deliver($exp{A}); - $exp{A}->set_attributes(flags => ['\\Recent', $flag]); + my %exp; + $exp{A} = $self->{gen}->generate(subject => "Message A"); + $exp{A}->set_body("set_flag $flag\r\n"); + $self->{instance}->deliver($exp{A}); + $exp{A}->set_attributes(flags => [ '\\Recent', $flag ]); - # Local delivery adds headers we can't predict or control, - # which change the SHA1 of delivered messages, so we can't - # be checking the GUIDs here. - $self->{store}->set_fetch_attributes('uid', 'flags'); - $self->check_messages(\%exp, check_guid => 0); + # Local delivery adds headers we can't predict or control, + # which change the SHA1 of delivered messages, so we can't + # be checking the GUIDs here. + $self->{store}->set_fetch_attributes('uid', 'flags'); + $self->check_messages(\%exp, check_guid => 0); } -sub test_set_user_flag_deliver -{ - my ($self) = @_; +sub test_set_user_flag_deliver { + my ($self) = @_; - $self->start_my_instances(); + $self->start_my_instances(); - # Data thanks to http://hipsteripsum.me - my $flag = '$Artisanal'; + # Data thanks to http://hipsteripsum.me + my $flag = '$Artisanal'; - my %exp; - $exp{A} = $self->{gen}->generate(subject => "Message A"); - $exp{A}->set_body("set_flag $flag\r\n"); - $self->{instance}->deliver($exp{A}); - $exp{A}->set_attributes(flags => ['\\Recent', $flag]); + my %exp; + $exp{A} = $self->{gen}->generate(subject => "Message A"); + $exp{A}->set_body("set_flag $flag\r\n"); + $self->{instance}->deliver($exp{A}); + $exp{A}->set_attributes(flags => [ '\\Recent', $flag ]); - # Local delivery adds headers we can't predict or control, - # which change the SHA1 of delivered messages, so we can't - # be checking the GUIDs here. - $self->{store}->set_fetch_attributes('uid', 'flags'); - $self->check_messages(\%exp, check_guid => 0); + # Local delivery adds headers we can't predict or control, + # which change the SHA1 of delivered messages, so we can't + # be checking the GUIDs here. + $self->{store}->set_fetch_attributes('uid', 'flags'); + $self->check_messages(\%exp, check_guid => 0); } -sub test_reconstruct_after_delivery -{ - my ($self) = @_; - - $self->start_my_instances(); +sub test_reconstruct_after_delivery { + my ($self) = @_; - xlog $self, "Testing reconstruct after delivery"; + $self->start_my_instances(); - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Testing reconstruct after delivery"; - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $msgs{1}->set_body("set_shared_annotation /comment testvalue\r\n"); - $imaptalk->create("INBOX.subfolder"); - $self->{instance}->deliver($msgs{1}, user => "cassandane"); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Check that the message made it"; - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $msgs{1}->set_body("set_shared_annotation /comment testvalue\r\n"); + $imaptalk->create("INBOX.subfolder"); + $self->{instance}->deliver($msgs{1}, user => "cassandane"); - # run a fresh reconstruct - my $out = "$self->{instance}->{basedir}/$self->{_name}-reconstruct.stdout"; - $self->{instance}->run_command( - { cyrus => 1, - redirects => { 'stdout' => $out }, - }, 'reconstruct', '-u', 'cassandane'); + xlog $self, "Check that the message made it"; + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); - # check the output - $out = slurp_file($out); - xlog $self, $out; - - $self->assert_does_not_match(qr/ updating /, $out); + # run a fresh reconstruct + my $out = "$self->{instance}->{basedir}/$self->{_name}-reconstruct.stdout"; + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { 'stdout' => $out }, + }, + 'reconstruct', + '-u', + 'cassandane' + ); + + # check the output + $out = slurp_file($out); + xlog $self, $out; + + $self->assert_does_not_match(qr/ updating /, $out); } - # Note: remove_annotation can't really be tested with local # delivery, just with the APPEND command. -sub test_fetch_after_annotate -{ - # This is a test for https://github.com/cyrusimap/cyrus-imapd/issues/2071 - my ($self) = @_; - - $self->start_my_instances(); - - my $flag = '$X-ME-Annot-2'; - my $imaptalk = $self->{store}->get_client(); - my $modseq; - my %msg; - - $imaptalk->select("INBOX"); - - xlog $self, "Create Message A"; - $msg{A} = $self->{gen}->generate(subject => "Message A"); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{A}->set_body("set_flag $flag\r\n"); - $self->{instance}->deliver($msg{A}); - - $msg{A}->set_attributes(flags => ['\\Recent', $flag]); - - $self->{store}->set_fetch_attributes('uid', 'flags', 'modseq'); - - xlog $self, "Fetch message A"; - my %handlers1; - { - $handlers1{fetch} = sub { - $self->assert_num_equals(scalar @{$_[1]{flags}}, 2); - $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); - $self->assert_str_equals($_[1]{flags}[1], "\$X-ME-Annot-2"); - }; - } - $imaptalk->_imap_cmd("uid fetch", 1, \%handlers1, '1', '(flags modseq)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Clear the $flag from the message A."; - my %handlers2; - { - $handlers2{fetch} = sub { - $modseq = $_[1]{modseq}[0]; - $self->assert_num_equals(scalar @{$_[1]{flags}}, 1); - $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); - }; - } - $imaptalk->store('1', '-flags', "($flag)"); - $imaptalk->_imap_cmd("uid fetch", 1, \%handlers2, '1', '(flags modseq)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Run xrunannotator"; - my %handlers3; - { - $handlers3{fetch} = sub { - $self->assert($_[1]{modseq}[0] > $modseq); - $self->assert_num_equals(scalar @{$_[1]{flags}}, 2); - $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); - $self->assert_str_equals($_[1]{flags}[1], "\$X-ME-Annot-2"); - }; - } - $imaptalk->_imap_cmd("uid xrunannotator", 0, {}, '1'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->_imap_cmd("uid fetch", 1, \%handlers3, '1', '(flags modseq)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); +sub test_fetch_after_annotate { + # This is a test for https://github.com/cyrusimap/cyrus-imapd/issues/2071 + my ($self) = @_; + + $self->start_my_instances(); + + my $flag = '$X-ME-Annot-2'; + my $imaptalk = $self->{store}->get_client(); + my $modseq; + my %msg; + + $imaptalk->select("INBOX"); + + xlog $self, "Create Message A"; + $msg{A} = $self->{gen}->generate(subject => "Message A"); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{A}->set_body("set_flag $flag\r\n"); + $self->{instance}->deliver($msg{A}); + + $msg{A}->set_attributes(flags => [ '\\Recent', $flag ]); + + $self->{store}->set_fetch_attributes('uid', 'flags', 'modseq'); + + xlog $self, "Fetch message A"; + my %handlers1; + { + $handlers1{fetch} = sub { + $self->assert_num_equals(scalar @{ $_[1]{flags} }, 2); + $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); + $self->assert_str_equals($_[1]{flags}[1], "\$X-ME-Annot-2"); + }; + } + $imaptalk->_imap_cmd("uid fetch", 1, \%handlers1, '1', '(flags modseq)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Clear the $flag from the message A."; + my %handlers2; + { + $handlers2{fetch} = sub { + $modseq = $_[1]{modseq}[0]; + $self->assert_num_equals(scalar @{ $_[1]{flags} }, 1); + $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); + }; + } + $imaptalk->store('1', '-flags', "($flag)"); + $imaptalk->_imap_cmd("uid fetch", 1, \%handlers2, '1', '(flags modseq)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Run xrunannotator"; + my %handlers3; + { + $handlers3{fetch} = sub { + $self->assert($_[1]{modseq}[0] > $modseq); + $self->assert_num_equals(scalar @{ $_[1]{flags} }, 2); + $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); + $self->assert_str_equals($_[1]{flags}[1], "\$X-ME-Annot-2"); + }; + } + $imaptalk->_imap_cmd("uid xrunannotator", 0, {}, '1'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->_imap_cmd("uid fetch", 1, \%handlers3, '1', '(flags modseq)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); } sub test_annotator_callout_disabled - :min_version_3_1 -{ - my ($self) = @_; - $self->{instance}->{config}->set(annotation_callout_disable_append => 'yes'); - - $self->start_my_instances(); - - my $flag = '$X-ME-Annot-2'; - my $imaptalk = $self->{store}->get_client(); - my $modseq; - my %msg; - - $imaptalk->select("INBOX"); - - xlog $self, "Create Message A"; - $msg{A} = $self->{gen}->generate(subject => "Message A"); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{A}->set_body("set_flag $flag\r\n"); - $self->{instance}->deliver($msg{A}); - - $msg{A}->set_attributes(flags => ['\\Recent', $flag]); - - $self->{store}->set_fetch_attributes('uid', 'flags', 'modseq'); - - xlog $self, "Fetch message A"; - my %handlers1; - { - $handlers1{fetch} = sub { - $self->assert_num_equals(scalar @{$_[1]{flags}}, 2); - $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); - $self->assert_str_equals($_[1]{flags}[1], "\$X-ME-Annot-2"); - }; - } - $imaptalk->_imap_cmd("uid fetch", 1, \%handlers1, '1', '(flags modseq)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Clear the $flag from the message A."; - my %handlers2; - { - $handlers2{fetch} = sub { - $modseq = $_[1]{modseq}[0]; - $self->assert_num_equals(scalar @{$_[1]{flags}}, 1); - $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); - }; - } - $imaptalk->store('1', '-flags', "($flag)"); - $imaptalk->_imap_cmd("uid fetch", 1, \%handlers2, '1', '(flags modseq)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Run xrunannotator"; - my %handlers3; - { - $handlers3{fetch} = sub { - $self->assert($_[1]{modseq}[0] == $modseq); - $self->assert_num_equals(scalar @{$_[1]{flags}}, 1); - $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); - }; - } - $imaptalk->_imap_cmd("uid xrunannotator", 0, {}, '1'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Nothing should have changed from the previous run of uid fetch."; - $imaptalk->_imap_cmd("uid fetch", 1, \%handlers3, '1', '(flags modseq)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + : min_version_3_1 { + my ($self) = @_; + $self->{instance}->{config}->set(annotation_callout_disable_append => 'yes'); + + $self->start_my_instances(); + + my $flag = '$X-ME-Annot-2'; + my $imaptalk = $self->{store}->get_client(); + my $modseq; + my %msg; + + $imaptalk->select("INBOX"); + + xlog $self, "Create Message A"; + $msg{A} = $self->{gen}->generate(subject => "Message A"); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{A}->set_body("set_flag $flag\r\n"); + $self->{instance}->deliver($msg{A}); + + $msg{A}->set_attributes(flags => [ '\\Recent', $flag ]); + + $self->{store}->set_fetch_attributes('uid', 'flags', 'modseq'); + + xlog $self, "Fetch message A"; + my %handlers1; + { + $handlers1{fetch} = sub { + $self->assert_num_equals(scalar @{ $_[1]{flags} }, 2); + $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); + $self->assert_str_equals($_[1]{flags}[1], "\$X-ME-Annot-2"); + }; + } + $imaptalk->_imap_cmd("uid fetch", 1, \%handlers1, '1', '(flags modseq)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Clear the $flag from the message A."; + my %handlers2; + { + $handlers2{fetch} = sub { + $modseq = $_[1]{modseq}[0]; + $self->assert_num_equals(scalar @{ $_[1]{flags} }, 1); + $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); + }; + } + $imaptalk->store('1', '-flags', "($flag)"); + $imaptalk->_imap_cmd("uid fetch", 1, \%handlers2, '1', '(flags modseq)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Run xrunannotator"; + my %handlers3; + { + $handlers3{fetch} = sub { + $self->assert($_[1]{modseq}[0] == $modseq); + $self->assert_num_equals(scalar @{ $_[1]{flags} }, 1); + $self->assert_str_equals($_[1]{flags}[0], "\\Recent"); + }; + } + $imaptalk->_imap_cmd("uid xrunannotator", 0, {}, '1'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Nothing should have changed from the previous run of uid fetch."; + $imaptalk->_imap_cmd("uid fetch", 1, \%handlers3, '1', '(flags modseq)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); } sub test_add_annot_splitconv - :min_version_3_1 :Conversations -{ - my ($self) = @_; - my %exp; - - $self->{instance}->{config}->set(conversations_max_thread => 5); - - $self->start_my_instances(); - - my $entry = '/comment'; - my $attrib = 'value.shared'; - # Data thanks to http://hipsteripsum.me - my $value1 = 'you_probably_havent_heard_of_them'; - - $self->{store}->set_fetch_attributes('uid', 'cid', 'basecid', "annotation ($entry $attrib)"); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating replies"; - for (1..4) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } - $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"B"}->set_attributes(uid => 6, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - - $exp{C} = $self->{gen}->generate(subject => "Re: Message A", references => [ $exp{A} ]); - $exp{C}->set_body("set_shared_annotation $entry $value1\r\n"); - $self->{instance}->deliver($exp{C}); - $exp{C}->set_annotation($entry, $attrib, $value1); - $exp{C}->set_attributes(uid => 7, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - - # Local delivery adds headers we can't predict or control, - # which change the SHA1 of delivered messages, so we can't - # be checking the GUIDs here. - $self->check_messages(\%exp, keyed_on => 'uid', check_guid => 0); + : min_version_3_1 : Conversations { + my ($self) = @_; + my %exp; + + $self->{instance}->{config}->set(conversations_max_thread => 5); + + $self->start_my_instances(); + + my $entry = '/comment'; + my $attrib = 'value.shared'; + # Data thanks to http://hipsteripsum.me + my $value1 = 'you_probably_havent_heard_of_them'; + + $self->{store}->set_fetch_attributes('uid', 'cid', 'basecid', + "annotation ($entry $attrib)"); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating replies"; + for (1 .. 4) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } + $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"B"}->set_attributes( + uid => 6, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + + $exp{C} = $self->{gen} + ->generate(subject => "Re: Message A", references => [ $exp{A} ]); + $exp{C}->set_body("set_shared_annotation $entry $value1\r\n"); + $self->{instance}->deliver($exp{C}); + $exp{C}->set_annotation($entry, $attrib, $value1); + $exp{C}->set_attributes( + uid => 7, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + + # Local delivery adds headers we can't predict or control, + # which change the SHA1 of delivered messages, so we can't + # be checking the GUIDs here. + $self->check_messages(\%exp, keyed_on => 'uid', check_guid => 0); } sub test_add_annot_splitconv_rerun - :min_version_3_1 :Conversations -{ - my ($self) = @_; - my %exp; - - $self->{instance}->{config}->set(conversations_max_thread => 5); - - $self->start_my_instances(); + : min_version_3_1 : Conversations { + my ($self) = @_; + my %exp; + + $self->{instance}->{config}->set(conversations_max_thread => 5); + + $self->start_my_instances(); + + my $entry = '/comment'; + my $attrib = 'value.shared'; + # Data thanks to http://hipsteripsum.me + my $value1 = 'you_probably_havent_heard_of_them'; + + $self->{store}->set_fetch_attributes('uid', 'cid', 'basecid', 'flags', + "annotation ($entry $attrib)"); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating replies"; + for (1 .. 4) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } + $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"B"}->set_attributes( + uid => 6, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + + $exp{C} = $self->{gen} + ->generate(subject => "Re: Message A", references => [ $exp{A} ]); + $exp{C}->set_body("set_shared_annotation $entry $value1\r\nset_flag \$X-FUN"); + $self->{instance}->deliver($exp{C}); + $exp{C}->set_attributes( + uid => 7, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + + # Local delivery adds headers we can't predict or control, + # which change the SHA1 of delivered messages, so we can't + # be checking the GUIDs here. + $self->check_messages(\%exp, keyed_on => 'uid', check_guid => 0); + + my $imaptalk = $self->{store}->get_client(); + $imaptalk->store('7', '-flags', '$X-FUN'); + $imaptalk->_imap_cmd("uid xrunannotator", 0, {}, '7'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $exp{C}->set_annotation($entry, $attrib, $value1); + + $self->check_messages(\%exp, keyed_on => 'uid', check_guid => 0); +} - my $entry = '/comment'; - my $attrib = 'value.shared'; - # Data thanks to http://hipsteripsum.me - my $value1 = 'you_probably_havent_heard_of_them'; +sub test_log_missing_acl { + my ($self) = @_; - $self->{store}->set_fetch_attributes('uid', 'cid', 'basecid', 'flags', "annotation ($entry $attrib)"); + $self->start_my_instances(); - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); + my $imap = $self->{store}->get_client(); + my $admin = $self->{adminstore}->get_client(); - xlog $self, "generating replies"; - for (1..4) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } - $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"B"}->set_attributes(uid => 6, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - - $exp{C} = $self->{gen}->generate(subject => "Re: Message A", references => [ $exp{A} ]); - $exp{C}->set_body("set_shared_annotation $entry $value1\r\nset_flag \$X-FUN"); - $self->{instance}->deliver($exp{C}); - $exp{C}->set_attributes(uid => 7, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - - # Local delivery adds headers we can't predict or control, - # which change the SHA1 of delivered messages, so we can't - # be checking the GUIDs here. - $self->check_messages(\%exp, keyed_on => 'uid', check_guid => 0); - - my $imaptalk = $self->{store}->get_client(); - $imaptalk->store('7', '-flags', '$X-FUN'); - $imaptalk->_imap_cmd("uid xrunannotator", 0, {}, '7'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $exp{C}->set_annotation($entry, $attrib, $value1); - - $self->check_messages(\%exp, keyed_on => 'uid', check_guid => 0); -} + $self->{instance}->create_user("other"); -sub test_log_missing_acl -{ - my ($self) = @_; - - $self->start_my_instances(); - - my $imap = $self->{store}->get_client(); - my $admin = $self->{adminstore}->get_client(); - - $self->{instance}->create_user("other"); - - my @testCases = ({ - flag => '$Artisanal', - acl => 'lrsitne', - need_rights => 'w' - }, { - flag => '\Seen', - acl => 'lritne', - need_rights => 's' - }); - - foreach (@testCases) { - $admin->setacl("user.other", "cassandane", $_->{acl}) or die; - - $self->{instance}->getsyslog(); - $self->{store}->set_folder('Other Users.other'); - $self->make_message("Email", body => "set_flag $_->{flag}\r\n", - store => $self->{store}) or die; - my $wantLog = "could not write flag due missing ACL: " - . "flag=<\\$_->{flag}> need_rights=<$_->{need_rights}>"; - $self->assert_syslog_matches($self->{instance}, qr{$wantLog}); + my @testCases = ( + { + flag => '$Artisanal', + acl => 'lrsitne', + need_rights => 'w' + }, + { + flag => '\Seen', + acl => 'lritne', + need_rights => 's' } + ); + + foreach (@testCases) { + $admin->setacl("user.other", "cassandane", $_->{acl}) or die; + + $self->{instance}->getsyslog(); + $self->{store}->set_folder('Other Users.other'); + $self->make_message( + "Email", + body => "set_flag $_->{flag}\r\n", + store => $self->{store} + ) or die; + my $wantLog = "could not write flag due missing ACL: " + . "flag=<\\$_->{flag}> need_rights=<$_->{need_rights}>"; + $self->assert_syslog_matches($self->{instance}, qr{$wantLog}); + } } 1; diff --git a/cassandane/Cassandane/Cyrus/Archive.pm b/cassandane/Cassandane/Cyrus/Archive.pm index 997fa1af9a..8930442b01 100644 --- a/cassandane/Cassandane/Cyrus/Archive.pm +++ b/cassandane/Cassandane/Cyrus/Archive.pm @@ -48,22 +48,19 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Util::Words; -sub new -{ - my ($class, @args) = @_; - return $class->SUPER::new({ adminstore => 1 }, @args); +sub new { + my ($class, @args) = @_; + return $class->SUPER::new({ adminstore => 1 }, @args); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # @@ -75,280 +72,300 @@ sub tear_down # isn't available to clients during the archive operation # sub test_archive_messages - :ArchivePartition :min_version_3_0 -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Append 3 messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $msg{C} = $self->make_message('Message C'); - $msg{C}->set_attributes(id => 3, - uid => 3, - flags => []); - $self->check_messages(\%msg); - - my $data = $self->{instance}->run_mbpath("-u", 'cassandane'); - my $datadir = $data->{data}; - my $archivedir = $data->{archive}; - - $self->assert_file_test("$datadir/1.", "-f"); - $self->assert_file_test("$datadir/2.", "-f"); - $self->assert_file_test("$datadir/3.", "-f"); - - $self->assert_not_file_test("$archivedir/1.", "-f"); - $self->assert_not_file_test("$archivedir/2.", "-f"); - $self->assert_not_file_test("$archivedir/3.", "-f"); - - xlog $self, "Run cyr_expire but no messages should move"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d' ); - - $self->assert_file_test("$datadir/1.", "-f"); - $self->assert_file_test("$datadir/2.", "-f"); - $self->assert_file_test("$datadir/3.", "-f"); - - $self->assert_not_file_test("$archivedir/1.", "-f"); - $self->assert_not_file_test("$archivedir/2.", "-f"); - $self->assert_not_file_test("$archivedir/3.", "-f"); - - xlog $self, "Run cyr_expire to archive now"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0' ); - - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); + : ArchivePartition : min_version_3_0 { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Append 3 messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $msg{C} = $self->make_message('Message C'); + $msg{C}->set_attributes( + id => 3, + uid => 3, + flags => [] + ); + $self->check_messages(\%msg); + + my $data = $self->{instance}->run_mbpath("-u", 'cassandane'); + my $datadir = $data->{data}; + my $archivedir = $data->{archive}; + + $self->assert_file_test("$datadir/1.", "-f"); + $self->assert_file_test("$datadir/2.", "-f"); + $self->assert_file_test("$datadir/3.", "-f"); + + $self->assert_not_file_test("$archivedir/1.", "-f"); + $self->assert_not_file_test("$archivedir/2.", "-f"); + $self->assert_not_file_test("$archivedir/3.", "-f"); + + xlog $self, "Run cyr_expire but no messages should move"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d'); + + $self->assert_file_test("$datadir/1.", "-f"); + $self->assert_file_test("$datadir/2.", "-f"); + $self->assert_file_test("$datadir/3.", "-f"); + + $self->assert_not_file_test("$archivedir/1.", "-f"); + $self->assert_not_file_test("$archivedir/2.", "-f"); + $self->assert_not_file_test("$archivedir/3.", "-f"); + + xlog $self, "Run cyr_expire to archive now"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0'); + + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); } sub test_archivenow_messages - :ArchiveNow :min_version_3_0 -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Append 3 messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $msg{C} = $self->make_message('Message C'); - $msg{C}->set_attributes(id => 3, - uid => 3, - flags => []); - $self->check_messages(\%msg); - - my $data = $self->{instance}->run_mbpath("-u", 'cassandane'); - my $datadir = $data->{data}; - my $archivedir = $data->{archive}; - - # already archived - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); - - xlog $self, "Run cyr_expire with old and messages stay archived"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d' ); - - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); - - xlog $self, "Run cyr_expire to archive now and messages stay archived"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0' ); - - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); + : ArchiveNow : min_version_3_0 { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Append 3 messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $msg{C} = $self->make_message('Message C'); + $msg{C}->set_attributes( + id => 3, + uid => 3, + flags => [] + ); + $self->check_messages(\%msg); + + my $data = $self->{instance}->run_mbpath("-u", 'cassandane'); + my $datadir = $data->{data}; + my $archivedir = $data->{archive}; + + # already archived + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); + + xlog $self, "Run cyr_expire with old and messages stay archived"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d'); + + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); + + xlog $self, "Run cyr_expire to archive now and messages stay archived"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0'); + + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); } 1; sub test_archive_messages_archive_annotation - :ArchivePartition :min_version_3_1 -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Append 3 messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $msg{C} = $self->make_message('Message C'); - $msg{C}->set_attributes(id => 3, - uid => 3, - flags => []); - $self->check_messages(\%msg); - - my $data = $self->{instance}->run_mbpath("-u", 'cassandane'); - my $datadir = $data->{data}; - my $archivedir = $data->{archive}; - - $self->assert_file_test("$datadir/1.", "-f"); - $self->assert_file_test("$datadir/2.", "-f"); - $self->assert_file_test("$datadir/3.", "-f"); - - $self->assert_not_file_test("$archivedir/1.", "-f"); - $self->assert_not_file_test("$archivedir/2.", "-f"); - $self->assert_not_file_test("$archivedir/3.", "-f"); - - xlog $self, "Run cyr_expire but no messages should move"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d' ); - - $self->assert_file_test("$datadir/1.", "-f"); - $self->assert_file_test("$datadir/2.", "-f"); - $self->assert_file_test("$datadir/3.", "-f"); - - $self->assert_not_file_test("$archivedir/1.", "-f"); - $self->assert_not_file_test("$archivedir/2.", "-f"); - $self->assert_not_file_test("$archivedir/3.", "-f"); - - $admintalk->setmetadata('user.cassandane', - "/shared/vendor/cmu/cyrus-imapd/archive", - '3'); - - xlog $self, "Run cyr_expire asking to archive now, but it shouldn't"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0' ); - - $self->assert_file_test("$datadir/1.", "-f"); - $self->assert_file_test("$datadir/2.", "-f"); - $self->assert_file_test("$datadir/3.", "-f"); - - $self->assert_not_file_test("$archivedir/1.", "-f"); - $self->assert_not_file_test("$archivedir/2.", "-f"); - $self->assert_not_file_test("$archivedir/3.", "-f"); - - xlog $self, "Run cyr_expire asking to archive now, with skip annotation"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0' , '-a'); - - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); + : ArchivePartition : min_version_3_1 { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Append 3 messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $msg{C} = $self->make_message('Message C'); + $msg{C}->set_attributes( + id => 3, + uid => 3, + flags => [] + ); + $self->check_messages(\%msg); + + my $data = $self->{instance}->run_mbpath("-u", 'cassandane'); + my $datadir = $data->{data}; + my $archivedir = $data->{archive}; + + $self->assert_file_test("$datadir/1.", "-f"); + $self->assert_file_test("$datadir/2.", "-f"); + $self->assert_file_test("$datadir/3.", "-f"); + + $self->assert_not_file_test("$archivedir/1.", "-f"); + $self->assert_not_file_test("$archivedir/2.", "-f"); + $self->assert_not_file_test("$archivedir/3.", "-f"); + + xlog $self, "Run cyr_expire but no messages should move"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d'); + + $self->assert_file_test("$datadir/1.", "-f"); + $self->assert_file_test("$datadir/2.", "-f"); + $self->assert_file_test("$datadir/3.", "-f"); + + $self->assert_not_file_test("$archivedir/1.", "-f"); + $self->assert_not_file_test("$archivedir/2.", "-f"); + $self->assert_not_file_test("$archivedir/3.", "-f"); + + $admintalk->setmetadata('user.cassandane', + "/shared/vendor/cmu/cyrus-imapd/archive", '3'); + + xlog $self, "Run cyr_expire asking to archive now, but it shouldn't"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0'); + + $self->assert_file_test("$datadir/1.", "-f"); + $self->assert_file_test("$datadir/2.", "-f"); + $self->assert_file_test("$datadir/3.", "-f"); + + $self->assert_not_file_test("$archivedir/1.", "-f"); + $self->assert_not_file_test("$archivedir/2.", "-f"); + $self->assert_not_file_test("$archivedir/3.", "-f"); + + xlog $self, "Run cyr_expire asking to archive now, with skip annotation"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0', '-a'); + + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); } sub test_archivenow_reconstruct - :ArchiveNow :min_version_3_0 -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Append 3 messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $msg{C} = $self->make_message('Message C'); - $msg{C}->set_attributes(id => 3, - uid => 3, - flags => []); - $self->check_messages(\%msg); - - my $data = $self->{instance}->run_mbpath("-u", 'cassandane'); - my $datadir = $data->{data}; - my $archivedir = $data->{archive}; - - # already archived - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); - - xlog $self, "Run cyr_expire with old and messages stay archived"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d' ); - - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); - - xlog $self, "Run cyr_expire to archive now and messages stay archived"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0' ); - - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); - - xlog $self, "Reconstruct doesn't lose files"; - - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', '-s'); - - $self->assert_not_file_test("$datadir/1.", "-f"); - $self->assert_not_file_test("$datadir/2.", "-f"); - $self->assert_not_file_test("$datadir/3.", "-f"); - - $self->assert_file_test("$archivedir/1.", "-f"); - $self->assert_file_test("$archivedir/2.", "-f"); - $self->assert_file_test("$archivedir/3.", "-f"); + : ArchiveNow : min_version_3_0 { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Append 3 messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $msg{C} = $self->make_message('Message C'); + $msg{C}->set_attributes( + id => 3, + uid => 3, + flags => [] + ); + $self->check_messages(\%msg); + + my $data = $self->{instance}->run_mbpath("-u", 'cassandane'); + my $datadir = $data->{data}; + my $archivedir = $data->{archive}; + + # already archived + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); + + xlog $self, "Run cyr_expire with old and messages stay archived"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d'); + + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); + + xlog $self, "Run cyr_expire to archive now and messages stay archived"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '0'); + + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); + + xlog $self, "Reconstruct doesn't lose files"; + + $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', '-s'); + + $self->assert_not_file_test("$datadir/1.", "-f"); + $self->assert_not_file_test("$datadir/2.", "-f"); + $self->assert_not_file_test("$datadir/3.", "-f"); + + $self->assert_file_test("$archivedir/1.", "-f"); + $self->assert_file_test("$archivedir/2.", "-f"); + $self->assert_file_test("$archivedir/3.", "-f"); } 1; diff --git a/cassandane/Cassandane/Cyrus/Autocreate.pm b/cassandane/Cassandane/Cyrus/Autocreate.pm index 3b9ef98384..2cc50a18de 100644 --- a/cassandane/Cassandane/Cyrus/Autocreate.pm +++ b/cassandane/Cassandane/Cyrus/Autocreate.pm @@ -48,174 +48,176 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - my $config = Cassandane::Config->default()->clone(); - - $config->set( - autocreate_post => 'yes', - autocreate_quota => '500000', - autocreate_inbox_folders => 'Drafts|Sent|Trash|SPAM|plus', - autocreate_subscribe_folder => 'Drafts|Sent|Trash|SPAM|plus', - autocreate_sieve_script => '@basedir@/conf/foo_sieve.script', - autocreate_acl => 'plus anyone p', - 'xlist-drafts' => 'Drafts', - 'xlist-junk' => 'SPAM', - 'xlist-sent' => 'Sent', - 'xlist-trash' => 'Trash', - ); - return $class->SUPER::new({ - config => $config, - adminstore => 1, - deliver => 1, - }, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config->default()->clone(); + + $config->set( + autocreate_post => 'yes', + autocreate_quota => '500000', + autocreate_inbox_folders => 'Drafts|Sent|Trash|SPAM|plus', + autocreate_subscribe_folder => 'Drafts|Sent|Trash|SPAM|plus', + autocreate_sieve_script => '@basedir@/conf/foo_sieve.script', + autocreate_acl => 'plus anyone p', + 'xlist-drafts' => 'Drafts', + 'xlist-junk' => 'SPAM', + 'xlist-sent' => 'Sent', + 'xlist-trash' => 'Trash', + ); + return $class->SUPER::new( + { + config => $config, + adminstore => 1, + deliver => 1, + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_autocreate_specialuse - :min_version_3_0 :needs_component_autocreate :NoAltNameSpace -{ - my ($self) = @_; - - my $svc = $self->{instance}->get_service('imap'); - my $store = $svc->create_store(username => 'foo'); - my $talk = $store->get_client(); - my $list = $talk->list('', '*', 'return', ['special-use']); - - my %map = ( - drafts => 'Drafts', - junk => 'SPAM', - sent => 'Sent', - trash => 'Trash', - ); - foreach my $item (@$list) { - my $key; - foreach my $flag (@{$item->[0]}) { - next unless $flag =~ m/\\(.*)/; - $key = $1; - last if $map{$key}; - } - my $name = delete $map{$key}; - next unless $name; - $self->assert_str_equals("INBOX.$name", $item->[2]); + : min_version_3_0 : needs_component_autocreate : NoAltNameSpace { + my ($self) = @_; + + my $svc = $self->{instance}->get_service('imap'); + my $store = $svc->create_store(username => 'foo'); + my $talk = $store->get_client(); + my $list = $talk->list('', '*', 'return', ['special-use']); + + my %map = ( + drafts => 'Drafts', + junk => 'SPAM', + sent => 'Sent', + trash => 'Trash', + ); + foreach my $item (@$list) { + my $key; + foreach my $flag (@{ $item->[0] }) { + next unless $flag =~ m/\\(.*)/; + $key = $1; + last if $map{$key}; } - $self->assert_num_equals(0, scalar keys %map); + my $name = delete $map{$key}; + next unless $name; + $self->assert_str_equals("INBOX.$name", $item->[2]); + } + $self->assert_num_equals(0, scalar keys %map); } sub test_autocreate_sieve_script_generation - :min_version_3_0 :needs_component_autocreate :needs_component_sieve -{ - my ($self) = @_; - - my $basedir = $self->{instance}->get_basedir(); - my $sieve_script_path = $basedir . "/conf/foo_sieve.script"; - my $hitfolder = "INBOX.NewFolder"; - my $testfolder = "INBOX.TestFolder"; - my $missfolder = "INBOX"; - - open(FH, '>', "$sieve_script_path") - or die "Cannot open $sieve_script_path for writing: $!"; - print FH "require \[\"fileinto\", \"mailbox\"\];"; - print FH "if mailboxexists \"$testfolder\" {"; - print FH "fileinto \"$hitfolder\";"; - print FH "}"; - close(FH); - - my $svc = $self->{instance}->get_service('imap'); - my $store = $svc->create_store(username => 'foo'); - my $talk = $store->get_client(); - - my $sievedir = $self->{instance}->get_sieve_script_dir('foo'); - $self->assert_file_test("$sievedir/foo_sieve.script.script", '-f'); - $self->assert_file_test("$sievedir/defaultbc", '-f'); - $self->assert_file_test("$sievedir/foo_sieve.script.bc", '-f'); + : min_version_3_0 : needs_component_autocreate : needs_component_sieve { + my ($self) = @_; + + my $basedir = $self->{instance}->get_basedir(); + my $sieve_script_path = $basedir . "/conf/foo_sieve.script"; + my $hitfolder = "INBOX.NewFolder"; + my $testfolder = "INBOX.TestFolder"; + my $missfolder = "INBOX"; + + open(FH, '>', "$sieve_script_path") + or die "Cannot open $sieve_script_path for writing: $!"; + print FH "require \[\"fileinto\", \"mailbox\"\];"; + print FH "if mailboxexists \"$testfolder\" {"; + print FH "fileinto \"$hitfolder\";"; + print FH "}"; + close(FH); + + my $svc = $self->{instance}->get_service('imap'); + my $store = $svc->create_store(username => 'foo'); + my $talk = $store->get_client(); + + my $sievedir = $self->{instance}->get_sieve_script_dir('foo'); + $self->assert_file_test("$sievedir/foo_sieve.script.script", '-f'); + $self->assert_file_test("$sievedir/defaultbc", '-f'); + $self->assert_file_test("$sievedir/foo_sieve.script.bc", '-f'); } sub test_autocreate_acl - :min_version_3_1 :needs_component_autocreate :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; - - my %folder_acls = ( - 'INBOX' => [qw( foo lrswipkxtecdan )], - 'INBOX.Drafts' => [qw( foo lrswipkxtecdan )], - 'INBOX.Sent' => [qw( foo lrswipkxtecdan )], - 'INBOX.SPAM' => [qw( foo lrswipkxtecdan )], - 'INBOX.Trash' => [qw( foo lrswipkxtecdan )], - 'INBOX.plus' => [qw( foo lrswipkxtecdan anyone p )], - ); - - my $svc = $self->{instance}->get_service('imap'); - my $store = $svc->create_store(username => 'foo'); - my $talk = $store->get_client(); - - while (my ($folder, $acl) = each %folder_acls) { - my $res = $talk->getacl($folder); - $self->assert_deep_equals($folder_acls{$folder}, $res); - } + : min_version_3_1 : needs_component_autocreate : needs_component_sieve : + NoAltNameSpace { + my ($self) = @_; + + my %folder_acls = ( + 'INBOX' => [qw( foo lrswipkxtecdan )], + 'INBOX.Drafts' => [qw( foo lrswipkxtecdan )], + 'INBOX.Sent' => [qw( foo lrswipkxtecdan )], + 'INBOX.SPAM' => [qw( foo lrswipkxtecdan )], + 'INBOX.Trash' => [qw( foo lrswipkxtecdan )], + 'INBOX.plus' => [qw( foo lrswipkxtecdan anyone p )], + ); + + my $svc = $self->{instance}->get_service('imap'); + my $store = $svc->create_store(username => 'foo'); + my $talk = $store->get_client(); + + while (my ($folder, $acl) = each %folder_acls) { + my $res = $talk->getacl($folder); + $self->assert_deep_equals($folder_acls{$folder}, $res); + } } sub test_legacymb_already_exists - :NoStartInstances :NoAltNamespace -{ - my ($self) = @_; - - # want a separate IMAP service with separate config containing - # the defaults (no autocreate!) plus mailbox_legacy_dirs: yes - my $leg_conf = Cassandane::Config->default()->clone(); - $leg_conf->set(mailbox_legacy_dirs => 'yes'); - - my $leg_svc = $self->{instance}->add_service( - name => 'imaplegacymb', - config => $leg_conf, - ); - - # now actually start everything - $self->_start_instances(); - - # create some mailboxes for user foo under legacy storage - my $leg_store = $leg_svc->create_store(username => 'admin', - type => 'imap'); - my $leg_talk = $leg_store->get_client(); - - $leg_talk->create('user.foo') or die; - $leg_talk->setacl('user.foo', foo => 'lrswipkxtecdn') or die; - $leg_talk->create('user.foo.bar') or die; - $leg_talk->setacl('user.foo.bar', foo => 'lrswipkxtecdn') or die; - - $leg_talk->logout(); - - # those mailboxes had better be under legacy storage - foreach my $mailbox (qw(user.foo user.foo.bar)) { - my $mbpath = $self->{instance}->run_mbpath($mailbox); - $self->assert_does_not_match(qr{/uuid/}, $mbpath->{data}); + : NoStartInstances : NoAltNamespace { + my ($self) = @_; + + # want a separate IMAP service with separate config containing + # the defaults (no autocreate!) plus mailbox_legacy_dirs: yes + my $leg_conf = Cassandane::Config->default()->clone(); + $leg_conf->set(mailbox_legacy_dirs => 'yes'); + + my $leg_svc = $self->{instance}->add_service( + name => 'imaplegacymb', + config => $leg_conf, + ); + + # now actually start everything + $self->_start_instances(); + + # create some mailboxes for user foo under legacy storage + my $leg_store = $leg_svc->create_store( + username => 'admin', + type => 'imap' + ); + my $leg_talk = $leg_store->get_client(); + + $leg_talk->create('user.foo') or die; + $leg_talk->setacl('user.foo', foo => 'lrswipkxtecdn') or die; + $leg_talk->create('user.foo.bar') or die; + $leg_talk->setacl('user.foo.bar', foo => 'lrswipkxtecdn') or die; + + $leg_talk->logout(); + + # those mailboxes had better be under legacy storage + foreach my $mailbox (qw(user.foo user.foo.bar)) { + my $mbpath = $self->{instance}->run_mbpath($mailbox); + $self->assert_does_not_match(qr{/uuid/}, $mbpath->{data}); + } + + # now log in as user foo -- better not get the default + # autocreate set! + + my $svc = $self->{instance}->get_service('imap'); + my $store = $svc->create_store(username => 'foo'); + my $talk = $store->get_client(); + + my $data = $talk->list("", "*"); + + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => '\\HasChildren', + 'INBOX.bar' => '\\HasNoChildren', } - - # now log in as user foo -- better not get the default - # autocreate set! - - my $svc = $self->{instance}->get_service('imap'); - my $store = $svc->create_store(username => 'foo'); - my $talk = $store->get_client(); - - my $data = $talk->list("", "*"); - - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => '\\HasChildren', - 'INBOX.bar' => '\\HasNoChildren', - }); + ); } 1; diff --git a/cassandane/Cassandane/Cyrus/Backups.pm b/cassandane/Cassandane/Cyrus/Backups.pm index 89f924c088..4f5a8c582a 100644 --- a/cassandane/Cassandane/Cyrus/Backups.pm +++ b/cassandane/Cassandane/Cyrus/Backups.pm @@ -51,588 +51,572 @@ use Cassandane::Instance; $Data::Dumper::Sortkeys = 1; -sub new -{ - my $class = shift; - return $class->SUPER::new({ backups => 1, adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ backups => 1, adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub do_backup -{ - my ($self, $params) = @_; - - die "params not a hashref" - if defined $params and ref $params ne 'HASH'; - - my $users = $params->{users}; - my $mailboxes = $params->{mailboxes}; - - if (defined $users) { - $users = [ $users ] if not ref $users; - die "users is not an array reference" if ref $users ne 'ARRAY'; - xlog $self, "backing up users: @{$users}"; - $self->{instance}->run_command( - { cyrus => 1}, - qw(sync_client -vv -n backup -u), @{$users}); - } - - if (defined $mailboxes) { - $mailboxes = [ $mailboxes ] if not ref $mailboxes; - die "mailboxes is not an array reference" if ref $mailboxes ne 'ARRAY'; - xlog $self, "backing up mailboxes: @{$mailboxes}"; - $self->{instance}->run_command( - { cyrus => 1 }, - qw(sync_client -vv -n backup -m), @{$mailboxes}); - } - - if (not defined $users and not defined $mailboxes) { - xlog $self, "backing up all users"; - # n.b. this does not include shared mailboxes! see sync_client(8) - $self->{instance}->run_command( - { cyrus => 1 }, - qw(sync_client -vv -n backup -A)); - } -} - -sub do_xbackup -{ - my ($self, $pattern, $channel) = @_; - - die "do_xbackup needs a pattern" if not $pattern; - $channel //= 'backup'; - - my $admintalk = $self->{adminstore}->get_client(); - - my %untagged; - my $handler = sub { - my ($response, $args, undef) = @_; - return if scalar @{$args} != 2; - my ($type, $val) = @{$args}; - push @{$untagged{uc $response}->{$type}}, $val; - }; - my %callbacks = ( - 'ok' => $handler, - 'no' => $handler, +sub do_backup { + my ($self, $params) = @_; + + die "params not a hashref" + if defined $params and ref $params ne 'HASH'; + + my $users = $params->{users}; + my $mailboxes = $params->{mailboxes}; + + if (defined $users) { + $users = [$users] if not ref $users; + die "users is not an array reference" if ref $users ne 'ARRAY'; + xlog $self, "backing up users: @{$users}"; + $self->{instance}->run_command({ cyrus => 1 }, + qw(sync_client -vv -n backup -u), @{$users}); + } + + if (defined $mailboxes) { + $mailboxes = [$mailboxes] if not ref $mailboxes; + die "mailboxes is not an array reference" if ref $mailboxes ne 'ARRAY'; + xlog $self, "backing up mailboxes: @{$mailboxes}"; + $self->{instance}->run_command( + { cyrus => 1 }, + qw(sync_client -vv -n backup -m), + @{$mailboxes} ); - - $admintalk->_imap_cmd('xbackup', 0, \%callbacks, - $pattern, $channel); - if (wantarray) { - return ($admintalk->get_last_completion_response(), \%untagged); - } - else { - return $admintalk->get_last_completion_response(); - } + } + + if (not defined $users and not defined $mailboxes) { + xlog $self, "backing up all users"; + # n.b. this does not include shared mailboxes! see sync_client(8) + $self->{instance} + ->run_command({ cyrus => 1 }, qw(sync_client -vv -n backup -A)); + } } -sub cyr_backup_json -{ - my ($self, $params, $subcommand, @args) = @_; - - die "params not a hashref" - if defined $params and ref $params ne 'HASH'; - die "invalid subcommand: $subcommand" - if not grep { $_ eq $subcommand } qw(chunks mailboxes messages headers); - - my $instance = $params->{instance} // $self->{backups}; - my $user = $params->{user} // 'cassandane'; - my $mailbox = $params->{mailbox}; - - my $out = "$instance->{basedir}/$self->{_name}" - . "-cyr_backup-$user-json-$subcommand.stdout"; - my $err = "$instance->{basedir}/$self->{_name}" - . "-cyr_backup-$user-json-$subcommand.stderr"; - - my ($mode, $backup); - if (defined $mailbox) { - $mode = '-m'; - $backup = $mailbox; - } - else { - $mode = '-u'; - $backup = $user; - } - - $instance->run_command( - { cyrus => 1, - redirects => { 'stdout' => $out, - 'stderr' => $err } }, - 'cyr_backup', $mode, $backup, 'json', $subcommand, @args - ); +sub do_xbackup { + my ($self, $pattern, $channel) = @_; + + die "do_xbackup needs a pattern" if not $pattern; + $channel //= 'backup'; + + my $admintalk = $self->{adminstore}->get_client(); + + my %untagged; + my $handler = sub { + my ($response, $args, undef) = @_; + return if scalar @{$args} != 2; + my ($type, $val) = @{$args}; + push @{ $untagged{ uc $response }->{$type} }, $val; + }; + my %callbacks = ( + 'ok' => $handler, + 'no' => $handler, + ); + + $admintalk->_imap_cmd('xbackup', 0, \%callbacks, $pattern, $channel); + if (wantarray) { + return ($admintalk->get_last_completion_response(), \%untagged); + } else { + return $admintalk->get_last_completion_response(); + } +} - return JSON::decode_json(slurp_file($out)); +sub cyr_backup_json { + my ($self, $params, $subcommand, @args) = @_; + + die "params not a hashref" + if defined $params and ref $params ne 'HASH'; + die "invalid subcommand: $subcommand" + if not grep { $_ eq $subcommand } qw(chunks mailboxes messages headers); + + my $instance = $params->{instance} // $self->{backups}; + my $user = $params->{user} // 'cassandane'; + my $mailbox = $params->{mailbox}; + + my $out = "$instance->{basedir}/$self->{_name}" + . "-cyr_backup-$user-json-$subcommand.stdout"; + my $err = "$instance->{basedir}/$self->{_name}" + . "-cyr_backup-$user-json-$subcommand.stderr"; + + my ($mode, $backup); + if (defined $mailbox) { + $mode = '-m'; + $backup = $mailbox; + } else { + $mode = '-u'; + $backup = $user; + } + + $instance->run_command( + { + cyrus => 1, + redirects => { + 'stdout' => $out, + 'stderr' => $err + } + }, + 'cyr_backup', + $mode, $backup, 'json', + $subcommand, + @args + ); + + return JSON::decode_json(slurp_file($out)); } -sub backup_exists -{ - my ($self, $mode, $backup) = @_; - - my $rc = $self->{backups}->run_command( - { - cyrus => 1, - handlers => { - exited_abnormally => sub { - my (undef, $code) = @_; - return $code - }, - }, - }, - 'ctl_backups', 'list', $mode, $backup - ); +sub backup_exists { + my ($self, $mode, $backup) = @_; - return $rc == 0; + my $rc = $self->{backups}->run_command( + { + cyrus => 1, + handlers => { + exited_abnormally => sub { + my (undef, $code) = @_; + return $code; + }, + }, + }, + 'ctl_backups', + 'list', + $mode, + $backup + ); + + return $rc == 0; } -sub assert_backups_exist -{ - my ($self, $params) = @_; +sub assert_backups_exist { + my ($self, $params) = @_; - my @users = exists $params->{users} ? @{$params->{users}} : (); - my @mailboxes = exists $params->{mailboxes} ? @{$params->{mailboxes}} : (); - my @filenames = exists $params->{filenames} ? @{$params->{filenames}} : (); + my @users = exists $params->{users} ? @{ $params->{users} } : (); + my @mailboxes = exists $params->{mailboxes} ? @{ $params->{mailboxes} } : (); + my @filenames = exists $params->{filenames} ? @{ $params->{filenames} } : (); - foreach my $u (@users) { - my $x = $self->backup_exists('-u', $u); - $self->assert($x, "no backup found for user $u"); - } + foreach my $u (@users) { + my $x = $self->backup_exists('-u', $u); + $self->assert($x, "no backup found for user $u"); + } - foreach my $m (@mailboxes) { - my $x = $self->backup_exists('-m', $m); - $self->assert($x, "no backup found for mailbox $m"); - } + foreach my $m (@mailboxes) { + my $x = $self->backup_exists('-m', $m); + $self->assert($x, "no backup found for mailbox $m"); + } - foreach my $f (@filenames) { - my $x = $self->backup_exists('-f', $f); - $self->assert($x, "no backup found for filename $f"); - } + foreach my $f (@filenames) { + my $x = $self->backup_exists('-f', $f); + $self->assert($x, "no backup found for filename $f"); + } } -sub assert_backups_not_exist -{ - my ($self, $params) = @_; +sub assert_backups_not_exist { + my ($self, $params) = @_; - my @users = exists $params->{users} ? @{$params->{users}} : (); - my @mailboxes = exists $params->{mailboxes} ? @{$params->{mailboxes}} : (); - my @filenames = exists $params->{filenames} ? @{$params->{filenames}} : (); + my @users = exists $params->{users} ? @{ $params->{users} } : (); + my @mailboxes = exists $params->{mailboxes} ? @{ $params->{mailboxes} } : (); + my @filenames = exists $params->{filenames} ? @{ $params->{filenames} } : (); - foreach my $u (@users) { - my $x = $self->backup_exists('-u', $u); - $self->assert(!$x, "unexpected backup found for user $u"); - } + foreach my $u (@users) { + my $x = $self->backup_exists('-u', $u); + $self->assert(!$x, "unexpected backup found for user $u"); + } - foreach my $m (@mailboxes) { - my $x = $self->backup_exists('-m', $m); - $self->assert(!$x, "unexpected backup found for mailbox $m"); - } + foreach my $m (@mailboxes) { + my $x = $self->backup_exists('-m', $m); + $self->assert(!$x, "unexpected backup found for mailbox $m"); + } - foreach my $f (@filenames) { - my $x = $self->backup_exists('-f', $f); - $self->assert(!$x, "unexpected backup found for filename $f"); - } + foreach my $f (@filenames) { + my $x = $self->backup_exists('-f', $f); + $self->assert(!$x, "unexpected backup found for filename $f"); + } } sub test_aaasetup - :min_version_3_0 :needs_component_backup -{ - my ($self) = @_; + : min_version_3_0 : needs_component_backup { + my ($self) = @_; - # does everything set up and tear down cleanly? - $self->assert(1); + # does everything set up and tear down cleanly? + $self->assert(1); } sub test_basic - :min_version_3_0 :needs_component_backup -{ - my ($self) = @_; + : min_version_3_0 : needs_component_backup { + my ($self) = @_; - $self->do_backup({ users => 'cassandane' }); + $self->do_backup({ users => 'cassandane' }); - my $chunks = $self->cyr_backup_json({}, 'chunks'); + my $chunks = $self->cyr_backup_json({}, 'chunks'); - $self->assert_equals(1, scalar @{$chunks}); - $self->assert_equals(0, $chunks->[0]->{offset}); - $self->assert_equals(1, $chunks->[0]->{id}); - # an empty chunk has a 29 byte prefix - # make sure the chunk isn't empty -- it should at least send through - # the state of an empty inbox - $self->assert($chunks->[0]->{length} > 29); + $self->assert_equals(1, scalar @{$chunks}); + $self->assert_equals(0, $chunks->[0]->{offset}); + $self->assert_equals(1, $chunks->[0]->{id}); + # an empty chunk has a 29 byte prefix + # make sure the chunk isn't empty -- it should at least send through + # the state of an empty inbox + $self->assert($chunks->[0]->{length} > 29); } sub test_messages - :min_version_3_0 :needs_component_backup -{ - my ($self) = @_; + : min_version_3_0 : needs_component_backup { + my ($self) = @_; - my %exp; + my %exp; - $exp{A} = $self->make_message("Message A"); - $exp{B} = $self->make_message("Message B"); - $exp{C} = $self->make_message("Message C"); - $exp{D} = $self->make_message("Message D"); + $exp{A} = $self->make_message("Message A"); + $exp{B} = $self->make_message("Message B"); + $exp{C} = $self->make_message("Message C"); + $exp{D} = $self->make_message("Message D"); - $self->do_backup({ users => 'cassandane' }); + $self->do_backup({ users => 'cassandane' }); - my $messages = $self->cyr_backup_json({}, 'messages'); + my $messages = $self->cyr_backup_json({}, 'messages'); - # backup should contain four messages - $self->assert_equals(4, scalar @{$messages}); + # backup should contain four messages + $self->assert_equals(4, scalar @{$messages}); - my $headers = $self->cyr_backup_json({}, 'headers', map { $_->{guid} } @{$messages}); + my $headers + = $self->cyr_backup_json({}, 'headers', map { $_->{guid} } @{$messages}); - # transform out enough data for comparison purposes - my %expected = map { - $_->get_guid() => $_->get_header('X-Cassandane-Unique') - } values %exp; + # transform out enough data for comparison purposes + my %expected = map { $_->get_guid() => $_->get_header('X-Cassandane-Unique') } + values %exp; - my %actual = map { - $_ => $headers->{$_}->{'X-Cassandane-Unique'}->[0] - } keys %{$headers}; + my %actual = map { $_ => $headers->{$_}->{'X-Cassandane-Unique'}->[0] } + keys %{$headers}; - $self->assert_deep_equals(\%expected, \%actual); + $self->assert_deep_equals(\%expected, \%actual); } sub test_shared_mailbox - :min_version_3_0 :needs_component_backup :NoAltNamespace -{ - my ($self) = @_; + : min_version_3_0 : needs_component_backup : NoAltNamespace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # should definitely not be able to create a user that would conflict - # with where shared mailbox backups are stored! - $admintalk->create('user.%SHARED'); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + # should definitely not be able to create a user that would conflict + # with where shared mailbox backups are stored! + $admintalk->create('user.%SHARED'); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $admintalk->create('shared.folder'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl('shared.folder', 'cassandane' => 'lrswipkxtecdn'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create('shared.folder'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('shared.folder', 'cassandane' => 'lrswipkxtecdn'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->{store}->set_folder('shared.folder'); - my %exp; - $exp{A} = $self->make_message("Message A"); - $exp{B} = $self->make_message("Message B"); - $exp{C} = $self->make_message("Message C"); - $exp{D} = $self->make_message("Message D"); + $self->{store}->set_folder('shared.folder'); + my %exp; + $exp{A} = $self->make_message("Message A"); + $exp{B} = $self->make_message("Message B"); + $exp{C} = $self->make_message("Message C"); + $exp{D} = $self->make_message("Message D"); - $self->do_backup({ mailboxes => 'shared.folder' }); + $self->do_backup({ mailboxes => 'shared.folder' }); - my $messages = $self->cyr_backup_json({ mailbox => 'shared.folder'}, - 'messages'); + my $messages + = $self->cyr_backup_json({ mailbox => 'shared.folder' }, 'messages'); - # backup should contain four messages - $self->assert_equals(4, scalar @{$messages}); + # backup should contain four messages + $self->assert_equals(4, scalar @{$messages}); - my $headers = $self->cyr_backup_json({ mailbox => 'shared.folder' }, - 'headers', - map { $_->{guid} } @{$messages}); + my $headers = $self->cyr_backup_json({ mailbox => 'shared.folder' }, + 'headers', map { $_->{guid} } @{$messages}); - # transform out enough data for comparison purposes - my %expected = map { - $_->get_guid() => $_->get_header('X-Cassandane-Unique') - } values %exp; + # transform out enough data for comparison purposes + my %expected = map { $_->get_guid() => $_->get_header('X-Cassandane-Unique') } + values %exp; - my %actual = map { - $_ => $headers->{$_}->{'X-Cassandane-Unique'}->[0] - } keys %{$headers}; + my %actual = map { $_ => $headers->{$_}->{'X-Cassandane-Unique'}->[0] } + keys %{$headers}; - $self->assert_deep_equals(\%expected, \%actual); + $self->assert_deep_equals(\%expected, \%actual); - # XXX probably don't do this like this - $self->{backups}->run_command( - { cyrus => 1 }, - qw(ctl_backups -S -vvv verify -m shared.folder) - ); + # XXX probably don't do this like this + $self->{backups}->run_command({ cyrus => 1 }, + qw(ctl_backups -S -vvv verify -m shared.folder)); } sub test_deleted_mailbox - :min_version_3_0 :needs_component_backup :NoAltNamespace -{ - my ($self) = @_; + : min_version_3_0 : needs_component_backup : NoAltNamespace { + my ($self) = @_; - my $usertalk = $self->{store}->get_client(); - $usertalk->create('INBOX.foo'); - $self->assert_str_equals('ok', $usertalk->get_last_completion_response()); + my $usertalk = $self->{store}->get_client(); + $usertalk->create('INBOX.foo'); + $self->assert_str_equals('ok', $usertalk->get_last_completion_response()); - $self->{store}->set_folder('INBOX.foo'); + $self->{store}->set_folder('INBOX.foo'); - my %exp; - $exp{A} = $self->make_message("Message A"); - $exp{B} = $self->make_message("Message B"); - $exp{C} = $self->make_message("Message C"); - $exp{D} = $self->make_message("Message D"); + my %exp; + $exp{A} = $self->make_message("Message A"); + $exp{B} = $self->make_message("Message B"); + $exp{C} = $self->make_message("Message C"); + $exp{D} = $self->make_message("Message D"); - $self->do_backup({ users => 'cassandane' }); + $self->do_backup({ users => 'cassandane' }); - # backup should contain four messages - my $messages = $self->cyr_backup_json({}, 'messages'); - $self->assert_equals(4, scalar @{$messages}); + # backup should contain four messages + my $messages = $self->cyr_backup_json({}, 'messages'); + $self->assert_equals(4, scalar @{$messages}); - my $mailboxes = $self->cyr_backup_json({}, 'mailboxes'); - $self->assert_equals(2, scalar @{$mailboxes}); - $self->assert_deep_equals([qw(user.cassandane user.cassandane.foo)], - [ map { $_->{mboxname} } @{$mailboxes} ]); + my $mailboxes = $self->cyr_backup_json({}, 'mailboxes'); + $self->assert_equals(2, scalar @{$mailboxes}); + $self->assert_deep_equals( + [qw(user.cassandane user.cassandane.foo)], + [ map { $_->{mboxname} } @{$mailboxes} ] + ); - # delete the mailbox - $usertalk->delete('INBOX.foo'); - $self->assert_str_equals('ok', $usertalk->get_last_completion_response()); + # delete the mailbox + $usertalk->delete('INBOX.foo'); + $self->assert_str_equals('ok', $usertalk->get_last_completion_response()); - $self->do_backup({ users => 'cassandane' }); + $self->do_backup({ users => 'cassandane' }); - $messages = $self->cyr_backup_json({}, 'messages'); - $self->assert_equals(4, scalar @{$messages}); + $messages = $self->cyr_backup_json({}, 'messages'); + $self->assert_equals(4, scalar @{$messages}); - $mailboxes = $self->cyr_backup_json({}, 'mailboxes'); - $self->assert_equals(2, scalar @{$mailboxes}); - $self->assert_deep_equals([qw(user.cassandane DELETED.user.cassandane.foo)], - [ map { $_->{mboxname} =~ s/\.[A-F0-9]{8}$//r } - @{$mailboxes} ]); + $mailboxes = $self->cyr_backup_json({}, 'mailboxes'); + $self->assert_equals(2, scalar @{$mailboxes}); + $self->assert_deep_equals([qw(user.cassandane DELETED.user.cassandane.foo)], + [ map { $_->{mboxname} =~ s/\.[A-F0-9]{8}$//r } @{$mailboxes} ]); - my $deleted_mboxname = $mailboxes->[1]->{mboxname}; + my $deleted_mboxname = $mailboxes->[1]->{mboxname}; - # should be able to find the correct backup by the deleted name - # and see the four messages in it - $messages = $self->cyr_backup_json({ mailbox => $deleted_mboxname }, - 'messages'); - $self->assert_equals(4, scalar @{$messages}); + # should be able to find the correct backup by the deleted name + # and see the four messages in it + $messages + = $self->cyr_backup_json({ mailbox => $deleted_mboxname }, 'messages'); + $self->assert_equals(4, scalar @{$messages}); } sub test_locks - :min_version_3_0 :needs_component_backup -{ - my ($self) = @_; - - # make sure there's a backup file - $self->do_backup({ users => 'cassandane' }); - - # lock it for a while - my $wait = 10; # seconds - my $sleeper = $self->{backups}->run_command( - { cyrus => 1, background => 1 }, - qw(ctl_backups -w -vvv lock -u cassandane -x ), "/bin/sleep $wait", - ); - - # give the sleeper a moment to start up so it can definitely get the - # lock without racing against the next bit... - sleep 2; - - # meanwhile, try to get another lock on the same backup - my $errfile = $self->{backups}->get_basedir() . "/ctl_backups_lock.stderr"; - my ($code, $output); - $self->{backups}->run_command( - { - cyrus => 1, - handlers => { - exited_abnormally => sub { (undef, $code) = @_ }, - }, - redirects => { - stderr => $errfile, - }, - }, - qw(ctl_backups -vvv lock -u cassandane -x ), "/bin/echo locked", - ); - - $output = slurp_file($errfile); - - # clean up after the sleeper - $self->{backups}->reap_command($sleeper); - - # expect the second lock failed, specifically due to being locked - $self->assert_num_equals(75, $code); # EX_TEMPFAIL - $self->assert_matches(qr{Mailbox is locked}, $output); + : min_version_3_0 : needs_component_backup { + my ($self) = @_; + + # make sure there's a backup file + $self->do_backup({ users => 'cassandane' }); + + # lock it for a while + my $wait = 10; # seconds + my $sleeper = $self->{backups}->run_command( + { cyrus => 1, background => 1 }, + qw(ctl_backups -w -vvv lock -u cassandane -x ), + "/bin/sleep $wait", + ); + + # give the sleeper a moment to start up so it can definitely get the + # lock without racing against the next bit... + sleep 2; + + # meanwhile, try to get another lock on the same backup + my $errfile = $self->{backups}->get_basedir() . "/ctl_backups_lock.stderr"; + my ($code, $output); + $self->{backups}->run_command( + { + cyrus => 1, + handlers => { + exited_abnormally => sub { (undef, $code) = @_ }, + }, + redirects => { + stderr => $errfile, + }, + }, + qw(ctl_backups -vvv lock -u cassandane -x ), + "/bin/echo locked", + ); + + $output = slurp_file($errfile); + + # clean up after the sleeper + $self->{backups}->reap_command($sleeper); + + # expect the second lock failed, specifically due to being locked + $self->assert_num_equals(75, $code); # EX_TEMPFAIL + $self->assert_matches(qr{Mailbox is locked}, $output); } sub test_xbackup - :min_version_3_0 :UnixHierarchySep :VirtDomains :needs_component_backup -{ - my ($self) = @_; - my $id = 1; - - my @users = qw( - user@example.com - foo.bar@example.com - ); - - my @folders = qw( Drafts Sent Trash ); - - # create the new users - my $admintalk = $self->{adminstore}->get_client(); - foreach my $u (@users) { - $self->{instance}->create_user($u, subdirs => \@folders); - } - - # we also want to test the cassandane user (which was already created) - unshift @users, 'cassandane'; + : min_version_3_0 : UnixHierarchySep : VirtDomains : needs_component_backup { + my ($self) = @_; + my $id = 1; + + my @users = qw( + user@example.com + foo.bar@example.com + ); + + my @folders = qw( Drafts Sent Trash ); + + # create the new users + my $admintalk = $self->{adminstore}->get_client(); + foreach my $u (@users) { + $self->{instance}->create_user($u, subdirs => \@folders); + } + + # we also want to test the cassandane user (which was already created) + unshift @users, 'cassandane'; + foreach my $f (@folders) { + $admintalk->create("user/cassandane/$f"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } + + # shouldn't be backup files for these users yet + $self->assert_backups_not_exist({ users => \@users }); + + # kick off a backup with xbackup and a pattern + my ($status, $details) = $self->do_xbackup('user/*'); + $self->assert_str_equals('ok', $status); + $self->assert_deep_equals([ sort @users ], + [ sort @{ $details->{OK}->{USER} } ]); + + # backups should exist now, but with no messages + $self->assert_backups_exist({ users => \@users }); + foreach my $u (@users) { + my $messages = $self->cyr_backup_json({ user => $u }, 'messages'); + $self->assert_num_equals(0, scalar @{$messages}); + } + + # add some content -- four messages per folder per user + my %exp; + foreach my $u (@users) { foreach my $f (@folders) { - $admintalk->create("user/cassandane/$f"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my ($l, $d) = split '@', $u; + my $p = "user/$l/$f"; + $p .= "\@$d" if $d; + $self->{adminstore}->set_folder($p); + for (1 .. 4) { + $exp{$u}->{$f}->{$id} + = $self->make_message("Message $id", store => $self->{adminstore}); + $id++; + } } + } - # shouldn't be backup files for these users yet - $self->assert_backups_not_exist({ users => \@users }); - - # kick off a backup with xbackup and a pattern - my ($status, $details) = $self->do_xbackup('user/*'); + # let's xbackup and check each user individually + foreach my $u (@users) { + # run xbackup + my ($status, $details) = $self->do_xbackup("user/$u"); $self->assert_str_equals('ok', $status); - $self->assert_deep_equals([sort @users], [sort @{$details->{OK}->{USER}}]); - - # backups should exist now, but with no messages - $self->assert_backups_exist({ users => \@users }); - foreach my $u (@users) { - my $messages = $self->cyr_backup_json({ user => $u }, 'messages'); - $self->assert_num_equals(0, scalar @{$messages}); - } - - # add some content -- four messages per folder per user - my %exp; - foreach my $u (@users) { - foreach my $f (@folders) { - my ($l, $d) = split '@', $u; - my $p = "user/$l/$f"; - $p .= "\@$d" if $d; - $self->{adminstore}->set_folder($p); - for (1..4) { - $exp{$u}->{$f}->{$id} = - $self->make_message("Message $id", - store => $self->{adminstore}); - $id++; - } - } - } + $self->assert_deep_equals([$u], $details->{OK}->{USER}); - # let's xbackup and check each user individually - foreach my $u (@users) { - # run xbackup - my ($status, $details) = $self->do_xbackup("user/$u"); - $self->assert_str_equals('ok', $status); - $self->assert_deep_equals([$u], $details->{OK}->{USER}); + # backup should contain four messages per folder + my $messages = $self->cyr_backup_json({ user => $u }, 'messages'); + $self->assert_num_equals(4 * scalar(@folders), scalar @{$messages}); - # backup should contain four messages per folder - my $messages = $self->cyr_backup_json({ user => $u }, 'messages'); - $self->assert_num_equals(4 * scalar(@folders), scalar @{$messages}); - - # check they're the right messages - my $headers = $self->cyr_backup_json({ user => $u }, 'headers', - map { $_->{guid} } @{$messages}); + # check they're the right messages + my $headers = $self->cyr_backup_json({ user => $u }, + 'headers', map { $_->{guid} } @{$messages}); - my %expected = map { - $_->get_guid() => $_->get_header('X-Cassandane-Unique') - } map { values %{$_} } values %{$exp{$u}}; + my %expected + = map { $_->get_guid() => $_->get_header('X-Cassandane-Unique') } + map { values %{$_} } values %{ $exp{$u} }; - my %actual = map { - $_ => $headers->{$_}->{'X-Cassandane-Unique'}->[0] - } keys %{$headers}; + my %actual = map { $_ => $headers->{$_}->{'X-Cassandane-Unique'}->[0] } + keys %{$headers}; - $self->assert_deep_equals(\%expected, \%actual); - } + $self->assert_deep_equals(\%expected, \%actual); + } - # let's also xbackup all users with a pattern - ($status, $details) = $self->do_xbackup("user/*"); - $self->assert_str_equals('ok', $status); + # let's also xbackup all users with a pattern + ($status, $details) = $self->do_xbackup("user/*"); + $self->assert_str_equals('ok', $status); - # each user should only be processed once, even though "user/*" pattern - # also matches all their subfolders - $self->assert_deep_equals([sort @users], [sort @{$details->{OK}->{USER}}]); + # each user should only be processed once, even though "user/*" pattern + # also matches all their subfolders + $self->assert_deep_equals([ sort @users ], + [ sort @{ $details->{OK}->{USER} } ]); } sub test_xbackup_shared - :min_version_3_0 :UnixHierarchySep :VirtDomains :needs_component_backup -{ - my ($self) = @_; - my $id = 1; - - my @folders = qw( sh1 sh2 ); - my @subfolders = qw( foo bar baz ); - - # create the shared folders - my $admintalk = $self->{adminstore}->get_client(); - foreach my $top (@folders) { - $admintalk->create($top); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl($top, 'admin' => 'lrswipkxtecdan'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - foreach my $sub (@subfolders) { - $admintalk->create("$top/$sub"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl("$top/$sub", 'admin' => 'lrswipkxtecdan'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - } - } + : min_version_3_0 : UnixHierarchySep : VirtDomains : needs_component_backup { + my ($self) = @_; + my $id = 1; - # shouldn't be backup files for these mailboxes yet - $self->assert_backups_not_exist({ mailboxes => \@folders }); + my @folders = qw( sh1 sh2 ); + my @subfolders = qw( foo bar baz ); - # kick off a backup with xbackup and a pattern - my ($status, $details) = $self->do_xbackup('sh*'); - $self->assert_str_equals('ok', $status); - $self->assert_num_equals(scalar(@folders) * (1 + scalar(@subfolders)), - scalar @{$details->{OK}->{MAILBOX}}); + # create the shared folders + my $admintalk = $self->{adminstore}->get_client(); + foreach my $top (@folders) { + $admintalk->create($top); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl($top, 'admin' => 'lrswipkxtecdan'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - # backups should exist now, but with no messages - $self->assert_backups_exist({ mailboxes => \@folders }); - foreach my $f (@folders) { - my $messages = $self->cyr_backup_json({ mailbox => $f }, 'messages'); - $self->assert_num_equals(0, scalar @{$messages}); + foreach my $sub (@subfolders) { + $admintalk->create("$top/$sub"); + $self->assert_str_equals('ok', + $admintalk->get_last_completion_response()); + $admintalk->setacl("$top/$sub", 'admin' => 'lrswipkxtecdan'); + $self->assert_str_equals('ok', + $admintalk->get_last_completion_response()); } - - # add some content -- four messages per folder - my %exp; - foreach my $top (@folders) { - foreach my $sub (@subfolders) { - my $p = "$top/$sub"; - $self->{adminstore}->set_folder($p); - for (1..4) { - $exp{$id} = - $self->make_message("Message $id", - store => $self->{adminstore}); - $id++; - } - } + } + + # shouldn't be backup files for these mailboxes yet + $self->assert_backups_not_exist({ mailboxes => \@folders }); + + # kick off a backup with xbackup and a pattern + my ($status, $details) = $self->do_xbackup('sh*'); + $self->assert_str_equals('ok', $status); + $self->assert_num_equals( + scalar(@folders) * (1 + scalar(@subfolders)), + scalar @{ $details->{OK}->{MAILBOX} } + ); + + # backups should exist now, but with no messages + $self->assert_backups_exist({ mailboxes => \@folders }); + foreach my $f (@folders) { + my $messages = $self->cyr_backup_json({ mailbox => $f }, 'messages'); + $self->assert_num_equals(0, scalar @{$messages}); + } + + # add some content -- four messages per folder + my %exp; + foreach my $top (@folders) { + foreach my $sub (@subfolders) { + my $p = "$top/$sub"; + $self->{adminstore}->set_folder($p); + for (1 .. 4) { + $exp{$id} + = $self->make_message("Message $id", store => $self->{adminstore}); + $id++; + } } + } - # xbackup again - ($status, $details) = $self->do_xbackup('sh*'); - $self->assert_str_equals('ok', $status); - $self->assert_num_equals(scalar(@folders) * (1 + scalar(@subfolders)), - scalar @{$details->{OK}->{MAILBOX}}); + # xbackup again + ($status, $details) = $self->do_xbackup('sh*'); + $self->assert_str_equals('ok', $status); + $self->assert_num_equals( + scalar(@folders) * (1 + scalar(@subfolders)), + scalar @{ $details->{OK}->{MAILBOX} } + ); - # backup should contain four messages per subfolder per folder - my $messages = $self->cyr_backup_json({ mailbox => $folders[0] }, - 'messages'); - $self->assert_num_equals(4 * scalar(@folders) * scalar(@subfolders), - scalar @{$messages}); + # backup should contain four messages per subfolder per folder + my $messages = $self->cyr_backup_json({ mailbox => $folders[0] }, 'messages'); + $self->assert_num_equals(4 * scalar(@folders) * scalar(@subfolders), + scalar @{$messages}); - # check they're the right messages - my $headers = $self->cyr_backup_json({ mailbox => $folders[0] }, 'headers', - map { $_->{guid} } @{$messages}); + # check they're the right messages + my $headers = $self->cyr_backup_json({ mailbox => $folders[0] }, + 'headers', map { $_->{guid} } @{$messages}); - my %expected = map { - $_->get_guid() => $_->get_header('X-Cassandane-Unique') - } values %exp; + my %expected = map { $_->get_guid() => $_->get_header('X-Cassandane-Unique') } + values %exp; - my %actual = map { - $_ => $headers->{$_}->{'X-Cassandane-Unique'}->[0] - } keys %{$headers}; + my %actual = map { $_ => $headers->{$_}->{'X-Cassandane-Unique'}->[0] } + keys %{$headers}; - $self->assert_deep_equals(\%expected, \%actual); + $self->assert_deep_equals(\%expected, \%actual); } 1; diff --git a/cassandane/Cassandane/Cyrus/Bug3072.pm b/cassandane/Cassandane/Cyrus/Bug3072.pm index 0340bc1fcb..8d7766337e 100644 --- a/cassandane/Cassandane/Cyrus/Bug3072.pm +++ b/cassandane/Cassandane/Cyrus/Bug3072.pm @@ -46,42 +46,38 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test COPY behaviour with a very long sequence set # -sub test_copy_longset_slow -{ - my ($self) = @_; +sub test_copy_longset_slow { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.dest"); - for (1..2000) { - $self->make_message("Message $_"); - } - my $list = join(',', map { $_ * 2 } 1..1000); + $imaptalk->create("INBOX.dest"); + for (1 .. 2000) { + $self->make_message("Message $_"); + } + my $list = join(',', map { $_ * 2 } 1 .. 1000); - $imaptalk->copy($list, "INBOX.dest"); + $imaptalk->copy($list, "INBOX.dest"); - # XXX this doesn't even verify that the messages were copied! + # XXX this doesn't even verify that the messages were copied! } 1; diff --git a/cassandane/Cassandane/Cyrus/Bug3470.pm b/cassandane/Cassandane/Cyrus/Bug3470.pm index 39d24e6d78..cfb9fc0e6a 100644 --- a/cassandane/Cassandane/Cyrus/Bug3470.pm +++ b/cassandane/Cassandane/Cyrus/Bug3470.pm @@ -46,124 +46,100 @@ use Data::Dumper; use lib '.'; use base qw(Cassandane::Cyrus::TestCase); -sub new -{ - my $class = shift; +sub new { + my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(virtdomains => 'userid'); - $config->set(unixhierarchysep => 'on'); - $config->set(altnamespace => 'yes'); + my $config = Cassandane::Config->default()->clone(); + $config->set(virtdomains => 'userid'); + $config->set(unixhierarchysep => 'on'); + $config->set(altnamespace => 'yes'); - return $class->SUPER::new({ config => $config }, @_); + return $class->SUPER::new({ config => $config }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # Bug #3470 folders - # sub folders only - $imaptalk->create("Drafts") || die; - $imaptalk->create("2001/05/wk18") || die; - $imaptalk->create("2001/05/wk19") || die; - $imaptalk->create("2001/05/wk20") || die; - $imaptalk->subscribe("2001/05/wk20") || die; + # Bug #3470 folders + # sub folders only + $imaptalk->create("Drafts") || die; + $imaptalk->create("2001/05/wk18") || die; + $imaptalk->create("2001/05/wk19") || die; + $imaptalk->create("2001/05/wk20") || die; + $imaptalk->subscribe("2001/05/wk20") || die; } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test LSUB behaviour # -sub test_list_percent -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - my @inbox_flags = qw( \\HasNoChildren ); - my @inter_flags = qw( \\HasChildren ); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj < 3) { - unshift @inbox_flags, qw( \\Noinferiors ); - unshift @inter_flags, qw( \\Noselect ); - } - elsif ($maj == 3 && $min < 5) { - unshift @inter_flags, qw( \\Noselect ); - } - - my $alldata = $imaptalk->list("", "%"); - $self->assert_deep_equals($alldata, [ - [ - \@inbox_flags, - '/', - 'INBOX' - ], - [ - \@inter_flags, - '/', - '2001' - ], - [ - [ - '\\HasNoChildren' - ], - '/', - 'Drafts' - ] - ], "LIST data mismatch: " . Dumper($alldata, \@inbox_flags)); +sub test_list_percent { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + my @inbox_flags = qw( \\HasNoChildren ); + my @inter_flags = qw( \\HasChildren ); + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj < 3) { + unshift @inbox_flags, qw( \\Noinferiors ); + unshift @inter_flags, qw( \\Noselect ); + } elsif ($maj == 3 && $min < 5) { + unshift @inter_flags, qw( \\Noselect ); + } + + my $alldata = $imaptalk->list("", "%"); + $self->assert_deep_equals( + $alldata, + [ + [ \@inbox_flags, '/', 'INBOX' ], + [ \@inter_flags, '/', '2001' ], + [ ['\\HasNoChildren'], '/', 'Drafts' ] + ], + "LIST data mismatch: " . Dumper($alldata, \@inbox_flags) + ); } # # Test LSUB behaviour # -sub test_list_2011 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - my @inter_flags = qw( \\HasChildren ); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj < 3 || ($maj == 3 && $min < 5)) { - unshift @inter_flags, qw( \\Noselect ); - } - - my $alldata = $imaptalk->list("", "2001"); - $self->assert_deep_equals($alldata, [ - [ - \@inter_flags, - '/', - '2001' - ] - ], "LIST data mismatch: " . Dumper($alldata)); +sub test_list_2011 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + my @inter_flags = qw( \\HasChildren ); + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj < 3 || ($maj == 3 && $min < 5)) { + unshift @inter_flags, qw( \\Noselect ); + } + + my $alldata = $imaptalk->list("", "2001"); + $self->assert_deep_equals( + $alldata, + [ [ \@inter_flags, '/', '2001' ] ], + "LIST data mismatch: " . Dumper($alldata) + ); } -sub test_lsub -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - my $alldata = $imaptalk->lsub("", "2001"); - $self->assert_deep_equals($alldata, [ - [ - [ - '\\Noselect', - '\\HasChildren' - ], - '/', - '2001' - ] - ], "LSUB data mismatch: " . Dumper($alldata)); +sub test_lsub { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + my $alldata = $imaptalk->lsub("", "2001"); + $self->assert_deep_equals( + $alldata, + [ [ [ '\\Noselect', '\\HasChildren' ], '/', '2001' ] ], + "LSUB data mismatch: " . Dumper($alldata) + ); } 1; diff --git a/cassandane/Cassandane/Cyrus/Bug3649.pm b/cassandane/Cassandane/Cyrus/Bug3649.pm index 0e27cafd3d..97ec041ff7 100644 --- a/cassandane/Cassandane/Cyrus/Bug3649.pm +++ b/cassandane/Cassandane/Cyrus/Bug3649.pm @@ -46,41 +46,38 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_delete_subuser -{ - my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); +sub test_delete_subuser { + my ($self) = @_; + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - xlog $self, "Test Cyrus extension which renames a user to a different partition"; + xlog $self, + "Test Cyrus extension which renames a user to a different partition"; - # create and prepare the user - $self->{instance}->create_user('admin1'); - $adminstore->set_folder('user.admin1'); - for ('A'..'Z') { - $self->make_message("Message $_", store => $adminstore); - } - $admintalk->unselect(); + # create and prepare the user + $self->{instance}->create_user('admin1'); + $adminstore->set_folder('user.admin1'); + for ('A' .. 'Z') { + $self->make_message("Message $_", store => $adminstore); + } + $admintalk->unselect(); - $admintalk->delete('user.admin1'); + $admintalk->delete('user.admin1'); } 1; diff --git a/cassandane/Cassandane/Cyrus/Bug3903.pm b/cassandane/Cassandane/Cyrus/Bug3903.pm index 05834e618f..387f68128f 100644 --- a/cassandane/Cassandane/Cyrus/Bug3903.pm +++ b/cassandane/Cassandane/Cyrus/Bug3903.pm @@ -45,97 +45,93 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(autocreate_quota => 101200); - return $class->SUPER::new({ - config => $config, - adminstore => 1, - }, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config->default()->clone(); + $config->set(autocreate_quota => 101200); + return $class->SUPER::new( + { + config => $config, + adminstore => 1, + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - $self->{instance}->create_user("foo", - subdirs => [ 'cassandane', ['cassandane', 'sent'] ]); + $self->{instance} + ->create_user("foo", subdirs => [ 'cassandane', [ 'cassandane', 'sent' ] ]); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->setacl("user.foo.cassandane.sent", "cassandane", "lrswp"); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->setacl("user.foo.cassandane.sent", "cassandane", "lrswp"); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_create_under_wrong_user - :NoAltNameSpace -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - - my $res = $talk->create('user.foo.cassandane.sent.Test1'); - $self->assert_null($res); # means it failed - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/permission denied/i); - - $res = $talk->create('user.foo.cassandane.Test2'); - $self->assert_null($res); # means it failed - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/permission denied/i); - - $res = $talk->create('user.foo.Test3'); - $self->assert_null($res); # means it failed - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/permission denied/i); + : NoAltNameSpace { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + + my $res = $talk->create('user.foo.cassandane.sent.Test1'); + $self->assert_null($res); # means it failed + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/permission denied/i); + + $res = $talk->create('user.foo.cassandane.Test2'); + $self->assert_null($res); # means it failed + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/permission denied/i); + + $res = $talk->create('user.foo.Test3'); + $self->assert_null($res); # means it failed + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/permission denied/i); } sub test_create_under_user - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $res = $talk->create('user.Test4'); - $self->assert_null($res); # means it failed - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/permission denied/i); + my $res = $talk->create('user.Test4'); + $self->assert_null($res); # means it failed + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/permission denied/i); } sub test_create_under_shared - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $res = $talk->create('shared.Test5'); - $self->assert_null($res); # means it failed - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/permission denied/i); + my $res = $talk->create('shared.Test5'); + $self->assert_null($res); # means it failed + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/permission denied/i); } sub test_create_at_top_level - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $res = $talk->create('Test6'); - $self->assert_null($res); # means it failed - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/permission denied/i); + my $res = $talk->create('Test6'); + $self->assert_null($res); # means it failed + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/permission denied/i); } 1; diff --git a/cassandane/Cassandane/Cyrus/Caldav.pm b/cassandane/Cassandane/Cyrus/Caldav.pm index 6d81015ef8..55f5c22d40 100644 --- a/cassandane/Cassandane/Cyrus/Caldav.pm +++ b/cassandane/Cassandane/Cyrus/Caldav.pm @@ -103,142 +103,139 @@ sub NEW_YORK { EOF } -sub new -{ - my $class = shift; +sub new { + my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane'); - $config->set(httpmodules => 'caldav'); - $config->set(calendar_user_address_set => 'example.com'); - $config->set(httpallowcompress => 'no'); - $config->set(caldav_historical_age => -1); - $config->set(icalendar_max_size => 100000); - $config->set(event_extra_params => 'vnd.cmu.davFilename vnd.cmu.davUid'); - $config->set(event_groups => 'calendar'); - return $class->SUPER::new({ - config => $config, - adminstore => 1, - services => ['imap', 'http'], - }, @_); + my $config = Cassandane::Config->default()->clone(); + $config->set(caldav_realm => 'Cassandane'); + $config->set(httpmodules => 'caldav'); + $config->set(calendar_user_address_set => 'example.com'); + $config->set(httpallowcompress => 'no'); + $config->set(caldav_historical_age => -1); + $config->set(icalendar_max_size => 100000); + $config->set(event_extra_params => 'vnd.cmu.davFilename vnd.cmu.davUid'); + $config->set(event_groups => 'calendar'); + return $class->SUPER::new( + { + config => $config, + adminstore => 1, + services => [ 'imap', 'http' ], + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $ENV{DEBUGDAV} = 1; - $ENV{JMAP_ALWAYS_FULL} = 1; +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $ENV{DEBUGDAV} = 1; + $ENV{JMAP_ALWAYS_FULL} = 1; } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub _all_keys_match -{ - my $a = shift; - my $b = shift; - my $errors = shift; +sub _all_keys_match { + my $a = shift; + my $b = shift; + my $errors = shift; - my $ref = ref($a); - unless ($ref eq ref($b)) { - push @$errors, "mismatched refs $ref / " . ref($b); - return 0; - } + my $ref = ref($a); + unless ($ref eq ref($b)) { + push @$errors, "mismatched refs $ref / " . ref($b); + return 0; + } - unless ($ref) { - unless (defined $a) { - return 1 unless defined $b; - return 0; - } - return 0 unless defined $b; - if (lc $a ne lc $b) { - push @$errors, "not equal $a / $b"; - return 0; - } - return 1; + unless ($ref) { + unless (defined $a) { + return 1 unless defined $b; + return 0; } + return 0 unless defined $b; + if (lc $a ne lc $b) { + push @$errors, "not equal $a / $b"; + return 0; + } + return 1; + } - if ($ref eq 'ARRAY') { - my @payloads = @$b; - my @nomatch; - foreach my $item (@$a) { - my $match; - my @rest; - foreach my $payload (@payloads) { - if (not $match and _all_keys_match($item, $payload, [])) { - $match = $payload; - } - else { - push @rest, $payload; - } - } - push @nomatch, $item unless $match; - @payloads = @rest; - } - if (@payloads or @nomatch) { - push @$errors, "failed to match\n" . Dumper(\@nomatch, \@payloads); - return 0; + if ($ref eq 'ARRAY') { + my @payloads = @$b; + my @nomatch; + foreach my $item (@$a) { + my $match; + my @rest; + foreach my $payload (@payloads) { + if (not $match and _all_keys_match($item, $payload, [])) { + $match = $payload; + } else { + push @rest, $payload; } - return 1; + } + push @nomatch, $item unless $match; + @payloads = @rest; + } + if (@payloads or @nomatch) { + push @$errors, "failed to match\n" . Dumper(\@nomatch, \@payloads); + return 0; } + return 1; + } - if ($ref eq 'HASH') { - foreach my $key (keys %$a) { - unless (exists $b->{$key}) { - push @$errors, "no key $key"; - return 0; - } - my @err; - unless (_all_keys_match($a->{$key}, $b->{$key}, \@err)) { - push @$errors, "mismatch for $key: @err"; - return 0; - } - } - return 1; + if ($ref eq 'HASH') { + foreach my $key (keys %$a) { + unless (exists $b->{$key}) { + push @$errors, "no key $key"; + return 0; + } + my @err; + unless (_all_keys_match($a->{$key}, $b->{$key}, \@err)) { + push @$errors, "mismatch for $key: @err"; + return 0; + } } + return 1; + } - if ($ref eq 'JSON::PP::Boolean' or $ref eq 'JSON::XS::Boolean') { - if ($a != $b) { - push @$errors, "mismatched boolean " . (!!$a) . " / " . (!!$b); - return 0; - } - return 1; + if ($ref eq 'JSON::PP::Boolean' or $ref eq 'JSON::XS::Boolean') { + if ($a != $b) { + push @$errors, "mismatched boolean " . (!!$a) . " / " . (!!$b); + return 0; } + return 1; + } - die "WEIRD REF $ref for $a"; + die "WEIRD REF $ref for $a"; } -sub assert_caldav_notified -{ - my $self = shift; - my @expected = @_; +sub assert_caldav_notified { + my $self = shift; + my @expected = @_; - my $newdata = $self->{instance}->getnotify(); - my @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; - my @payloads = map { decode_json($_->{MESSAGE}) } @imip; - foreach my $payload (@payloads) { - ($payload->{event}) = $self->{caldav}->vcalendarToEvents($payload->{ical}); - $payload->{method} = delete $payload->{event}{method}; - } + my $newdata = $self->{instance}->getnotify(); + my @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; + my @payloads = map { decode_json($_->{MESSAGE}) } @imip; + foreach my $payload (@payloads) { + ($payload->{event}) = $self->{caldav}->vcalendarToEvents($payload->{ical}); + $payload->{method} = delete $payload->{event}{method}; + } - my @err; - unless (_all_keys_match(\@expected, \@payloads, \@err)) { - $self->fail("@err"); - } + my @err; + unless (_all_keys_match(\@expected, \@payloads, \@err)) { + $self->fail("@err"); + } } sub _put_event { - my $self = shift; - my $CalendarId = shift; - my %props = @_; - my $uuid = delete $props{uuid} || $self->{caldav}->genuuid(); - my $href = "$CalendarId/$uuid.ics"; + my $self = shift; + my $CalendarId = shift; + my %props = @_; + my $uuid = delete $props{uuid} || $self->{caldav}->genuuid(); + my $href = "$CalendarId/$uuid.ics"; - my $card = <{caldav}->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $self->{caldav} + ->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); } -sub bogus_test_rfc6638_3_2_1_setpartstat_agentserver -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; +sub bogus_test_rfc6638_3_2_1_setpartstat_agentserver { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "attempt to set the partstat to something other than NEEDS-ACTION"; - # XXX - the server should reject this - $self->_put_event($CalendarId, lines => <_put_event($CalendarId, lines => <assert_caldav_notified( - { recipient => "test2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "test2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); } ############ REPLIES ############# sub slurp { - my $testdir = shift; - my $name = shift; - my $ext = shift; + my $testdir = shift; + my $name = shift; + my $ext = shift; - return slurp_file("$testdir/$name.$ext"); + return slurp_file("$testdir/$name.$ext"); } sub _safeeq { - my ($a, $b) = @_; - my $json = JSON::XS->new->canonical; - return $json->encode([$a]) eq $json->encode([$b]); + my ($a, $b) = @_; + my $json = JSON::XS->new->canonical; + return $json->encode([$a]) eq $json->encode([$b]); } use Cassandane::Tiny::Loader 'tiny-tests/Caldav'; diff --git a/cassandane/Cassandane/Cyrus/CaldavAlarm.pm b/cassandane/Cassandane/Cyrus/CaldavAlarm.pm index 526011e184..32f24f0c8c 100644 --- a/cassandane/Cassandane/Cyrus/CaldavAlarm.pm +++ b/cassandane/Cassandane/Cyrus/CaldavAlarm.pm @@ -53,115 +53,114 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; +sub new { + my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane'); - $config->set(conversations => 'yes'); - $config->set(httpmodules => 'caldav jmap tzdist'); - $config->set(httpallowcompress => 'no'); - $config->set(caldav_historical_age => -1); - $config->set(calendar_minimum_alarm_interval => '61s'); - $config->set(jmap_nonstandard_extensions => 'yes'); - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - services => ['imap', 'http'], - }, @_); + my $config = Cassandane::Config->default()->clone(); + $config->set(caldav_realm => 'Cassandane'); + $config->set(conversations => 'yes'); + $config->set(httpmodules => 'caldav jmap tzdist'); + $config->set(httpallowcompress => 'no'); + $config->set(caldav_historical_age => -1); + $config->set(calendar_minimum_alarm_interval => '61s'); + $config->set(jmap_nonstandard_extensions => 'yes'); + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + services => [ 'imap', 'http' ], + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - $self->{caldav} = Net::CalDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'urn:ietf:params:jmap:calendars:preferences', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/debug', - ]); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + $self->{caldav} = Net::CalDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'urn:ietf:params:jmap:calendars:preferences', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/debug', + ]); } sub _can_match { - my $event = shift; - my $want = shift; + my $event = shift; + my $want = shift; - # I wrote a really good one of these for Caldav, but this will do for now - foreach my $key (keys %$want) { - return 0 if not exists $event->{$key}; - return 0 if $event->{$key} ne $want->{$key}; - } + # I wrote a really good one of these for Caldav, but this will do for now + foreach my $key (keys %$want) { + return 0 if not exists $event->{$key}; + return 0 if $event->{$key} ne $want->{$key}; + } - return 1; + return 1; } sub assert_alarms { - my $self = shift; - my @want = @_; - # pick first calendar alarm from notifications - my $data = $self->{instance}->getnotify(); - if ($self->{replica}) { - my $more = $self->{replica}->getnotify(); - push @$data, @$more; - } - my @events; - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @events, $e; - } - } + my $self = shift; + my @want = @_; + # pick first calendar alarm from notifications + my $data = $self->{instance}->getnotify(); + if ($self->{replica}) { + my $more = $self->{replica}->getnotify(); + push @$data, @$more; + } + my @events; + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @events, $e; + } } + } - my @left; - while (my $event = shift @events) { - my $found = 0; - my @newwant; - foreach my $data (@want) { - if (not $found and _can_match($event, $data)) { - $found = 1; - } - else { - push @newwant, $data; - } - } - if (not $found) { - push @left, $event; - } - @want = @newwant; + my @left; + while (my $event = shift @events) { + my $found = 0; + my @newwant; + foreach my $data (@want) { + if (not $found and _can_match($event, $data)) { + $found = 1; + } else { + push @newwant, $data; + } } - - if (@want or @left) { - my $dump = Data::Dumper->Dump([\@want, \@left], [qw(want left)]); - $self->assert_equals(0, scalar @want, - "expected events were not received:\n$dump"); - $self->assert_equals(0, scalar @left, - "unexpected extra events were received:\n$dump"); + if (not $found) { + push @left, $event; } + @want = @newwant; + } + + if (@want or @left) { + my $dump = Data::Dumper->Dump([ \@want, \@left ], [qw(want left)]); + $self->assert_equals(0, scalar @want, + "expected events were not received:\n$dump"); + $self->assert_equals(0, scalar @left, + "unexpected extra events were received:\n$dump"); + } } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } use Cassandane::Tiny::Loader 'tiny-tests/CaldavAlarm'; diff --git a/cassandane/Cassandane/Cyrus/Carddav.pm b/cassandane/Cassandane/Cyrus/Carddav.pm index 35e43988e5..d55c4f1825 100644 --- a/cassandane/Cassandane/Cyrus/Carddav.pm +++ b/cassandane/Cassandane/Cyrus/Carddav.pm @@ -53,33 +53,33 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; +sub new { + my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane'); - $config->set(httpmodules => 'carddav caldav'); - $config->set(httpallowcompress => 'no'); - $config->set(vcard_max_size => 100000); - return $class->SUPER::new({ - adminstore => 1, - config => $config, - services => ['imap', 'http'], - }, @_); + my $config = Cassandane::Config->default()->clone(); + $config->set(caldav_realm => 'Cassandane'); + $config->set(httpmodules => 'carddav caldav'); + $config->set(httpallowcompress => 'no'); + $config->set(vcard_max_size => 100000); + return $class->SUPER::new( + { + adminstore => 1, + config => $config, + services => [ 'imap', 'http' ], + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $ENV{DEBUGDAV} = 1; +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $ENV{DEBUGDAV} = 1; } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } use Cassandane::Tiny::Loader 'tiny-tests/Carddav'; diff --git a/cassandane/Cassandane/Cyrus/ClamAV.pm b/cassandane/Cassandane/Cyrus/ClamAV.pm index 4af504b969..287efbe765 100644 --- a/cassandane/Cassandane/Cyrus/ClamAV.pm +++ b/cassandane/Cassandane/Cyrus/ClamAV.pm @@ -52,191 +52,182 @@ use Cassandane::Instance; $Data::Dumper::Sortkeys = 1; my %eicar_attached = ( - mime_type => "multipart/mixed", - mime_boundary => "boundary", - body => "" - . "--boundary\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body" - . "\r\n" - . "--boundary\r\n" - . "Content-Disposition: attachment; filename=eicar.txt;\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - # This is the EICAR AV test file: - # http://www.eicar.org/83-0-Anti-Malware-Testfile.html - . 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' - . "\r\n" - . "--boundary\r\n", + mime_type => "multipart/mixed", + mime_boundary => "boundary", + body => "" + . "--boundary\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "body" . "\r\n" + . "--boundary\r\n" + . "Content-Disposition: attachment; filename=eicar.txt;\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + # This is the EICAR AV test file: + # http://www.eicar.org/83-0-Anti-Malware-Testfile.html + . 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' + . "\r\n" + . "--boundary\r\n", ); -my %custom_header = ( - 'extra_headers' => [ - [ 'x-delete-me' => 'please' ], - ], -); +my %custom_header = ('extra_headers' => [ [ 'x-delete-me' => 'please' ], ],); -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_aaasetup - :needs_dependency_clamav -{ - my ($self) = @_; + : needs_dependency_clamav { + my ($self) = @_; - # does everything set up and tear down cleanly? - $self->assert(1); + # does everything set up and tear down cleanly? + $self->assert(1); } # This test uses the AV engine, which can be very slow to initialise. sub test_remove_infected_slow - :needs_dependency_clamav :NoAltNamespace -{ - my ($self) = @_; - - # set up a shared folder that's easy to write to - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('shared.folder'); - $admintalk->setacl('shared.folder', 'cassandane' => 'lrswipkxtecd'); - - $self->{store}->set_fetch_attributes(qw(uid flags)); - - my $talk = $self->{store}->get_client(); - $talk->select("INBOX"); - $self->assert_num_equals(1, $talk->uid()); - $talk->select("shared.folder"); - $self->assert_num_equals(1, $talk->uid()); - - # put some test messages in INBOX (and verify) - $self->{store}->set_folder("INBOX"); - my %cass_exp; - $cass_exp{1} = $self->make_message("eicar attached", uid => 1, %eicar_attached); - $cass_exp{2} = $self->make_message("clean", uid => 2); - $self->check_messages(\%cass_exp, ( keyed_on => 'uid' )); - - # put some test messages in shared.folder (and verify) - $self->{store}->set_folder("shared.folder"); - my %shared_exp; - $shared_exp{1} = $self->make_message("eicar attached", uid => 1, %eicar_attached); - $shared_exp{2} = $self->make_message("clean", uid => 2); - $self->check_messages(\%shared_exp, ( keyed_on => 'uid' )); - - # run cyr_virusscan - my $out = "$self->{instance}->{basedir}/$self->{_name}-cyr_virusscan.stdout"; - $self->{instance}->run_command( - { cyrus => 1, - redirects => { 'stdout' => $out }, - }, 'cyr_virusscan', '-r'); - - # check the output - # user.cassandane 1 UNREAD Eicar-Test-Signature - # shared.folder 1 UNREAD Eicar-Test-Signature - $out = slurp_file($out); - xlog $self, $out; - - # XXX is there a better way than hard coding UID:1 ? - my ($v) = Cassandane::Instance->get_version(); - if ($v >= 3) { - $self->assert_matches( - qr/user\.cassandane\s+1\s+UNREAD\s+Eicar(?:-Test){0,1}-Signature/, - $out); - $self->assert_matches( - qr/shared\.folder\s+1\s+UNREAD\s+Eicar(?:-Test){0,1}-Signature/, - $out); - } - else { - # pre-3.0 a different output format was used - $self->assert_matches( - qr/Working\son\sshared\.folder\.\.\.\nVirus\sdetected\sin\smessage\s1:\sEicar(?:-Test){0,1}-Signature/, - $out); - $self->assert_matches( - qr/Working\son\suser\.cassandane\.\.\.\nVirus\sdetected\sin\smessage\s1:\sEicar(?:-Test){0,1}-Signature/, - $out); - } + : needs_dependency_clamav : NoAltNamespace { + my ($self) = @_; + + # set up a shared folder that's easy to write to + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('shared.folder'); + $admintalk->setacl('shared.folder', 'cassandane' => 'lrswipkxtecd'); + + $self->{store}->set_fetch_attributes(qw(uid flags)); + + my $talk = $self->{store}->get_client(); + $talk->select("INBOX"); + $self->assert_num_equals(1, $talk->uid()); + $talk->select("shared.folder"); + $self->assert_num_equals(1, $talk->uid()); + + # put some test messages in INBOX (and verify) + $self->{store}->set_folder("INBOX"); + my %cass_exp; + $cass_exp{1} + = $self->make_message("eicar attached", uid => 1, %eicar_attached); + $cass_exp{2} = $self->make_message("clean", uid => 2); + $self->check_messages(\%cass_exp, (keyed_on => 'uid')); + + # put some test messages in shared.folder (and verify) + $self->{store}->set_folder("shared.folder"); + my %shared_exp; + $shared_exp{1} + = $self->make_message("eicar attached", uid => 1, %eicar_attached); + $shared_exp{2} = $self->make_message("clean", uid => 2); + $self->check_messages(\%shared_exp, (keyed_on => 'uid')); + + # run cyr_virusscan + my $out = "$self->{instance}->{basedir}/$self->{_name}-cyr_virusscan.stdout"; + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { 'stdout' => $out }, + }, + 'cyr_virusscan', + '-r' + ); + + # check the output + # user.cassandane 1 UNREAD Eicar-Test-Signature + # shared.folder 1 UNREAD Eicar-Test-Signature + $out = slurp_file($out); + xlog $self, $out; + + # XXX is there a better way than hard coding UID:1 ? + my ($v) = Cassandane::Instance->get_version(); + if ($v >= 3) { + $self->assert_matches( + qr/user\.cassandane\s+1\s+UNREAD\s+Eicar(?:-Test){0,1}-Signature/, $out); + $self->assert_matches( + qr/shared\.folder\s+1\s+UNREAD\s+Eicar(?:-Test){0,1}-Signature/, $out); + } else { + # pre-3.0 a different output format was used + $self->assert_matches( + qr/Working\son\sshared\.folder\.\.\.\nVirus\sdetected\sin\smessage\s1:\sEicar(?:-Test){0,1}-Signature/, + $out + ); + $self->assert_matches( + qr/Working\son\suser\.cassandane\.\.\.\nVirus\sdetected\sin\smessage\s1:\sEicar(?:-Test){0,1}-Signature/, + $out + ); + } - # make sure the infected ones were expunged, but the clean ones weren't - $self->{store}->set_folder("INBOX"); - delete $cass_exp{1}; - $self->check_messages(\%cass_exp, ( keyed_on => 'uid' )); + # make sure the infected ones were expunged, but the clean ones weren't + $self->{store}->set_folder("INBOX"); + delete $cass_exp{1}; + $self->check_messages(\%cass_exp, (keyed_on => 'uid')); - $self->{store}->set_folder("shared.folder"); - delete $shared_exp{1}; - $self->check_messages(\%shared_exp, ( keyed_on => 'uid' )); + $self->{store}->set_folder("shared.folder"); + delete $shared_exp{1}; + $self->check_messages(\%shared_exp, (keyed_on => 'uid')); } # This test uses the '-s search-string' invocation, which is much faster # than waiting for the AV engine to load when we just care about whether # the notification gets sent sub test_notify_deleted - :needs_dependency_clamav -{ - my ($self) = @_; - - $self->{store}->set_fetch_attributes(qw(uid flags)); - - # put some test messages in INBOX (and verify) - $self->{store}->set_folder("INBOX"); - my %cass_exp; - $cass_exp{1} = $self->make_message("custom header 1", uid => 1, %custom_header); - $cass_exp{2} = $self->make_message("custom header 2", uid => 2, %custom_header); - $self->check_messages(\%cass_exp, ( keyed_on => 'uid' )); - - # run cyr_virusscan - $self->{instance}->run_command({ cyrus => 1, }, - 'cyr_virusscan', '-r', '-n', - '-s', 'header "x-delete-me" "please"'); - - # let's see what's in there now - my $found_notifications = 0; - $self->{store}->read_begin(); - while (my $msg = $self->{store}->read_message()) { - # should not be any of our test messages remaining - $self->assert_null($msg->get_header('x-cassandane-unique')); - - # if we find something that looks like a notification, check it - if ($msg->get_header('message-id') =~ m{^get_body(); -# xlog $self, "body:\n>>>>>>\n$body<<<<<<"; - - # make sure report body includes all our infected tests - foreach my $exp (values %cass_exp) { - my $message_id = $exp->get_header('message-id'); - $self->assert_matches(qr/Message-ID: $message_id/, $body); - - my $subject = $exp->get_header('subject'); - $self->assert_matches(qr/Subject: $subject/, $body); - - my $uid = $exp->get_attribute('uid'); - $self->assert_matches(qr/IMAP UID: $uid/, $body); - } - - # make sure the message was removed for the reason we expect - $self->assert_matches(qr/Cyrus Administrator Targeted Removal/, - $body); - } + : needs_dependency_clamav { + my ($self) = @_; + + $self->{store}->set_fetch_attributes(qw(uid flags)); + + # put some test messages in INBOX (and verify) + $self->{store}->set_folder("INBOX"); + my %cass_exp; + $cass_exp{1} + = $self->make_message("custom header 1", uid => 1, %custom_header); + $cass_exp{2} + = $self->make_message("custom header 2", uid => 2, %custom_header); + $self->check_messages(\%cass_exp, (keyed_on => 'uid')); + + # run cyr_virusscan + $self->{instance}->run_command({ cyrus => 1, }, + 'cyr_virusscan', '-r', '-n', '-s', 'header "x-delete-me" "please"'); + + # let's see what's in there now + my $found_notifications = 0; + $self->{store}->read_begin(); + while (my $msg = $self->{store}->read_message()) { + # should not be any of our test messages remaining + $self->assert_null($msg->get_header('x-cassandane-unique')); + + # if we find something that looks like a notification, check it + if ($msg->get_header('message-id') =~ m{^get_body(); + # xlog $self, "body:\n>>>>>>\n$body<<<<<<"; + + # make sure report body includes all our infected tests + foreach my $exp (values %cass_exp) { + my $message_id = $exp->get_header('message-id'); + $self->assert_matches(qr/Message-ID: $message_id/, $body); + + my $subject = $exp->get_header('subject'); + $self->assert_matches(qr/Subject: $subject/, $body); + + my $uid = $exp->get_attribute('uid'); + $self->assert_matches(qr/IMAP UID: $uid/, $body); + } + + # make sure the message was removed for the reason we expect + $self->assert_matches(qr/Cyrus Administrator Targeted Removal/, $body); } - $self->{store}->read_end(); + } + $self->{store}->read_end(); - # finally, there should've been exactly one notification email sent - $self->assert_num_equals(1, $found_notifications); + # finally, there should've been exactly one notification email sent + $self->assert_num_equals(1, $found_notifications); } # This test uses the '-s search-string' invocation, which is much faster @@ -245,81 +236,79 @@ sub test_notify_deleted # XXX https://github.com/cyrusimap/cyrus-imapd/issues/2516 might be # XXX backported to 3.0 if anyone volunteers to test it sub test_custom_notify_deleted - :needs_dependency_clamav :NoStartInstances - :min_version_3_1 -{ - my ($self) = @_; - - # set up a custom notification template - $self->{instance}->{config}->set( - virusscan_notification_subject => 'custom ½ subject', - virusscan_notification_template => - abs_path('data/custom-notification-template'), - ); - $self->_start_instances(); - - $self->{store}->set_fetch_attributes(qw(uid flags)); - - # put some test messages in INBOX (and verify) - $self->{store}->set_folder("INBOX"); - my %cass_exp; - $cass_exp{1} = $self->make_message("custom header 1", uid => 1, %custom_header); - $cass_exp{2} = $self->make_message("custom header 2", uid => 2, %custom_header); - $self->check_messages(\%cass_exp, ( keyed_on => 'uid' )); - - # run cyr_virusscan - $self->{instance}->run_command({ cyrus => 1, }, - 'cyr_virusscan', '-r', '-n', - '-s', 'header "x-delete-me" "please"'); - - # let's see what's in there now - my $found_notifications = 0; - $self->{store}->read_begin(); - while (my $msg = $self->{store}->read_message()) { - # should not be any of our test messages remaining - $self->assert_null($msg->get_header('x-cassandane-unique')); - - # if we find something that looks like a notification, check it - if ($msg->get_header('message-id') =~ m{^get_header('subject'); -# xlog $self, "subject: $subject"; - - # make sure our custom subject was used (and correctly encoded) - $self->assert_str_equals('=?UTF-8?Q?custom_=C2=BD_subject?=', - $subject); - - my $body = $msg->get_body(); -# xlog $self, "body:\n>>>>>>\n$body<<<<<<"; - - # make sure report body includes all our infected tests - foreach my $exp (values %cass_exp) { - my $message_id = $exp->get_header('message-id'); - $self->assert_matches(qr/Message-ID: $message_id/, $body); - - my $subject = $exp->get_header('subject'); - $self->assert_matches(qr/Subject: $subject/, $body); - - my $uid = $exp->get_attribute('uid'); - $self->assert_matches(qr/IMAP UID: $uid/, $body); - } - - # make sure the message was removed for the reason we expect - $self->assert_matches(qr/Cyrus Administrator Targeted Removal/, - $body); - - # make sure our custom notification template was used - $self->assert_matches(qr/^custom notification!/, $body); - - # make sure message was qp-encoded - $self->assert_matches(qr/with =C2=BD as much 8bit/, $body); - } + : needs_dependency_clamav : NoStartInstances + : min_version_3_1 { + my ($self) = @_; + + # set up a custom notification template + $self->{instance}->{config}->set( + virusscan_notification_subject => 'custom ½ subject', + virusscan_notification_template => + abs_path('data/custom-notification-template'), + ); + $self->_start_instances(); + + $self->{store}->set_fetch_attributes(qw(uid flags)); + + # put some test messages in INBOX (and verify) + $self->{store}->set_folder("INBOX"); + my %cass_exp; + $cass_exp{1} + = $self->make_message("custom header 1", uid => 1, %custom_header); + $cass_exp{2} + = $self->make_message("custom header 2", uid => 2, %custom_header); + $self->check_messages(\%cass_exp, (keyed_on => 'uid')); + + # run cyr_virusscan + $self->{instance}->run_command({ cyrus => 1, }, + 'cyr_virusscan', '-r', '-n', '-s', 'header "x-delete-me" "please"'); + + # let's see what's in there now + my $found_notifications = 0; + $self->{store}->read_begin(); + while (my $msg = $self->{store}->read_message()) { + # should not be any of our test messages remaining + $self->assert_null($msg->get_header('x-cassandane-unique')); + + # if we find something that looks like a notification, check it + if ($msg->get_header('message-id') =~ m{^get_header('subject'); + # xlog $self, "subject: $subject"; + + # make sure our custom subject was used (and correctly encoded) + $self->assert_str_equals('=?UTF-8?Q?custom_=C2=BD_subject?=', $subject); + + my $body = $msg->get_body(); + # xlog $self, "body:\n>>>>>>\n$body<<<<<<"; + + # make sure report body includes all our infected tests + foreach my $exp (values %cass_exp) { + my $message_id = $exp->get_header('message-id'); + $self->assert_matches(qr/Message-ID: $message_id/, $body); + + my $subject = $exp->get_header('subject'); + $self->assert_matches(qr/Subject: $subject/, $body); + + my $uid = $exp->get_attribute('uid'); + $self->assert_matches(qr/IMAP UID: $uid/, $body); + } + + # make sure the message was removed for the reason we expect + $self->assert_matches(qr/Cyrus Administrator Targeted Removal/, $body); + + # make sure our custom notification template was used + $self->assert_matches(qr/^custom notification!/, $body); + + # make sure message was qp-encoded + $self->assert_matches(qr/with =C2=BD as much 8bit/, $body); } - $self->{store}->read_end(); + } + $self->{store}->read_end(); - # finally, there should've been exactly one notification email sent - $self->assert_num_equals(1, $found_notifications); + # finally, there should've been exactly one notification email sent + $self->assert_num_equals(1, $found_notifications); } 1; diff --git a/cassandane/Cassandane/Cyrus/Conversations.pm b/cassandane/Cassandane/Cyrus/Conversations.pm index 232f6270c8..62b72e67cf 100644 --- a/cassandane/Cassandane/Cyrus/Conversations.pm +++ b/cassandane/Cassandane/Cyrus/Conversations.pm @@ -48,259 +48,300 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::ThreadedGenerator; use Cassandane::Util::Log; use Cassandane::Util::DateTime qw(to_iso8601 from_iso8601 - from_rfc822 - to_rfc3501 from_rfc3501); + from_rfc822 + to_rfc3501 from_rfc3501); use lib '../perl/imap'; use Cyrus::IndexFile; -sub new -{ - my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); - - my $buildinfo = Cassandane::BuildInfo->new(); - - # if we're gonna try and run jmap tests, set up config for it - if ($buildinfo->get('component', 'jmap')) { - $config->set(caldav_realm => 'Cassandane', - conversations => 'yes', - conversations_counted_flags => "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", - jmapsubmission_deleteonsend => 'no', - ctl_conversationsdb_conversations_max_thread => 5, - httpmodules => 'carddav caldav jmap', - httpallowcompress => 'no'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - services => [ 'imap', 'http' ] - }, @args); - } - else { - $config->set(conversations => 'yes', - conversations_counted_flags => "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", - ctl_conversationsdb_conversations_max_thread => 5); - - return $class->SUPER::new({ config => $config }, @args); - } +sub new { + my ($class, @args) = @_; + my $config = Cassandane::Config->default()->clone(); + + my $buildinfo = Cassandane::BuildInfo->new(); + + # if we're gonna try and run jmap tests, set up config for it + if ($buildinfo->get('component', 'jmap')) { + $config->set( + caldav_realm => 'Cassandane', + conversations => 'yes', + conversations_counted_flags => + "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", + jmapsubmission_deleteonsend => 'no', + ctl_conversationsdb_conversations_max_thread => 5, + httpmodules => 'carddav caldav jmap', + httpallowcompress => 'no' + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + services => [ 'imap', 'http' ] + }, + @args + ); + } else { + $config->set( + conversations => 'yes', + conversations_counted_flags => + "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", + ctl_conversationsdb_conversations_max_thread => 5 + ); + + return $class->SUPER::new({ config => $config }, @args); + } } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - my ($maj, $min) = Cassandane::Instance->get_version(); - - # basecid was added after 3.0 - if ($maj > 3 or ($maj == 3 and $min > 0)) { - $self->{store}->set_fetch_attributes('uid', 'cid', 'basecid'); - } - else { - $self->{store}->set_fetch_attributes('uid', 'cid'); - } +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + my ($maj, $min) = Cassandane::Instance->get_version(); + + # basecid was added after 3.0 + if ($maj > 3 or ($maj == 3 and $min > 0)) { + $self->{store}->set_fetch_attributes('uid', 'cid', 'basecid'); + } else { + $self->{store}->set_fetch_attributes('uid', 'cid'); + } } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # The resulting CID when a clash happens is supposed to be # the MAXIMUM of all the CIDs. Here we use the fact that # CIDs are expressed in a form where lexical order is the # same as numeric order. -sub choose_cid -{ - my (@cids) = @_; - @cids = sort { $b cmp $a } @cids; - return $cids[0]; +sub choose_cid { + my (@cids) = @_; + @cids = sort { $b cmp $a } @cids; + return $cids[0]; } # # Test APPEND of messages to IMAP # sub test_append - :min_version_3_0 -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating message B"; - $exp{B} = $self->make_message("Message B"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating message C"; - $exp{C} = $self->make_message("Message C"); - $exp{C}->set_attributes(uid => 3, cid => $exp{C}->make_cid()); - my $actual = $self->check_messages(\%exp); - - xlog $self, "generating message D"; - $exp{D} = $self->make_message("Message D"); - $exp{D}->set_attributes(uid => 4, cid => $exp{D}->make_cid()); - $self->check_messages(\%exp); + : min_version_3_0 { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating message B"; + $exp{B} = $self->make_message("Message B"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating message C"; + $exp{C} = $self->make_message("Message C"); + $exp{C}->set_attributes(uid => 3, cid => $exp{C}->make_cid()); + my $actual = $self->check_messages(\%exp); + + xlog $self, "generating message D"; + $exp{D} = $self->make_message("Message D"); + $exp{D}->set_attributes(uid => 4, cid => $exp{D}->make_cid()); + $self->check_messages(\%exp); } # # Test APPEND of messages to IMAP # sub test_append_reply - :min_version_3_0 -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating message B"; - $exp{B} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{B}->set_attributes(uid => 2, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); + : min_version_3_0 { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating message B"; + $exp{B} = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{B}->set_attributes(uid => 2, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); } # # Test APPEND of messages to IMAP # sub test_append_reply_200 - :min_version_3_1 -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating replies"; - for (1..99) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } - $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"B"}->set_attributes(uid => 101, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - for (1..99) { - $exp{"B$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"B$_"}->set_attributes(uid => 101+$_, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - } - $exp{"C"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"C"}->set_attributes(uid => 201, cid => $exp{C}->make_cid(), basecid => $exp{A}->make_cid()); - - $self->check_messages(\%exp, keyed_on => 'uid'); + : min_version_3_1 { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating replies"; + for (1 .. 99) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } + $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"B"}->set_attributes( + uid => 101, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + for (1 .. 99) { + $exp{"B$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"B$_"}->set_attributes( + uid => 101 + $_, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + $exp{"C"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"C"}->set_attributes( + uid => 201, + cid => $exp{C}->make_cid(), + basecid => $exp{A}->make_cid() + ); + + $self->check_messages(\%exp, keyed_on => 'uid'); } # # Test MOVE of messages after conversation split # sub test_move_200 - :min_version_3_1 -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating replies"; - for (1..99) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } - $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"B"}->set_attributes(uid => 101, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - for (1..99) { - $exp{"B$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"B$_"}->set_attributes(uid => 101+$_, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - } - $exp{"C"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"C"}->set_attributes(uid => 201, cid => $exp{C}->make_cid(), basecid => $exp{A}->make_cid()); - - $self->check_messages(\%exp, keyed_on => 'uid'); - - my $talk = $self->{store}->get_client(); - - $talk->create("INBOX.foo"); - $talk->select("INBOX"); - # NOTE: 110 here becomes 109 after '9' is already moved - $talk->fetch('9,110', '(emailid threadid)'); - $talk->move('9', "INBOX.foo"); - $talk->move('109', "INBOX.foo"); - $talk->select("INBOX.foo"); - my $res = $talk->fetch('1:2', '(emailid threadid)'); - my $emailid1 = $res->{1}{emailid}[0]; - my $threadid1 = $res->{1}{threadid}[0]; - my $emailid2 = $res->{2}{emailid}[0]; - my $threadid2 = $res->{2}{threadid}[0]; - $self->assert_str_equals($threadid1, 'T' . $exp{A}->make_cid()); - $self->assert_str_equals($threadid2, 'T' . $exp{B}->make_cid()); - - # XXX probably should split the jmap stuff below into a separate - # XXX test, so we can just mark it :needs_component_jmap instead - # XXX of hacking it up like this... :) - my $buildinfo = Cassandane::BuildInfo->new(); - if (not $buildinfo->get('component', 'jmap')) { - return; - } - - my $jmap = $self->{jmap}; - xlog $self, "create bar mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "bar", - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $bar = $res->[0][1]{created}{"1"}{id}; - - $res = $jmap->CallMethods([ - ['Email/set', { update => { - $emailid1 => { mailboxIds => { $bar => $JSON::true } }, - $emailid2 => { mailboxIds => { $bar => $JSON::true } }, - }}, "R1"] - ]); - - $self->assert_str_equals('Email/set', $res->[0][0]); - $self->assert(exists $res->[0][1]{updated}{$emailid1}); - $self->assert(exists $res->[0][1]{updated}{$emailid2}); - $self->assert_str_equals('R1', $res->[0][2]); - - $res = $jmap->CallMethods([ - ['Email/get', { ids => [$emailid1,$emailid2], properties => ['threadId'] - }, "R1"] - ]); - - $self->assert_str_equals('Email/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my %map = map { $_->{id} => $_->{threadId} } @{$res->[0][1]{list}}; - $self->assert_str_equals($map{$emailid1}, $threadid1); - $self->assert_str_equals($map{$emailid2}, $threadid2); + : min_version_3_1 { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating replies"; + for (1 .. 99) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } + $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"B"}->set_attributes( + uid => 101, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + for (1 .. 99) { + $exp{"B$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"B$_"}->set_attributes( + uid => 101 + $_, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + $exp{"C"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"C"}->set_attributes( + uid => 201, + cid => $exp{C}->make_cid(), + basecid => $exp{A}->make_cid() + ); + + $self->check_messages(\%exp, keyed_on => 'uid'); + + my $talk = $self->{store}->get_client(); + + $talk->create("INBOX.foo"); + $talk->select("INBOX"); + # NOTE: 110 here becomes 109 after '9' is already moved + $talk->fetch('9,110', '(emailid threadid)'); + $talk->move('9', "INBOX.foo"); + $talk->move('109', "INBOX.foo"); + $talk->select("INBOX.foo"); + my $res = $talk->fetch('1:2', '(emailid threadid)'); + my $emailid1 = $res->{1}{emailid}[0]; + my $threadid1 = $res->{1}{threadid}[0]; + my $emailid2 = $res->{2}{emailid}[0]; + my $threadid2 = $res->{2}{threadid}[0]; + $self->assert_str_equals($threadid1, 'T' . $exp{A}->make_cid()); + $self->assert_str_equals($threadid2, 'T' . $exp{B}->make_cid()); + + # XXX probably should split the jmap stuff below into a separate + # XXX test, so we can just mark it :needs_component_jmap instead + # XXX of hacking it up like this... :) + my $buildinfo = Cassandane::BuildInfo->new(); + if (not $buildinfo->get('component', 'jmap')) { + return; + } + + my $jmap = $self->{jmap}; + xlog $self, "create bar mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "bar", + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $bar = $res->[0][1]{created}{"1"}{id}; + + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailid1 => { mailboxIds => { $bar => $JSON::true } }, + $emailid2 => { mailboxIds => { $bar => $JSON::true } }, + } + }, + "R1" + ] ]); + + $self->assert_str_equals('Email/set', $res->[0][0]); + $self->assert(exists $res->[0][1]{updated}{$emailid1}); + $self->assert(exists $res->[0][1]{updated}{$emailid2}); + $self->assert_str_equals('R1', $res->[0][2]); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [ $emailid1, $emailid2 ], + properties => ['threadId'] + }, + "R1" + ] ]); + + $self->assert_str_equals('Email/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my %map = map { $_->{id} => $_->{threadId} } @{ $res->[0][1]{list} }; + $self->assert_str_equals($map{$emailid1}, $threadid1); + $self->assert_str_equals($map{$emailid2}, $threadid2); } # @@ -314,438 +355,510 @@ sub test_move_200 # decoded from the real world! # sub test_normalise_nonascii_whitespace - :min_version_3_0 -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - # we saw in the wild a message with an encoded nbsp in the subject... - $exp{A} = $self->make_message("=?UTF-8?Q?hello=C2=A0there?="); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating message B"; - # ... but the reply had replaced this with a normal space - $exp{B} = $self->make_message("Re: hello there", references => [ $exp{A} ]); - $exp{B}->set_attributes(uid => 2, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); + : min_version_3_0 { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + # we saw in the wild a message with an encoded nbsp in the subject... + $exp{A} = $self->make_message("=?UTF-8?Q?hello=C2=A0there?="); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating message B"; + # ... but the reply had replaced this with a normal space + $exp{B} = $self->make_message("Re: hello there", references => [ $exp{A} ]); + $exp{B}->set_attributes(uid => 2, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); } # # test reconstruct of larger conversation # sub test_reconstruct_splitconv - :min_version_3_1 -{ - my ($self) = @_; - my %exp; - - my $talk = $self->{store}->get_client(); - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($talk->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating replies"; - for (1..20) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } - - $talk->create('foo'); - $talk->copy('1:*', 'foo'); - - $self->check_messages(\%exp, keyed_on => 'uid'); - - # first run WITHOUT splitting - $self->{instance}->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-R', '-r'); - - $self->check_messages(\%exp, keyed_on => 'uid'); - $talk->select("foo"); - $self->check_messages(\%exp, keyed_on => 'uid'); - $talk->select("INBOX"); - - # then run WITH splitting, and see the changed CIDs - $self->{instance}->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-R', '-r', '-S'); - - for (5..9) { - $exp{"A$_"}->set_attributes(cid => $exp{"A5"}->make_cid(), basecid => $exp{A}->make_cid()); - } - for (10..14) { - $exp{"A$_"}->set_attributes(cid => $exp{"A10"}->make_cid(), basecid => $exp{A}->make_cid()); - } - for (15..19) { - $exp{"A$_"}->set_attributes(cid => $exp{"A15"}->make_cid(), basecid => $exp{A}->make_cid()); - } - $exp{"A20"}->set_attributes(cid => $exp{"A20"}->make_cid(), basecid => $exp{A}->make_cid()); - - $self->check_messages(\%exp, keyed_on => 'uid'); - $talk->select("foo"); - $self->check_messages(\%exp, keyed_on => 'uid'); - $talk->select("INBOX"); - - # zero everything out - $self->{instance}->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-z', 'cassandane'); - - # rebuild - $self->{instance}->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-b', 'cassandane'); - - $self->check_messages(\%exp, keyed_on => 'uid'); - $talk->select("foo"); - $self->check_messages(\%exp, keyed_on => 'uid'); - $talk->select("INBOX"); - - # support for -Z was added after 3.8 - my ($maj, $min) = Cassandane::Instance->get_version(); - return if ($maj < 3 or ($maj == 3 and $min < 8)); - - # zero out ONLY two CIDs - $self->{instance}->run_command({ cyrus => 1 }, 'ctl_conversationsdb', - '-Z' => $exp{"A15"}->make_cid(), - '-Z' => $exp{"A10"}->make_cid(), - 'cassandane'); - for (10..19) { - $exp{"A$_"}->set_attributes(cid => undef, basecid => undef); - } - - $self->check_messages(\%exp, keyed_on => 'uid'); - $talk->select("foo"); - $self->check_messages(\%exp, keyed_on => 'uid'); - $talk->select("INBOX"); + : min_version_3_1 { + my ($self) = @_; + my %exp; + + my $talk = $self->{store}->get_client(); + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($talk->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating replies"; + for (1 .. 20) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } + + $talk->create('foo'); + $talk->copy('1:*', 'foo'); + + $self->check_messages(\%exp, keyed_on => 'uid'); + + # first run WITHOUT splitting + $self->{instance} + ->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-R', '-r'); + + $self->check_messages(\%exp, keyed_on => 'uid'); + $talk->select("foo"); + $self->check_messages(\%exp, keyed_on => 'uid'); + $talk->select("INBOX"); + + # then run WITH splitting, and see the changed CIDs + $self->{instance} + ->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-R', '-r', '-S'); + + for (5 .. 9) { + $exp{"A$_"}->set_attributes( + cid => $exp{"A5"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + for (10 .. 14) { + $exp{"A$_"}->set_attributes( + cid => $exp{"A10"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + for (15 .. 19) { + $exp{"A$_"}->set_attributes( + cid => $exp{"A15"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + $exp{"A20"}->set_attributes( + cid => $exp{"A20"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + + $self->check_messages(\%exp, keyed_on => 'uid'); + $talk->select("foo"); + $self->check_messages(\%exp, keyed_on => 'uid'); + $talk->select("INBOX"); + + # zero everything out + $self->{instance} + ->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-z', 'cassandane'); + + # rebuild + $self->{instance} + ->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-b', 'cassandane'); + + $self->check_messages(\%exp, keyed_on => 'uid'); + $talk->select("foo"); + $self->check_messages(\%exp, keyed_on => 'uid'); + $talk->select("INBOX"); + + # support for -Z was added after 3.8 + my ($maj, $min) = Cassandane::Instance->get_version(); + return if ($maj < 3 or ($maj == 3 and $min < 8)); + + # zero out ONLY two CIDs + $self->{instance}->run_command( + { cyrus => 1 }, 'ctl_conversationsdb', + '-Z' => $exp{"A15"}->make_cid(), + '-Z' => $exp{"A10"}->make_cid(), + 'cassandane' + ); + for (10 .. 19) { + $exp{"A$_"}->set_attributes(cid => undef, basecid => undef); + } + + $self->check_messages(\%exp, keyed_on => 'uid'); + $talk->select("foo"); + $self->check_messages(\%exp, keyed_on => 'uid'); + $talk->select("INBOX"); } # # Test APPEND of messages to IMAP # -sub _munge_annot_crc -{ - my ($instance, $file, $value) = @_; +sub _munge_annot_crc { + my ($instance, $file, $value) = @_; - # this needs a bit of magic to know where to write... so - # we do some hard-coded cyrus.index handling - my $fh = IO::File->new($file, "+<"); - die "NO SUCH FILE $file" unless $fh; - my $index = Cyrus::IndexFile->new($fh); + # this needs a bit of magic to know where to write... so + # we do some hard-coded cyrus.index handling + my $fh = IO::File->new($file, "+<"); + die "NO SUCH FILE $file" unless $fh; + my $index = Cyrus::IndexFile->new($fh); - my $header = $index->header(); - $header->{SyncCRCsAnnot} = $value; - $index->rewrite_header($header); + my $header = $index->header(); + $header->{SyncCRCsAnnot} = $value; + $index->rewrite_header($header); - $fh->close(); + $fh->close(); } + sub test_replication_reply_200 - :min_version_3_1 :needs_component_replication -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - $master_store->set_fetch_attributes('uid', 'cid', 'basecid'); - $replica_store->set_fetch_attributes('uid', 'cid', 'basecid'); - - $self->assert($master_store->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - xlog $self, "checking message A on master"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "running replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - xlog $self, "checking message A on replica"; - $self->check_messages(\%exp, store => $replica_store); - - xlog $self, "generating replies"; - for (1..99) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ], store => $master_store); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } - $exp{"B"} = $self->make_message("Re: Message A", references => [ $exp{A} ], store => $master_store); - $exp{"B"}->set_attributes(uid => 101, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - for (1..99) { - $exp{"B$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ], store => $master_store); - $exp{"B$_"}->set_attributes(uid => 101+$_, cid => $exp{B}->make_cid(), basecid => $exp{A}->make_cid()); - } - $exp{"C"} = $self->make_message("Re: Message A", references => [ $exp{A} ], store => $master_store); - $exp{"C"}->set_attributes(uid => 201, cid => $exp{C}->make_cid(), basecid => $exp{A}->make_cid()); - - # this shouldn't make any difference, but it doesn when you're not logging annotation - # usage for split conversations properly, so just leaving it here to break this unrelated-ish test and gain - # the benefits of check_replication's annotsize check - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', '-u' => 'cassandane'); - - $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); - $self->run_replication(); - $self->check_replication('cassandane'); - $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); - - # corrupt the sync_annot_crc at both ends and check that we can fix it without syncback - xlog $self, "Damaging annotations CRCs"; - my $mpath = $self->{instance}->folder_to_directory('user.cassandane'); - my $rpath = $self->{replica}->folder_to_directory('user.cassandane'); - _munge_annot_crc($self->{instance}, "$mpath/cyrus.index", 1); - _munge_annot_crc($self->{replica}, "$rpath/cyrus.index", 2); - - $self->run_replication(nosyncback => 1); - $self->check_replication('cassandane'); - $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); - - xlog $self, "Creating a message on the replica now to make sure it gets the right CID"; - $exp{"D"} = $self->make_message("Re: Message A", references => [ $exp{A} ], store => $replica_store); - $exp{"D"}->set_attributes(uid => 202, cid => $exp{C}->make_cid(), basecid => $exp{A}->make_cid()); - $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); + : min_version_3_1 : needs_component_replication { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + $master_store->set_fetch_attributes('uid', 'cid', 'basecid'); + $replica_store->set_fetch_attributes('uid', 'cid', 'basecid'); + + $self->assert($master_store->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + xlog $self, "checking message A on master"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "running replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + xlog $self, "checking message A on replica"; + $self->check_messages(\%exp, store => $replica_store); + + xlog $self, "generating replies"; + for (1 .. 99) { + $exp{"A$_"} = $self->make_message( + "Re: Message A", + references => [ $exp{A} ], + store => $master_store + ); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } + $exp{"B"} = $self->make_message( + "Re: Message A", + references => [ $exp{A} ], + store => $master_store + ); + $exp{"B"}->set_attributes( + uid => 101, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + for (1 .. 99) { + $exp{"B$_"} = $self->make_message( + "Re: Message A", + references => [ $exp{A} ], + store => $master_store + ); + $exp{"B$_"}->set_attributes( + uid => 101 + $_, + cid => $exp{B}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + $exp{"C"} = $self->make_message( + "Re: Message A", + references => [ $exp{A} ], + store => $master_store + ); + $exp{"C"}->set_attributes( + uid => 201, + cid => $exp{C}->make_cid(), + basecid => $exp{A}->make_cid() + ); + +# this shouldn't make any difference, but it doesn when you're not logging annotation +# usage for split conversations properly, so just leaving it here to break this unrelated-ish test and gain +# the benefits of check_replication's annotsize check + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', '-u' => 'cassandane'); + + $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); + $self->run_replication(); + $self->check_replication('cassandane'); + $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); + +# corrupt the sync_annot_crc at both ends and check that we can fix it without syncback + xlog $self, "Damaging annotations CRCs"; + my $mpath = $self->{instance}->folder_to_directory('user.cassandane'); + my $rpath = $self->{replica}->folder_to_directory('user.cassandane'); + _munge_annot_crc($self->{instance}, "$mpath/cyrus.index", 1); + _munge_annot_crc($self->{replica}, "$rpath/cyrus.index", 2); + + $self->run_replication(nosyncback => 1); + $self->check_replication('cassandane'); + $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); + + xlog $self, + "Creating a message on the replica now to make sure it gets the right CID"; + $exp{"D"} = $self->make_message( + "Re: Message A", + references => [ $exp{A} ], + store => $replica_store + ); + $exp{"D"}->set_attributes( + uid => 202, + cid => $exp{C}->make_cid(), + basecid => $exp{A}->make_cid() + ); + $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); } # # Test APPEND of messages to IMAP # sub test_replication_reconstruct - :min_version_3_1 :needs_component_replication -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - $master_store->set_fetch_attributes('uid', 'cid', 'basecid'); - $replica_store->set_fetch_attributes('uid', 'cid', 'basecid'); - - $self->assert($master_store->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - xlog $self, "checking message A on master"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "running replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - xlog $self, "checking message A on replica"; - $self->check_messages(\%exp, store => $replica_store); - - xlog $self, "generating replies"; - for (1..20) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ], store => $master_store); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } - - $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); - $self->run_replication(); - $self->check_replication('cassandane'); - $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); - - $self->{instance}->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-R', '-r', '-S'); - - for (5..9) { - $exp{"A$_"}->set_attributes(cid => $exp{"A5"}->make_cid(), basecid => $exp{A}->make_cid()); - } - for (10..14) { - $exp{"A$_"}->set_attributes(cid => $exp{"A10"}->make_cid(), basecid => $exp{A}->make_cid()); - } - for (15..19) { - $exp{"A$_"}->set_attributes(cid => $exp{"A15"}->make_cid(), basecid => $exp{A}->make_cid()); - } - $exp{"A20"}->set_attributes(cid => $exp{"A20"}->make_cid(), basecid => $exp{A}->make_cid()); - - $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); - $self->run_replication(); - $self->check_replication('cassandane'); - $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); - - xlog $self, "Creating a message on the replica now to make sure it gets the right CID"; - $exp{"D"} = $self->make_message("Re: Message A", references => [ $exp{A} ], store => $replica_store); - $exp{"D"}->set_attributes(uid => 22, cid => $exp{"A20"}->make_cid(), basecid => $exp{A}->make_cid()); - $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); + : min_version_3_1 : needs_component_replication { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + $master_store->set_fetch_attributes('uid', 'cid', 'basecid'); + $replica_store->set_fetch_attributes('uid', 'cid', 'basecid'); + + $self->assert($master_store->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + xlog $self, "checking message A on master"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "running replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + xlog $self, "checking message A on replica"; + $self->check_messages(\%exp, store => $replica_store); + + xlog $self, "generating replies"; + for (1 .. 20) { + $exp{"A$_"} = $self->make_message( + "Re: Message A", + references => [ $exp{A} ], + store => $master_store + ); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } + + $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); + $self->run_replication(); + $self->check_replication('cassandane'); + $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'ctl_conversationsdb', '-R', '-r', '-S'); + + for (5 .. 9) { + $exp{"A$_"}->set_attributes( + cid => $exp{"A5"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + for (10 .. 14) { + $exp{"A$_"}->set_attributes( + cid => $exp{"A10"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + for (15 .. 19) { + $exp{"A$_"}->set_attributes( + cid => $exp{"A15"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + } + $exp{"A20"}->set_attributes( + cid => $exp{"A20"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + + $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); + $self->run_replication(); + $self->check_replication('cassandane'); + $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); + + xlog $self, + "Creating a message on the replica now to make sure it gets the right CID"; + $exp{"D"} = $self->make_message( + "Re: Message A", + references => [ $exp{A} ], + store => $replica_store + ); + $exp{"D"}->set_attributes( + uid => 22, + cid => $exp{"A20"}->make_cid(), + basecid => $exp{A}->make_cid() + ); + $self->check_messages(\%exp, keyed_on => 'uid', store => $replica_store); } - # # Test APPEND of messages to IMAP which results in a CID clash. # sub bogus_test_append_clash - :min_version_3_0 -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating message B"; - $exp{B} = $self->make_message("Message B"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - my $actual = $self->check_messages(\%exp); - - xlog $self, "generating message C"; - my $ElCid = choose_cid($exp{A}->get_attribute('cid'), - $exp{B}->get_attribute('cid')); - $exp{C} = $self->make_message("Message C", - references => [ $exp{A}, $exp{B} ], - ); - $exp{C}->set_attributes(uid => 3, cid => $ElCid); - - # Since IRIS-293, inserting this message will have the side effect - # of renumbering some of the existing messages. Predict and test - # which messages get renumbered. - my $nextuid = 4; - foreach my $s (qw(A B)) - { - if ($actual->{"Message $s"}->make_cid() ne $ElCid) - { - $exp{$s}->set_attributes(uid => $nextuid, cid => $ElCid); - $nextuid++; - } + : min_version_3_0 { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating message B"; + $exp{B} = $self->make_message("Message B"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + my $actual = $self->check_messages(\%exp); + + xlog $self, "generating message C"; + my $ElCid + = choose_cid($exp{A}->get_attribute('cid'), $exp{B}->get_attribute('cid')); + $exp{C} + = $self->make_message("Message C", references => [ $exp{A}, $exp{B} ],); + $exp{C}->set_attributes(uid => 3, cid => $ElCid); + + # Since IRIS-293, inserting this message will have the side effect + # of renumbering some of the existing messages. Predict and test + # which messages get renumbered. + my $nextuid = 4; + foreach my $s (qw(A B)) { + if ($actual->{"Message $s"}->make_cid() ne $ElCid) { + $exp{$s}->set_attributes(uid => $nextuid, cid => $ElCid); + $nextuid++; } + } - $self->check_messages(\%exp); + $self->check_messages(\%exp); } # # Test APPEND of messages to IMAP which results in multiple CID clashes. # sub bogus_test_double_clash - :min_version_3_0 -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating message B"; - $exp{B} = $self->make_message("Message B"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - $self->check_messages(\%exp); - - xlog $self, "generating message C"; - $exp{C} = $self->make_message("Message C"); - $exp{C}->set_attributes(uid => 3, cid => $exp{C}->make_cid()); - my $actual = $self->check_messages(\%exp); - - xlog $self, "generating message D"; - my $ElCid = choose_cid($exp{A}->get_attribute('cid'), - $exp{B}->get_attribute('cid'), - $exp{C}->get_attribute('cid')); - $exp{D} = $self->make_message("Message D", - references => [ $exp{A}, $exp{B}, $exp{C} ], - ); - $exp{D}->set_attributes(uid => 4, cid => $ElCid); - - # Since IRIS-293, inserting this message will have the side effect - # of renumbering some of the existing messages. Predict and test - # which messages get renumbered. - my $nextuid = 5; - foreach my $s (qw(A B C)) - { - if ($actual->{"Message $s"}->make_cid() ne $ElCid) - { - $exp{$s}->set_attributes(uid => $nextuid, cid => $ElCid); - $nextuid++; - } + : min_version_3_0 { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating message B"; + $exp{B} = $self->make_message("Message B"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + $self->check_messages(\%exp); + + xlog $self, "generating message C"; + $exp{C} = $self->make_message("Message C"); + $exp{C}->set_attributes(uid => 3, cid => $exp{C}->make_cid()); + my $actual = $self->check_messages(\%exp); + + xlog $self, "generating message D"; + my $ElCid = choose_cid( + $exp{A}->get_attribute('cid'), + $exp{B}->get_attribute('cid'), + $exp{C}->get_attribute('cid') + ); + $exp{D} = $self->make_message("Message D", + references => [ $exp{A}, $exp{B}, $exp{C} ],); + $exp{D}->set_attributes(uid => 4, cid => $ElCid); + + # Since IRIS-293, inserting this message will have the side effect + # of renumbering some of the existing messages. Predict and test + # which messages get renumbered. + my $nextuid = 5; + foreach my $s (qw(A B C)) { + if ($actual->{"Message $s"}->make_cid() ne $ElCid) { + $exp{$s}->set_attributes(uid => $nextuid, cid => $ElCid); + $nextuid++; } + } - $self->check_messages(\%exp); + $self->check_messages(\%exp); } # # Test that a CID clash resolved on the master is replicated # sub bogus_test_replication_clash - :min_version_3_0 :needs_component_replication -{ - my ($self) = @_; - my %exp; - - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - - $master_store->set_fetch_attributes('uid', 'cid'); - $replica_store->set_fetch_attributes('uid', 'cid'); - - # Double check that we're connected to the servers - # we wanted to be connected to. - $self->assert($master_store->{host} eq $replica_store->{host}); - $self->assert($master_store->{port} != $replica_store->{port}); - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($master_store->get_client()->capability()->{xconversations}); - $self->assert($replica_store->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->run_replication(); - $self->check_replication('cassandane'); - $self->check_messages(\%exp, store => $master_store); - $self->check_messages(\%exp, store => $replica_store); - - xlog $self, "generating message B"; - $exp{B} = $self->make_message("Message B", store => $master_store); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - $self->run_replication(); - $self->check_replication('cassandane'); - $self->check_messages(\%exp, store => $master_store); - $self->check_messages(\%exp, store => $replica_store); - - xlog $self, "generating message C"; - $exp{C} = $self->make_message("Message C", store => $master_store); - $exp{C}->set_attributes(uid => 3, cid => $exp{C}->make_cid()); - $self->run_replication(); - $self->check_replication('cassandane'); - my $actual = $self->check_messages(\%exp, store => $master_store); - $self->check_messages(\%exp, store => $replica_store); - - xlog $self, "generating message D"; - my $ElCid = choose_cid($exp{A}->get_attribute('cid'), - $exp{B}->get_attribute('cid'), - $exp{C}->get_attribute('cid')); - $exp{D} = $self->make_message("Message D", - store => $master_store, - references => [ $exp{A}, $exp{B}, $exp{C} ], - ); - $exp{D}->set_attributes(uid => 4, cid => $ElCid); - - # Since IRIS-293, inserting this message will have the side effect - # of renumbering some of the existing messages. Predict and test - # which messages get renumbered. - my $nextuid = 5; - foreach my $s (qw(A B C)) - { - if ($actual->{"Message $s"}->make_cid() ne $ElCid) - { - $exp{$s}->set_attributes(uid => $nextuid, cid => $ElCid); - $nextuid++; - } + : min_version_3_0 : needs_component_replication { + my ($self) = @_; + my %exp; + + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + + $master_store->set_fetch_attributes('uid', 'cid'); + $replica_store->set_fetch_attributes('uid', 'cid'); + + # Double check that we're connected to the servers + # we wanted to be connected to. + $self->assert($master_store->{host} eq $replica_store->{host}); + $self->assert($master_store->{port} != $replica_store->{port}); + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($master_store->get_client()->capability()->{xconversations}); + $self->assert($replica_store->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->run_replication(); + $self->check_replication('cassandane'); + $self->check_messages(\%exp, store => $master_store); + $self->check_messages(\%exp, store => $replica_store); + + xlog $self, "generating message B"; + $exp{B} = $self->make_message("Message B", store => $master_store); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + $self->run_replication(); + $self->check_replication('cassandane'); + $self->check_messages(\%exp, store => $master_store); + $self->check_messages(\%exp, store => $replica_store); + + xlog $self, "generating message C"; + $exp{C} = $self->make_message("Message C", store => $master_store); + $exp{C}->set_attributes(uid => 3, cid => $exp{C}->make_cid()); + $self->run_replication(); + $self->check_replication('cassandane'); + my $actual = $self->check_messages(\%exp, store => $master_store); + $self->check_messages(\%exp, store => $replica_store); + + xlog $self, "generating message D"; + my $ElCid = choose_cid( + $exp{A}->get_attribute('cid'), + $exp{B}->get_attribute('cid'), + $exp{C}->get_attribute('cid') + ); + $exp{D} = $self->make_message( + "Message D", + store => $master_store, + references => [ $exp{A}, $exp{B}, $exp{C} ], + ); + $exp{D}->set_attributes(uid => 4, cid => $ElCid); + + # Since IRIS-293, inserting this message will have the side effect + # of renumbering some of the existing messages. Predict and test + # which messages get renumbered. + my $nextuid = 5; + foreach my $s (qw(A B C)) { + if ($actual->{"Message $s"}->make_cid() ne $ElCid) { + $exp{$s}->set_attributes(uid => $nextuid, cid => $ElCid); + $nextuid++; } + } - $self->run_replication(); - $self->check_replication('cassandane'); - $self->check_messages(\%exp, store => $master_store); - $self->check_messages(\%exp, store => $replica_store); + $self->run_replication(); + $self->check_replication('cassandane'); + $self->check_messages(\%exp, store => $master_store); + $self->check_messages(\%exp, store => $replica_store); } # @@ -754,239 +867,236 @@ sub bogus_test_replication_clash # conversations but not any of Message-ID, References, or In-Reply-To. # sub bogus_test_fm_webui_draft - :min_version_3_0 -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->{gen}->generate(subject => 'Draft message A'); - $exp{A}->remove_headers('Message-ID'); -# $exp{A}->add_header('X-ME-Message-ID', ''); - $exp{A}->add_header('X-ME-Message-ID', ''); - $exp{A}->set_attribute(cid => $exp{A}->make_cid()); - - $self->{store}->write_begin(); - $self->{store}->write_message($exp{A}); - $self->{store}->write_end(); - $self->check_messages(\%exp); - - xlog $self, "generating message B"; - $exp{B} = $exp{A}->clone(); - $exp{B}->set_headers('Subject', 'Draft message B'); - $exp{B}->set_body("Completely different text here\r\n"); - - $self->{store}->write_begin(); - $self->{store}->write_message($exp{B}); - $self->{store}->write_end(); - $self->check_messages(\%exp); + : min_version_3_0 { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->{gen}->generate(subject => 'Draft message A'); + $exp{A}->remove_headers('Message-ID'); + # $exp{A}->add_header('X-ME-Message-ID', ''); + $exp{A}->add_header('X-ME-Message-ID', ''); + $exp{A}->set_attribute(cid => $exp{A}->make_cid()); + + $self->{store}->write_begin(); + $self->{store}->write_message($exp{A}); + $self->{store}->write_end(); + $self->check_messages(\%exp); + + xlog $self, "generating message B"; + $exp{B} = $exp{A}->clone(); + $exp{B}->set_headers('Subject', 'Draft message B'); + $exp{B}->set_body("Completely different text here\r\n"); + + $self->{store}->write_begin(); + $self->{store}->write_message($exp{B}); + $self->{store}->write_end(); + $self->check_messages(\%exp); } # # Test a COPY between folders owned by different users # sub bogus_test_cross_user_copy - :min_version_3_0 -{ - my ($self) = @_; - my $bobuser = "bob"; - my $bobfolder = "user.$bobuser"; - - xlog $self, "Testing COPY between folders owned by different users [IRIS-893]"; - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - my $srv = $self->{instance}->get_service('imap'); - - $self->{instance}->create_user($bobuser); - - my $adminstore = $srv->create_store(username => 'admin'); - my $adminclient = $adminstore->get_client(); - $adminclient->setacl('user.cassandane', $bobuser => 'lrswipkxtecda') - or die "Cannot setacl on user.cassandane: $@"; - - xlog $self, "generating two messages"; - my %exp; - $exp{A} = $self->{gen}->generate(subject => 'Message A'); - my $cid = $exp{A}->make_cid(); - $exp{A}->set_attribute(cid => $cid); - $exp{B} = $self->{gen}->generate(subject => 'Message B', - references => [ $exp{A} ]); - $exp{B}->set_attribute(cid => $cid); - - xlog $self, "Writing messaged to user.cassandane"; - $self->{store}->write_begin(); - $self->{store}->write_message($exp{A}); - $self->{store}->write_message($exp{B}); - $self->{store}->write_end(); - xlog $self, "Check that the messages made it"; - $self->check_messages(\%exp); - - my $bobstore = $srv->create_store(username => $bobuser); - $bobstore->set_fetch_attributes('uid', 'cid'); - my $bobclient = $bobstore->get_client(); - $bobstore->set_folder('user.cassandane'); - $bobstore->_select(); - $bobclient->copy(2, $bobfolder) - or die "Cannot COPY message to $bobfolder"; - - xlog $self, "Check that the message made it to $bobfolder"; - my %bobexp; - $bobexp{B} = $exp{B}->clone(); - $bobexp{B}->set_attributes(uid => 1, cid => $exp{B}->make_cid()); - $bobstore->set_folder($bobfolder); - $self->check_messages(\%bobexp, store => $bobstore); + : min_version_3_0 { + my ($self) = @_; + my $bobuser = "bob"; + my $bobfolder = "user.$bobuser"; + + xlog $self, + "Testing COPY between folders owned by different users [IRIS-893]"; + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + my $srv = $self->{instance}->get_service('imap'); + + $self->{instance}->create_user($bobuser); + + my $adminstore = $srv->create_store(username => 'admin'); + my $adminclient = $adminstore->get_client(); + $adminclient->setacl('user.cassandane', $bobuser => 'lrswipkxtecda') + or die "Cannot setacl on user.cassandane: $@"; + + xlog $self, "generating two messages"; + my %exp; + $exp{A} = $self->{gen}->generate(subject => 'Message A'); + my $cid = $exp{A}->make_cid(); + $exp{A}->set_attribute(cid => $cid); + $exp{B} = $self->{gen}->generate( + subject => 'Message B', + references => [ $exp{A} ] + ); + $exp{B}->set_attribute(cid => $cid); + + xlog $self, "Writing messaged to user.cassandane"; + $self->{store}->write_begin(); + $self->{store}->write_message($exp{A}); + $self->{store}->write_message($exp{B}); + $self->{store}->write_end(); + xlog $self, "Check that the messages made it"; + $self->check_messages(\%exp); + + my $bobstore = $srv->create_store(username => $bobuser); + $bobstore->set_fetch_attributes('uid', 'cid'); + my $bobclient = $bobstore->get_client(); + $bobstore->set_folder('user.cassandane'); + $bobstore->_select(); + $bobclient->copy(2, $bobfolder) + or die "Cannot COPY message to $bobfolder"; + + xlog $self, "Check that the message made it to $bobfolder"; + my %bobexp; + $bobexp{B} = $exp{B}->clone(); + $bobexp{B}->set_attributes(uid => 1, cid => $exp{B}->make_cid()); + $bobstore->set_folder($bobfolder); + $self->check_messages(\%bobexp, store => $bobstore); } # # Test APPEND of messages to IMAP # sub test_replication_trashseen - :min_version_3_1 :needs_component_replication -{ - my ($self) = @_; - my %exp; - - # check IMAP server has the XCONVERSATIONS capability - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - $master_store->set_fetch_attributes('uid', 'cid'); - $replica_store->set_fetch_attributes('uid', 'cid'); - - my $mtalk = $master_store->get_client(); - - $self->assert($mtalk->capability()->{xconversations}); - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - xlog $self, "checking message A on master"; - $self->check_messages(\%exp, store => $master_store); - - $mtalk->create("INBOX.Trash"); - $mtalk->select("INBOX"); - $mtalk->store('1', '+flags', '\\Seen'); - $mtalk->move('1', 'INBOX.Trash'); - $mtalk->select('INBOX.Trash'); - $mtalk->store('1', '-flags', '\\Seen'); - - xlog $self, "running replication"; - $self->run_replication(); - $self->check_replication('cassandane'); + : min_version_3_1 : needs_component_replication { + my ($self) = @_; + my %exp; + + # check IMAP server has the XCONVERSATIONS capability + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + $master_store->set_fetch_attributes('uid', 'cid'); + $replica_store->set_fetch_attributes('uid', 'cid'); + + my $mtalk = $master_store->get_client(); + + $self->assert($mtalk->capability()->{xconversations}); + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + xlog $self, "checking message A on master"; + $self->check_messages(\%exp, store => $master_store); + + $mtalk->create("INBOX.Trash"); + $mtalk->select("INBOX"); + $mtalk->store('1', '+flags', '\\Seen'); + $mtalk->move('1', 'INBOX.Trash'); + $mtalk->select('INBOX.Trash'); + $mtalk->store('1', '-flags', '\\Seen'); + + xlog $self, "running replication"; + $self->run_replication(); + $self->check_replication('cassandane'); } # # Test limits on GUID duplicates # sub test_guid_duplicate_same_folder - :min_version_3_3 :LowEmailLimits -{ - my ($self) = @_; - my %exp; - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - my $talk = $self->{store}->get_client(); - - $talk->create("INBOX.dest"); - - $talk->select("INBOX"); - my $r1 = $talk->copy("1", "INBOX.dest"); - my $r2 = $talk->copy("1", "INBOX.dest"); - my $r3 = $talk->copy("1", "INBOX.dest"); - $self->assert_not_null($r1); - $self->assert_not_null($r2); - $self->assert_null($r3); - $self->assert_matches(qr/Too many identical emails/, $talk->get_last_error()); - - $self->assert_syslog_matches($self->{instance}, - qr{IOERROR: conversations GUID limit}); - - $talk->select("INBOX.dest"); - my $data = $talk->fetch("1:*", "(emailid threadid uid)"); - $self->assert_not_null($data->{1}); - $self->assert_not_null($data->{2}); - $self->assert_null($data->{3}); + : min_version_3_3 : LowEmailLimits { + my ($self) = @_; + my %exp; + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + my $talk = $self->{store}->get_client(); + + $talk->create("INBOX.dest"); + + $talk->select("INBOX"); + my $r1 = $talk->copy("1", "INBOX.dest"); + my $r2 = $talk->copy("1", "INBOX.dest"); + my $r3 = $talk->copy("1", "INBOX.dest"); + $self->assert_not_null($r1); + $self->assert_not_null($r2); + $self->assert_null($r3); + $self->assert_matches(qr/Too many identical emails/, $talk->get_last_error()); + + $self->assert_syslog_matches($self->{instance}, + qr{IOERROR: conversations GUID limit}); + + $talk->select("INBOX.dest"); + my $data = $talk->fetch("1:*", "(emailid threadid uid)"); + $self->assert_not_null($data->{1}); + $self->assert_not_null($data->{2}); + $self->assert_null($data->{3}); } sub test_guid_duplicate_total_count - :min_version_3_3 :LowEmailLimits -{ - my ($self) = @_; - my %exp; - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - my $talk = $self->{store}->get_client(); - - $talk->create("INBOX.M1"); - $talk->create("INBOX.M2"); - $talk->create("INBOX.M3"); - $talk->create("INBOX.M4"); - $talk->create("INBOX.M5"); - - $talk->select("INBOX"); - - my $r1 = $talk->copy("1", "INBOX.M1"); - my $r2 = $talk->copy("1", "INBOX.M2"); - my $r3 = $talk->copy("1", "INBOX.M3"); - my $r4 = $talk->copy("1", "INBOX.M4"); - my $r5 = $talk->copy("1", "INBOX.M5"); - - $self->assert_not_null($r1); - $self->assert_not_null($r2); - $self->assert_not_null($r3); - $self->assert_not_null($r4); - $self->assert_null($r5); - $self->assert_matches(qr/Too many identical emails/, $talk->get_last_error()); - $self->assert_syslog_matches($self->{instance}, - qr{IOERROR: conversations GUID limit}); + : min_version_3_3 : LowEmailLimits { + my ($self) = @_; + my %exp; + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + + my $talk = $self->{store}->get_client(); + + $talk->create("INBOX.M1"); + $talk->create("INBOX.M2"); + $talk->create("INBOX.M3"); + $talk->create("INBOX.M4"); + $talk->create("INBOX.M5"); + + $talk->select("INBOX"); + + my $r1 = $talk->copy("1", "INBOX.M1"); + my $r2 = $talk->copy("1", "INBOX.M2"); + my $r3 = $talk->copy("1", "INBOX.M3"); + my $r4 = $talk->copy("1", "INBOX.M4"); + my $r5 = $talk->copy("1", "INBOX.M5"); + + $self->assert_not_null($r1); + $self->assert_not_null($r2); + $self->assert_not_null($r3); + $self->assert_not_null($r4); + $self->assert_null($r5); + $self->assert_matches(qr/Too many identical emails/, $talk->get_last_error()); + $self->assert_syslog_matches($self->{instance}, + qr{IOERROR: conversations GUID limit}); } # # Test limits on GUID duplicates # sub test_guid_duplicate_expunges - :min_version_3_3 :LowEmailLimits :DelayedExpunge -{ - my ($self) = @_; - my %exp; - - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); - - my $talk = $self->{store}->get_client(); - - $talk->create("INBOX.dest"); - - for (1..9) { - $talk->select("INBOX"); - my $r = $talk->copy("1", "INBOX.dest"); - $self->assert_not_null($r); - $talk->select("INBOX.dest"); - $talk->store('1:*', '+flags', '(\\Deleted)'); - $talk->expunge(); - } + : min_version_3_3 : LowEmailLimits : DelayedExpunge { + my ($self) = @_; + my %exp; + + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); + my $talk = $self->{store}->get_client(); + + $talk->create("INBOX.dest"); + + for (1 .. 9) { $talk->select("INBOX"); my $r = $talk->copy("1", "INBOX.dest"); - $self->assert_null($r); - $self->assert_matches(qr/Too many identical emails/, $talk->get_last_error()); + $self->assert_not_null($r); + $talk->select("INBOX.dest"); + $talk->store('1:*', '+flags', '(\\Deleted)'); + $talk->expunge(); + } - $self->assert_syslog_matches($self->{instance}, - qr{IOERROR: conversations GUID limit}); + $talk->select("INBOX"); + my $r = $talk->copy("1", "INBOX.dest"); + $self->assert_null($r); + $self->assert_matches(qr/Too many identical emails/, $talk->get_last_error()); + + $self->assert_syslog_matches($self->{instance}, + qr{IOERROR: conversations GUID limit}); } # Test APPEND of two messages, the second of which has a different subject, @@ -994,182 +1104,187 @@ sub test_guid_duplicate_expunges # and make sure they don't thread # sub test_x_me_message_id_nomatch_threading - :min_version_3_0 -{ - my ($self) = @_; - my %exp; - - xlog $self, "generating message A"; - $exp{A} = $self->{gen}->generate(subject => 'Message A'); - $exp{A}->set_headers('Message-ID', ''); - $exp{A}->set_attribute(cid => $exp{A}->make_cid()); - - $self->{store}->write_begin(); - $self->{store}->write_message($exp{A}); - $self->{store}->write_end(); - $self->check_messages(\%exp); - - xlog $self, "generating message B"; - $exp{B} = $exp{A}->clone(); - $exp{B}->set_headers('Message-ID', ''); - $exp{B}->set_headers('In-Reply-To', ''); - $exp{B}->set_headers('X-ME-Message-ID', ''); - $exp{B}->set_headers('Subject', 'Message B'); - $exp{B}->set_body("Completely different text here\r\n"); - $exp{B}->set_attribute(cid => $exp{B}->make_cid()); - - $self->{store}->write_begin(); - $self->{store}->write_message($exp{B}); - $self->{store}->write_end(); - $self->check_messages(\%exp); + : min_version_3_0 { + my ($self) = @_; + my %exp; + + xlog $self, "generating message A"; + $exp{A} = $self->{gen}->generate(subject => 'Message A'); + $exp{A}->set_headers('Message-ID', ''); + $exp{A}->set_attribute(cid => $exp{A}->make_cid()); + + $self->{store}->write_begin(); + $self->{store}->write_message($exp{A}); + $self->{store}->write_end(); + $self->check_messages(\%exp); + + xlog $self, "generating message B"; + $exp{B} = $exp{A}->clone(); + $exp{B}->set_headers('Message-ID', ''); + $exp{B}->set_headers('In-Reply-To', ''); + $exp{B}->set_headers('X-ME-Message-ID', ''); + $exp{B}->set_headers('Subject', 'Message B'); + $exp{B}->set_body("Completely different text here\r\n"); + $exp{B}->set_attribute(cid => $exp{B}->make_cid()); + + $self->{store}->write_begin(); + $self->{store}->write_message($exp{B}); + $self->{store}->write_end(); + $self->check_messages(\%exp); } sub test_rename_between_users - :NoAltNameSpace -{ - my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + : NoAltNameSpace { + my ($self) = @_; + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", cassandane => 'lrswipkxtecdn'); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", cassandane => 'lrswipkxtecdn'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - $self->{store}->set_folder("INBOX"); - $self->make_message("Inbox Msg"); + $self->{store}->set_folder("INBOX"); + $self->make_message("Inbox Msg"); - $talk->create("INBOX.foo"); - $self->{store}->set_folder("INBOX.foo"); - $self->make_message("Foo Msg"); + $talk->create("INBOX.foo"); + $self->{store}->set_folder("INBOX.foo"); + $self->make_message("Foo Msg"); - $talk->create("INBOX.bar"); - $self->{store}->set_folder("INBOX.bar"); - $self->make_message("Bar Msg"); + $talk->create("INBOX.bar"); + $self->{store}->set_folder("INBOX.bar"); + $self->make_message("Bar Msg"); - $self->{store}->set_folder("user.manifold"); - $self->make_message("Man Msg"); + $self->{store}->set_folder("user.manifold"); + $self->make_message("Man Msg"); - my $dirs = $self->{instance}->run_mbpath(-u => 'cassandane'); - my $mdirs = $self->{instance}->run_mbpath(-u => 'manifold'); + my $dirs = $self->{instance}->run_mbpath(-u => 'cassandane'); + my $mdirs = $self->{instance}->run_mbpath(-u => 'manifold'); - # folder IDs should be "INBOX", "foo", "bar" + # folder IDs should be "INBOX", "foo", "bar" - my $res = $talk->status('INBOX.foo', ['mailboxid']); - my $fooid = $res->{'mailboxid'}->[0]; + my $res = $talk->status('INBOX.foo', ['mailboxid']); + my $fooid = $res->{'mailboxid'}->[0]; - my %data = $self->{instance}->run_dbcommand($dirs->{user}{conversations}, 'twoskip', ['SHOW']); - my %mdata = $self->{instance}->run_dbcommand($mdirs->{user}{conversations}, 'twoskip', ['SHOW']); + my %data = $self->{instance} + ->run_dbcommand($dirs->{user}{conversations}, 'twoskip', ['SHOW']); + my %mdata = $self->{instance} + ->run_dbcommand($mdirs->{user}{conversations}, 'twoskip', ['SHOW']); - my $folders = Cyrus::DList->parse_string($data{'$FOLDER_IDS'})->as_perl; - my $mfolders = Cyrus::DList->parse_string($mdata{'$FOLDER_IDS'})->as_perl; + my $folders = Cyrus::DList->parse_string($data{'$FOLDER_IDS'})->as_perl; + my $mfolders = Cyrus::DList->parse_string($mdata{'$FOLDER_IDS'})->as_perl; - $self->assert_num_equals(3, scalar @$folders); - $self->assert_num_equals(1, scalar @$mfolders); - $self->assert_str_equals($fooid, $folders->[1]); + $self->assert_num_equals(3, scalar @$folders); + $self->assert_num_equals(1, scalar @$mfolders); + $self->assert_str_equals($fooid, $folders->[1]); - xlog $self, "Rename folder to other user"; - $talk->rename("INBOX.foo", "user.manifold.foo"); + xlog $self, "Rename folder to other user"; + $talk->rename("INBOX.foo", "user.manifold.foo"); - $admintalk->create('user.manifold.extra'); - $self->{store}->set_folder("user.manifold.extra"); - $self->make_message("Extra Msg"); + $admintalk->create('user.manifold.extra'); + $self->{store}->set_folder("user.manifold.extra"); + $self->make_message("Extra Msg"); - %data = $self->{instance}->run_dbcommand($dirs->{user}{conversations}, 'twoskip', ['SHOW']); - %mdata = $self->{instance}->run_dbcommand($mdirs->{user}{conversations}, 'twoskip', ['SHOW']); + %data = $self->{instance} + ->run_dbcommand($dirs->{user}{conversations}, 'twoskip', ['SHOW']); + %mdata = $self->{instance} + ->run_dbcommand($mdirs->{user}{conversations}, 'twoskip', ['SHOW']); - $folders = Cyrus::DList->parse_string($data{'$FOLDER_IDS'})->as_perl; - $mfolders = Cyrus::DList->parse_string($mdata{'$FOLDER_IDS'})->as_perl; - $self->assert_num_equals(3, scalar @$folders); - $self->assert_num_equals(3, scalar @$mfolders); - $self->assert_str_equals('-', $folders->[1]); - $self->assert_str_equals($fooid, $mfolders->[1]); + $folders = Cyrus::DList->parse_string($data{'$FOLDER_IDS'})->as_perl; + $mfolders = Cyrus::DList->parse_string($mdata{'$FOLDER_IDS'})->as_perl; + $self->assert_num_equals(3, scalar @$folders); + $self->assert_num_equals(3, scalar @$mfolders); + $self->assert_str_equals('-', $folders->[1]); + $self->assert_str_equals($fooid, $mfolders->[1]); - $talk->create("INBOX.again"); - $self->{store}->set_folder("INBOX.again"); - $self->make_message("Again Msg"); + $talk->create("INBOX.again"); + $self->{store}->set_folder("INBOX.again"); + $self->make_message("Again Msg"); - $res = $talk->status('INBOX.again', ['mailboxid']); - my $againid = $res->{'mailboxid'}->[0]; + $res = $talk->status('INBOX.again', ['mailboxid']); + my $againid = $res->{'mailboxid'}->[0]; - $talk->rename("user.manifold.foo", "INBOX.foo"); + $talk->rename("user.manifold.foo", "INBOX.foo"); - %data = $self->{instance}->run_dbcommand($dirs->{user}{conversations}, 'twoskip', ['SHOW']); - %mdata = $self->{instance}->run_dbcommand($mdirs->{user}{conversations}, 'twoskip', ['SHOW']); - $folders = Cyrus::DList->parse_string($data{'$FOLDER_IDS'})->as_perl; - $mfolders = Cyrus::DList->parse_string($mdata{'$FOLDER_IDS'})->as_perl; - $self->assert_num_equals(4, scalar @$folders); - $self->assert_num_equals(3, scalar @$mfolders); - $self->assert_str_equals($againid, $folders->[1]); - $self->assert_str_equals($fooid, $folders->[3]); - $self->assert_str_equals('-', $mfolders->[1]); + %data = $self->{instance} + ->run_dbcommand($dirs->{user}{conversations}, 'twoskip', ['SHOW']); + %mdata = $self->{instance} + ->run_dbcommand($mdirs->{user}{conversations}, 'twoskip', ['SHOW']); + $folders = Cyrus::DList->parse_string($data{'$FOLDER_IDS'})->as_perl; + $mfolders = Cyrus::DList->parse_string($mdata{'$FOLDER_IDS'})->as_perl; + $self->assert_num_equals(4, scalar @$folders); + $self->assert_num_equals(3, scalar @$mfolders); + $self->assert_str_equals($againid, $folders->[1]); + $self->assert_str_equals($fooid, $folders->[3]); + $self->assert_str_equals('-', $mfolders->[1]); } # # Test user rename without splitting conversations # sub test_rename_user_nosplitconv - :AllowMoves :Replication -{ - my ($self) = @_; + : AllowMoves : Replication { + my ($self) = @_; - xlog $self, "Test user rename without splitting conversations"; + xlog $self, "Test user rename without splitting conversations"; - my %exp; + my %exp; - # check IMAP server has the XCONVERSATIONS capability - my $master_store = $self->{master_store}; - $self->assert($master_store->get_client()->capability()->{xconversations}); + # check IMAP server has the XCONVERSATIONS capability + my $master_store = $self->{master_store}; + $self->assert($master_store->get_client()->capability()->{xconversations}); - $master_store->set_fetch_attributes('uid', 'cid', 'basecid'); + $master_store->set_fetch_attributes('uid', 'cid', 'basecid'); - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); - xlog $self, "generating replies"; - for (1..20) { - $exp{"A$_"} = $self->make_message("Re: Message A", - references => [ $exp{A} ], - store => $master_store); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } + xlog $self, "generating replies"; + for (1 .. 20) { + $exp{"A$_"} = $self->make_message( + "Re: Message A", + references => [ $exp{A} ], + store => $master_store + ); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } - my $talk = $master_store->get_client(); - $talk->create('foo'); - $talk->copy('1:*', 'foo'); + my $talk = $master_store->get_client(); + $talk->create('foo'); + $talk->copy('1:*', 'foo'); - $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); - $self->check_conversations(); + $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); + $self->check_conversations(); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - # Reduce the conversation thread size - my $config = $self->{instance}->{config}; - $config->set(conversations_max_thread => 5); - $config->generate($self->{instance}->_imapd_conf()); + # Reduce the conversation thread size + my $config = $self->{instance}->{config}; + $config->set(conversations_max_thread => 5); + $config->generate($self->{instance}->_imapd_conf()); - $config = $self->{replica}->{config}; - $config->set(conversations_max_thread => 5); - $config->generate($self->{replica}->_imapd_conf()); + $config = $self->{replica}->{config}; + $config->set(conversations_max_thread => 5); + $config->generate($self->{replica}->_imapd_conf()); - # Rename the user - my $admintalk = $self->{adminstore}->get_client(); - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert(not $admintalk->get_last_error()); + # Rename the user + my $admintalk = $self->{adminstore}->get_client(); + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert(not $admintalk->get_last_error()); - $self->{adminstore}->set_folder("user.newuser"); - $self->{adminstore}->set_fetch_attributes('uid', 'cid', 'basecid'); - $self->check_messages(\%exp, keyed_on => 'uid', store => $self->{adminstore}); - $self->check_conversations(); + $self->{adminstore}->set_folder("user.newuser"); + $self->{adminstore}->set_fetch_attributes('uid', 'cid', 'basecid'); + $self->check_messages(\%exp, keyed_on => 'uid', store => $self->{adminstore}); + $self->check_conversations(); - $self->run_replication(user => 'newuser'); - $self->check_replication('newuser'); + $self->run_replication(user => 'newuser'); + $self->check_replication('newuser'); } 1; diff --git a/cassandane/Cassandane/Cyrus/Create.pm b/cassandane/Cassandane/Cyrus/Create.pm index 117182540d..5c8d9a6f51 100644 --- a/cassandane/Cassandane/Cyrus/Create.pm +++ b/cassandane/Cassandane/Cyrus/Create.pm @@ -49,161 +49,139 @@ use Cassandane::Instance; $Data::Dumper::Sortkeys = 1; -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_bad_userids -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my @bad_userids = ( - 'user', - 'user.anyone', - 'user.anonymous', - 'user.%SHARED', - #'user..foo', # silently fixed by namespace conversion - ); - - foreach my $u (@bad_userids) { - $admintalk->create($u); - $self->assert_str_equals('no', - $admintalk->get_last_completion_response()); - } +sub test_bad_userids { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my @bad_userids = ( + 'user', + 'user.anyone', + 'user.anonymous', + 'user.%SHARED', + #'user..foo', # silently fixed by namespace conversion + ); + + foreach my $u (@bad_userids) { + $admintalk->create($u); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + } } sub test_bad_userids_unixhs - :UnixHierarchySep -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my @bad_userids = ( - 'user', - 'user/anyone', - 'user/anonymous', - 'user/%SHARED', - #'user//foo', # silently fixed by namespace conversion - ); - - foreach my $u (@bad_userids) { - $admintalk->create($u); - $self->assert_str_equals('no', - $admintalk->get_last_completion_response()); - } + : UnixHierarchySep { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my @bad_userids = ( + 'user', + 'user/anyone', + 'user/anonymous', + 'user/%SHARED', + #'user//foo', # silently fixed by namespace conversion + ); + + foreach my $u (@bad_userids) { + $admintalk->create($u); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + } } -sub test_good_userids -{ - my ($self) = @_; +sub test_good_userids { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - my @good_userids = ( - 'user.$RACL', - ); + my @good_userids = ('user.$RACL',); - foreach my $u (@good_userids) { - $admintalk->create($u); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - } + foreach my $u (@good_userids) { + $admintalk->create($u); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } } sub test_good_userids_unixhs - :UnixHierarchySep -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my @good_userids = ( - 'user/$RACL', - 'user/.foo', # with unixhs, this is not a double-sep! - ); - - foreach my $u (@good_userids) { - $admintalk->create($u); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - } + : UnixHierarchySep { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my @good_userids = ( + 'user/$RACL', + 'user/.foo', # with unixhs, this is not a double-sep! + ); + + foreach my $u (@good_userids) { + $admintalk->create($u); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } } -sub test_bad_mailboxes -{ - my ($self) = @_; +sub test_bad_mailboxes { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - my @bad_mailboxes = ( - '$RACL', - '$RACL$U$anyone$user.foo', - 'domain.com!user.foo', # virtdomains=off - #'user.cassandane..blah', # silently fixed by namespace conversion - ); + my @bad_mailboxes = ( + '$RACL', + '$RACL$U$anyone$user.foo', + 'domain.com!user.foo', # virtdomains=off + #'user.cassandane..blah', # silently fixed by namespace conversion + ); - foreach my $m (@bad_mailboxes) { - $admintalk->create($m); - $self->assert_str_equals('no', - $admintalk->get_last_completion_response()); - } + foreach my $m (@bad_mailboxes) { + $admintalk->create($m); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + } } sub test_good_mailboxes_unixhs - :UnixHierarchySep -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my @good_mailboxes = ( - 'user/cassandane/$RACL', - 'user/cassandane/.foo', # with unixhs, this is not a double-sep! - 'user/foo.', - 'user/foo./bar', # with unixhs, this is not a double-sep! - ); - - foreach my $m (@good_mailboxes) { - $admintalk->create($m); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - } + : UnixHierarchySep { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my @good_mailboxes = ( + 'user/cassandane/$RACL', + 'user/cassandane/.foo', # with unixhs, this is not a double-sep! + 'user/foo.', + 'user/foo./bar', # with unixhs, this is not a double-sep! + ); + + foreach my $m (@good_mailboxes) { + $admintalk->create($m); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } } sub test_good_mailboxes_virtdomains - :VirtDomains -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my @good_mailboxes = ( - 'user.cassandane.$RACL', - 'user.foo@domain.com', - ); - - foreach my $m (@good_mailboxes) { - $admintalk->create($m); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - } + : VirtDomains { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my @good_mailboxes = ('user.cassandane.$RACL', 'user.foo@domain.com',); + + foreach my $m (@good_mailboxes) { + $admintalk->create($m); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } } 1; diff --git a/cassandane/Cassandane/Cyrus/CtlMboxlist.pm b/cassandane/Cassandane/Cyrus/CtlMboxlist.pm index 9cd514043c..c117fde3ba 100644 --- a/cassandane/Cassandane/Cyrus/CtlMboxlist.pm +++ b/cassandane/Cassandane/Cyrus/CtlMboxlist.pm @@ -49,101 +49,97 @@ use Cassandane::Util::Log; use Cassandane::Util::Slurp; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub fudge_mtimes -{ - my ($hash) = @_; +sub fudge_mtimes { + my ($hash) = @_; - foreach my $v (values %{$hash}) { - if (exists $v->{mtime}) { - $v->{mtime} = 1; - } + foreach my $v (values %{$hash}) { + if (exists $v->{mtime}) { + $v->{mtime} = 1; } + } } sub test_dump_undump - :AltNamespace :UnixHierarchySep -{ - my ($self) = @_; - - # set up some mailboxes for the cassandane user - my $expected = $self->populate_user( - $self->{instance}, - $self->{store}, - [qw(INBOX Drafts Big Big/Red Big/Red/Dog)] - ); - - # sanity check - $self->check_user($self->{instance}, $self->{store}, $expected); - - # stop the instance - $self->{store}->disconnect(); - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; - - # will refer to this a lot - my $basedir = $self->{instance}->get_basedir(); - - # get a dump - my $dump1file = "$basedir/$$-dump.out1"; - my $dump1content = $self->{instance}->read_mailboxes_db({ - outfile => $dump1file - }); - - # move aside original mailboxes.db - my $mboxlist_db_path = $self->{instance}->{config}->get('mboxlist_db_path'); - $mboxlist_db_path //= "$basedir/conf/mailboxes.db"; - rename $mboxlist_db_path, "$mboxlist_db_path.orig" - or die "rename $mboxlist_db_path $mboxlist_db_path.orig: $!"; - - # undump the dump into a new mailboxes.db - my $errfile = $basedir . "/$$-undump.err"; - $self->{instance}->run_command({ - cyrus => 1, - redirects => { - stdin => $dump1file, - stderr => $errfile, - }, - }, 'ctl_mboxlist', '-u'); - - my $errors = slurp_file($errfile); - - # should be no errors reported by the undump - $self->assert_str_equals(q{}, $errors); - - # start the instance back up and reconnect the store - $self->{instance}->start(); - $self->{store}->connect(); - - # user's mailboxes should be as they were - $self->check_user($self->{instance}, $self->{store}, $expected); - - # a second dump should produce the same output - # ... though mtimes will differ, so fudge those first - my $dump2file = "$basedir/$$-dump.out2"; - my $dump2content = $self->{instance}->read_mailboxes_db({ - outfile => $dump2file - }); - fudge_mtimes($dump1content); - fudge_mtimes($dump2content); - $self->assert_deep_equals($dump1content, $dump2content); + : AltNamespace : UnixHierarchySep { + my ($self) = @_; + + # set up some mailboxes for the cassandane user + my $expected = $self->populate_user($self->{instance}, $self->{store}, + [qw(INBOX Drafts Big Big/Red Big/Red/Dog)]); + + # sanity check + $self->check_user($self->{instance}, $self->{store}, $expected); + + # stop the instance + $self->{store}->disconnect(); + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; + + # will refer to this a lot + my $basedir = $self->{instance}->get_basedir(); + + # get a dump + my $dump1file = "$basedir/$$-dump.out1"; + my $dump1content = $self->{instance}->read_mailboxes_db({ + outfile => $dump1file + }); + + # move aside original mailboxes.db + my $mboxlist_db_path = $self->{instance}->{config}->get('mboxlist_db_path'); + $mboxlist_db_path //= "$basedir/conf/mailboxes.db"; + rename $mboxlist_db_path, "$mboxlist_db_path.orig" + or die "rename $mboxlist_db_path $mboxlist_db_path.orig: $!"; + + # undump the dump into a new mailboxes.db + my $errfile = $basedir . "/$$-undump.err"; + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { + stdin => $dump1file, + stderr => $errfile, + }, + }, + 'ctl_mboxlist', + '-u' + ); + + my $errors = slurp_file($errfile); + + # should be no errors reported by the undump + $self->assert_str_equals(q{}, $errors); + + # start the instance back up and reconnect the store + $self->{instance}->start(); + $self->{store}->connect(); + + # user's mailboxes should be as they were + $self->check_user($self->{instance}, $self->{store}, $expected); + + # a second dump should produce the same output + # ... though mtimes will differ, so fudge those first + my $dump2file = "$basedir/$$-dump.out2"; + my $dump2content = $self->{instance}->read_mailboxes_db({ + outfile => $dump2file + }); + fudge_mtimes($dump1content); + fudge_mtimes($dump2content); + $self->assert_deep_equals($dump1content, $dump2content); } 1; diff --git a/cassandane/Cassandane/Cyrus/CyrusDB.pm b/cassandane/Cassandane/Cyrus/CyrusDB.pm index e933ac285b..626e70d387 100644 --- a/cassandane/Cassandane/Cyrus/CyrusDB.pm +++ b/cassandane/Cassandane/Cyrus/CyrusDB.pm @@ -53,392 +53,377 @@ use lib '../perl/imap'; use Cyrus::DList; use Cyrus::HeaderFile; -sub new -{ - my $class = shift; - return $class->SUPER::new({ start_instances => 0 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ start_instances => 0 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # Some databases aren't created automatically during a minimal # startup on a new install, so run some commands such that they # become extant. -sub _force_db_creations -{ - my ($self) = @_; - - # create a backups.db -- but only if backups are compiled in! - eval { - $self->{instance}->_find_binary('ctl_backups'); - - xlog $self, "autovivify a backups.db"; - $self->{instance}->run_command({ - cyrus => 1, - }, 'ctl_backups', 'list'); - }; +sub _force_db_creations { + my ($self) = @_; + + # create a backups.db -- but only if backups are compiled in! + eval { + $self->{instance}->_find_binary('ctl_backups'); + + xlog $self, "autovivify a backups.db"; + $self->{instance}->run_command( + { + cyrus => 1, + }, + 'ctl_backups', + 'list' + ); + }; } -sub test_alternate_quotadb_path -{ - my ($self) = @_; +sub test_alternate_quotadb_path { + my ($self) = @_; - my $quota_db_path = $self->{instance}->get_basedir() - . '/conf/non-default-quotas.db'; + my $quota_db_path + = $self->{instance}->get_basedir() . '/conf/non-default-quotas.db'; - $self->{instance}->{config}->set(quota_db => 'twoskip'); - $self->{instance}->{config}->set(quota_db_path => $quota_db_path); - $self->{instance}->start(); + $self->{instance}->{config}->set(quota_db => 'twoskip'); + $self->{instance}->{config}->set(quota_db_path => $quota_db_path); + $self->{instance}->start(); - $self->_force_db_creations(); + $self->_force_db_creations(); - # Check that ctl_cyrusdb -c (checkpoint) uses correct db filename. - # If it mistakenly tries to use the default filename, it will error - # out due to it not existing. - eval { - $self->{instance}->run_command({ - cyrus => 1, - }, 'ctl_cyrusdb', '-c'); - }; - $self->assert(not $@); + # Check that ctl_cyrusdb -c (checkpoint) uses correct db filename. + # If it mistakenly tries to use the default filename, it will error + # out due to it not existing. + eval { $self->{instance}->run_command({ cyrus => 1, }, 'ctl_cyrusdb', '-c'); }; + $self->assert(not $@); - # TODO more/better checks + # TODO more/better checks } -sub test_mboxlistdb_skiplist -{ - my ($self) = @_; +sub test_mboxlistdb_skiplist { + my ($self) = @_; - $self->{instance}->{config}->set(mboxlist_db => 'skiplist'); - $self->{instance}->start(); + $self->{instance}->{config}->set(mboxlist_db => 'skiplist'); + $self->{instance}->start(); - # 'ctl_cyrusdb -r' will run on startup, and it should not crash! + # 'ctl_cyrusdb -r' will run on startup, and it should not crash! } sub test_recover_uniqueid_from_header_legacymb - :min_version_3_6 :MailboxLegacyDirs -{ - my ($self) = @_; - my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - - # first start will set up cassandane user - $self->_start_instances(); - my $basedir = $self->{instance}->get_basedir(); - my $mailboxes_db = "$basedir/conf/mailboxes.db"; - $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); - - # find out the uniqueid of the inbox - my $imaptalk = $self->{store}->get_client(); - my $res = $imaptalk->getmetadata("INBOX", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $uniqueid = $res->{INBOX}{$entry}; - xlog "XXX got uniqueid: " . Dumper \$uniqueid; - $self->assert_not_null($uniqueid); - $imaptalk->logout(); - undef $imaptalk; - - # stop service while tinkering - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; - - # lose that uniqueid from mailboxes.db - my $I = "I$uniqueid"; - my $N = "Nuser\x1fcassandane"; - $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", - [ 'DELETE', $I ]); - my (undef, $mbentry) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $N]); - my $dlist = Cyrus::DList->parse_string($mbentry); - my $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - $hash->{I} = undef; - $dlist = Cyrus::DList->new_perl('', $hash); - $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - [ 'SET', $N, $dlist->as_string() ]); - - my %updated = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", ['SHOW']); - xlog "updated mailboxes.db: " . Dumper \%updated; - - # bring service back up - # ctl_cyrusdb -r should find and fix the missing uniqueid - $self->{instance}->getsyslog(); - $self->{instance}->start(); - my $syslog = join(q{}, $self->{instance}->getsyslog()); - - # should have still existed in cyrus.header - $self->assert_does_not_match( - qr{mailbox header had no uniqueid, creating one}, $syslog); - - # expect to find the log line - $self->assert_matches(qr{mbentry had no uniqueid, setting from header}, - $syslog); - - # header should have the same uniqueid as before - my $cyrus_header = $self->{instance}->folder_to_directory('INBOX') - . '/cyrus.header'; - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - my $hf = Cyrus::HeaderFile->new_file($cyrus_header); - $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); - - # mbentry should have the same uniqueid as before - (undef, $mbentry) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $N]); - $dlist = Cyrus::DList->parse_string($mbentry); - $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - - # $I entry should be back - my ($key, $value) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $I]); - $self->assert_str_equals($I, $key); - $dlist = Cyrus::DList->parse_string($value); - $hash = $dlist->as_perl(); - $self->assert_str_equals("user\x1fcassandane", $hash->{N}); + : min_version_3_6 : MailboxLegacyDirs { + my ($self) = @_; + my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + + # first start will set up cassandane user + $self->_start_instances(); + my $basedir = $self->{instance}->get_basedir(); + my $mailboxes_db = "$basedir/conf/mailboxes.db"; + $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); + + # find out the uniqueid of the inbox + my $imaptalk = $self->{store}->get_client(); + my $res = $imaptalk->getmetadata("INBOX", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $uniqueid = $res->{INBOX}{$entry}; + xlog "XXX got uniqueid: " . Dumper \$uniqueid; + $self->assert_not_null($uniqueid); + $imaptalk->logout(); + undef $imaptalk; + + # stop service while tinkering + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; + + # lose that uniqueid from mailboxes.db + my $I = "I$uniqueid"; + my $N = "Nuser\x1fcassandane"; + $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", [ 'DELETE', $I ]); + my (undef, $mbentry) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $N ]); + my $dlist = Cyrus::DList->parse_string($mbentry); + my $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + $hash->{I} = undef; + $dlist = Cyrus::DList->new_perl('', $hash); + $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", + [ 'SET', $N, $dlist->as_string() ]); + + my %updated + = $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", ['SHOW']); + xlog "updated mailboxes.db: " . Dumper \%updated; + + # bring service back up + # ctl_cyrusdb -r should find and fix the missing uniqueid + $self->{instance}->getsyslog(); + $self->{instance}->start(); + my $syslog = join(q{}, $self->{instance}->getsyslog()); + + # should have still existed in cyrus.header + $self->assert_does_not_match(qr{mailbox header had no uniqueid, creating one}, + $syslog); + + # expect to find the log line + $self->assert_matches(qr{mbentry had no uniqueid, setting from header}, + $syslog); + + # header should have the same uniqueid as before + my $cyrus_header + = $self->{instance}->folder_to_directory('INBOX') . '/cyrus.header'; + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + my $hf = Cyrus::HeaderFile->new_file($cyrus_header); + $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); + + # mbentry should have the same uniqueid as before + (undef, $mbentry) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $N ]); + $dlist = Cyrus::DList->parse_string($mbentry); + $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + + # $I entry should be back + my ($key, $value) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $I ]); + $self->assert_str_equals($I, $key); + $dlist = Cyrus::DList->parse_string($value); + $hash = $dlist->as_perl(); + $self->assert_str_equals("user\x1fcassandane", $hash->{N}); } sub test_recover_create_missing_uniqueid_legacymb - :min_version_3_6 :MailboxLegacyDirs -{ - my ($self) = @_; - my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - - # first start will set up cassandane user - $self->_start_instances(); - my $basedir = $self->{instance}->get_basedir(); - my $mailboxes_db = "$basedir/conf/mailboxes.db"; - $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); - - # find out the uniqueid of the inbox - my $imaptalk = $self->{store}->get_client(); - my $res = $imaptalk->getmetadata("INBOX", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $uniqueid = $res->{INBOX}{$entry}; - $self->assert_not_null($uniqueid); - $imaptalk->logout(); - undef $imaptalk; - - # stop service while tinkering - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; - - # lose that uniqueid from mailboxes.db - my $I = "I$uniqueid"; - my $N = "Nuser\x1fcassandane"; - $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", - [ 'DELETE', $I ]); - my (undef, $mbentry) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $N]); - my $dlist = Cyrus::DList->parse_string($mbentry); - my $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - $hash->{I} = undef; - $dlist = Cyrus::DList->new_perl('', $hash); - $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - [ 'SET', $N, $dlist->as_string() ]); - - my %updated = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", ['SHOW']); - xlog "updated mailboxes.db: " . Dumper \%updated; - - # lose it from cyrus.header too - my $cyrus_header = $self->{instance}->folder_to_directory('INBOX') - . '/cyrus.header'; - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - copy($cyrus_header, "$cyrus_header.OLD"); - my $hf = Cyrus::HeaderFile->new_file("$cyrus_header.OLD"); - $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); - $hf->{header}->{UniqueId} = undef; - my $out = IO::File->new($cyrus_header, 'w'); - $hf->write_header($out, $hf->{header}); - - # bring service back up - # ctl_cyrusdb -r should find and fix the missing uniqueid - $self->{instance}->getsyslog(); - $self->{instance}->start(); - my $syslog = join(q{}, $self->{instance}->getsyslog()); - - # expect to find it was missing in the header - $self->assert_matches(qr{mailbox header had no uniqueid, creating one}, - $syslog); - - # expect to find it was missing from mbentry - $self->assert_matches(qr{mbentry had no uniqueid, setting from header}, - $syslog); - - # should not be the same uniqueid as before - $imaptalk = $self->{store}->get_client(); - $res = $imaptalk->getmetadata("INBOX", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_not_null($res->{INBOX}{$entry}); - my $newuniqueid = $res->{INBOX}{$entry}; - $self->assert_str_not_equals($uniqueid, $newuniqueid); - - # header file should have the new uniqueid - $cyrus_header = $self->{instance}->folder_to_directory('INBOX') - . '/cyrus.header'; - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - $hf = Cyrus::HeaderFile->new_file($cyrus_header); - $self->assert_str_equals($newuniqueid, $hf->{header}->{UniqueId}); - - # mbentry should have the new uniqueid - (undef, $mbentry) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $N]); - $dlist = Cyrus::DList->parse_string($mbentry); - $hash = $dlist->as_perl(); - $self->assert_str_equals($newuniqueid, $hash->{I}); - - # new runq entry should exist - my $newI = "I$newuniqueid"; - my ($key, $value) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $newI]); - $self->assert_str_equals($newI, $key); - $dlist = Cyrus::DList->parse_string($value); - $hash = $dlist->as_perl(); - $self->assert_str_equals("user\x1fcassandane", $hash->{N}); + : min_version_3_6 : MailboxLegacyDirs { + my ($self) = @_; + my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + + # first start will set up cassandane user + $self->_start_instances(); + my $basedir = $self->{instance}->get_basedir(); + my $mailboxes_db = "$basedir/conf/mailboxes.db"; + $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); + + # find out the uniqueid of the inbox + my $imaptalk = $self->{store}->get_client(); + my $res = $imaptalk->getmetadata("INBOX", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $uniqueid = $res->{INBOX}{$entry}; + $self->assert_not_null($uniqueid); + $imaptalk->logout(); + undef $imaptalk; + + # stop service while tinkering + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; + + # lose that uniqueid from mailboxes.db + my $I = "I$uniqueid"; + my $N = "Nuser\x1fcassandane"; + $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", [ 'DELETE', $I ]); + my (undef, $mbentry) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $N ]); + my $dlist = Cyrus::DList->parse_string($mbentry); + my $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + $hash->{I} = undef; + $dlist = Cyrus::DList->new_perl('', $hash); + $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", + [ 'SET', $N, $dlist->as_string() ]); + + my %updated + = $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", ['SHOW']); + xlog "updated mailboxes.db: " . Dumper \%updated; + + # lose it from cyrus.header too + my $cyrus_header + = $self->{instance}->folder_to_directory('INBOX') . '/cyrus.header'; + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + copy($cyrus_header, "$cyrus_header.OLD"); + my $hf = Cyrus::HeaderFile->new_file("$cyrus_header.OLD"); + $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); + $hf->{header}->{UniqueId} = undef; + my $out = IO::File->new($cyrus_header, 'w'); + $hf->write_header($out, $hf->{header}); + + # bring service back up + # ctl_cyrusdb -r should find and fix the missing uniqueid + $self->{instance}->getsyslog(); + $self->{instance}->start(); + my $syslog = join(q{}, $self->{instance}->getsyslog()); + + # expect to find it was missing in the header + $self->assert_matches(qr{mailbox header had no uniqueid, creating one}, + $syslog); + + # expect to find it was missing from mbentry + $self->assert_matches(qr{mbentry had no uniqueid, setting from header}, + $syslog); + + # should not be the same uniqueid as before + $imaptalk = $self->{store}->get_client(); + $res = $imaptalk->getmetadata("INBOX", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_not_null($res->{INBOX}{$entry}); + my $newuniqueid = $res->{INBOX}{$entry}; + $self->assert_str_not_equals($uniqueid, $newuniqueid); + + # header file should have the new uniqueid + $cyrus_header + = $self->{instance}->folder_to_directory('INBOX') . '/cyrus.header'; + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + $hf = Cyrus::HeaderFile->new_file($cyrus_header); + $self->assert_str_equals($newuniqueid, $hf->{header}->{UniqueId}); + + # mbentry should have the new uniqueid + (undef, $mbentry) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $N ]); + $dlist = Cyrus::DList->parse_string($mbentry); + $hash = $dlist->as_perl(); + $self->assert_str_equals($newuniqueid, $hash->{I}); + + # new runq entry should exist + my $newI = "I$newuniqueid"; + my ($key, $value) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $newI ]); + $self->assert_str_equals($newI, $key); + $dlist = Cyrus::DList->parse_string($value); + $hash = $dlist->as_perl(); + $self->assert_str_equals("user\x1fcassandane", $hash->{N}); } -sub test_recover_skipstamp -{ - my ($self) = @_; +sub test_recover_skipstamp { + my ($self) = @_; - my $dbdir = $self->{instance}->get_basedir() . "/conf/db"; + my $dbdir = $self->{instance}->get_basedir() . "/conf/db"; - # no 'ctl_cyrusdb -r' on startup - $self->{instance}->remove_start('recover'); - $self->{instance}->start(); + # no 'ctl_cyrusdb -r' on startup + $self->{instance}->remove_start('recover'); + $self->{instance}->start(); - # expect skipstamp file to be missing - $self->assert_not_file_test("$dbdir/skipstamp", '-e'); + # expect skipstamp file to be missing + $self->assert_not_file_test("$dbdir/skipstamp", '-e'); - # cyrus processes will whinge about missing skipstamp file - if ($self->{instance}->{have_syslog_replacement}) { - my $syslog = join "\n", $self->{instance}->getsyslog(); - $self->assert_matches(qr/skipstamp is missing/, $syslog); - $self->assert_matches(qr/DBERROR: skipstamp/, $syslog); - } + # cyrus processes will whinge about missing skipstamp file + if ($self->{instance}->{have_syslog_replacement}) { + my $syslog = join "\n", $self->{instance}->getsyslog(); + $self->assert_matches(qr/skipstamp is missing/, $syslog); + $self->assert_matches(qr/DBERROR: skipstamp/, $syslog); + } - # shut down, enable recover, and restart - $self->{instance}->stop(); - # n.b. no "re_use_dir" here, because we need cyrus.conf regenerated - $self->{instance}->add_recover(); - $self->{instance}->start(); + # shut down, enable recover, and restart + $self->{instance}->stop(); + # n.b. no "re_use_dir" here, because we need cyrus.conf regenerated + $self->{instance}->add_recover(); + $self->{instance}->start(); - # skipstamp file should be present now - $self->assert_file_test("$dbdir/skipstamp", '-e'); + # skipstamp file should be present now + $self->assert_file_test("$dbdir/skipstamp", '-e'); - if ($self->{instance}->{have_syslog_replacement}) { - my $syslog = join "\n", $self->{instance}->getsyslog(); + if ($self->{instance}->{have_syslog_replacement}) { + my $syslog = join "\n", $self->{instance}->getsyslog(); - # recover should have logged itself updating skipstamp - $self->assert_matches(qr/updating recovery stamp/, $syslog); + # recover should have logged itself updating skipstamp + $self->assert_matches(qr/updating recovery stamp/, $syslog); - # cyrus processes should not whinge about missing skipstamp file - $self->assert_does_not_match(qr/skipstamp is missing/, $syslog); - $self->assert_does_not_match(qr/DBERROR: skipstamp/, $syslog); - } + # cyrus processes should not whinge about missing skipstamp file + $self->assert_does_not_match(qr/skipstamp is missing/, $syslog); + $self->assert_does_not_match(qr/DBERROR: skipstamp/, $syslog); + } } -sub create_empty_file -{ - my ($fname) = @_; +sub create_empty_file { + my ($fname) = @_; - open my $fh, '>', $fname - or die "create_empty_file($fname): $!"; - close $fh; + open my $fh, '>', $fname + or die "create_empty_file($fname): $!"; + close $fh; } -sub test_recover_skipcleanshutdown -{ - my ($self) = @_; +sub test_recover_skipcleanshutdown { + my ($self) = @_; - my $dbdir = $self->{instance}->get_basedir() . "/conf/db"; + my $dbdir = $self->{instance}->get_basedir() . "/conf/db"; - # need to start up once to create a reusable basedir - $self->{instance}->start(); - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; + # need to start up once to create a reusable basedir + $self->{instance}->start(); + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; - # act like we were previously shut down cleanly by some rc script, - # but without a skipstamp somehow - create_empty_file("$dbdir/skipcleanshutdown"); - unlink "$dbdir/skipstamp"; - $self->assert_not_file_test("$dbdir/skipstamp", '-e'); + # act like we were previously shut down cleanly by some rc script, + # but without a skipstamp somehow + create_empty_file("$dbdir/skipcleanshutdown"); + unlink "$dbdir/skipstamp"; + $self->assert_not_file_test("$dbdir/skipstamp", '-e'); - # start 'er up - $self->{instance}->start(); + # start 'er up + $self->{instance}->start(); - # recover should have created a skipstamp file, despite skipcleanshutdown - $self->assert_file_test("$dbdir/skipstamp", '-e'); - my $prev_skipstamp_mtime = (stat "$dbdir/skipstamp")[9]; + # recover should have created a skipstamp file, despite skipcleanshutdown + $self->assert_file_test("$dbdir/skipstamp", '-e'); + my $prev_skipstamp_mtime = (stat "$dbdir/skipstamp")[9]; - # and skipcleanshutdown should have been removed - $self->assert_not_file_test("$dbdir/skipcleanshutdown", '-e'); + # and skipcleanshutdown should have been removed + $self->assert_not_file_test("$dbdir/skipcleanshutdown", '-e'); - if ($self->{instance}->{have_syslog_replacement}) { - my $syslog = join "\n", $self->{instance}->getsyslog(); + if ($self->{instance}->{have_syslog_replacement}) { + my $syslog = join "\n", $self->{instance}->getsyslog(); - # recover should not claim this was a normal start - $self->assert_does_not_match(qr/starting normally/, $syslog); + # recover should not claim this was a normal start + $self->assert_does_not_match(qr/starting normally/, $syslog); - # recover should have logged itself updating skipstamp - $self->assert_matches(qr/updating recovery stamp/, $syslog); + # recover should have logged itself updating skipstamp + $self->assert_matches(qr/updating recovery stamp/, $syslog); - # cyrus processes should not whinge about missing skipstamp file - $self->assert_does_not_match(qr/skipstamp is missing/, $syslog); - $self->assert_does_not_match(qr/DBERROR: skipstamp/, $syslog); - } + # cyrus processes should not whinge about missing skipstamp file + $self->assert_does_not_match(qr/skipstamp is missing/, $syslog); + $self->assert_does_not_match(qr/DBERROR: skipstamp/, $syslog); + } - # shut down "cleanly" again, but this time leaving skipstamp alone - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; - create_empty_file("$dbdir/skipcleanshutdown"); + # shut down "cleanly" again, but this time leaving skipstamp alone + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; + create_empty_file("$dbdir/skipcleanshutdown"); - # restart - $self->{instance}->start(); + # restart + $self->{instance}->start(); - # skipstamp file should be present and unmodified since previous run - $self->assert_file_test("$dbdir/skipstamp", '-e'); - my $skipstamp_mtime = (stat "$dbdir/skipstamp")[9]; - $self->assert_num_equals($prev_skipstamp_mtime, $skipstamp_mtime); + # skipstamp file should be present and unmodified since previous run + $self->assert_file_test("$dbdir/skipstamp", '-e'); + my $skipstamp_mtime = (stat "$dbdir/skipstamp")[9]; + $self->assert_num_equals($prev_skipstamp_mtime, $skipstamp_mtime); - # and skipcleanshutdown should have been removed - $self->assert_not_file_test("$dbdir/skipcleanshutdown", '-e'); + # and skipcleanshutdown should have been removed + $self->assert_not_file_test("$dbdir/skipcleanshutdown", '-e'); - if ($self->{instance}->{have_syslog_replacement}) { - my $syslog = join "\n", $self->{instance}->getsyslog(); + if ($self->{instance}->{have_syslog_replacement}) { + my $syslog = join "\n", $self->{instance}->getsyslog(); - # recover should claim this was a normal start - $self->assert_matches(qr/starting normally/, $syslog); + # recover should claim this was a normal start + $self->assert_matches(qr/starting normally/, $syslog); - # recover should not have updated skipstamp - $self->assert_does_not_match(qr/updating recovery stamp/, $syslog); + # recover should not have updated skipstamp + $self->assert_does_not_match(qr/updating recovery stamp/, $syslog); - # cyrus processes should not whinge about missing skipstamp file - $self->assert_does_not_match(qr/skipstamp is missing/, $syslog); - $self->assert_does_not_match(qr/DBERROR: skipstamp/, $syslog); - } + # cyrus processes should not whinge about missing skipstamp file + $self->assert_does_not_match(qr/skipstamp is missing/, $syslog); + $self->assert_does_not_match(qr/DBERROR: skipstamp/, $syslog); + } } 1; diff --git a/cassandane/Cassandane/Cyrus/DBLookup.pm b/cassandane/Cassandane/Cyrus/DBLookup.pm index d6927d3a24..dbf063d624 100644 --- a/cassandane/Cassandane/Cyrus/DBLookup.pm +++ b/cassandane/Cassandane/Cyrus/DBLookup.pm @@ -52,55 +52,54 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane'); - $config->set(httpmodules => 'carddav caldav'); - $config->set(httpallowcompress => 'no'); - return $class->SUPER::new({ - adminstore => 1, - config => $config, - services => ['imap', 'http'], - }, @_); +sub new { + my $class = shift; + + my $config = Cassandane::Config->default()->clone(); + $config->set(caldav_realm => 'Cassandane'); + $config->set(httpmodules => 'carddav caldav'); + $config->set(httpallowcompress => 'no'); + return $class->SUPER::new( + { + adminstore => 1, + config => $config, + services => [ 'imap', 'http' ], + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - $self->{carddav} = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + $self->{carddav} = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_email2uids - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); - my $Str = <new_fromstring($Str); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($Str); - my $uid = $CardDAV->NewContact($Id, $VCard); + my $uid = $CardDAV->NewContact($Id, $VCard); - my $res = $CardDAV->Request('GET', '/dblookup/email2uids', '', - User => 'cassandane', - Key => "user\@example.com", - Mailbox => 'foo', - ); + my $res = $CardDAV->Request( + 'GET', '/dblookup/email2uids', '', + User => 'cassandane', + Key => "user\@example.com", + Mailbox => 'foo', + ); - # XXX: actually compare to the UID + # XXX: actually compare to the UID } sub test_email2details - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_1 : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); - my $Str = <new_fromstring($Str); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($Str); - my $uid = $CardDAV->NewContact($Id, $VCard); + my $uid = $CardDAV->NewContact($Id, $VCard); - my $res = $CardDAV->Request('GET', '/dblookup/email2details', '', - User => 'cassandane', - Key => "user\@example.com", - Mailbox => 'foo', - ); + my $res = $CardDAV->Request( + 'GET', '/dblookup/email2details', '', + User => 'cassandane', + Key => "user\@example.com", + Mailbox => 'foo', + ); - # XXX: actually compare to the UID + # XXX: actually compare to the UID } 1; diff --git a/cassandane/Cassandane/Cyrus/Delete.pm b/cassandane/Cassandane/Cyrus/Delete.pm index 10f9fb6030..6ac2427796 100644 --- a/cassandane/Cassandane/Cyrus/Delete.pm +++ b/cassandane/Cassandane/Cyrus/Delete.pm @@ -46,1043 +46,1030 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use File::Basename; -sub new -{ - my ($class, @args) = @_; - - my $buildinfo = Cassandane::BuildInfo->new(); - - if ($buildinfo->get('component', 'httpd')) { - my $config = Cassandane::Config->default()->clone(); - - $config->set(conversations => 'yes', - httpmodules => 'carddav caldav'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - services => [ 'imap', 'http', 'sieve' ] - }, @args); - } - else { - return $class->SUPER::new({ adminstore => 1 }, @args); - } +sub new { + my ($class, @args) = @_; + + my $buildinfo = Cassandane::BuildInfo->new(); + + if ($buildinfo->get('component', 'httpd')) { + my $config = Cassandane::Config->default()->clone(); + + $config->set( + conversations => 'yes', + httpmodules => 'carddav caldav' + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + services => [ 'imap', 'http', 'sieve' ] + }, + @args + ); + } else { + return $class->SUPER::new({ adminstore => 1 }, @args); + } } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub check_folder_ondisk -{ - my ($self, $folder, %params) = @_; - - my $instance = delete $params{instance} || $self->{instance}; - my $deleted = delete $params{deleted} || 0; - my $exp = delete $params{expected}; - die "Bad params: " . join(' ', keys %params) - if scalar %params; - - my $display_folder = ($deleted ? "DELETED " : "") . $folder; - xlog $self, "Checking that $display_folder exists on disk"; - - my $dir; - if ($deleted) - { - my @dirs = $instance->folder_to_deleted_directories($folder); - $self->assert_equals(1, scalar(@dirs), - "too many directories for $display_folder"); - $dir = shift @dirs; - } - else - { - $dir = $instance->folder_to_directory($folder); - } - - $self->assert_not_null($dir, - "directory missing for $display_folder"); - $self->assert( -f "$dir/cyrus.header", - "cyrus.header missing for $display_folder"); - $self->assert( -f "$dir/cyrus.index", - "cyrus.index missing for $display_folder"); - - if (defined $exp) - { - map - { - my $uid = $_->uid(); - $self->assert( -f "$dir/$uid.", - "message $uid missing for $display_folder"); - } values %$exp; - } +sub check_folder_ondisk { + my ($self, $folder, %params) = @_; + + my $instance = delete $params{instance} || $self->{instance}; + my $deleted = delete $params{deleted} || 0; + my $exp = delete $params{expected}; + die "Bad params: " . join(' ', keys %params) + if scalar %params; + + my $display_folder = ($deleted ? "DELETED " : "") . $folder; + xlog $self, "Checking that $display_folder exists on disk"; + + my $dir; + if ($deleted) { + my @dirs = $instance->folder_to_deleted_directories($folder); + $self->assert_equals(1, scalar(@dirs), + "too many directories for $display_folder"); + $dir = shift @dirs; + } else { + $dir = $instance->folder_to_directory($folder); + } + + $self->assert_not_null($dir, "directory missing for $display_folder"); + $self->assert(-f "$dir/cyrus.header", + "cyrus.header missing for $display_folder"); + $self->assert(-f "$dir/cyrus.index", + "cyrus.index missing for $display_folder"); + + if (defined $exp) { + map { + my $uid = $_->uid(); + $self->assert(-f "$dir/$uid.", + "message $uid missing for $display_folder"); + } values %$exp; + } } -sub check_folder_not_ondisk -{ - my ($self, $folder, %params) = @_; - - my $instance = delete $params{instance} || $self->{instance}; - my $deleted = delete $params{deleted} || 0; - die "Bad params: " . join(' ', keys %params) - if scalar %params; - - my $display_folder = ($deleted ? "DELETED " : "") . $folder; - xlog $self, "Checking that $display_folder does not exist on disk"; - - if ($deleted) - { - my @dirs = $instance->folder_to_deleted_directories($folder); - $self->assert_equals(0, scalar(@dirs), - "directory unexpectedly present for $display_folder"); - } - else - { - my $dir = $instance->folder_to_directory($folder); - $self->assert_null($dir, - "directory unexpectedly present for $display_folder"); - } +sub check_folder_not_ondisk { + my ($self, $folder, %params) = @_; + + my $instance = delete $params{instance} || $self->{instance}; + my $deleted = delete $params{deleted} || 0; + die "Bad params: " . join(' ', keys %params) + if scalar %params; + + my $display_folder = ($deleted ? "DELETED " : "") . $folder; + xlog $self, "Checking that $display_folder does not exist on disk"; + + if ($deleted) { + my @dirs = $instance->folder_to_deleted_directories($folder); + $self->assert_equals(0, scalar(@dirs), + "directory unexpectedly present for $display_folder"); + } else { + my $dir = $instance->folder_to_directory($folder); + $self->assert_null($dir, + "directory unexpectedly present for $display_folder"); + } } -sub check_syslog -{ - my ($self, $instance) = @_; +sub check_syslog { + my ($self, $instance) = @_; - my $remove_empty_pat = qr/Remove of supposedly empty directory/; + my $remove_empty_pat = qr/Remove of supposedly empty directory/; - $self->assert_null($instance->_check_syslog($remove_empty_pat)); + $self->assert_null($instance->_check_syslog($remove_empty_pat)); } sub test_self_inbox_imm - :ImmediateDelete :SemidelayedExpunge :NoAltNameSpace -{ - my ($self) = @_; - - xlog $self, "Testing that a non-admin can delete an a subfolder"; - xlog $self, "but cannot delete their own INBOX, immediate delete version"; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - my $subfolder = 'INBOX.foo'; - - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Generate a message in $inbox"; - my %exp_inbox; - $exp_inbox{A} = $self->make_message("Message $inbox A"); - $self->check_messages(\%exp_inbox); - - xlog $self, "Generate a message in $subfolder"; - my %exp_sub; - $store->set_folder($subfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $exp_sub{A} = $self->make_message("Message $subfolder A"); - $self->check_messages(\%exp_sub); - - $self->check_folder_ondisk($inbox, expected => \%exp_inbox); - $self->check_folder_ondisk($subfolder, expected => \%exp_sub); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - xlog $self, "can delete the subfolder"; - $talk->unselect(); - $talk->delete($subfolder) - or $self->fail("Cannot delete folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Cannot select the subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - xlog $self, "But the message in $inbox is still there"; - $store->set_folder($inbox); - $store->_select(); - $self->check_messages(\%exp_inbox); - - xlog $self, "cannot delete our own $inbox"; - $talk->delete($inbox); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Operation is not supported/i, $talk->get_last_error()); - - xlog $self, "And the message in $inbox is still there"; - $store->set_folder($inbox); - $store->_select(); - $self->check_messages(\%exp_inbox); - - $self->check_folder_ondisk($inbox, expected => \%exp_inbox); - $self->check_folder_not_ondisk($subfolder); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - $self->check_syslog($self->{instance}); + : ImmediateDelete : SemidelayedExpunge : NoAltNameSpace { + my ($self) = @_; + + xlog $self, "Testing that a non-admin can delete an a subfolder"; + xlog $self, "but cannot delete their own INBOX, immediate delete version"; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + my $subfolder = 'INBOX.foo'; + + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Generate a message in $inbox"; + my %exp_inbox; + $exp_inbox{A} = $self->make_message("Message $inbox A"); + $self->check_messages(\%exp_inbox); + + xlog $self, "Generate a message in $subfolder"; + my %exp_sub; + $store->set_folder($subfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $exp_sub{A} = $self->make_message("Message $subfolder A"); + $self->check_messages(\%exp_sub); + + $self->check_folder_ondisk($inbox, expected => \%exp_inbox); + $self->check_folder_ondisk($subfolder, expected => \%exp_sub); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + xlog $self, "can delete the subfolder"; + $talk->unselect(); + $talk->delete($subfolder) + or $self->fail("Cannot delete folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Cannot select the subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + xlog $self, "But the message in $inbox is still there"; + $store->set_folder($inbox); + $store->_select(); + $self->check_messages(\%exp_inbox); + + xlog $self, "cannot delete our own $inbox"; + $talk->delete($inbox); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Operation is not supported/i, + $talk->get_last_error()); + + xlog $self, "And the message in $inbox is still there"; + $store->set_folder($inbox); + $store->_select(); + $self->check_messages(\%exp_inbox); + + $self->check_folder_ondisk($inbox, expected => \%exp_inbox); + $self->check_folder_not_ondisk($subfolder); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + $self->check_syslog($self->{instance}); } sub test_self_inbox_del - :DelayedDelete :SemidelayedExpunge :NoAltNameSpace -{ - my ($self) = @_; - - xlog $self, "Testing that a non-admin can delete an a subfolder"; - xlog $self, "but cannot delete their own INBOX, delayed delete version"; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - my $subfolder = 'INBOX.foo'; - - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Generate a message in $inbox"; - my %exp_inbox; - $exp_inbox{A} = $self->make_message("Message $inbox A"); - $self->check_messages(\%exp_inbox); - - xlog $self, "Generate a message in $subfolder"; - my %exp_sub; - $store->set_folder($subfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $exp_sub{A} = $self->make_message("Message $subfolder A"); - $self->check_messages(\%exp_sub); - - $self->check_folder_ondisk($inbox, expected => \%exp_inbox); - $self->check_folder_ondisk($subfolder, expected => \%exp_sub); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - xlog $self, "can delete the subfolder"; - $talk->unselect(); - $talk->delete($subfolder) - or $self->fail("Cannot delete folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Cannot select the subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - xlog $self, "But the message in $inbox is still there"; - $store->set_folder($inbox); - $store->_select(); - $self->check_messages(\%exp_inbox); - - xlog $self, "cannot delete our own $inbox"; - $talk->delete($inbox); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Operation is not supported/i, $talk->get_last_error()); - - xlog $self, "And the message in $inbox is still there"; - $store->set_folder($inbox); - $store->_select(); - $self->check_messages(\%exp_inbox); - - $self->check_folder_ondisk($inbox, expected => \%exp_inbox); - $self->check_folder_not_ondisk($subfolder); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_ondisk($subfolder, deleted => 1, expected => \%exp_sub); - - $self->run_delayed_expunge(); - - $self->check_folder_ondisk($inbox, expected => \%exp_inbox); - $self->check_folder_not_ondisk($subfolder); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - $self->check_syslog($self->{instance}); + : DelayedDelete : SemidelayedExpunge : NoAltNameSpace { + my ($self) = @_; + + xlog $self, "Testing that a non-admin can delete an a subfolder"; + xlog $self, "but cannot delete their own INBOX, delayed delete version"; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + my $subfolder = 'INBOX.foo'; + + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Generate a message in $inbox"; + my %exp_inbox; + $exp_inbox{A} = $self->make_message("Message $inbox A"); + $self->check_messages(\%exp_inbox); + + xlog $self, "Generate a message in $subfolder"; + my %exp_sub; + $store->set_folder($subfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $exp_sub{A} = $self->make_message("Message $subfolder A"); + $self->check_messages(\%exp_sub); + + $self->check_folder_ondisk($inbox, expected => \%exp_inbox); + $self->check_folder_ondisk($subfolder, expected => \%exp_sub); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + xlog $self, "can delete the subfolder"; + $talk->unselect(); + $talk->delete($subfolder) + or $self->fail("Cannot delete folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Cannot select the subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + xlog $self, "But the message in $inbox is still there"; + $store->set_folder($inbox); + $store->_select(); + $self->check_messages(\%exp_inbox); + + xlog $self, "cannot delete our own $inbox"; + $talk->delete($inbox); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Operation is not supported/i, + $talk->get_last_error()); + + xlog $self, "And the message in $inbox is still there"; + $store->set_folder($inbox); + $store->_select(); + $self->check_messages(\%exp_inbox); + + $self->check_folder_ondisk($inbox, expected => \%exp_inbox); + $self->check_folder_not_ondisk($subfolder); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_ondisk($subfolder, deleted => 1, expected => \%exp_sub); + + $self->run_delayed_expunge(); + + $self->check_folder_ondisk($inbox, expected => \%exp_inbox); + $self->check_folder_not_ondisk($subfolder); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + $self->check_syslog($self->{instance}); } # old version of this test for builds without newer httpd features # n.b. 2.5 httpd can't be built anymore because of dependency on # very old libical sub test_admin_inbox_imm_legacy - :ImmediateDelete :SemidelayedExpunge :NoAltNameSpace -{ - my ($self) = @_; - - xlog $self, "Testing that an admin can delete the INBOX of a user"; - xlog $self, "and it will delete the whole user, immediate delete version"; - - # can't do the magic disconnect handling on older perl - return if ($] < 5.010); - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - my $inbox = 'user.cassandane'; - my $subfolder = 'user.cassandane.foo'; - - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Generate a message in $inbox"; - my %exp_inbox; - $exp_inbox{A} = $self->make_message("Message $inbox A"); - $self->check_messages(\%exp_inbox); - - xlog $self, "Generate a message in $subfolder"; - my %exp_sub; - $store->set_folder($subfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $exp_sub{A} = $self->make_message("Message $subfolder A"); - $self->check_messages(\%exp_sub); - $talk->unselect(); - - $self->check_folder_ondisk($inbox, expected => \%exp_inbox); - $self->check_folder_ondisk($subfolder, expected => \%exp_sub); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - xlog $self, "admin can delete $inbox"; - $admintalk->delete($inbox); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - { - # shut up - local $SIG{__DIE__}; - local $SIG{__WARN__} = sub { 1 }; - - xlog $self, "Client was disconnected"; - my $Res = eval { $talk->select($inbox) }; - $self->assert_null($Res); - - # reconnect - $talk = $store->get_client(); - } - - xlog $self, "Cannot select $inbox anymore"; - $talk->select($inbox); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - xlog $self, "Cannot select $subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - $self->check_folder_not_ondisk($inbox); - $self->check_folder_not_ondisk($subfolder); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - $self->check_syslog($self->{instance}); + : ImmediateDelete : SemidelayedExpunge : NoAltNameSpace { + my ($self) = @_; + + xlog $self, "Testing that an admin can delete the INBOX of a user"; + xlog $self, "and it will delete the whole user, immediate delete version"; + + # can't do the magic disconnect handling on older perl + return if ($] < 5.010); + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $inbox = 'user.cassandane'; + my $subfolder = 'user.cassandane.foo'; + + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Generate a message in $inbox"; + my %exp_inbox; + $exp_inbox{A} = $self->make_message("Message $inbox A"); + $self->check_messages(\%exp_inbox); + + xlog $self, "Generate a message in $subfolder"; + my %exp_sub; + $store->set_folder($subfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $exp_sub{A} = $self->make_message("Message $subfolder A"); + $self->check_messages(\%exp_sub); + $talk->unselect(); + + $self->check_folder_ondisk($inbox, expected => \%exp_inbox); + $self->check_folder_ondisk($subfolder, expected => \%exp_sub); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + xlog $self, "admin can delete $inbox"; + $admintalk->delete($inbox); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + { + # shut up + local $SIG{__DIE__}; + local $SIG{__WARN__} = sub { 1 }; + + xlog $self, "Client was disconnected"; + my $Res = eval { $talk->select($inbox) }; + $self->assert_null($Res); + + # reconnect + $talk = $store->get_client(); + } + + xlog $self, "Cannot select $inbox anymore"; + $talk->select($inbox); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + xlog $self, "Cannot select $subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + $self->check_folder_not_ondisk($inbox); + $self->check_folder_not_ondisk($subfolder); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + $self->check_syslog($self->{instance}); } sub test_admin_inbox_imm - :ImmediateDelete :SemidelayedExpunge :NoAltNameSpace - :needs_component_httpd -{ - my ($self) = @_; - - xlog $self, "Testing that an admin can delete the INBOX of a user"; - xlog $self, "and it will delete the whole user, immediate delete version"; - - # can't do the magic disconnect handling on older perl - return if ($] < 5.010); - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - my $inbox = 'user.cassandane'; - my $subfolder = 'user.cassandane.foo'; - my $sharedfolder = 'shared'; - - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Create a shared folder"; - $admintalk->create($sharedfolder) - or $self->fail("Cannot create folder $sharedfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $admintalk->setacl($sharedfolder, admin => 'lrswipkxtecdan'); - $admintalk->setacl($sharedfolder, cassandane => 'lrsip'); - - xlog $self, "Generate a message in $inbox"; - my %exp_inbox; - $exp_inbox{A} = $self->make_message("Message $inbox A"); - $self->check_messages(\%exp_inbox); - - xlog $self, "Generate a message in $subfolder"; - my %exp_sub; - $store->set_folder($subfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $exp_sub{A} = $self->make_message("Message $subfolder A"); - $self->check_messages(\%exp_sub); - $talk->unselect(); - - xlog $self, "Generate a message in $sharedfolder"; - my %exp_shared; - $store->set_folder($sharedfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $exp_shared{A} = $self->make_message("Message $sharedfolder A"); - $self->check_messages(\%exp_shared); - - xlog $self, "Set \\Seen on message A"; - $talk->store('1', '+flags', '(\\Seen)'); - $talk->unselect(); - - $self->check_folder_ondisk($inbox, expected => \%exp_inbox); - $self->check_folder_ondisk($subfolder, expected => \%exp_sub); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - xlog $self, "Subscribe to INBOX"; - $talk->subscribe("INBOX"); - - xlog $self, "Install a sieve script"; - $self->{instance}->install_sieve_script(<{store}; + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $inbox = 'user.cassandane'; + my $subfolder = 'user.cassandane.foo'; + my $sharedfolder = 'shared'; + + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Create a shared folder"; + $admintalk->create($sharedfolder) + or $self->fail("Cannot create folder $sharedfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $admintalk->setacl($sharedfolder, admin => 'lrswipkxtecdan'); + $admintalk->setacl($sharedfolder, cassandane => 'lrsip'); + + xlog $self, "Generate a message in $inbox"; + my %exp_inbox; + $exp_inbox{A} = $self->make_message("Message $inbox A"); + $self->check_messages(\%exp_inbox); + + xlog $self, "Generate a message in $subfolder"; + my %exp_sub; + $store->set_folder($subfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $exp_sub{A} = $self->make_message("Message $subfolder A"); + $self->check_messages(\%exp_sub); + $talk->unselect(); + + xlog $self, "Generate a message in $sharedfolder"; + my %exp_shared; + $store->set_folder($sharedfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $exp_shared{A} = $self->make_message("Message $sharedfolder A"); + $self->check_messages(\%exp_shared); + + xlog $self, "Set \\Seen on message A"; + $talk->store('1', '+flags', '(\\Seen)'); + $talk->unselect(); + + $self->check_folder_ondisk($inbox, expected => \%exp_inbox); + $self->check_folder_ondisk($subfolder, expected => \%exp_sub); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + xlog $self, "Subscribe to INBOX"; + $talk->subscribe("INBOX"); + + xlog $self, "Install a sieve script"; + $self->{instance}->install_sieve_script( + <{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "Verify user data files/directories exist"; - my $data = $self->{instance}->run_mbpath('-u', 'cassandane'); - $self->assert_file_test($data->{user}{'sub'}, '-f'); - $self->assert_file_test($data->{user}{seen}, '-f'); - $self->assert_file_test($data->{user}{dav}, '-f'); - $self->assert_file_test($data->{user}{counters}, '-f'); - $self->assert_file_test($data->{user}{conversations}, '-f'); - $self->assert_file_test($data->{user}{xapianactive}, '-f'); - $self->assert_file_test("$data->{user}{sieve}/defaultbc", '-f'); - $self->assert_file_test($data->{xapian}{t1}, '-d'); - - xlog $self, "admin can delete $inbox"; - $admintalk->delete($inbox); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - { - # shut up - local $SIG{__DIE__}; - local $SIG{__WARN__} = sub { 1 }; - - xlog $self, "Client was disconnected"; - my $Res = eval { $talk->select($inbox) }; - $self->assert_null($Res); - - # reconnect - $talk = $store->get_client(); - } - - xlog $self, "Cannot select $inbox anymore"; - $talk->select($inbox); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - xlog $self, "Cannot select $subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - $self->check_folder_not_ondisk($inbox); - $self->check_folder_not_ondisk($subfolder); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - my ($maj, $min) = Cassandane::Instance->get_version(); - - xlog $self, "Verify user data directories have been deleted"; - if (($maj > 3 || ($maj == 3 && $min > 4)) - && !$self->{instance}->{config}->get_bool('mailbox_legacy_dirs')) - { - # Entire UUID-hashed directory should be removed - $self->assert_not_file_test(dirname($data->{user}{dav}), '-e'); - } - else { - # Name-hashed directory will be left behind, so check individual files - $self->assert_not_file_test($data->{user}{'sub'}, '-e'); - $self->assert_not_file_test($data->{user}{seen}, '-e'); - $self->assert_not_file_test($data->{user}{dav}, '-e'); - $self->assert_not_file_test($data->{user}{counters}, '-e'); - $self->assert_not_file_test($data->{user}{conversations}, '-e'); - $self->assert_not_file_test($data->{user}{xapianactive}, '-e'); - } - $self->assert_not_file_test($data->{user}{sieve}, '-e'); - $self->assert_not_file_test($data->{xapian}{t1}, '-e'); - - $self->check_syslog($self->{instance}); + ); + + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "Verify user data files/directories exist"; + my $data = $self->{instance}->run_mbpath('-u', 'cassandane'); + $self->assert_file_test($data->{user}{'sub'}, '-f'); + $self->assert_file_test($data->{user}{seen}, '-f'); + $self->assert_file_test($data->{user}{dav}, '-f'); + $self->assert_file_test($data->{user}{counters}, '-f'); + $self->assert_file_test($data->{user}{conversations}, '-f'); + $self->assert_file_test($data->{user}{xapianactive}, '-f'); + $self->assert_file_test("$data->{user}{sieve}/defaultbc", '-f'); + $self->assert_file_test($data->{xapian}{t1}, '-d'); + + xlog $self, "admin can delete $inbox"; + $admintalk->delete($inbox); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + { + # shut up + local $SIG{__DIE__}; + local $SIG{__WARN__} = sub { 1 }; + + xlog $self, "Client was disconnected"; + my $Res = eval { $talk->select($inbox) }; + $self->assert_null($Res); + + # reconnect + $talk = $store->get_client(); + } + + xlog $self, "Cannot select $inbox anymore"; + $talk->select($inbox); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + xlog $self, "Cannot select $subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + $self->check_folder_not_ondisk($inbox); + $self->check_folder_not_ondisk($subfolder); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + my ($maj, $min) = Cassandane::Instance->get_version(); + + xlog $self, "Verify user data directories have been deleted"; + if (($maj > 3 || ($maj == 3 && $min > 4)) + && !$self->{instance}->{config}->get_bool('mailbox_legacy_dirs')) + { + # Entire UUID-hashed directory should be removed + $self->assert_not_file_test(dirname($data->{user}{dav}), '-e'); + } else { + # Name-hashed directory will be left behind, so check individual files + $self->assert_not_file_test($data->{user}{'sub'}, '-e'); + $self->assert_not_file_test($data->{user}{seen}, '-e'); + $self->assert_not_file_test($data->{user}{dav}, '-e'); + $self->assert_not_file_test($data->{user}{counters}, '-e'); + $self->assert_not_file_test($data->{user}{conversations}, '-e'); + $self->assert_not_file_test($data->{user}{xapianactive}, '-e'); + } + $self->assert_not_file_test($data->{user}{sieve}, '-e'); + $self->assert_not_file_test($data->{xapian}{t1}, '-e'); + + $self->check_syslog($self->{instance}); } sub test_admin_inbox_del - :DelayedDelete :SemidelayedExpunge :NoAltNameSpace -{ - my ($self) = @_; - - xlog $self, "Testing that an admin can delete the INBOX of a user"; - xlog $self, "and it will delete the whole user, delayed delete version"; - - # can't do the magic disconnect handling on older perl - return if ($] < 5.010); - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - my $inbox = 'user.cassandane'; - my $subfolder = 'user.cassandane.foo'; - - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Generate a message in $inbox"; - my %exp_inbox; - $exp_inbox{A} = $self->make_message("Message $inbox A"); - $self->check_messages(\%exp_inbox); - - xlog $self, "Generate a message in $subfolder"; - my %exp_sub; - $store->set_folder($subfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $exp_sub{A} = $self->make_message("Message $subfolder A"); - $self->check_messages(\%exp_sub); - $talk->unselect(); - - $self->check_folder_ondisk($inbox, expected => \%exp_inbox); - $self->check_folder_ondisk($subfolder, expected => \%exp_sub); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - xlog $self, "admin can delete $inbox"; - $admintalk->delete($inbox); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - { - # shut up - local $SIG{__DIE__}; - local $SIG{__WARN__} = sub { 1 }; - - xlog $self, "Client was disconnected"; - my $Res = eval { $talk->select($inbox) }; - $self->assert_null($Res); - - # reconnect - $talk = $store->get_client(); - } - - xlog $self, "Cannot select $inbox anymore"; - $talk->select($inbox); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - xlog $self, "Cannot select $subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - $self->check_folder_not_ondisk($inbox); - $self->check_folder_not_ondisk($subfolder); - $self->check_folder_ondisk($inbox, deleted => 1, expected => \%exp_inbox); - $self->check_folder_ondisk($subfolder, deleted => 1, expected => \%exp_sub); - - $self->run_delayed_expunge(); - - $self->check_folder_not_ondisk($inbox); - $self->check_folder_not_ondisk($subfolder); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - $self->check_syslog($self->{instance}); + : DelayedDelete : SemidelayedExpunge : NoAltNameSpace { + my ($self) = @_; + + xlog $self, "Testing that an admin can delete the INBOX of a user"; + xlog $self, "and it will delete the whole user, delayed delete version"; + + # can't do the magic disconnect handling on older perl + return if ($] < 5.010); + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $inbox = 'user.cassandane'; + my $subfolder = 'user.cassandane.foo'; + + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Generate a message in $inbox"; + my %exp_inbox; + $exp_inbox{A} = $self->make_message("Message $inbox A"); + $self->check_messages(\%exp_inbox); + + xlog $self, "Generate a message in $subfolder"; + my %exp_sub; + $store->set_folder($subfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $exp_sub{A} = $self->make_message("Message $subfolder A"); + $self->check_messages(\%exp_sub); + $talk->unselect(); + + $self->check_folder_ondisk($inbox, expected => \%exp_inbox); + $self->check_folder_ondisk($subfolder, expected => \%exp_sub); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + xlog $self, "admin can delete $inbox"; + $admintalk->delete($inbox); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + { + # shut up + local $SIG{__DIE__}; + local $SIG{__WARN__} = sub { 1 }; + + xlog $self, "Client was disconnected"; + my $Res = eval { $talk->select($inbox) }; + $self->assert_null($Res); + + # reconnect + $talk = $store->get_client(); + } + + xlog $self, "Cannot select $inbox anymore"; + $talk->select($inbox); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + xlog $self, "Cannot select $subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + $self->check_folder_not_ondisk($inbox); + $self->check_folder_not_ondisk($subfolder); + $self->check_folder_ondisk($inbox, deleted => 1, expected => \%exp_inbox); + $self->check_folder_ondisk($subfolder, deleted => 1, expected => \%exp_sub); + + $self->run_delayed_expunge(); + + $self->check_folder_not_ondisk($inbox); + $self->check_folder_not_ondisk($subfolder); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + $self->check_syslog($self->{instance}); } sub test_bz3781 - :ImmediateDelete :SemidelayedExpunge :NoAltNameSpace -{ - my ($self) = @_; + : ImmediateDelete : SemidelayedExpunge : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing that a folder can be deleted when there is"; - xlog $self, "unexpected files in the proc directory (Bug 3781)"; + xlog $self, "Testing that a folder can be deleted when there is"; + xlog $self, "unexpected files in the proc directory (Bug 3781)"; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'user.cassandane'; - my $subfolder = 'user.cassandane.foo'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'user.cassandane'; + my $subfolder = 'user.cassandane.foo'; - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->check_folder_ondisk($subfolder); + $self->check_folder_ondisk($subfolder); - xlog $self, "Create unexpected files in proc directory"; - my $procdir = $self->{instance}->{basedir} . "/conf/proc"; - POSIX::close(POSIX::creat("$procdir/xxx", 0600)); # non-numeric name - POSIX::close(POSIX::creat("$procdir/123", 0600)); # valid name but empty + xlog $self, "Create unexpected files in proc directory"; + my $procdir = $self->{instance}->{basedir} . "/conf/proc"; + POSIX::close(POSIX::creat("$procdir/xxx", 0600)); # non-numeric name + POSIX::close(POSIX::creat("$procdir/123", 0600)); # valid name but empty - xlog $self, "can delete $subfolder"; - $talk->delete($subfolder); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "can delete $subfolder"; + $talk->delete($subfolder); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "Cannot select $subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + xlog $self, "Cannot select $subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - $self->check_folder_not_ondisk($subfolder); + $self->check_folder_not_ondisk($subfolder); - # We should have generated an IOERROR - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: bogus filename/); + # We should have generated an IOERROR + $self->assert_syslog_matches($self->{instance}, qr/IOERROR: bogus filename/); } sub test_cyr_expire_delete - :DelayedDelete :min_version_3_0 :NoAltNameSpace -{ - my ($self) = @_; - - my $store = $self->{store}; - my $adminstore = $self->{adminstore}; - my $talk = $store->get_client(); - my $admintalk = $adminstore->get_client(); - - my $inbox = 'INBOX'; - my $subfoldername = 'foo'; - my $subfolder = 'INBOX.foo'; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Append a messages to $inbox"; - my %msg_inbox; - $msg_inbox{A} = $self->make_message('Message A in $inbox'); - $self->check_messages(\%msg_inbox); - - xlog $self, "Append 3 messages to $subfolder"; - my %msg_sub; - $store->set_folder($subfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $msg_sub{A} = $self->make_message('Message A in $subfolder'); - $msg_sub{B} = $self->make_message('Message B in $subfolder'); - $msg_sub{C} = $self->make_message('Message C in $subfolder'); - $self->check_messages(\%msg_sub); - - $self->check_folder_ondisk($inbox, expected => \%msg_inbox); - $self->check_folder_ondisk($subfolder, expected => \%msg_sub); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - xlog $self, "Delete $subfolder"; - $talk->unselect(); - $talk->delete($subfolder) - or $self->fail("Cannot delete folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Ensure we can't select $subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - $self->check_folder_not_ondisk($subfolder); - - xlog $self, "Ensure we still have messages in $inbox"; - $store->set_folder($inbox); - $store->_select(); - $self->check_messages(\%msg_inbox); - - my ($datapath) = $self->{instance}->folder_to_deleted_directories("user.cassandane.$subfoldername"); - $self->assert_not_null($datapath); - - xlog $self, "Run cyr_expire -D now."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0' ); - - # the folder should not exist now! - $self->assert_not_file_test($datapath, '-d'); - - # and not exist from mbpath either... - $self->assert_null($self->{instance}->folder_to_deleted_directories("user.cassandane.$subfoldername")); - - $self->check_syslog($self->{instance}); + : DelayedDelete : min_version_3_0 : NoAltNameSpace { + my ($self) = @_; + + my $store = $self->{store}; + my $adminstore = $self->{adminstore}; + my $talk = $store->get_client(); + my $admintalk = $adminstore->get_client(); + + my $inbox = 'INBOX'; + my $subfoldername = 'foo'; + my $subfolder = 'INBOX.foo'; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Append a messages to $inbox"; + my %msg_inbox; + $msg_inbox{A} = $self->make_message('Message A in $inbox'); + $self->check_messages(\%msg_inbox); + + xlog $self, "Append 3 messages to $subfolder"; + my %msg_sub; + $store->set_folder($subfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $msg_sub{A} = $self->make_message('Message A in $subfolder'); + $msg_sub{B} = $self->make_message('Message B in $subfolder'); + $msg_sub{C} = $self->make_message('Message C in $subfolder'); + $self->check_messages(\%msg_sub); + + $self->check_folder_ondisk($inbox, expected => \%msg_inbox); + $self->check_folder_ondisk($subfolder, expected => \%msg_sub); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + xlog $self, "Delete $subfolder"; + $talk->unselect(); + $talk->delete($subfolder) + or $self->fail("Cannot delete folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Ensure we can't select $subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + $self->check_folder_not_ondisk($subfolder); + + xlog $self, "Ensure we still have messages in $inbox"; + $store->set_folder($inbox); + $store->_select(); + $self->check_messages(\%msg_inbox); + + my ($datapath) + = $self->{instance} + ->folder_to_deleted_directories("user.cassandane.$subfoldername"); + $self->assert_not_null($datapath); + + xlog $self, "Run cyr_expire -D now."; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0'); + + # the folder should not exist now! + $self->assert_not_file_test($datapath, '-d'); + + # and not exist from mbpath either... + $self->assert_null($self->{instance} + ->folder_to_deleted_directories("user.cassandane.$subfoldername")); + + $self->check_syslog($self->{instance}); } sub test_allowdeleted - :AllowDeleted :DelayedDelete :min_version_3_1 :NoAltNameSpace -{ - my ($self) = @_; - - my $store = $self->{store}; - my $adminstore = $self->{adminstore}; - my $talk = $store->get_client(); - my $admintalk = $adminstore->get_client(); - - my $inbox = 'INBOX'; - my $subfolder = 'INBOX.foo'; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - $self->make_message('A message'); - $talk->select("INBOX"); - $talk->copy("1:*", $subfolder); - $talk->unselect(); - - xlog $self, "Delete $subfolder"; - $talk->delete($subfolder) - or $self->fail("Cannot delete folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Check standard list only included Inbox"; - my $result = $talk->list('', '*'); - $self->assert_num_equals(1, scalar(@$result)); - - xlog $self, "Check include-deleted LIST includes deleted mailbox"; - $result = $talk->list(['VENDOR.CMU-INCLUDE-DELETED'], '', '*'); - $self->assert_num_equals(2, scalar(@$result)); - $self->assert_str_equals("INBOX", $result->[0][2]); - $self->assert_matches(qr/^DELETED./, $result->[1][2]); - - xlog $self, "Check that select of DELETED folder works and finds messages"; - $talk->select($result->[1][2]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_num_equals(1, $talk->get_response_code('exists')); - - $self->check_syslog($self->{instance}); + : AllowDeleted : DelayedDelete : min_version_3_1 : NoAltNameSpace { + my ($self) = @_; + + my $store = $self->{store}; + my $adminstore = $self->{adminstore}; + my $talk = $store->get_client(); + my $admintalk = $adminstore->get_client(); + + my $inbox = 'INBOX'; + my $subfolder = 'INBOX.foo'; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + $self->make_message('A message'); + $talk->select("INBOX"); + $talk->copy("1:*", $subfolder); + $talk->unselect(); + + xlog $self, "Delete $subfolder"; + $talk->delete($subfolder) + or $self->fail("Cannot delete folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Check standard list only included Inbox"; + my $result = $talk->list('', '*'); + $self->assert_num_equals(1, scalar(@$result)); + + xlog $self, "Check include-deleted LIST includes deleted mailbox"; + $result = $talk->list(['VENDOR.CMU-INCLUDE-DELETED'], '', '*'); + $self->assert_num_equals(2, scalar(@$result)); + $self->assert_str_equals("INBOX", $result->[0][2]); + $self->assert_matches(qr/^DELETED./, $result->[1][2]); + + xlog $self, "Check that select of DELETED folder works and finds messages"; + $talk->select($result->[1][2]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_num_equals(1, $talk->get_response_code('exists')); + + $self->check_syslog($self->{instance}); } sub test_cyr_expire_delete_with_annotation - :DelayedDelete :min_version_3_1 :NoAltNameSpace -{ - my ($self) = @_; - - my $store = $self->{store}; - my $adminstore = $self->{adminstore}; - my $talk = $store->get_client(); - my $admintalk = $adminstore->get_client(); - - my $inbox = 'INBOX'; - my $subfoldername = 'foo'; - my $subfolder = 'INBOX.foo'; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Append a messages to $inbox"; - my %msg_inbox; - $msg_inbox{A} = $self->make_message('Message A in $inbox'); - $self->check_messages(\%msg_inbox); - - xlog $self, "Setting /vendor/cmu/cyrus-imapd/delete annotation."; - $talk->setmetadata($subfolder, "/shared/vendor/cmu/cyrus-imapd/delete", '3'); - - xlog $self, "Append 3 messages to $subfolder"; - my %msg_sub; - $store->set_folder($subfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $msg_sub{A} = $self->make_message('Message A in $subfolder'); - $msg_sub{B} = $self->make_message('Message B in $subfolder'); - $msg_sub{C} = $self->make_message('Message C in $subfolder'); - $self->check_messages(\%msg_sub); - - $self->check_folder_ondisk($inbox, expected => \%msg_inbox); - $self->check_folder_ondisk($subfolder, expected => \%msg_sub); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - xlog $self, "Delete $subfolder"; - $talk->unselect(); - $talk->delete($subfolder) - or $self->fail("Cannot delete folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Ensure we can't select $subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - $self->check_folder_not_ondisk($subfolder); - - xlog $self, "Ensure we still have messages in $inbox"; - $store->set_folder($inbox); - $store->_select(); - $self->check_messages(\%msg_inbox); - - my ($path) = $self->{instance}->folder_to_deleted_directories("user.cassandane.$subfoldername"); - $self->assert_file_test($path, '-d'); - - xlog $self, "Run cyr_expire -D now, it shouldn't delete."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0' ); - $self->assert_file_test($path, '-d'); - - xlog $self, "Run cyr_expire -D now, with -a, skipping annotation."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0', '-a' ); - $self->assert_not_file_test($path, '-d'); - - $self->check_syslog($self->{instance}); + : DelayedDelete : min_version_3_1 : NoAltNameSpace { + my ($self) = @_; + + my $store = $self->{store}; + my $adminstore = $self->{adminstore}; + my $talk = $store->get_client(); + my $admintalk = $adminstore->get_client(); + + my $inbox = 'INBOX'; + my $subfoldername = 'foo'; + my $subfolder = 'INBOX.foo'; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Append a messages to $inbox"; + my %msg_inbox; + $msg_inbox{A} = $self->make_message('Message A in $inbox'); + $self->check_messages(\%msg_inbox); + + xlog $self, "Setting /vendor/cmu/cyrus-imapd/delete annotation."; + $talk->setmetadata($subfolder, "/shared/vendor/cmu/cyrus-imapd/delete", '3'); + + xlog $self, "Append 3 messages to $subfolder"; + my %msg_sub; + $store->set_folder($subfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $msg_sub{A} = $self->make_message('Message A in $subfolder'); + $msg_sub{B} = $self->make_message('Message B in $subfolder'); + $msg_sub{C} = $self->make_message('Message C in $subfolder'); + $self->check_messages(\%msg_sub); + + $self->check_folder_ondisk($inbox, expected => \%msg_inbox); + $self->check_folder_ondisk($subfolder, expected => \%msg_sub); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + xlog $self, "Delete $subfolder"; + $talk->unselect(); + $talk->delete($subfolder) + or $self->fail("Cannot delete folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Ensure we can't select $subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + $self->check_folder_not_ondisk($subfolder); + + xlog $self, "Ensure we still have messages in $inbox"; + $store->set_folder($inbox); + $store->_select(); + $self->check_messages(\%msg_inbox); + + my ($path) + = $self->{instance} + ->folder_to_deleted_directories("user.cassandane.$subfoldername"); + $self->assert_file_test($path, '-d'); + + xlog $self, "Run cyr_expire -D now, it shouldn't delete."; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0'); + $self->assert_file_test($path, '-d'); + + xlog $self, "Run cyr_expire -D now, with -a, skipping annotation."; + $self->{instance} + ->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0', '-a'); + $self->assert_not_file_test($path, '-d'); + + $self->check_syslog($self->{instance}); } # https://github.com/cyrusimap/cyrus-imapd/issues/2413 sub test_cyr_expire_dont_resurrect_convdb - :Conversations :DelayedDelete :min_version_3_0 :NoAltNameSpace -{ - my ($self) = @_; - - my $store = $self->{store}; - my $adminstore = $self->{adminstore}; - my $talk = $store->get_client(); - my $admintalk = $adminstore->get_client(); - - my $basedir = $self->{instance}->{basedir}; - - my $inbox = 'INBOX'; - my $subfoldername = 'foo'; - my $subfolder = 'INBOX.foo'; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Append a messages to $inbox"; - my %msg_inbox; - $msg_inbox{A} = $self->make_message('Message A in $inbox'); - $self->check_messages(\%msg_inbox); - - xlog $self, "Append 3 messages to $subfolder"; - my %msg_sub; - $store->set_folder($subfolder); - $store->_select(); - $self->{gen}->set_next_uid(1); - $msg_sub{A} = $self->make_message('Message A in $subfolder'); - $msg_sub{B} = $self->make_message('Message B in $subfolder'); - $msg_sub{C} = $self->make_message('Message C in $subfolder'); - $self->check_messages(\%msg_sub); - - $self->check_folder_ondisk($inbox, expected => \%msg_inbox); - $self->check_folder_ondisk($subfolder, expected => \%msg_sub); - $self->check_folder_not_ondisk($inbox, deleted => 1); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - # expect user has a conversations database - my $convdbfile = $self->{instance}->get_conf_user_file("cassandane", "conversations"); - $self->assert_file_test($convdbfile, '-f'); - - # log cassandane user out before it gets thrown out anyway - undef $talk; - $store->disconnect(); - - xlog $self, "Delete cassandane user"; - $admintalk->delete("user.cassandane"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - # expect user does not have a conversations database - $self->assert_not_file_test($convdbfile, '-f'); - $self->check_folder_not_ondisk($inbox); - $self->check_folder_ondisk($inbox, deleted => 1); - - xlog $self, "Run cyr_expire -E now."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-E' => '1' ); - $self->check_folder_ondisk($inbox, deleted => 1); - - # expect user does not have a conversations database - $self->assert_not_file_test($convdbfile, '-f'); - - xlog $self, "Run cyr_expire -D now."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0' ); - $self->check_folder_not_ondisk($inbox, deleted => 1); - - # expect user does not have a conversations database - $self->assert_not_file_test($convdbfile, '-f'); - - $self->check_syslog($self->{instance}); + : Conversations : DelayedDelete : min_version_3_0 : NoAltNameSpace { + my ($self) = @_; + + my $store = $self->{store}; + my $adminstore = $self->{adminstore}; + my $talk = $store->get_client(); + my $admintalk = $adminstore->get_client(); + + my $basedir = $self->{instance}->{basedir}; + + my $inbox = 'INBOX'; + my $subfoldername = 'foo'; + my $subfolder = 'INBOX.foo'; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Append a messages to $inbox"; + my %msg_inbox; + $msg_inbox{A} = $self->make_message('Message A in $inbox'); + $self->check_messages(\%msg_inbox); + + xlog $self, "Append 3 messages to $subfolder"; + my %msg_sub; + $store->set_folder($subfolder); + $store->_select(); + $self->{gen}->set_next_uid(1); + $msg_sub{A} = $self->make_message('Message A in $subfolder'); + $msg_sub{B} = $self->make_message('Message B in $subfolder'); + $msg_sub{C} = $self->make_message('Message C in $subfolder'); + $self->check_messages(\%msg_sub); + + $self->check_folder_ondisk($inbox, expected => \%msg_inbox); + $self->check_folder_ondisk($subfolder, expected => \%msg_sub); + $self->check_folder_not_ondisk($inbox, deleted => 1); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + # expect user has a conversations database + my $convdbfile + = $self->{instance}->get_conf_user_file("cassandane", "conversations"); + $self->assert_file_test($convdbfile, '-f'); + + # log cassandane user out before it gets thrown out anyway + undef $talk; + $store->disconnect(); + + xlog $self, "Delete cassandane user"; + $admintalk->delete("user.cassandane"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # expect user does not have a conversations database + $self->assert_not_file_test($convdbfile, '-f'); + $self->check_folder_not_ondisk($inbox); + $self->check_folder_ondisk($inbox, deleted => 1); + + xlog $self, "Run cyr_expire -E now."; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-E' => '1'); + $self->check_folder_ondisk($inbox, deleted => 1); + + # expect user does not have a conversations database + $self->assert_not_file_test($convdbfile, '-f'); + + xlog $self, "Run cyr_expire -D now."; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0'); + $self->check_folder_not_ondisk($inbox, deleted => 1); + + # expect user does not have a conversations database + $self->assert_not_file_test($convdbfile, '-f'); + + $self->check_syslog($self->{instance}); } sub test_no_delete_with_children - :DelayedDelete :min_version_3_3 -{ - my ($self) = @_; + : DelayedDelete : min_version_3_3 { + my ($self) = @_; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $subfolder = 'INBOX.foo'; - my $subsubfolder = 'INBOX.foo.bar'; + my $subfolder = 'INBOX.foo'; + my $subsubfolder = 'INBOX.foo.bar'; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $talk->create($subsubfolder) - or $self->fail("Cannot create folder $subsubfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->create($subsubfolder) + or $self->fail("Cannot create folder $subsubfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $talk->delete($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); + $talk->delete($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->check_syslog($self->{instance}); + $self->check_syslog($self->{instance}); } sub test_cyr_expire_inherit_annot - :DelayedDelete :min_version_3_9 :NoAltNameSpace -{ - my ($self) = @_; - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "Create subfolder"; - my $subfolder = 'INBOX.A'; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Set /vendor/cmu/cyrus-imapd/expire annotation on inbox"; - $talk->setmetadata('INBOX', "/shared/vendor/cmu/cyrus-imapd/expire", '1s'); - $self->assert_str_equals('ok', $talk->get_last_completion_response); - - xlog $self, "Create message"; - $store->set_folder($subfolder); - $self->make_message('msg1') or die; - - $talk->unselect(); - $talk->select($subfolder); - $self->assert_num_equals(1, $talk->get_response_code('exists')); - - xlog $self, "Run cyr_expire"; - sleep(2); - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '1s' ); - - $talk->unselect(); - $talk->select($subfolder); - $self->assert_num_equals(0, $talk->get_response_code('exists')); - - $self->check_syslog($self->{instance}); + : DelayedDelete : min_version_3_9 : NoAltNameSpace { + my ($self) = @_; + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "Create subfolder"; + my $subfolder = 'INBOX.A'; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Set /vendor/cmu/cyrus-imapd/expire annotation on inbox"; + $talk->setmetadata('INBOX', "/shared/vendor/cmu/cyrus-imapd/expire", '1s'); + $self->assert_str_equals('ok', $talk->get_last_completion_response); + + xlog $self, "Create message"; + $store->set_folder($subfolder); + $self->make_message('msg1') or die; + + $talk->unselect(); + $talk->select($subfolder); + $self->assert_num_equals(1, $talk->get_response_code('exists')); + + xlog $self, "Run cyr_expire"; + sleep(2); + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '1s'); + + $talk->unselect(); + $talk->select($subfolder); + $self->assert_num_equals(0, $talk->get_response_code('exists')); + + $self->check_syslog($self->{instance}); } sub test_cyr_expire_noexpire - :DelayedDelete :min_version_3_9 :NoAltNameSpace -{ - my ($self) = @_; - my $store = $self->{store}; - my $talk = $store->get_client(); + : DelayedDelete : min_version_3_9 : NoAltNameSpace { + my ($self) = @_; + my $store = $self->{store}; + my $talk = $store->get_client(); - my $noexpire_annot = '/shared/vendor/cmu/cyrus-imapd/noexpire_until'; + my $noexpire_annot = '/shared/vendor/cmu/cyrus-imapd/noexpire_until'; - xlog $self, "Create subfolder"; - my $subfolder = 'INBOX.A'; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "Create subfolder"; + my $subfolder = 'INBOX.A'; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "Set /vendor/cmu/cyrus-imapd/expire annotation on subfolder"; - $talk->setmetadata($subfolder, "/shared/vendor/cmu/cyrus-imapd/expire", '1s'); - $self->assert_str_equals('ok', $talk->get_last_completion_response); + xlog $self, "Set /vendor/cmu/cyrus-imapd/expire annotation on subfolder"; + $talk->setmetadata($subfolder, "/shared/vendor/cmu/cyrus-imapd/expire", '1s'); + $self->assert_str_equals('ok', $talk->get_last_completion_response); - xlog $self, "Create message"; - $store->set_folder($subfolder); - $self->make_message('msg1') or die; + xlog $self, "Create message"; + $store->set_folder($subfolder); + $self->make_message('msg1') or die; - xlog $self, "Set $noexpire_annot annotation on inbox"; - $talk->setmetadata('INBOX', $noexpire_annot, '0'); - $self->assert_str_equals('ok', $talk->get_last_completion_response); + xlog $self, "Set $noexpire_annot annotation on inbox"; + $talk->setmetadata('INBOX', $noexpire_annot, '0'); + $self->assert_str_equals('ok', $talk->get_last_completion_response); - $talk->unselect(); - $talk->select($subfolder); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + $talk->unselect(); + $talk->select($subfolder); + $self->assert_num_equals(1, $talk->get_response_code('exists')); - sleep(2); - xlog $self, "Run cyr_expire"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '1s', '-v', '-v', '-v' ); + sleep(2); + xlog $self, "Run cyr_expire"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '1s', '-v', '-v', '-v'); - $talk->unselect(); - $talk->select($subfolder); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + $talk->unselect(); + $talk->select($subfolder); + $self->assert_num_equals(1, $talk->get_response_code('exists')); - xlog $self, "Remove $noexpire_annot from inbox"; - $talk->setmetadata('INBOX', $noexpire_annot, ''); - $self->assert_str_equals('ok', $talk->get_last_completion_response); + xlog $self, "Remove $noexpire_annot from inbox"; + $talk->setmetadata('INBOX', $noexpire_annot, ''); + $self->assert_str_equals('ok', $talk->get_last_completion_response); - xlog $self, "Run cyr_expire"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '1s', '-v', '-v', '-v' ); + xlog $self, "Run cyr_expire"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '1s', '-v', '-v', '-v'); - $talk->unselect(); - $talk->select($subfolder); - $self->assert_num_equals(0, $talk->get_response_code('exists')); + $talk->unselect(); + $talk->select($subfolder); + $self->assert_num_equals(0, $talk->get_response_code('exists')); - $self->check_syslog($self->{instance}); + $self->check_syslog($self->{instance}); } sub test_cyr_expire_delete_noexpire - :DelayedDelete :min_version_3_9 :NoAltNameSpace -{ - my ($self) = @_; - my $store = $self->{store}; - my $adminstore = $self->{adminstore}; - my $talk = $store->get_client(); - my $admintalk = $adminstore->get_client(); - - my $noexpire_annot = '/shared/vendor/cmu/cyrus-imapd/noexpire_until'; - - my $subfoldername = 'foo'; - my $subfolder = 'INBOX.foo'; - $talk->create($subfolder) - or $self->fail("Cannot create folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Setting /vendor/cmu/cyrus-imapd/delete annotation."; - $talk->setmetadata($subfolder, "/shared/vendor/cmu/cyrus-imapd/delete", '1s'); - - $self->check_folder_ondisk($subfolder); - $self->check_folder_not_ondisk($subfolder, deleted => 1); - - xlog $self, "Delete $subfolder"; - $talk->unselect(); - $talk->delete($subfolder) - or $self->fail("Cannot delete folder $subfolder: $@"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Ensure we can't select $subfolder anymore"; - $talk->select($subfolder); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); - - $self->check_folder_not_ondisk($subfolder); - - my ($path) = $self->{instance}->folder_to_deleted_directories("user.cassandane.$subfoldername"); - $self->assert(-d "$path"); - - xlog $self, "Set $noexpire_annot annotation on inbox"; - $talk->setmetadata('INBOX', $noexpire_annot, '0'); - $self->assert_str_equals('ok', $talk->get_last_completion_response); - - sleep(2); - xlog $self, "Run cyr_expire"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '1s' ); - $self->assert(-d "$path"); - - xlog $self, "Remove $noexpire_annot annotation from inbox"; - $talk->setmetadata('INBOX', $noexpire_annot, ''); - $self->assert_str_equals('ok', $talk->get_last_completion_response); - - xlog $self, "Run cyr_expire"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '1s' ); - $self->assert(!-d "$path"); - - $self->check_syslog($self->{instance}); + : DelayedDelete : min_version_3_9 : NoAltNameSpace { + my ($self) = @_; + my $store = $self->{store}; + my $adminstore = $self->{adminstore}; + my $talk = $store->get_client(); + my $admintalk = $adminstore->get_client(); + + my $noexpire_annot = '/shared/vendor/cmu/cyrus-imapd/noexpire_until'; + + my $subfoldername = 'foo'; + my $subfolder = 'INBOX.foo'; + $talk->create($subfolder) + or $self->fail("Cannot create folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Setting /vendor/cmu/cyrus-imapd/delete annotation."; + $talk->setmetadata($subfolder, "/shared/vendor/cmu/cyrus-imapd/delete", '1s'); + + $self->check_folder_ondisk($subfolder); + $self->check_folder_not_ondisk($subfolder, deleted => 1); + + xlog $self, "Delete $subfolder"; + $talk->unselect(); + $talk->delete($subfolder) + or $self->fail("Cannot delete folder $subfolder: $@"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Ensure we can't select $subfolder anymore"; + $talk->select($subfolder); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, $talk->get_last_error()); + + $self->check_folder_not_ondisk($subfolder); + + my ($path) + = $self->{instance} + ->folder_to_deleted_directories("user.cassandane.$subfoldername"); + $self->assert(-d "$path"); + + xlog $self, "Set $noexpire_annot annotation on inbox"; + $talk->setmetadata('INBOX', $noexpire_annot, '0'); + $self->assert_str_equals('ok', $talk->get_last_completion_response); + + sleep(2); + xlog $self, "Run cyr_expire"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '1s'); + $self->assert(-d "$path"); + + xlog $self, "Remove $noexpire_annot annotation from inbox"; + $talk->setmetadata('INBOX', $noexpire_annot, ''); + $self->assert_str_equals('ok', $talk->get_last_completion_response); + + xlog $self, "Run cyr_expire"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '1s'); + $self->assert(!-d "$path"); + + $self->check_syslog($self->{instance}); } 1; diff --git a/cassandane/Cassandane/Cyrus/Delivery.pm b/cassandane/Cassandane/Cyrus/Delivery.pm index 6e0c814de7..e0bcf90342 100644 --- a/cassandane/Cassandane/Cyrus/Delivery.pm +++ b/cassandane/Cassandane/Cyrus/Delivery.pm @@ -46,534 +46,528 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -Cassandane::Cyrus::TestCase::magic(DuplicateSuppressionOff => sub { +Cassandane::Cyrus::TestCase::magic( + DuplicateSuppressionOff => sub { shift->config_set(duplicatesuppression => 0); -}); -Cassandane::Cyrus::TestCase::magic(DuplicateSuppressionOn => sub { + } +); +Cassandane::Cyrus::TestCase::magic( + DuplicateSuppressionOn => sub { shift->config_set(duplicatesuppression => 1); -}); -Cassandane::Cyrus::TestCase::magic(FuzzyMatch => sub { + } +); +Cassandane::Cyrus::TestCase::magic( + FuzzyMatch => sub { shift->config_set(lmtp_fuzzy_mailbox_match => 1); -}); -sub new -{ - my $class = shift; - return $class->SUPER::new({ - deliver => 1, - adminstore => 1, - }, @_); + } +); + +sub new { + my $class = shift; + return $class->SUPER::new( + { + deliver => 1, + adminstore => 1, + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_plus_address_exact - :FuzzyMatch :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour of plus addressing where case matches"; + xlog $self, "Testing behaviour of plus addressing where case matches"; - my $folder = "INBOX.telephone"; + my $folder = "INBOX.telephone"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane+telephone"); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, user => "cassandane+telephone"); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_underscore - :FuzzyMatch :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour of plus addressing where case matches"; + xlog $self, "Testing behaviour of plus addressing where case matches"; - my $folder = "INBOX.- minusland"; + my $folder = "INBOX.- minusland"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane+-_minusland"); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, user => "cassandane+-_minusland"); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_case - :FuzzyMatch :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour of plus addressing where case matches"; + xlog $self, "Testing behaviour of plus addressing where case matches"; - my $folder = "INBOX.ApplePie"; + my $folder = "INBOX.ApplePie"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane+applepie"); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, user => "cassandane+applepie"); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_case_defdomain - :FuzzyMatch :VirtDomains :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : VirtDomains : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour of plus addressing where case matches"; + xlog $self, "Testing behaviour of plus addressing where case matches"; - my $folder = "INBOX.ApplePie"; + my $folder = "INBOX.ApplePie"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane+applepie\@defdomain"); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance} + ->deliver($msgs{1}, user => "cassandane+applepie\@defdomain"); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_case_bogusdomain - :FuzzyMatch :VirtDomains -{ - my ($self) = @_; - - xlog $self, "Testing behaviour of plus addressing where case matches"; - - my $folder = "INBOX.ApplePie"; - - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); - - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - my $r = $self->{instance}->deliver( - $msgs{1}, - user => "cassandane+applepie\@bogusdomain" - ); - # expect deliver to exit with EC_DATAERR - $self->assert_not_equals(0, $r); - - xlog $self, "Check that the message didn't make it"; - $self->{store}->set_folder($folder); - $self->check_messages({}, check_guid => 0, keyed_on => 'uid'); + : FuzzyMatch : VirtDomains { + my ($self) = @_; + + xlog $self, "Testing behaviour of plus addressing where case matches"; + + my $folder = "INBOX.ApplePie"; + + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); + + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + my $r = $self->{instance} + ->deliver($msgs{1}, user => "cassandane+applepie\@bogusdomain"); + # expect deliver to exit with EC_DATAERR + $self->assert_not_equals(0, $r); + + xlog $self, "Check that the message didn't make it"; + $self->{store}->set_folder($folder); + $self->check_messages({}, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_bothupper - :FuzzyMatch :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour of plus addressing where case matches"; + xlog $self, "Testing behaviour of plus addressing where case matches"; - my $folder = "INBOX.FlatPack"; + my $folder = "INBOX.FlatPack"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane+FlatPack"); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, user => "cassandane+FlatPack"); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_partial - :FuzzyMatch :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour of plus addressing where subfolder doesn't exist"; + xlog $self, + "Testing behaviour of plus addressing where subfolder doesn't exist"; - my $folder = "INBOX.lists"; + my $folder = "INBOX.lists"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane+lists.nonexists"); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, user => "cassandane+lists.nonexists"); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_partial_case - :FuzzyMatch :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour of plus addressing where subfolder doesn't exist"; + xlog $self, + "Testing behaviour of plus addressing where subfolder doesn't exist"; - my $folder = "INBOX.Twists"; + my $folder = "INBOX.Twists"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane+twists.nonexists"); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, user => "cassandane+twists.nonexists"); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_partial_bothupper - :FuzzyMatch :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour of plus addressing where subfolder doesn't exist"; + xlog $self, + "Testing behaviour of plus addressing where subfolder doesn't exist"; - my $folder = "INBOX.Projects"; + my $folder = "INBOX.Projects"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane+Projects.Grass"); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, user => "cassandane+Projects.Grass"); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_plus_address_partial_virtdom - :FuzzyMatch :VirtDomains :NoAltNameSpace -{ - my ($self) = @_; + : FuzzyMatch : VirtDomains : NoAltNameSpace { + my ($self) = @_; + + xlog $self, "Testing behaviour of plus addressing with virtdomains"; + + my $admintalk = $self->{adminstore}->get_client(); + + $self->{instance}->create_user("domuser\@example.com"); + my $domstore + = $self->{instance}->get_service('imap') + ->create_store(username => "domuser\@example.com") + || die "can't create store"; + $self->{store} = $domstore; + my $domtalk = $domstore->get_client(); + + my $folder = "INBOX.Projects"; + + xlog $self, "Create folders"; + $domtalk->create($folder) + or die "Cannot create $folder: $@"; + $domstore->set_fetch_attributes('uid'); + + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance} + ->deliver($msgs{1}, user => "domuser+Projects.Grass\@example.com"); + + xlog $self, "Check that the message made it"; + $domstore->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); +} - xlog $self, "Testing behaviour of plus addressing with virtdomains"; +sub test_plus_address_shared + : AltNamespace : UnixHierarchySep { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my $shared_mailbox = 'shared/foo'; + + $admintalk->create($shared_mailbox); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl($shared_mailbox, 'cassandane' => 'lrs'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl($shared_mailbox, 'anyone' => 'p'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + my $ret = $self->{instance} + ->deliver($msgs{1}, user => "+$shared_mailbox\@example.com"); + $self->assert_equals(0, $ret); + + xlog $self, "Check that the message made it"; + $self->{store}->set_folder("Shared Folders/$shared_mailbox"); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); +} - my $admintalk = $self->{adminstore}->get_client(); +sub test_duplicate_suppression_off + : DuplicateSuppressionOff : NoAltNameSpace { + my ($self) = @_; + + xlog $self, "Testing behaviour with duplicate suppression off"; + + # test data from hipsteripsum.me + my $folder = "INBOX.thundercats"; + + xlog $self, "Create the target folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; + $self->{store}->set_fetch_attributes('uid'); + + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, folder => $folder); + + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + + xlog $self, "Try to deliver the same message again"; + $self->{instance}->deliver($msgs{1}, folder => $folder); + + xlog $self, "Check that second copy of the message made it"; + $msgs{2} = $msgs{1}->clone(); + $msgs{2}->set_attribute(uid => 2); + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); +} - $self->{instance}->create_user("domuser\@example.com"); - my $domstore = $self->{instance}->get_service('imap')->create_store(username => "domuser\@example.com") || die "can't create store"; - $self->{store} = $domstore; - my $domtalk = $domstore->get_client(); +sub test_duplicate_suppression_on + : DuplicateSuppressionOn : NoAltNameSpace { + my ($self) = @_; - my $folder = "INBOX.Projects"; + xlog $self, "Testing behaviour with duplicate suppression on"; - xlog $self, "Create folders"; - $domtalk->create($folder) - or die "Cannot create $folder: $@"; - $domstore->set_fetch_attributes('uid'); + # test data from hipsteripsum.me + my $folder1 = "INBOX.sustainable"; + my $folder2 = "INBOX.artisan"; - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "domuser+Projects.Grass\@example.com"); + xlog $self, "Create the target folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder1) + or die "Cannot create $folder1: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Check that the message made it"; - $domstore->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); -} + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, folder => $folder1); -sub test_plus_address_shared - :AltNamespace :UnixHierarchySep -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my $shared_mailbox = 'shared/foo'; - - $admintalk->create($shared_mailbox); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl($shared_mailbox, 'cassandane' => 'lrs'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl($shared_mailbox, 'anyone' => 'p'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - my $ret = $self->{instance}->deliver( - $msgs{1}, - user => "+$shared_mailbox\@example.com" - ); - $self->assert_equals(0, $ret); - - xlog $self, "Check that the message made it"; - $self->{store}->set_folder("Shared Folders/$shared_mailbox"); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); -} + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder1); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); -sub test_duplicate_suppression_off - :DuplicateSuppressionOff :NoAltNameSpace -{ - my ($self) = @_; - - xlog $self, "Testing behaviour with duplicate suppression off"; - - # test data from hipsteripsum.me - my $folder = "INBOX.thundercats"; - - xlog $self, "Create the target folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; - $self->{store}->set_fetch_attributes('uid'); - - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, folder => $folder); - - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); - - xlog $self, "Try to deliver the same message again"; - $self->{instance}->deliver($msgs{1}, folder => $folder); - - xlog $self, "Check that second copy of the message made it"; - $msgs{2} = $msgs{1}->clone(); - $msgs{2}->set_attribute(uid => 2); - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); -} + xlog $self, "Try to deliver the same message again"; + $self->{instance}->deliver($msgs{1}, folder => $folder1); + xlog $self, "Check that second copy of the message didn't make it"; + $self->{store}->set_folder($folder1); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); -sub test_duplicate_suppression_on - :DuplicateSuppressionOn :NoAltNameSpace -{ - my ($self) = @_; - - xlog $self, "Testing behaviour with duplicate suppression on"; - - # test data from hipsteripsum.me - my $folder1 = "INBOX.sustainable"; - my $folder2 = "INBOX.artisan"; - - xlog $self, "Create the target folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder1) - or die "Cannot create $folder1: $@"; - $self->{store}->set_fetch_attributes('uid'); - - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, folder => $folder1); - - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder1); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); - - xlog $self, "Try to deliver the same message again"; - $self->{instance}->deliver($msgs{1}, folder => $folder1); - - xlog $self, "Check that second copy of the message didn't make it"; - $self->{store}->set_folder($folder1); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); - - xlog $self, "Rename the folder"; - $imaptalk->rename($folder1, $folder2) - or die "Cannot rename $folder1 to $folder2: $@"; - - xlog $self, "Try to deliver the same message again"; - $self->{instance}->deliver($msgs{1}, folder => $folder2); - - xlog $self, "Check that third copy of the message DIDN'T make it"; - # This is the whole point of duplicate_mailbox_mode = uniqueid. - $self->{store}->set_folder($folder2); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Rename the folder"; + $imaptalk->rename($folder1, $folder2) + or die "Cannot rename $folder1 to $folder2: $@"; + + xlog $self, "Try to deliver the same message again"; + $self->{instance}->deliver($msgs{1}, folder => $folder2); + + xlog $self, "Check that third copy of the message DIDN'T make it"; + # This is the whole point of duplicate_mailbox_mode = uniqueid. + $self->{store}->set_folder($folder2); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_duplicate_suppression_on_delete - :DuplicateSuppressionOn :NoAltNameSpace -{ - my ($self) = @_; + : DuplicateSuppressionOn : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour with duplicate suppression on"; - xlog $self, "interaction with DELETE + CREATE [IRIS-723]"; + xlog $self, "Testing behaviour with duplicate suppression on"; + xlog $self, "interaction with DELETE + CREATE [IRIS-723]"; - # test data from hipsteripsum.me - my $folder = "INBOX.mixtape"; + # test data from hipsteripsum.me + my $folder = "INBOX.mixtape"; - xlog $self, "Create the target folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; + xlog $self, "Create the target folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msgs{1}, folder => $folder); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msgs{1}, folder => $folder); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); - xlog $self, "Delete the folder"; - $imaptalk->unselect(); - $imaptalk->delete($folder) - or die "Cannot delete $folder: $@"; + xlog $self, "Delete the folder"; + $imaptalk->unselect(); + $imaptalk->delete($folder) + or die "Cannot delete $folder: $@"; - xlog $self, "Create another folder of the same name"; - $imaptalk->create($folder) - or die "Cannot create another $folder: $@"; + xlog $self, "Create another folder of the same name"; + $imaptalk->create($folder) + or die "Cannot create another $folder: $@"; - xlog $self, "Check that all messages are gone"; - $self->{store}->set_folder($folder); - $self->check_messages({}, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that all messages are gone"; + $self->{store}->set_folder($folder); + $self->check_messages({}, check_guid => 0, keyed_on => 'uid'); - xlog $self, "Try to deliver the same message to the new folder"; - $self->{instance}->deliver($msgs{1}, folder => $folder); + xlog $self, "Try to deliver the same message to the new folder"; + $self->{instance}->deliver($msgs{1}, folder => $folder); - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_duplicate_suppression_on_badmbox - :DuplicateSuppressionOn :NoAltNameSpace -{ - my ($self) = @_; + : DuplicateSuppressionOn : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing behaviour with duplicate suppression on"; - xlog $self, "interaction with attempted delivery to a"; - xlog $self, "non-existant mailbox"; + xlog $self, "Testing behaviour with duplicate suppression on"; + xlog $self, "interaction with attempted delivery to a"; + xlog $self, "non-existant mailbox"; - my $folder = "INBOX.nonesuch"; - # DO NOT create the target folder + my $folder = "INBOX.nonesuch"; + # DO NOT create the target folder - $self->{store}->set_fetch_attributes('uid'); + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver a message"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, folder => $folder); + xlog $self, "Deliver a message"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, folder => $folder); - xlog $self, "Check that the message made it, to INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it, to INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); - xlog $self, "Create a folder of the given name"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; + xlog $self, "Create a folder of the given name"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; - xlog $self, "Try to deliver the same message to the new folder"; - $self->{instance}->deliver($msgs{1}, folder => $folder); + xlog $self, "Try to deliver the same message to the new folder"; + $self->{instance}->deliver($msgs{1}, folder => $folder); - xlog $self, "Check that the message made it, to the given folder"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + xlog $self, "Check that the message made it, to the given folder"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); } sub test_auditlog_size - :min_version_3_5 -{ - my ($self) = @_; - - xlog $self, "Testing whether appended message size is auditlogged"; - - # discard syslogs from setup - $self->{instance}->getsyslog(); - - xlog $self, "Deliver a message"; - my $folder = "INBOX"; - my %msgs; - $msgs{1} = $self->{gen}->generate(subject => "Message 1"); - $msgs{1}->set_attribute(uid => 1); - $self->{instance}->deliver($msgs{1}, user => "cassandane"); - - xlog $self, "Check that the message made it"; - $self->{store}->set_folder($folder); - $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); - - xlog $self, "Check the correct size was auditlogged"; - my @appends = $self->{instance}->getsyslog(qr/auditlog: append .* uid=<1>/); - $self->assert_num_equals(1, scalar @appends); - - # delivery will add some headers, so it will be larger - my $expected_size = $msgs{1}->size(); - my ($actual_size) = $appends[0] =~ m/ size=<([0-9]+)>/; - $self->assert_num_gte($expected_size, $actual_size); + : min_version_3_5 { + my ($self) = @_; + + xlog $self, "Testing whether appended message size is auditlogged"; + + # discard syslogs from setup + $self->{instance}->getsyslog(); + + xlog $self, "Deliver a message"; + my $folder = "INBOX"; + my %msgs; + $msgs{1} = $self->{gen}->generate(subject => "Message 1"); + $msgs{1}->set_attribute(uid => 1); + $self->{instance}->deliver($msgs{1}, user => "cassandane"); + + xlog $self, "Check that the message made it"; + $self->{store}->set_folder($folder); + $self->check_messages(\%msgs, check_guid => 0, keyed_on => 'uid'); + + xlog $self, "Check the correct size was auditlogged"; + my @appends = $self->{instance}->getsyslog(qr/auditlog: append .* uid=<1>/); + $self->assert_num_equals(1, scalar @appends); + + # delivery will add some headers, so it will be larger + my $expected_size = $msgs{1}->size(); + my ($actual_size) = $appends[0] =~ m/ size=<([0-9]+)>/; + $self->assert_num_gte($expected_size, $actual_size); } 1; diff --git a/cassandane/Cassandane/Cyrus/Deny.pm b/cassandane/Cassandane/Cyrus/Deny.pm index 4451debbc9..8764e8156d 100644 --- a/cassandane/Cassandane/Cyrus/Deny.pm +++ b/cassandane/Cassandane/Cyrus/Deny.pm @@ -45,138 +45,136 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_basic -{ - my ($self) = @_; - - xlog $self, "Test the cyr_deny utility with the imap service"; - - # Data thanks to hipsteripsum.me - my @cases = ({ - # test default options - user => 'helvetica', - opts => [ ], - can_login => 0, - },{ - # test the -s option with our service - user => 'portland', - opts => [ '-s', 'imap' ], - can_login => 0, - },{ - # test the -s option with another service - user => 'stumptown', - opts => [ '-s', 'godard' ], - can_login => 1, - },{ - # test the -m option - user => 'mustache', - opts => [ '-m', 'Bugger off, you' ], - can_login => 0, - },{ - # control case - no cyr_deny command run - user => 'vegan', - can_login => 1, - }); - - - xlog $self, "Create all users"; - foreach my $case (@cases) - { - $self->{instance}->create_user($case->{user}); - } +sub test_basic { + my ($self) = @_; - xlog $self, "Running cyr_deny for some users"; - foreach my $case (@cases) - { - next unless defined $case->{opts}; - $self->{instance}->run_command({ cyrus => 1 }, - 'cyr_deny', @{$case->{opts}}, $case->{user}); - } + xlog $self, "Test the cyr_deny utility with the imap service"; - my $svc = $self->{instance}->get_service('imap'); - foreach my $case (@cases) + # Data thanks to hipsteripsum.me + my @cases = ( + { + # test default options + user => 'helvetica', + opts => [], + can_login => 0, + }, { - xlog $self, "Trying to log in as user $case->{user}"; - my $store = $svc->create_store(username => $case->{user}); - if ($case->{can_login}) - { - xlog $self, "Expecting this to succeed"; - my $talk = $store->get_client(); - my $r = $talk->status('inbox', [ 'messages' ]); - $self->assert_deep_equals({ messages => 0 }, $r); - $talk = undef; - } - else - { - xlog $self, "Expecting this to fail"; - eval { $store->get_client(); }; - my $exception = $@; - $self->assert_matches(qr/no - login failed: authorization failure/i, $exception); - } + # test the -s option with our service + user => 'portland', + opts => [ '-s', 'imap' ], + can_login => 0, + }, + { + # test the -s option with another service + user => 'stumptown', + opts => [ '-s', 'godard' ], + can_login => 1, + }, + { + # test the -m option + user => 'mustache', + opts => [ '-m', 'Bugger off, you' ], + can_login => 0, + }, + { + # control case - no cyr_deny command run + user => 'vegan', + can_login => 1, + } + ); + + xlog $self, "Create all users"; + foreach my $case (@cases) { + $self->{instance}->create_user($case->{user}); + } + + xlog $self, "Running cyr_deny for some users"; + foreach my $case (@cases) { + next unless defined $case->{opts}; + $self->{instance}->run_command( + { cyrus => 1 }, + 'cyr_deny', @{ $case->{opts} }, + $case->{user} + ); + } + + my $svc = $self->{instance}->get_service('imap'); + foreach my $case (@cases) { + xlog $self, "Trying to log in as user $case->{user}"; + my $store = $svc->create_store(username => $case->{user}); + if ($case->{can_login}) { + xlog $self, "Expecting this to succeed"; + my $talk = $store->get_client(); + my $r = $talk->status('inbox', ['messages']); + $self->assert_deep_equals({ messages => 0 }, $r); + $talk = undef; + } else { + xlog $self, "Expecting this to fail"; + eval { $store->get_client(); }; + my $exception = $@; + $self->assert_matches(qr/no - login failed: authorization failure/i, + $exception); } + } } -sub test_connected -{ - my ($self) = @_; +sub test_connected { + my ($self) = @_; - xlog $self, "Test that cyr_deny shuts down any connected sessions"; + xlog $self, "Test that cyr_deny shuts down any connected sessions"; - xlog $self, "Create a user"; - my $user = 'gastropub'; - $self->{instance}->create_user($user); + xlog $self, "Create a user"; + my $user = 'gastropub'; + $self->{instance}->create_user($user); - xlog $self, "Set up a logged-in client for each of two users"; - my $cass_talk = $self->{store}->get_client(); + xlog $self, "Set up a logged-in client for each of two users"; + my $cass_talk = $self->{store}->get_client(); - my $svc = $self->{instance}->get_service('imap'); - my $user_store = $svc->create_store(username => $user); - my $user_talk = $user_store->get_client(); + my $svc = $self->{instance}->get_service('imap'); + my $user_store = $svc->create_store(username => $user); + my $user_talk = $user_store->get_client(); - xlog $self, "Check that we can run a command in each of the two clients"; - my $res; - $res = $cass_talk->status('inbox', [ 'messages' ]); - $self->assert_deep_equals({ messages => 0 }, $res); - $res = $user_talk->status('inbox', [ 'messages' ]); - $self->assert_deep_equals({ messages => 0 }, $res); + xlog $self, "Check that we can run a command in each of the two clients"; + my $res; + $res = $cass_talk->status('inbox', ['messages']); + $self->assert_deep_equals({ messages => 0 }, $res); + $res = $user_talk->status('inbox', ['messages']); + $self->assert_deep_equals({ messages => 0 }, $res); - $user_talk->clear_response_code('alert'); + $user_talk->clear_response_code('alert'); - xlog $self, "Deny the user"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_deny', $user); + xlog $self, "Deny the user"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_deny', $user); - xlog $self, "Check that we can run a command in the unaffected user"; - $res = $cass_talk->status('inbox', [ 'messages' ]); - $self->assert_deep_equals({ messages => 0 }, $res); + xlog $self, "Check that we can run a command in the unaffected user"; + $res = $cass_talk->status('inbox', ['messages']); + $self->assert_deep_equals({ messages => 0 }, $res); - xlog $self, "Check that the affected user is disconnected"; - $res = undef; - # Either is_open will return undef, or die; both of these - # are good. If it returned 1 we should worry. - eval { $res = $user_talk->is_open(); }; - $self->assert_null($res); + xlog $self, "Check that the affected user is disconnected"; + $res = undef; + # Either is_open will return undef, or die; both of these + # are good. If it returned 1 we should worry. + eval { $res = $user_talk->is_open(); }; + $self->assert_null($res); - # Could do this, but Mail::IMAPTalk drops ALERTs in a BYE response -# $self->assert_matches(qr/Access to this service has been blocked/i, -# $user_talk->get_response_code('alert')); + # Could do this, but Mail::IMAPTalk drops ALERTs in a BYE response + # $self->assert_matches(qr/Access to this service has been blocked/i, + # $user_talk->get_response_code('alert')); } 1; diff --git a/cassandane/Cassandane/Cyrus/Expunge.pm b/cassandane/Cassandane/Cyrus/Expunge.pm index 82e958f501..122326fae9 100644 --- a/cassandane/Cassandane/Cyrus/Expunge.pm +++ b/cassandane/Cassandane/Cyrus/Expunge.pm @@ -46,236 +46,230 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my ($class, @args) = @_; - return $class->SUPER::new({ adminstore => 1 }, @args); +sub new { + my ($class, @args) = @_; + return $class->SUPER::new({ adminstore => 1 }, @args); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_status_after_expunge -{ - my ($self, $folder, %params) = @_; +sub test_status_after_expunge { + my ($self, $folder, %params) = @_; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $subfolder = 'INBOX.foo'; + my $subfolder = 'INBOX.foo'; - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or die "Cannot create folder $subfolder: $@"; - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or die "Cannot create folder $subfolder: $@"; + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "Generate messages in $subfolder"; - $store->set_folder($subfolder); - $store->_select(); - for (1..5) { - $self->make_message("Message $subfolder $_"); - } - $talk->unselect(); - $talk->select($subfolder); + xlog $self, "Generate messages in $subfolder"; + $store->set_folder($subfolder); + $store->_select(); + for (1 .. 5) { + $self->make_message("Message $subfolder $_"); + } + $talk->unselect(); + $talk->select($subfolder); - my $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); - $self->assert_equals(5, $stat->{unseen}); - $self->assert_equals(5, $stat->{messages}); + my $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); + $self->assert_equals(5, $stat->{unseen}); + $self->assert_equals(5, $stat->{messages}); - $talk->store('1,3,5', '+flags', '(\\Seen)'); + $talk->store('1,3,5', '+flags', '(\\Seen)'); - $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); - $self->assert_equals(2, $stat->{unseen}); - $self->assert_equals(5, $stat->{messages}); + $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); + $self->assert_equals(2, $stat->{unseen}); + $self->assert_equals(5, $stat->{messages}); - $talk->store('1:*', '+flags', '(\\Deleted \\Seen)'); - $talk->expunge(); + $talk->store('1:*', '+flags', '(\\Deleted \\Seen)'); + $talk->expunge(); - $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); - $self->assert_equals(0, $stat->{unseen}); - $self->assert_equals(0, $stat->{messages}); + $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); + $self->assert_equals(0, $stat->{unseen}); + $self->assert_equals(0, $stat->{messages}); } sub test_auditlog_size - :min_version_3_5 -{ - my ($self, $folder, %params) = @_; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - my $subfolder = 'INBOX.foo'; - - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or die "Cannot create folder $subfolder: $@"; - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Generate messages in $subfolder"; - $store->set_folder($subfolder); - $store->_select(); - for (1..5) { - $self->make_message("Message $subfolder $_"); - } - $talk->unselect(); - $talk->select($subfolder); - - # discard syslogs from setup - $self->{instance}->getsyslog(); - - my $resp = $talk->fetch('1,3,5', 'RFC822.SIZE'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($resp); - my %expected_sizes = map { - $_ => $resp->{$_}->{'rfc822.size'} - } keys %{$resp}; - - $talk->store('1,3,5', '+flags', '(\\Deleted \\Seen)'); - $talk->expunge(); - - if ($self->{instance}->{have_syslog_replacement}) { - my @auditlogs = $self->{instance}->getsyslog(qr/auditlog: expunge/); - - my %actual_sizes = map { - m/ uid=<([0-9]+)>.* size=<([0-9]+)>/ - } @auditlogs; - - $self->assert_deep_equals(\%expected_sizes, \%actual_sizes); - } + : min_version_3_5 { + my ($self, $folder, %params) = @_; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + my $subfolder = 'INBOX.foo'; + + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or die "Cannot create folder $subfolder: $@"; + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Generate messages in $subfolder"; + $store->set_folder($subfolder); + $store->_select(); + for (1 .. 5) { + $self->make_message("Message $subfolder $_"); + } + $talk->unselect(); + $talk->select($subfolder); + + # discard syslogs from setup + $self->{instance}->getsyslog(); + + my $resp = $talk->fetch('1,3,5', 'RFC822.SIZE'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($resp); + my %expected_sizes = map { $_ => $resp->{$_}->{'rfc822.size'} } keys %{$resp}; + + $talk->store('1,3,5', '+flags', '(\\Deleted \\Seen)'); + $talk->expunge(); + + if ($self->{instance}->{have_syslog_replacement}) { + my @auditlogs = $self->{instance}->getsyslog(qr/auditlog: expunge/); + + my %actual_sizes = map { m/ uid=<([0-9]+)>.* size=<([0-9]+)>/ } @auditlogs; + + $self->assert_deep_equals(\%expected_sizes, \%actual_sizes); + } } sub test_allowdeleted - :AllowDeleted :DelayedExpunge :min_version_3_1 -{ - my ($self, $folder, %params) = @_; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - my $subfolder = 'INBOX.foo'; - - xlog $self, "First create a sub folder"; - $talk->create($subfolder) - or die "Cannot create folder $subfolder: $@"; - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Generate messages in $subfolder"; - $store->set_folder($subfolder); - $store->_select(); - for (1..5) { - $self->make_message("Message $subfolder $_"); - } - $talk->unselect(); - $talk->select($subfolder); - - my $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); - $self->assert_equals(5, $stat->{unseen}); - $self->assert_equals(5, $stat->{messages}); - - my $oldemailids = $talk->fetch('1:*', 'emailid'); - my @oldemailids = map { $oldemailids->{$_}{emailid}[0] } sort { $a <=> $b } keys %$oldemailids; - - $talk->store('1,3,5', '+flags', '(\\Deleted)'); - $talk->expunge(); - - $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); - $self->assert_equals(2, $stat->{unseen}); - $self->assert_equals(2, $stat->{messages}); - - xlog $self, "regular select finds 2 messages"; - $talk->unselect(); - $talk->select($subfolder); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_num_equals(2, $talk->get_response_code('exists')); - - xlog $self, "include-expunged select finds 5 messages"; - $talk->unselect(); - # this API is janky - $talk->select($subfolder, '(vendor.cmu-include-expunged)' => 1); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_num_equals(5, $talk->get_response_code('exists')); - - my $newemailids = $talk->fetch('1:*', 'emailid'); - my @newemailids = map { $newemailids->{$_}{emailid}[0] } sort { $a <=> $b } keys %$newemailids; - $self->assert_deep_equals(\@oldemailids, \@newemailids, Data::Dumper::Dumper(\@oldemailids, \@newemailids)); - - xlog $self, "copy of deleted messages recreates them"; - $talk->copy('1,3,5', $subfolder); - $talk->unselect(); - $talk->select($subfolder); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_num_equals(5, $talk->get_response_code('exists')); - - xlog $self, "new mailbox contains the same emails"; - $newemailids = $talk->fetch('1:*', 'emailid'); - @newemailids = map { $newemailids->{$_}{emailid}[0] } sort { $a <=> $b } keys %$newemailids; - $self->assert_deep_equals([sort @oldemailids], [sort @newemailids], - Data::Dumper::Dumper([sort @oldemailids], [sort @newemailids])); + : AllowDeleted : DelayedExpunge : min_version_3_1 { + my ($self, $folder, %params) = @_; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + my $subfolder = 'INBOX.foo'; + + xlog $self, "First create a sub folder"; + $talk->create($subfolder) + or die "Cannot create folder $subfolder: $@"; + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Generate messages in $subfolder"; + $store->set_folder($subfolder); + $store->_select(); + for (1 .. 5) { + $self->make_message("Message $subfolder $_"); + } + $talk->unselect(); + $talk->select($subfolder); + + my $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); + $self->assert_equals(5, $stat->{unseen}); + $self->assert_equals(5, $stat->{messages}); + + my $oldemailids = $talk->fetch('1:*', 'emailid'); + my @oldemailids = map { $oldemailids->{$_}{emailid}[0] } + sort { $a <=> $b } keys %$oldemailids; + + $talk->store('1,3,5', '+flags', '(\\Deleted)'); + $talk->expunge(); + + $stat = $talk->status($subfolder, '(highestmodseq unseen messages)'); + $self->assert_equals(2, $stat->{unseen}); + $self->assert_equals(2, $stat->{messages}); + + xlog $self, "regular select finds 2 messages"; + $talk->unselect(); + $talk->select($subfolder); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_num_equals(2, $talk->get_response_code('exists')); + + xlog $self, "include-expunged select finds 5 messages"; + $talk->unselect(); + # this API is janky + $talk->select($subfolder, '(vendor.cmu-include-expunged)' => 1); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_num_equals(5, $talk->get_response_code('exists')); + + my $newemailids = $talk->fetch('1:*', 'emailid'); + my @newemailids = map { $newemailids->{$_}{emailid}[0] } + sort { $a <=> $b } keys %$newemailids; + $self->assert_deep_equals(\@oldemailids, \@newemailids, + Data::Dumper::Dumper(\@oldemailids, \@newemailids)); + + xlog $self, "copy of deleted messages recreates them"; + $talk->copy('1,3,5', $subfolder); + $talk->unselect(); + $talk->select($subfolder); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_num_equals(5, $talk->get_response_code('exists')); + + xlog $self, "new mailbox contains the same emails"; + $newemailids = $talk->fetch('1:*', 'emailid'); + @newemailids = map { $newemailids->{$_}{emailid}[0] } + sort { $a <=> $b } keys %$newemailids; + $self->assert_deep_equals( + [ sort @oldemailids ], + [ sort @newemailids ], + Data::Dumper::Dumper([ sort @oldemailids ], [ sort @newemailids ]) + ); } # XXX this isn't really the right place for this test sub test_ipurge_mboxevent - :NoAltNameSpace -{ - my ($self) = @_; - - my $shared_folder = 'shared.folder'; - - # set up a shared folder that's easy to write to - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create($shared_folder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl($shared_folder, 'cassandane' => 'lrswipkxtecd'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - # put some test messages in shared.folder - my $talk = $self->{store}->get_client(); - $self->{store}->set_folder($shared_folder); - $self->{store}->_select(); - for (1..5) { - $self->make_message("message in $shared_folder $_"); - } - $talk->unselect(); - $talk->select($shared_folder); - - my $stat = $talk->status($shared_folder, '(highestmodseq unseen messages)'); - $self->assert_num_equals(5, $stat->{unseen}); - $self->assert_num_equals(5, $stat->{messages}); - - # consume/discard earlier events that we don't care about - $self->{instance}->getnotify(); - - # run ipurge, and collect any mboxevents it generates - $self->{instance}->run_command( - { cyrus => 1 }, - qw( ipurge -v -i -d 2 ), $shared_folder - ); - my $events = $self->{instance}->getnotify(); - - # if it stays selected you see the intermittent state - $talk->unselect(); - - # the messages we just created should've been expunged - $stat = $talk->status($shared_folder, '(highestmodseq unseen messages)'); - $self->assert_num_equals(0, $stat->{unseen}); - $self->assert_num_equals(0, $stat->{messages}); - - # examine the mboxevents - foreach (@{$events}) { - my $e = decode_json($_->{MESSAGE}); - # uri must contain the mailbox! - $self->assert_matches(qr{^imap://(?:[^/]+)/shared\.folder;UIDVALIDITY=}, - $e->{uri}); - } + : NoAltNameSpace { + my ($self) = @_; + + my $shared_folder = 'shared.folder'; + + # set up a shared folder that's easy to write to + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create($shared_folder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl($shared_folder, 'cassandane' => 'lrswipkxtecd'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # put some test messages in shared.folder + my $talk = $self->{store}->get_client(); + $self->{store}->set_folder($shared_folder); + $self->{store}->_select(); + for (1 .. 5) { + $self->make_message("message in $shared_folder $_"); + } + $talk->unselect(); + $talk->select($shared_folder); + + my $stat = $talk->status($shared_folder, '(highestmodseq unseen messages)'); + $self->assert_num_equals(5, $stat->{unseen}); + $self->assert_num_equals(5, $stat->{messages}); + + # consume/discard earlier events that we don't care about + $self->{instance}->getnotify(); + + # run ipurge, and collect any mboxevents it generates + $self->{instance} + ->run_command({ cyrus => 1 }, qw( ipurge -v -i -d 2 ), $shared_folder); + my $events = $self->{instance}->getnotify(); + + # if it stays selected you see the intermittent state + $talk->unselect(); + + # the messages we just created should've been expunged + $stat = $talk->status($shared_folder, '(highestmodseq unseen messages)'); + $self->assert_num_equals(0, $stat->{unseen}); + $self->assert_num_equals(0, $stat->{messages}); + + # examine the mboxevents + foreach (@{$events}) { + my $e = decode_json($_->{MESSAGE}); + # uri must contain the mailbox! + $self->assert_matches(qr{^imap://(?:[^/]+)/shared\.folder;UIDVALIDITY=}, + $e->{uri}); + } } 1; diff --git a/cassandane/Cassandane/Cyrus/FastMail.pm b/cassandane/Cassandane/Cyrus/FastMail.pm index 1352990243..57b82ad42d 100644 --- a/cassandane/Cassandane/Cyrus/FastMail.pm +++ b/cassandane/Cassandane/Cyrus/FastMail.pm @@ -57,139 +57,144 @@ use charnames ':full'; our $RNUM = 1; -sub new -{ - my ($class, @args) = @_; +sub new { + my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane', - conversations => 'yes', - httpmodules => 'carddav caldav jmap', - httpallowcompress => 'no', - allowusermoves => 'yes', - altnamespace => 'no', - anyoneuseracl => 'no', - archive_enabled => 'yes', - autoexpunge => 'yes', - caldav_allowattach => 'yes', - caldav_allowscheduling => 'yes', - caldav_create_attach => 'yes', - caldav_create_default => 'no', - caldav_create_sched => 'yes', - caldav_realm => 'FastMail', - calendar_component_set => 'VEVENT', - crossdomains => 'yes', - crossdomains_onlyother => 'yes', - annotation_allow_undefined => 'yes', - conversations => 'yes', - conversations_counted_flags => '\\Draft \\Flagged $IsMailingList $IsNotification $HasAttachment $HasTD', - conversations_max_thread => '100', - mailbox_initial_flags => '$X-ME-Annot-2 $IsMailingList $IsNotification $HasAttachment $HasTD', - defaultacl => 'admin lrswipkxtecdan', - defaultdomain => 'internal', - delete_unsubscribe => 'yes', - expunge_mode => 'delayed', - hashimapspool => 'on', - httpallowcompress => 'no', - httpkeepalive => '0', - httpmodules => 'caldav carddav jmap', - httpprettytelemetry => 'yes', - imapidresponse => 'no', - imapmagicplus => 'yes', - implicit_owner_rights => 'lkn', - internaldate_heuristic => 'receivedheader', - jmap_preview_annot => '/shared/vendor/messagingengine.com/preview', - jmap_nonstandard_extensions => 'yes', - jmapauth_allowsasl => 'yes', - lmtp_fuzzy_mailbox_match => 'yes', - lmtp_exclude_specialuse => '\XChats \XTemplates \XNotes \Drafts \Snoozed', - lmtp_over_quota_perm_failure => 'yes', - maxheaderlines => '4096', - maxword => '8388608', - maxquoted => '8388608', - munge8bit => 'no', - notesmailbox => 'Notes', - popsubfolders => 'yes', - popuseacl => 'yes', - postmaster => 'postmaster@example.com', - quota_db => 'quotalegacy', - quota_use_conversations => 'yes', - quotawarnpercent => '98', - reverseacls => 'yes', - rfc3028_strict => 'no', - savedate => 'yes', - sieve_extensions => 'fileinto reject vacation imapflags notify envelope body relational regex subaddress copy mailbox mboxmetadata servermetadata date index variables imap4flags editheader duplicate vacation-seconds fcc x-cyrus-jmapquery x-cyrus-snooze x-cyrus-log mailboxid special-use', - sieve_utf8fileinto => 'yes', - sieve_use_lmtp_reject => 'no', - sievenotifier => 'mailto', - sieve_maxscriptsize => '1024K', - sieve_vacation_min_response => '60', - specialusealways => 'yes', - specialuse_extra => '\\XChats \\XTemplates \\XNotes', - statuscache => 'on', - subscription_db => 'flat', - suppress_capabilities => 'URLAUTH URLAUTH=BINARY', - tcp_keepalive => 'yes', - timeout => '60', - unix_group_enable => 'no', - unixhierarchysep => 'no', - virtdomains => 'userid', - search_engine => 'xapian', - search_index_headers => 'no', - search_batchsize => '8192', - search_maxtime => '30', - search_snippet_length => '160', - search_query_language => 'yes', - search_index_language => 'yes', - telemetry_bysessionid => 'yes', - delete_mode => 'delayed', - pop3alt_uidl_format => 'dovecot', - event_content_inclusion_mode => 'standard', - event_content_size => '1', - event_exclude_specialuse => '\\Junk', - event_extra_params => 'modseq vnd.fastmail.clientId service uidnext vnd.fastmail.sessionId vnd.cmu.envelope vnd.fastmail.convUnseen vnd.fastmail.convExists vnd.fastmail.cid vnd.cmu.mbtype vnd.cmu.davFilename vnd.cmu.davUid vnd.cmu.mailboxACL vnd.fastmail.counters messages vnd.cmu.unseenMessages flagNames vnd.cmu.emailid vnd.cmu.threadid vnd.cmu.visibleUsers', - event_groups => 'mailbox message flags calendar applepushservice', - event_notifier => 'pusher', - sync_log => 'yes', - ); + my $config = Cassandane::Config->default()->clone(); + $config->set( + caldav_realm => 'Cassandane', + conversations => 'yes', + httpmodules => 'carddav caldav jmap', + httpallowcompress => 'no', + allowusermoves => 'yes', + altnamespace => 'no', + anyoneuseracl => 'no', + archive_enabled => 'yes', + autoexpunge => 'yes', + caldav_allowattach => 'yes', + caldav_allowscheduling => 'yes', + caldav_create_attach => 'yes', + caldav_create_default => 'no', + caldav_create_sched => 'yes', + caldav_realm => 'FastMail', + calendar_component_set => 'VEVENT', + crossdomains => 'yes', + crossdomains_onlyother => 'yes', + annotation_allow_undefined => 'yes', + conversations => 'yes', + conversations_counted_flags => + '\\Draft \\Flagged $IsMailingList $IsNotification $HasAttachment $HasTD', + conversations_max_thread => '100', + mailbox_initial_flags => + '$X-ME-Annot-2 $IsMailingList $IsNotification $HasAttachment $HasTD', + defaultacl => 'admin lrswipkxtecdan', + defaultdomain => 'internal', + delete_unsubscribe => 'yes', + expunge_mode => 'delayed', + hashimapspool => 'on', + httpallowcompress => 'no', + httpkeepalive => '0', + httpmodules => 'caldav carddav jmap', + httpprettytelemetry => 'yes', + imapidresponse => 'no', + imapmagicplus => 'yes', + implicit_owner_rights => 'lkn', + internaldate_heuristic => 'receivedheader', + jmap_preview_annot => '/shared/vendor/messagingengine.com/preview', + jmap_nonstandard_extensions => 'yes', + jmapauth_allowsasl => 'yes', + lmtp_fuzzy_mailbox_match => 'yes', + lmtp_exclude_specialuse => '\XChats \XTemplates \XNotes \Drafts \Snoozed', + lmtp_over_quota_perm_failure => 'yes', + maxheaderlines => '4096', + maxword => '8388608', + maxquoted => '8388608', + munge8bit => 'no', + notesmailbox => 'Notes', + popsubfolders => 'yes', + popuseacl => 'yes', + postmaster => 'postmaster@example.com', + quota_db => 'quotalegacy', + quota_use_conversations => 'yes', + quotawarnpercent => '98', + reverseacls => 'yes', + rfc3028_strict => 'no', + savedate => 'yes', + sieve_extensions => + 'fileinto reject vacation imapflags notify envelope body relational regex subaddress copy mailbox mboxmetadata servermetadata date index variables imap4flags editheader duplicate vacation-seconds fcc x-cyrus-jmapquery x-cyrus-snooze x-cyrus-log mailboxid special-use', + sieve_utf8fileinto => 'yes', + sieve_use_lmtp_reject => 'no', + sievenotifier => 'mailto', + sieve_maxscriptsize => '1024K', + sieve_vacation_min_response => '60', + specialusealways => 'yes', + specialuse_extra => '\\XChats \\XTemplates \\XNotes', + statuscache => 'on', + subscription_db => 'flat', + suppress_capabilities => 'URLAUTH URLAUTH=BINARY', + tcp_keepalive => 'yes', + timeout => '60', + unix_group_enable => 'no', + unixhierarchysep => 'no', + virtdomains => 'userid', + search_engine => 'xapian', + search_index_headers => 'no', + search_batchsize => '8192', + search_maxtime => '30', + search_snippet_length => '160', + search_query_language => 'yes', + search_index_language => 'yes', + telemetry_bysessionid => 'yes', + delete_mode => 'delayed', + pop3alt_uidl_format => 'dovecot', + event_content_inclusion_mode => 'standard', + event_content_size => '1', + event_exclude_specialuse => '\\Junk', + event_extra_params => + 'modseq vnd.fastmail.clientId service uidnext vnd.fastmail.sessionId vnd.cmu.envelope vnd.fastmail.convUnseen vnd.fastmail.convExists vnd.fastmail.cid vnd.cmu.mbtype vnd.cmu.davFilename vnd.cmu.davUid vnd.cmu.mailboxACL vnd.fastmail.counters messages vnd.cmu.unseenMessages flagNames vnd.cmu.emailid vnd.cmu.threadid vnd.cmu.visibleUsers', + event_groups => 'mailbox message flags calendar applepushservice', + event_notifier => 'pusher', + sync_log => 'yes', + ); - return $class->SUPER::new({ - config => $config, - jmap => 1, - deliver => 1, - adminstore => 1, - services => [ 'imap', 'http', 'sieve' ] - }, @args); + return $class->SUPER::new( + { + config => $config, + jmap => 1, + deliver => 1, + adminstore => 1, + services => [ 'imap', 'http', 'sieve' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - ]); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + ]); } # XXX Cheating and just passing in all the using strings that cyrus # XXX recognises -- these were ripped from http_jmap.h, try to keep # XXX them up to date! :) sub default_using { - return qw( - urn:ietf:params:jmap:core - urn:ietf:params:jmap:mail - urn:ietf:params:jmap:submission - https://cyrusimap.org/ns/jmap/blob - urn:ietf:params:jmap:calendars - https://cyrusimap.org/ns/jmap/contacts - https://cyrusimap.org/ns/jmap/calendars - https://cyrusimap.org/ns/jmap/mail - https://cyrusimap.org/ns/jmap/performance - https://cyrusimap.org/ns/jmap/debug - https://cyrusimap.org/ns/jmap/quota - ); + return qw( + urn:ietf:params:jmap:core + urn:ietf:params:jmap:mail + urn:ietf:params:jmap:submission + https://cyrusimap.org/ns/jmap/blob + urn:ietf:params:jmap:calendars + https://cyrusimap.org/ns/jmap/contacts + https://cyrusimap.org/ns/jmap/calendars + https://cyrusimap.org/ns/jmap/mail + https://cyrusimap.org/ns/jmap/performance + https://cyrusimap.org/ns/jmap/debug + https://cyrusimap.org/ns/jmap/quota + ); } # XXX This is here as documentation -- these ones are supported by @@ -199,58 +204,54 @@ sub default_using { # urn:ietf:params:jmap:websocket # ); -sub _fmjmap_req -{ - my ($self, $cmd, %args) = @_; - my $jmap = delete $args{jmap} || $self->{jmap}; +sub _fmjmap_req { + my ($self, $cmd, %args) = @_; + my $jmap = delete $args{jmap} || $self->{jmap}; - my $rnum = "R" . $RNUM++; - my $res = $jmap->Request({methodCalls => [[$cmd, \%args, $rnum]], - using => [ $self->default_using ] }); - my $res1 = $res->{methodResponses}[0]; - $self->assert_not_null($res1); - $self->assert_str_equals($rnum, $res1->[2]); - return $res1; + my $rnum = "R" . $RNUM++; + my $res = $jmap->Request({ + methodCalls => [ [ $cmd, \%args, $rnum ] ], + using => [ $self->default_using ] + }); + my $res1 = $res->{methodResponses}[0]; + $self->assert_not_null($res1); + $self->assert_str_equals($rnum, $res1->[2]); + return $res1; } -sub _fmjmap_ok -{ - my ($self, $cmd, %args) = @_; - my $res = $self->_fmjmap_req($cmd, %args); - $self->assert_str_equals($cmd, $res->[0]); - return $res->[1]; +sub _fmjmap_ok { + my ($self, $cmd, %args) = @_; + my $res = $self->_fmjmap_req($cmd, %args); + $self->assert_str_equals($cmd, $res->[0]); + return $res->[1]; } -sub _fmjmap_err -{ - my ($self, $cmd, %args) = @_; - my $res = $self->_fmjmap_req($cmd, %args); - $self->assert_str_equals("error", $res->[0]); - return $res->[1]; +sub _fmjmap_err { + my ($self, $cmd, %args) = @_; + my $res = $self->_fmjmap_req($cmd, %args); + $self->assert_str_equals("error", $res->[0]); + return $res->[1]; } -sub _set_quotaroot -{ - my ($self, $quotaroot) = @_; - $self->{quotaroot} = $quotaroot; +sub _set_quotaroot { + my ($self, $quotaroot) = @_; + $self->{quotaroot} = $quotaroot; } -sub _set_quotalimits -{ - my ($self, %resources) = @_; - my $admintalk = $self->{adminstore}->get_client(); +sub _set_quotalimits { + my ($self, %resources) = @_; + my $admintalk = $self->{adminstore}->get_client(); - my $quotaroot = delete $resources{quotaroot} || $self->{quotaroot}; - my @quotalist; - foreach my $resource (keys %resources) - { - my $limit = $resources{$resource} - or die "No limit specified for $resource"; - push(@quotalist, uc($resource), $limit); - } - $self->{limits}->{$quotaroot} = { @quotalist }; - $admintalk->setquota($quotaroot, \@quotalist); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + my $quotaroot = delete $resources{quotaroot} || $self->{quotaroot}; + my @quotalist; + foreach my $resource (keys %resources) { + my $limit = $resources{$resource} + or die "No limit specified for $resource"; + push(@quotalist, uc($resource), $limit); + } + $self->{limits}->{$quotaroot} = {@quotalist}; + $admintalk->setquota($quotaroot, \@quotalist); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } use Cassandane::Tiny::Loader 'tiny-tests/FastMail'; diff --git a/cassandane/Cassandane/Cyrus/Fetch.pm b/cassandane/Cassandane/Cyrus/Fetch.pm index 743524136b..95e99ac2bc 100644 --- a/cassandane/Cassandane/Cyrus/Fetch.pm +++ b/cassandane/Cassandane/Cyrus/Fetch.pm @@ -52,226 +52,226 @@ use Cassandane::Util::Log; $Data::Dumper::Sortkeys = 1; -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test COPY behaviour with a very long sequence set # -sub test_fetch_header -{ - my ($self) = @_; +sub test_fetch_header { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.dest"); - $self->make_message("Test Message"); + $imaptalk->create("INBOX.dest"); + $self->make_message("Test Message"); - # unfortunately, you can't see the data that went over the wire very easily - # in IMAPTalk - but we know the headers will be in a literal, so.. - my $body = ""; - $imaptalk->literal_handle_control(new IO::Scalar \$body); - my $res = $imaptalk->fetch('1', '(UID FLAGS BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)])'); - $imaptalk->literal_handle_control(0); + # unfortunately, you can't see the data that went over the wire very easily + # in IMAPTalk - but we know the headers will be in a literal, so.. + my $body = ""; + $imaptalk->literal_handle_control(new IO::Scalar \$body); + my $res = $imaptalk->fetch('1', + '(UID FLAGS BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)])'); + $imaptalk->literal_handle_control(0); - $self->assert(defined $res, "Fetch feturned a response"); - $self->assert_matches(qr/^Message-ID: <[^>]+>\s+$/, $body); + $self->assert(defined $res, "Fetch feturned a response"); + $self->assert_matches(qr/^Message-ID: <[^>]+>\s+$/, $body); } # https://github.com/cyrusimap/cyrus-imapd/issues/21 sub test_duplicate_headers - :min_version_3_0 -{ - my ($self) = @_; - - my $from1 = Cassandane::Address->new(localpart => 'firstsender', - domain => 'example.com'); - my $from2 = Cassandane::Address->new(localpart => 'secondsender', - domain => 'example.com'); - - my $rcpt1 = Cassandane::Address->new(localpart => 'firstrecipient', - domain => 'example.com'); - my $rcpt2 = Cassandane::Address->new(localpart => 'secondrecipient', - domain => 'example.com'); - - my $cc1 = Cassandane::Address->new(localpart => 'firstcc', - domain => 'example.com'); - my $cc2 = Cassandane::Address->new(localpart => 'secondcc', - domain => 'example.com'); - - my $bcc1 = Cassandane::Address->new(localpart => 'firstbcc', - domain => 'example.com'); - my $bcc2 = Cassandane::Address->new(localpart => 'secondbcc', - domain => 'example.com'); - - my $date1 = DateTime->from_epoch(epoch => time()); - my $date2 = DateTime->from_epoch(epoch => time() - 2); - - my $msg = $self->make_message( - 'subject1', - from => $from1, - to => $rcpt1, - cc => $cc1, - bcc => $bcc1, - messageid => 'messageid1@example.com', - date => $date1, - extra_headers => [ - [subject => 'subject2'], - [from => $from2->as_string() ], - [to => $rcpt2->as_string() ], - [cc => $cc2->as_string() ], - [bcc => $bcc2->as_string() ], - ['message-id' => '' ], - [date => to_rfc822($date2) ], - ], - ); - - # Verify that it created duplicate headers, and didn't collate the values. - # If it collated the values, this test proves nothing. - $self->assert_equals(scalar(grep { $_->{name} eq 'subject' } @{$msg->{headers}}), 2); - $self->assert_equals(scalar(grep { $_->{name} eq 'from' } @{$msg->{headers}}), 2); - $self->assert_equals(scalar(grep { $_->{name} eq 'to' } @{$msg->{headers}}), 2); - $self->assert_equals(scalar(grep { $_->{name} eq 'cc' } @{$msg->{headers}}), 2); - $self->assert_equals(scalar(grep { $_->{name} eq 'bcc' } @{$msg->{headers}}), 2); - - # XXX Cassandane::Message's add_header() appends rather than prepends. - # So we currently expect all the "second" values, when we would prefer - # to expect the "first" ones. - my %exp = ( - Subject => 'subject2', - From => $from2->address(), - To => $rcpt2->address(), - Cc => $cc2->address(), - Bcc => $bcc2->address(), - Date => to_rfc822($date2), - 'Message-ID' => '', - 'In-Reply-To' => undef, - ); - - my $imaptalk = $self->{store}->get_client(); - my $res = $imaptalk->fetch('1', 'ENVELOPE'); - - # XXX what behaviour do we expect from Sender and Reply-To headers? - delete $res->{1}->{envelope}->{Sender}; - delete $res->{1}->{envelope}->{'Reply-To'}; - - $self->assert_deep_equals(\%exp, $res->{1}->{envelope}); + : min_version_3_0 { + my ($self) = @_; + + my $from1 = Cassandane::Address->new( + localpart => 'firstsender', + domain => 'example.com' + ); + my $from2 = Cassandane::Address->new( + localpart => 'secondsender', + domain => 'example.com' + ); + + my $rcpt1 = Cassandane::Address->new( + localpart => 'firstrecipient', + domain => 'example.com' + ); + my $rcpt2 = Cassandane::Address->new( + localpart => 'secondrecipient', + domain => 'example.com' + ); + + my $cc1 = Cassandane::Address->new( + localpart => 'firstcc', + domain => 'example.com' + ); + my $cc2 = Cassandane::Address->new( + localpart => 'secondcc', + domain => 'example.com' + ); + + my $bcc1 = Cassandane::Address->new( + localpart => 'firstbcc', + domain => 'example.com' + ); + my $bcc2 = Cassandane::Address->new( + localpart => 'secondbcc', + domain => 'example.com' + ); + + my $date1 = DateTime->from_epoch(epoch => time()); + my $date2 = DateTime->from_epoch(epoch => time() - 2); + + my $msg = $self->make_message( + 'subject1', + from => $from1, + to => $rcpt1, + cc => $cc1, + bcc => $bcc1, + messageid => 'messageid1@example.com', + date => $date1, + extra_headers => [ + [ subject => 'subject2' ], + [ from => $from2->as_string() ], + [ to => $rcpt2->as_string() ], + [ cc => $cc2->as_string() ], + [ bcc => $bcc2->as_string() ], + [ 'message-id' => '' ], + [ date => to_rfc822($date2) ], + ], + ); + + # Verify that it created duplicate headers, and didn't collate the values. + # If it collated the values, this test proves nothing. + $self->assert_equals( + scalar(grep { $_->{name} eq 'subject' } @{ $msg->{headers} }), 2); + $self->assert_equals( + scalar(grep { $_->{name} eq 'from' } @{ $msg->{headers} }), 2); + $self->assert_equals(scalar(grep { $_->{name} eq 'to' } @{ $msg->{headers} }), + 2); + $self->assert_equals(scalar(grep { $_->{name} eq 'cc' } @{ $msg->{headers} }), + 2); + $self->assert_equals( + scalar(grep { $_->{name} eq 'bcc' } @{ $msg->{headers} }), 2); + + # XXX Cassandane::Message's add_header() appends rather than prepends. + # So we currently expect all the "second" values, when we would prefer + # to expect the "first" ones. + my %exp = ( + Subject => 'subject2', + From => $from2->address(), + To => $rcpt2->address(), + Cc => $cc2->address(), + Bcc => $bcc2->address(), + Date => to_rfc822($date2), + 'Message-ID' => '', + 'In-Reply-To' => undef, + ); + + my $imaptalk = $self->{store}->get_client(); + my $res = $imaptalk->fetch('1', 'ENVELOPE'); + + # XXX what behaviour do we expect from Sender and Reply-To headers? + delete $res->{1}->{envelope}->{Sender}; + delete $res->{1}->{envelope}->{'Reply-To'}; + + $self->assert_deep_equals(\%exp, $res->{1}->{envelope}); } -sub test_header_multiple -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - - my $extra_headers = [ - ['x-nice-day-for', 'start again (come on)' ], - ['x-awkward', 'interjection' ], - ['x-nice-day-for', 'white wedding' ], - ['x-nice-day-for', 'start agaaain' ], - ]; - - my %exp; - $exp{1} = $self->make_message('message 1', - 'extra_headers' => $extra_headers); - $exp{2} = $self->make_message('nice day'); - $self->check_messages(\%exp); - - my $res = $talk->fetch('1:*', - '(BODY.PEEK[HEADER.FIELDS (x-nice-day-for)])'); - $self->assert_num_equals(2, scalar keys %{$res}); - - my $expected = { - 'x-nice-day-for' => [ - 'start again (come on)', - 'white wedding', - 'start agaaain', - ], - }; - $self->assert_deep_equals($expected, $res->{1}->{headers}); - $self->assert_deep_equals({}, $res->{2}->{headers}); -} - -sub test_fetch_section -{ - my ($self) = @_; +sub test_header_multiple { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - # Start body - my $body = "--047d7b33dd729737fe04d3bde348\r\n"; + my $extra_headers = [ + [ 'x-nice-day-for', 'start again (come on)' ], + [ 'x-awkward', 'interjection' ], + [ 'x-nice-day-for', 'white wedding' ], + [ 'x-nice-day-for', 'start agaaain' ], + ]; - # Subpart 1 - $body .= "" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "\r\n" - . "body1" - . "\r\n"; + my %exp; + $exp{1} = $self->make_message('message 1', 'extra_headers' => $extra_headers); + $exp{2} = $self->make_message('nice day'); + $self->check_messages(\%exp); - # Subpart 2 - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: text/html;charset=\"ISO-8859-1\"\r\n" - . "\r\n" - . "

body2

" - . "\r\n"; + my $res = $talk->fetch('1:*', '(BODY.PEEK[HEADER.FIELDS (x-nice-day-for)])'); + $self->assert_num_equals(2, scalar keys %{$res}); - # Subpart 3 - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: multipart/mixed;boundary=frontier\r\n" - . "\r\n"; - - # Subpart 3.1 - $body .= "--frontier\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body31" - . "\r\n"; - - # Subpart 3.2 - $body .= "--frontier\r\n" - . "Content-Type: multipart/mixed;boundary=border\r\n" - . "\r\n" - . "body32" - . "\r\n"; - - # Subpart 3.2.1 - $body .= "--border\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body321" - . "\r\n"; + my $expected = { 'x-nice-day-for' => + [ 'start again (come on)', 'white wedding', 'start agaaain', ], }; + $self->assert_deep_equals($expected, $res->{1}->{headers}); + $self->assert_deep_equals({}, $res->{2}->{headers}); +} - # Subpart 3.2.2 - $body .= "--border\r\n" +sub test_fetch_section { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + # Start body + my $body = "--047d7b33dd729737fe04d3bde348\r\n"; + + # Subpart 1 + $body .= "" + . "Content-Type: text/plain; charset=UTF-8\r\n" . "\r\n" . "body1" . "\r\n"; + + # Subpart 2 + $body + .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: text/html;charset=\"ISO-8859-1\"\r\n" . "\r\n" + . "

body2

" . "\r\n"; + + # Subpart 3 + $body .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: multipart/mixed;boundary=frontier\r\n" . "\r\n"; + + # Subpart 3.1 + $body + .= "--frontier\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "body31" . "\r\n"; + + # Subpart 3.2 + $body + .= "--frontier\r\n" + . "Content-Type: multipart/mixed;boundary=border\r\n" . "\r\n" + . "body32" . "\r\n"; + + # Subpart 3.2.1 + $body + .= "--border\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "body321" . "\r\n"; + + # Subpart 3.2.2 + $body + .= "--border\r\n" . "Content-Type: application/octet-stream\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . "PGh0bWw+CiAgPGhlYWQ+CiAg==" - . "\r\n"; + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "PGh0bWw+CiAgPGhlYWQ+CiAg==" . "\r\n"; - # End subpart 3.2 - $body .= "--border--\r\n"; + # End subpart 3.2 + $body .= "--border--\r\n"; - # End subpart 3 - $body .= "--frontier--\r\n"; + # End subpart 3 + $body .= "--frontier--\r\n"; - # Subpart 4 - my $msg4 = "" + # Subpart 4 + my $msg4 + = "" . "Return-Path: \r\n" . "Mime-Version: 1.0\r\n" . "Content-Type: text/plain\r\n" @@ -280,17 +280,15 @@ sub test_fetch_section . "From: Ava T. Nguyen \r\n" . "Message-ID: \r\n" . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "body4"; - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . $msg4 - . "\r\n"; - - # Subpart 5 - my $msg5 = "" + . "To: Test User \r\n" . "\r\n" . "body4"; + $body + .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . $msg4 . "\r\n"; + + # Subpart 5 + my $msg5 + = "" . "Return-Path: \r\n" . "Mime-Version: 1.0\r\n" . "Content-Type: multipart/mixed;boundary=subpart5\r\n" @@ -299,386 +297,361 @@ sub test_fetch_section . "From: blu\@local\r\n" . "Message-ID: \r\n" . "Date: Wed, 06 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" + . "To: Test User \r\n" . "\r\n" . "--subpart5\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body51" - . "\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "body51" . "\r\n" . "--subpart5\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body52" - . "\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "body52" . "\r\n" . "--subpart5--\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . $msg5 - . "\r\n"; + $body + .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . $msg5 . "\r\n"; - # End body - $body .= "--047d7b33dd729737fe04d3bde348--"; + # End body + $body .= "--047d7b33dd729737fe04d3bde348--"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body - ); + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body + ); - my $res; + my $res; - $res = $imaptalk->fetch('1', '(BODY[1])'); - $self->assert_str_equals($res->{'1'}->{body}, "body1"); + $res = $imaptalk->fetch('1', '(BODY[1])'); + $self->assert_str_equals($res->{'1'}->{body}, "body1"); - $res = $imaptalk->fetch('1', '(BODY[2])'); - $self->assert_str_equals($res->{'1'}->{body}, "

body2

"); + $res = $imaptalk->fetch('1', '(BODY[2])'); + $self->assert_str_equals($res->{'1'}->{body}, + "

body2

"); - $res = $imaptalk->fetch('1', '(BODY[3.1])'); - $self->assert_str_equals($res->{'1'}->{body}, "body31"); + $res = $imaptalk->fetch('1', '(BODY[3.1])'); + $self->assert_str_equals($res->{'1'}->{body}, "body31"); - $res = $imaptalk->fetch('1', '(BODY[3.2.1])'); - $self->assert_str_equals($res->{'1'}->{body}, "body321"); + $res = $imaptalk->fetch('1', '(BODY[3.2.1])'); + $self->assert_str_equals($res->{'1'}->{body}, "body321"); - $res = $imaptalk->fetch('1', '(BODY[3.2.1.MIME])'); - $self->assert($res->{'1'}->{body} =~ m/Content-Type/); - $self->assert(not $res->{'1'}->{body} =~ m/body321/); + $res = $imaptalk->fetch('1', '(BODY[3.2.1.MIME])'); + $self->assert($res->{'1'}->{body} =~ m/Content-Type/); + $self->assert(not $res->{'1'}->{body} =~ m/body321/); - $res = $imaptalk->fetch('1', '(BODY[3.2.2])'); - $self->assert_str_equals($res->{'1'}->{body}, "PGh0bWw+CiAgPGhlYWQ+CiAg=="); + $res = $imaptalk->fetch('1', '(BODY[3.2.2])'); + $self->assert_str_equals($res->{'1'}->{body}, "PGh0bWw+CiAgPGhlYWQ+CiAg=="); - $res = $imaptalk->fetch('1', '(BODY[3.2.2]<4.3>)'); - $self->assert_str_equals($res->{'1'}->{body}, substr("PGh0bWw+CiAgPGhlYWQ+CiAg==", 4, 3)); + $res = $imaptalk->fetch('1', '(BODY[3.2.2]<4.3>)'); + $self->assert_str_equals($res->{'1'}->{body}, + substr("PGh0bWw+CiAgPGhlYWQ+CiAg==", 4, 3)); - $res = $imaptalk->fetch('1', '(BODY.PEEK[4.HEADER.FIELDS (CONTENT-TYPE)])'); - $self->assert_str_equals($res->{'1'}->{headers}->{"content-type"}[0], "text/plain"); + $res = $imaptalk->fetch('1', '(BODY.PEEK[4.HEADER.FIELDS (CONTENT-TYPE)])'); + $self->assert_str_equals($res->{'1'}->{headers}->{"content-type"}[0], + "text/plain"); - $res = $imaptalk->fetch('1', '(BODY[4.1.MIME])'); - $self->assert($res->{'1'}->{body} =~ m/Content-Type/); + $res = $imaptalk->fetch('1', '(BODY[4.1.MIME])'); + $self->assert($res->{'1'}->{body} =~ m/Content-Type/); - $res = $imaptalk->fetch('1', '(BODY[4])'); - $self->assert_str_equals($res->{'1'}->{body}, $msg4); + $res = $imaptalk->fetch('1', '(BODY[4])'); + $self->assert_str_equals($res->{'1'}->{body}, $msg4); - $res = $imaptalk->fetch('1', '(BODY[5.2])'); - $self->assert_str_equals($res->{'1'}->{body}, "body52"); + $res = $imaptalk->fetch('1', '(BODY[5.2])'); + $self->assert_str_equals($res->{'1'}->{body}, "body52"); - # Check for some bogus subparts - $res = $imaptalk->fetch('1', '(BODY[3.2.3])'); - $self->assert_null($res->{'1'}->{body}); + # Check for some bogus subparts + $res = $imaptalk->fetch('1', '(BODY[3.2.3])'); + $self->assert_null($res->{'1'}->{body}); - $res = $imaptalk->fetch('1', '(BODY[3.2.1.2])'); - $self->assert_null($res->{'1'}->{body}); + $res = $imaptalk->fetch('1', '(BODY[3.2.1.2])'); + $self->assert_null($res->{'1'}->{body}); - $res = $imaptalk->fetch('1', '(BODY[4.2])'); - $self->assert_null($res->{'1'}->{body}); + $res = $imaptalk->fetch('1', '(BODY[4.2])'); + $self->assert_null($res->{'1'}->{body}); - $res = $imaptalk->fetch('1', '(BODY[-1])'); - $self->assert_null($res->{'1'}->{body}); + $res = $imaptalk->fetch('1', '(BODY[-1])'); + $self->assert_null($res->{'1'}->{body}); } -sub test_fetch_section_multipart -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - # Start body - my $body = "--047d7b33dd729737fe04d3bde348\r\n"; - - # Subpart 1 - $body .= "" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "\r\n" - . "body1" - . "\r\n"; - - # Subpart 2 - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: text/html;charset=\"ISO-8859-1\"\r\n" - . "\r\n" - . "

body2

" - . "\r\n"; - - # Subpart 3 - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: multipart/mixed;boundary=frontier\r\n" - . "\r\n"; - - # Subpart 3.1 - $body .= "--frontier\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body31" - . "\r\n"; - - # Subpart 3.2 - $body .= "--frontier\r\n" - . "Content-Type: multipart/mixed;boundary=border\r\n" - . "\r\n" - . "body32" - . "\r\n"; - - # Subpart 3.2.1 - $body .= "--border\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body321" - . "\r\n"; - - # Subpart 3.2.2 - $body .= "--border\r\n" +sub test_fetch_section_multipart { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + # Start body + my $body = "--047d7b33dd729737fe04d3bde348\r\n"; + + # Subpart 1 + $body .= "" + . "Content-Type: text/plain; charset=UTF-8\r\n" . "\r\n" . "body1" . "\r\n"; + + # Subpart 2 + $body + .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: text/html;charset=\"ISO-8859-1\"\r\n" . "\r\n" + . "

body2

" . "\r\n"; + + # Subpart 3 + $body .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: multipart/mixed;boundary=frontier\r\n" . "\r\n"; + + # Subpart 3.1 + $body + .= "--frontier\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "body31" . "\r\n"; + + # Subpart 3.2 + $body + .= "--frontier\r\n" + . "Content-Type: multipart/mixed;boundary=border\r\n" . "\r\n" + . "body32" . "\r\n"; + + # Subpart 3.2.1 + $body + .= "--border\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "body321" . "\r\n"; + + # Subpart 3.2.2 + $body + .= "--border\r\n" . "Content-Type: application/octet-stream\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . "PGh0bWw+CiAgPGhlYWQ+CiAg==" - . "\r\n"; + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "PGh0bWw+CiAgPGhlYWQ+CiAg==" . "\r\n"; - # End subpart 3.2 - $body .= "--border--\r\n"; + # End subpart 3.2 + $body .= "--border--\r\n"; - # End subpart 3 - $body .= "--frontier--\r\n"; + # End subpart 3 + $body .= "--frontier--\r\n"; - # End body - $body .= "--047d7b33dd729737fe04d3bde348--"; + # End body + $body .= "--047d7b33dd729737fe04d3bde348--"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body - ); + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body + ); - my $res; + my $res; - $res = $imaptalk->fetch('1', '(BODY[1])'); - $self->assert_str_equals($res->{'1'}->{body}, "body1"); + $res = $imaptalk->fetch('1', '(BODY[1])'); + $self->assert_str_equals($res->{'1'}->{body}, "body1"); - $res = $imaptalk->fetch('1', '(BODY[2])'); - $self->assert_str_equals($res->{'1'}->{body}, "

body2

"); + $res = $imaptalk->fetch('1', '(BODY[2])'); + $self->assert_str_equals($res->{'1'}->{body}, + "

body2

"); - $res = $imaptalk->fetch('1', '(BODY[3.1])'); - $self->assert_str_equals($res->{'1'}->{body}, "body31"); + $res = $imaptalk->fetch('1', '(BODY[3.1])'); + $self->assert_str_equals($res->{'1'}->{body}, "body31"); - $res = $imaptalk->fetch('1', '(BODY[3.2.1])'); - $self->assert_str_equals($res->{'1'}->{body}, "body321"); + $res = $imaptalk->fetch('1', '(BODY[3.2.1])'); + $self->assert_str_equals($res->{'1'}->{body}, "body321"); - $res = $imaptalk->fetch('1', '(BODY[3.2.1.MIME])'); - $self->assert($res->{'1'}->{body} =~ m/Content-Type/); - $self->assert(not $res->{'1'}->{body} =~ m/body321/); + $res = $imaptalk->fetch('1', '(BODY[3.2.1.MIME])'); + $self->assert($res->{'1'}->{body} =~ m/Content-Type/); + $self->assert(not $res->{'1'}->{body} =~ m/body321/); - $res = $imaptalk->fetch('1', '(BODY[3.2.2])'); - $self->assert_str_equals($res->{'1'}->{body}, "PGh0bWw+CiAgPGhlYWQ+CiAg=="); + $res = $imaptalk->fetch('1', '(BODY[3.2.2])'); + $self->assert_str_equals($res->{'1'}->{body}, "PGh0bWw+CiAgPGhlYWQ+CiAg=="); - $res = $imaptalk->fetch('1', '(BODY[3.2.2]<4.3>)'); - $self->assert_str_equals($res->{'1'}->{body}, substr("PGh0bWw+CiAgPGhlYWQ+CiAg==", 4, 3)); + $res = $imaptalk->fetch('1', '(BODY[3.2.2]<4.3>)'); + $self->assert_str_equals($res->{'1'}->{body}, + substr("PGh0bWw+CiAgPGhlYWQ+CiAg==", 4, 3)); - # Check for some bogus subparts - $res = $imaptalk->fetch('1', '(BODY[3.2.3])'); - $self->assert_null($res->{'1'}->{body}); + # Check for some bogus subparts + $res = $imaptalk->fetch('1', '(BODY[3.2.3])'); + $self->assert_null($res->{'1'}->{body}); - $res = $imaptalk->fetch('1', '(BODY[3.2.1.2])'); - $self->assert_null($res->{'1'}->{body}); + $res = $imaptalk->fetch('1', '(BODY[3.2.1.2])'); + $self->assert_null($res->{'1'}->{body}); - $res = $imaptalk->fetch('1', '(BODY[4.2])'); - $self->assert_null($res->{'1'}->{body}); + $res = $imaptalk->fetch('1', '(BODY[4.2])'); + $self->assert_null($res->{'1'}->{body}); - $res = $imaptalk->fetch('1', '(BODY[-1])'); - $self->assert_null($res->{'1'}->{body}); + $res = $imaptalk->fetch('1', '(BODY[-1])'); + $self->assert_null($res->{'1'}->{body}); } -sub test_fetch_section_rfc822digest -{ - my ($self) = @_; +sub test_fetch_section_rfc822digest { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $ct = "multipart/digest; boundary=\"foo\""; - my $from = "sub\@domain.org"; - my $date = "Sun, 12 Aug 2012 12:34:56 +0300"; - my $subj = "submsg"; + my $ct = "multipart/digest; boundary=\"foo\""; + my $from = "sub\@domain.org"; + my $date = "Sun, 12 Aug 2012 12:34:56 +0300"; + my $subj = "submsg"; - my $body = "" + my $body + = "" . "From: $from\r\n" . "Date: $date\r\n" . "Subject: $subj\r\n" - . "Content-Type: $ct\r\n" - . "\r\n" - . "prologue\r\n" - . "\r\n" - . "--foo\r\n" - . "\r\n" + . "Content-Type: $ct\r\n" . "\r\n" + . "prologue\r\n" . "\r\n" + . "--foo\r\n" . "\r\n" . "From: m1\@example.com\r\n" - . "Subject: m1\r\n" - . "\r\n" - . "m1 body\r\n" - . "\r\n" + . "Subject: m1\r\n" . "\r\n" + . "m1 body\r\n" . "\r\n" . "--foo\r\n" - . "X-Mime: m2 header\r\n" - . "\r\n" + . "X-Mime: m2 header\r\n" . "\r\n" . "From: m2\@example.com\r\n" - . "Subject: m2\r\n" - . "\r\n" - . "m2 body\r\n" - . "\r\n" - . "--foo--\r\n" - . "\r\n" - . "epilogue\r\n" - . "\r\n"; - - $self->make_message("foo", - mime_type => "message/rfc822", - body => $body, - ); - - my $res; - - $res = $imaptalk->fetch('1', '(BODY.PEEK[TEXT])'); - $self->assert_str_equals($res->{'1'}->{body}, $body); - - $res = $imaptalk->fetch('1', '(BODY.PEEK[1])'); - $self->assert_str_equals($res->{'1'}->{body}, $body); - - $res = $imaptalk->fetch('1', '(BODY.PEEK[1.HEADER])'); - $self->assert_str_equals($res->{'1'}->{headers}->{"content-type"}[0], $ct); - $self->assert_str_equals($res->{'1'}->{headers}->{"date"}[0], $date); - $self->assert_str_equals($res->{'1'}->{headers}->{"from"}[0], $from); - $self->assert_str_equals($res->{'1'}->{headers}->{"subject"}[0], $subj); + . "Subject: m2\r\n" . "\r\n" + . "m2 body\r\n" . "\r\n" + . "--foo--\r\n" . "\r\n" + . "epilogue\r\n" . "\r\n"; + + $self->make_message( + "foo", + mime_type => "message/rfc822", + body => $body, + ); + + my $res; + + $res = $imaptalk->fetch('1', '(BODY.PEEK[TEXT])'); + $self->assert_str_equals($res->{'1'}->{body}, $body); + + $res = $imaptalk->fetch('1', '(BODY.PEEK[1])'); + $self->assert_str_equals($res->{'1'}->{body}, $body); + + $res = $imaptalk->fetch('1', '(BODY.PEEK[1.HEADER])'); + $self->assert_str_equals($res->{'1'}->{headers}->{"content-type"}[0], $ct); + $self->assert_str_equals($res->{'1'}->{headers}->{"date"}[0], $date); + $self->assert_str_equals($res->{'1'}->{headers}->{"from"}[0], $from); + $self->assert_str_equals($res->{'1'}->{headers}->{"subject"}[0], $subj); } -sub test_fetch_section_rfc822 -{ - my ($self) = @_; +sub test_fetch_section_rfc822 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $body = "" + my $body + = "" . "From: sub\@domain.org\r\n" . "Date: Sun, 12 Aug 2012 12:34:56 +0300\r\n" - . "Subject: submsg\r\n" - . "\r\n" - . "foo"; + . "Subject: submsg\r\n" . "\r\n" . "foo"; - $self->make_message("foo", - mime_type => "message/rfc822", - body => $body, - ); + $self->make_message( + "foo", + mime_type => "message/rfc822", + body => $body, + ); - my $res; + my $res; - $res = $imaptalk->fetch('1', '(BODY.PEEK[TEXT])'); - $self->assert_str_equals($res->{'1'}->{body}, $body); + $res = $imaptalk->fetch('1', '(BODY.PEEK[TEXT])'); + $self->assert_str_equals($res->{'1'}->{body}, $body); - $res = $imaptalk->fetch('1', '(BODY.PEEK[1])'); - $self->assert_str_equals($res->{'1'}->{body}, $body); + $res = $imaptalk->fetch('1', '(BODY.PEEK[1])'); + $self->assert_str_equals($res->{'1'}->{body}, $body); - $res = $imaptalk->fetch('1', '(BODY.PEEK[1.TEXT])'); - $self->assert_str_equals($res->{'1'}->{body}, "foo"); + $res = $imaptalk->fetch('1', '(BODY.PEEK[1.TEXT])'); + $self->assert_str_equals($res->{'1'}->{body}, "foo"); - $res = $imaptalk->fetch('1', '(BODY.PEEK[1.1])'); - $self->assert_str_equals($res->{'1'}->{body}, "foo"); + $res = $imaptalk->fetch('1', '(BODY.PEEK[1.1])'); + $self->assert_str_equals($res->{'1'}->{body}, "foo"); } +sub test_fetch_section_nomultipart { + my ($self) = @_; -sub test_fetch_section_nomultipart -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->make_message( - "foo", - from => Cassandane::Address->new( - localpart => 'foo', - domain => 'example.com', - ), - mime_type => "text/plain", - body => "body1", - ); + $self->make_message( + "foo", + from => Cassandane::Address->new( + localpart => 'foo', + domain => 'example.com', + ), + mime_type => "text/plain", + body => "body1", + ); - my $res; + my $res; - $res = $imaptalk->fetch('1', '(BODY[1])'); - $self->assert_str_equals($res->{'1'}->{body}, "body1"); + $res = $imaptalk->fetch('1', '(BODY[1])'); + $self->assert_str_equals($res->{'1'}->{body}, "body1"); - # RFC 3501: "Every message has at least one part number." - $res = $imaptalk->fetch('1', '(BODY[1.MIME])'); - $self->assert($res->{'1'}->{body} =~ m/Content-Type/); - $self->assert(not $res->{'1'}->{body} =~ m/body1/); + # RFC 3501: "Every message has at least one part number." + $res = $imaptalk->fetch('1', '(BODY[1.MIME])'); + $self->assert($res->{'1'}->{body} =~ m/Content-Type/); + $self->assert(not $res->{'1'}->{body} =~ m/body1/); - $res = $imaptalk->fetch('1', '(BODY[HEADER])'); - $self->assert($res->{'1'}->{body} =~ m/Content-Type/); + $res = $imaptalk->fetch('1', '(BODY[HEADER])'); + $self->assert($res->{'1'}->{body} =~ m/Content-Type/); - $res = $imaptalk->fetch('1', '(BODY.PEEK[HEADER.FIELDS (FROM)])'); - $self->assert_str_equals($res->{'1'}->{headers}->{from}[0], ""); + $res = $imaptalk->fetch('1', '(BODY.PEEK[HEADER.FIELDS (FROM)])'); + $self->assert_str_equals($res->{'1'}->{headers}->{from}[0], + ""); - $res = $imaptalk->fetch('1', '(BODY[1.HEADER])'); - $self->assert_null($res->{'1'}->{body}); + $res = $imaptalk->fetch('1', '(BODY[1.HEADER])'); + $self->assert_null($res->{'1'}->{body}); - # invalid - $res = $imaptalk->fetch('1', '(BODY[0])'); - $self->assert_null($res->{'1'}->{body}); + # invalid + $res = $imaptalk->fetch('1', '(BODY[0])'); + $self->assert_null($res->{'1'}->{body}); - # invalid - $res = $imaptalk->fetch('1', '(BODY[1.1])'); - $self->assert_null($res->{'1'}->{body}); + # invalid + $res = $imaptalk->fetch('1', '(BODY[1.1])'); + $self->assert_null($res->{'1'}->{body}); - # invalid - $res = $imaptalk->fetch('1', '(BODY[0.1])'); - $self->assert_null($res->{'1'}->{body}); + # invalid + $res = $imaptalk->fetch('1', '(BODY[0.1])'); + $self->assert_null($res->{'1'}->{body}); - # invalid - $res = $imaptalk->fetch('1', '(BODY[1.0])'); - $self->assert_null($res->{'1'}->{body}); + # invalid + $res = $imaptalk->fetch('1', '(BODY[1.0])'); + $self->assert_null($res->{'1'}->{body}); - $res = $imaptalk->fetch('1', '(BINARY[1]<0.2>)'); - $self->assert_str_equals($res->{'1'}->{binary}, "bo"); + $res = $imaptalk->fetch('1', '(BINARY[1]<0.2>)'); + $self->assert_str_equals($res->{'1'}->{binary}, "bo"); - $res = $imaptalk->fetch('1', '(BINARY[1]<2.10>)'); - $self->assert_str_equals($res->{'1'}->{binary}, "dy1"); + $res = $imaptalk->fetch('1', '(BINARY[1]<2.10>)'); + $self->assert_str_equals($res->{'1'}->{binary}, "dy1"); - $res = $imaptalk->fetch('1', '(BINARY[1]<10.12>)'); - $self->assert_str_equals($res->{'1'}->{binary}, ""); + $res = $imaptalk->fetch('1', '(BINARY[1]<10.12>)'); + $self->assert_str_equals($res->{'1'}->{binary}, ""); } -sub test_fetch_urlfetch -{ - my ($self) = @_; +sub test_fetch_urlfetch { + my ($self) = @_; - my %exp_sub; - my $store = $self->{store}; - my $talk = $store->get_client(); + my %exp_sub; + my $store = $self->{store}; + my $talk = $store->get_client(); - $store->set_folder("INBOX"); - $store->_select(); - $self->{gen}->set_next_uid(1); + $store->set_folder("INBOX"); + $store->_select(); + $self->{gen}->set_next_uid(1); - my $body; + my $body; - # Subpart 1 - $body = "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "\r\n" - . "body1" - . "\r\n"; + # Subpart 1 + $body = "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" . "\r\n" . "body1" . "\r\n"; - # Subpart 2 - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: multipart/mixed;boundary=frontier\r\n" - . "\r\n"; + # Subpart 2 + $body .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: multipart/mixed;boundary=frontier\r\n" . "\r\n"; - # Subpart 2.1 - $body .= "--frontier\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body21" - . "\r\n"; + # Subpart 2.1 + $body + .= "--frontier\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "body21" . "\r\n"; - # End subpart 2 - $body .= "--frontier--\r\n"; + # End subpart 2 + $body .= "--frontier--\r\n"; - # Subpart 3 - my $msg3 = "" + # Subpart 3 + my $msg3 + = "" . "Return-Path: \r\n" . "Mime-Version: 1.0\r\n" . "Content-Type: text/plain\r\n" @@ -687,451 +660,455 @@ sub test_fetch_urlfetch . "From: Ava T. Nguyen \r\n" . "Message-ID: \r\n" . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "body3"; - - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . $msg3 - . "\r\n"; - - # End body - $body .= "--047d7b33dd729737fe04d3bde348--"; - - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body - ); - - my $uid; - my %handlers = - ( - appenduid => sub - { - my ($cmd, $ids) = @_; - $uid = ${$ids}[1]; - }, - ); - - my $res; - - # Copy the whole message - $res = $talk->_imap_cmd('append', 0, \%handlers, - 'INBOX', [], "14-Jul-2013 17:01:02 +0000", - "CATENATE", [ - "URL", "/INBOX/;uid=1/;section=HEADER", - "URL", "/INBOX/;uid=1/;section=TEXT", - ], - ); - $self->assert_not_null($uid); - $res = $talk->fetch($uid, '(BODY.PEEK[TEXT])'); - $self->assert_str_equals($res->{$uid}->{body}, $body); - - # Merge the headers of an embedded RFC822 message with a plaintext subpart - $res = $talk->_imap_cmd('append', 0, \%handlers, - 'INBOX', [], "14-Jul-2013 17:01:02 +0000", - "CATENATE", [ - "URL", "/INBOX/;uid=1/;section=3.HEADER", - "URL", "/INBOX/;uid=1/;section=2.1", - ], - ); - $self->assert_not_null($uid); - $res = $talk->fetch($uid, '(BODY.PEEK[TEXT] BODY.PEEK[HEADER.FIELDS (CONTENT-TYPE)])'); - $self->assert_str_equals($res->{$uid}->{headers}->{'content-type'}[0], "text/plain"); - $self->assert_str_equals($res->{$uid}->{body}, "body21"); - - # Extract an embedded RFC822 message into a new standalone message - $res = $talk->_imap_cmd('append', 0, \%handlers, - 'INBOX', [], "14-Jul-2013 17:01:02 +0000", - "CATENATE", [ - "URL", "/INBOX/;uid=1/;section=3", - ], - ); - $self->assert_not_null($uid); - $res = $talk->fetch($uid, '(BODY.PEEK[TEXT] BODY.PEEK[HEADER.FIELDS (CONTENT-TYPE)])'); - $self->assert_str_equals($res->{$uid}->{headers}->{'content-type'}[0], "text/plain"); - $self->assert_str_equals($res->{$uid}->{body}, "body3"); - - # Extract part of an embedded RFC822 message into a new standalone message - $res = $talk->_imap_cmd('append', 0, \%handlers, - 'INBOX', [], "14-Jul-2013 17:01:02 +0000", - "CATENATE", [ - "URL", "/INBOX/;uid=1/;section=3.HEADER", - "URL", "/INBOX/;uid=1/;section=3.TEXT;partial=1.3", - ], - ); - $self->assert_not_null($uid); - $res = $talk->fetch($uid, '(BODY.PEEK[TEXT] BODY.PEEK[HEADER.FIELDS (CONTENT-TYPE)])'); - $self->assert_str_equals($res->{$uid}->{headers}->{'content-type'}[0], "text/plain"); - $self->assert_str_equals($res->{$uid}->{body}, "ody"); + . "To: Test User \r\n" . "\r\n" . "body3"; + + $body + .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . $msg3 . "\r\n"; + + # End body + $body .= "--047d7b33dd729737fe04d3bde348--"; + + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body + ); + + my $uid; + my %handlers = ( + appenduid => sub { + my ($cmd, $ids) = @_; + $uid = ${$ids}[1]; + }, + ); + + my $res; + + # Copy the whole message + $res = $talk->_imap_cmd( + 'append', 0, + \%handlers, + 'INBOX', + [], + "14-Jul-2013 17:01:02 +0000", + "CATENATE", + [ + "URL", "/INBOX/;uid=1/;section=HEADER", + "URL", "/INBOX/;uid=1/;section=TEXT", + ], + ); + $self->assert_not_null($uid); + $res = $talk->fetch($uid, '(BODY.PEEK[TEXT])'); + $self->assert_str_equals($res->{$uid}->{body}, $body); + + # Merge the headers of an embedded RFC822 message with a plaintext subpart + $res = $talk->_imap_cmd( + 'append', 0, + \%handlers, + 'INBOX', + [], + "14-Jul-2013 17:01:02 +0000", + "CATENATE", + [ + "URL", "/INBOX/;uid=1/;section=3.HEADER", + "URL", "/INBOX/;uid=1/;section=2.1", + ], + ); + $self->assert_not_null($uid); + $res = $talk->fetch($uid, + '(BODY.PEEK[TEXT] BODY.PEEK[HEADER.FIELDS (CONTENT-TYPE)])'); + $self->assert_str_equals($res->{$uid}->{headers}->{'content-type'}[0], + "text/plain"); + $self->assert_str_equals($res->{$uid}->{body}, "body21"); + + # Extract an embedded RFC822 message into a new standalone message + $res = $talk->_imap_cmd( + 'append', 0, \%handlers, 'INBOX', [], "14-Jul-2013 17:01:02 +0000", + "CATENATE", [ "URL", "/INBOX/;uid=1/;section=3", ], + ); + $self->assert_not_null($uid); + $res = $talk->fetch($uid, + '(BODY.PEEK[TEXT] BODY.PEEK[HEADER.FIELDS (CONTENT-TYPE)])'); + $self->assert_str_equals($res->{$uid}->{headers}->{'content-type'}[0], + "text/plain"); + $self->assert_str_equals($res->{$uid}->{body}, "body3"); + + # Extract part of an embedded RFC822 message into a new standalone message + $res = $talk->_imap_cmd( + 'append', 0, + \%handlers, + 'INBOX', + [], + "14-Jul-2013 17:01:02 +0000", + "CATENATE", + [ + "URL", "/INBOX/;uid=1/;section=3.HEADER", + "URL", "/INBOX/;uid=1/;section=3.TEXT;partial=1.3", + ], + ); + $self->assert_not_null($uid); + $res = $talk->fetch($uid, + '(BODY.PEEK[TEXT] BODY.PEEK[HEADER.FIELDS (CONTENT-TYPE)])'); + $self->assert_str_equals($res->{$uid}->{headers}->{'content-type'}[0], + "text/plain"); + $self->assert_str_equals($res->{$uid}->{body}, "ody"); } -sub test_fetch_flags_before_exists -{ - my ($self) = @_; +sub test_fetch_flags_before_exists { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->select('user.cassandane'); - $self->make_message("Test Message"); - # this sets the state with EXISTS = 1 - $admintalk->fetch('1:*', '(flags)'); + $admintalk->select('user.cassandane'); + $self->make_message("Test Message"); + # this sets the state with EXISTS = 1 + $admintalk->fetch('1:*', '(flags)'); - $self->make_message("Test Message"); -# $res = $admintalk->fetch('1:*', '(flags)'); + $self->make_message("Test Message"); + # $res = $admintalk->fetch('1:*', '(flags)'); - # need to make our own handlers - my %handlers; - { - my $sawfetch = -1; - use Data::Dumper; - $handlers{fetch} = sub { $sawfetch = $_[2] if $sawfetch < $_[2] }; - $handlers{exists} = sub { die "Got exists count too late for $_[2]" if $_[2] <= $sawfetch }; - } + # need to make our own handlers + my %handlers; + { + my $sawfetch = -1; + use Data::Dumper; + $handlers{fetch} = sub { $sawfetch = $_[2] if $sawfetch < $_[2] }; + $handlers{exists} + = sub { die "Got exists count too late for $_[2]" if $_[2] <= $sawfetch }; + } - # expecting to see EXISTS 2 before FETCH 2 - $admintalk->_imap_cmd("fetch", 1, \%handlers, \'1:*', '(flags)'); + # expecting to see EXISTS 2 before FETCH 2 + $admintalk->_imap_cmd("fetch", 1, \%handlers, \'1:*', '(flags)'); } sub test_tell_exists_count_earlier - :min_version_3_0 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->select('user.cassandane'); - - $self->make_message("Test Message 1"); - $admintalk->fetch('1:*', '(flags)'); - - $self->make_message("Test Message 2"); - $admintalk->fetch('2:*', '(flags)'); - - $self->make_message("Test Message 3"); - $admintalk->fetch('3:*', '(uid flags)'); - - $admintalk->unselect(); - $admintalk->select('user.cassandane'); - - $imaptalk->store('2', '+flags', '(\\Flagged)'); - $self->make_message("Test Message 4"); - - my %handlers; - { - my $sawfetch = -1; - my $sawmsg2fetch = -1; - my $sawmsg3fetch = -1; - my $sawmsg4fetch = -1; - use Data::Dumper; - $handlers{fetch} = sub { - $sawfetch = 1 if $sawfetch < 0; - - if ($_[2] == 2) { $sawmsg2fetch = $_[2] } - elsif ($_[2] == 3) { - $sawmsg3fetch = $_[2]; - die "Got FETCH for 3 before 2" if $sawmsg2fetch < 0; - } - elsif ($_[2] == 3) { - $sawmsg4fetch = $_[2]; - die "Got FETCH for 4 before 2" if $sawmsg2fetch < 0; - die "Got FETCH for 4 before 3" if $sawmsg3fetch < 0; - } - else { } - }; - $handlers{exists} = sub { die "Got EXISTS after FETCH for $_[2]" if $sawfetch > 0; }; - } - - $admintalk->_imap_cmd("fetch", 1, \%handlers, \'3:*', '(uid flags)'); + : min_version_3_0 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->select('user.cassandane'); + + $self->make_message("Test Message 1"); + $admintalk->fetch('1:*', '(flags)'); + + $self->make_message("Test Message 2"); + $admintalk->fetch('2:*', '(flags)'); + + $self->make_message("Test Message 3"); + $admintalk->fetch('3:*', '(uid flags)'); + + $admintalk->unselect(); + $admintalk->select('user.cassandane'); + + $imaptalk->store('2', '+flags', '(\\Flagged)'); + $self->make_message("Test Message 4"); + + my %handlers; + { + my $sawfetch = -1; + my $sawmsg2fetch = -1; + my $sawmsg3fetch = -1; + my $sawmsg4fetch = -1; + use Data::Dumper; + $handlers{fetch} = sub { + $sawfetch = 1 if $sawfetch < 0; + + if ($_[2] == 2) { $sawmsg2fetch = $_[2] } + elsif ($_[2] == 3) { + $sawmsg3fetch = $_[2]; + die "Got FETCH for 3 before 2" if $sawmsg2fetch < 0; + } elsif ($_[2] == 3) { + $sawmsg4fetch = $_[2]; + die "Got FETCH for 4 before 2" if $sawmsg2fetch < 0; + die "Got FETCH for 4 before 3" if $sawmsg3fetch < 0; + } else { + } + }; + $handlers{exists} + = sub { die "Got EXISTS after FETCH for $_[2]" if $sawfetch > 0; }; + } + + $admintalk->_imap_cmd("fetch", 1, \%handlers, \'3:*', '(uid flags)'); } sub test_mailboxids - :min_version_3_1 :Conversations -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - my $res = $imaptalk->status('INBOX', ['mailboxid']); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - my $inbox_id = $res->{'mailboxid'}->[0]; - $self->assert_not_null($inbox_id); - - $imaptalk->create("INBOX.target"); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->status('INBOX.target', ['mailboxid']); - my $target_id = $res->{'mailboxid'}->[0]; - $self->assert_not_null($target_id); - - # make a message - my $msg = $self->make_message("test message"); - my $uid = $msg->{attrs}->{uid}; - - # expect to find it in INBOX only - $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals([$inbox_id], $res->{1}->{'mailboxids'}); - $self->assert_deep_equals(['INBOX'], $res->{1}->{'mailboxes'}); - - # copy it to INBOX.target - $imaptalk->copy($uid, "INBOX.target"); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - # expect to find it in INBOX and INBOX.target - $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals([$inbox_id, $target_id], - $res->{1}->{'mailboxids'}); - $self->assert_deep_equals(['INBOX', 'INBOX.target'], - $res->{1}->{'mailboxes'}); - - # delete it from INBOX - $imaptalk->store('1', '+FLAGS', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - # expect to find it in INBOX.target only - $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals([$target_id], - $res->{1}->{'mailboxids'}); - $self->assert_deep_equals(['INBOX.target'], - $res->{1}->{'mailboxes'}); - - # expunge INBOX - $imaptalk->expunge(); - - # expect to find it in INBOX.target only - $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $imaptalk->select('INBOX.target'); - $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals([$target_id], - $res->{1}->{'mailboxids'}); - $self->assert_deep_equals(['INBOX.target'], - $res->{1}->{'mailboxes'}); + : min_version_3_1 : Conversations { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + my $res = $imaptalk->status('INBOX', ['mailboxid']); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + my $inbox_id = $res->{'mailboxid'}->[0]; + $self->assert_not_null($inbox_id); + + $imaptalk->create("INBOX.target"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $res = $imaptalk->status('INBOX.target', ['mailboxid']); + my $target_id = $res->{'mailboxid'}->[0]; + $self->assert_not_null($target_id); + + # make a message + my $msg = $self->make_message("test message"); + my $uid = $msg->{attrs}->{uid}; + + # expect to find it in INBOX only + $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals([$inbox_id], $res->{1}->{'mailboxids'}); + $self->assert_deep_equals(['INBOX'], $res->{1}->{'mailboxes'}); + + # copy it to INBOX.target + $imaptalk->copy($uid, "INBOX.target"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + # expect to find it in INBOX and INBOX.target + $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals([ $inbox_id, $target_id ], + $res->{1}->{'mailboxids'}); + $self->assert_deep_equals([ 'INBOX', 'INBOX.target' ], + $res->{1}->{'mailboxes'}); + + # delete it from INBOX + $imaptalk->store('1', '+FLAGS', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + # expect to find it in INBOX.target only + $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals([$target_id], $res->{1}->{'mailboxids'}); + $self->assert_deep_equals(['INBOX.target'], $res->{1}->{'mailboxes'}); + + # expunge INBOX + $imaptalk->expunge(); + + # expect to find it in INBOX.target only + $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $imaptalk->select('INBOX.target'); + $res = $imaptalk->fetch('1', '(MAILBOXES MAILBOXIDS)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals([$target_id], $res->{1}->{'mailboxids'}); + $self->assert_deep_equals(['INBOX.target'], $res->{1}->{'mailboxes'}); } sub test_mailboxids_noconversations - :min_version_3_1 -{ - my ($self) = @_; + : min_version_3_1 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # make a message - my $msg = $self->make_message("test message"); - my $uid = $msg->{attrs}->{uid}; + # make a message + my $msg = $self->make_message("test message"); + my $uid = $msg->{attrs}->{uid}; - # expect FETCH MAILBOXES to be rejected - my $res = $imaptalk->fetch('1', '(MAILBOXES)'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # expect FETCH MAILBOXES to be rejected + my $res = $imaptalk->fetch('1', '(MAILBOXES)'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # expect FETCH MAILBOXIDS to be rejected - $res = $imaptalk->fetch('1', '(MAILBOXIDS)'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # expect FETCH MAILBOXIDS to be rejected + $res = $imaptalk->fetch('1', '(MAILBOXIDS)'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); } # test for older draft preview behaviour, obsoleted by publication of # RFC 8970 sub test_preview_args_legacy - :min_version_3_1 :max_version_3_4 :Conversations -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : Conversations { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # make a message - my $msg = $self->make_message("test message"); - my $uid = $msg->{attrs}->{uid}; + # make a message + my $msg = $self->make_message("test message"); + my $uid = $msg->{attrs}->{uid}; - my $res; + my $res; - # expect no name to be accepted - $res = $imaptalk->fetch('1', '(PREVIEW)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + # expect no name to be accepted + $res = $imaptalk->fetch('1', '(PREVIEW)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - # expect no name to be accepted - $res = $imaptalk->fetch('1', '(PREVIEW ())'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + # expect no name to be accepted + $res = $imaptalk->fetch('1', '(PREVIEW ())'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - # expect bad name to be rejected - $res = $imaptalk->fetch('1', '(PREVIEW (FUZZY=BUZZY))'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # expect bad name to be rejected + $res = $imaptalk->fetch('1', '(PREVIEW (FUZZY=BUZZY))'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # expect fuzzy name to be accepted - $res = $imaptalk->fetch('1', '(PREVIEW (FUZZY))'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + # expect fuzzy name to be accepted + $res = $imaptalk->fetch('1', '(PREVIEW (FUZZY))'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - # expect lazy fuzzy name to be accepted - $res = $imaptalk->fetch('1', '(PREVIEW (LAZY=FUZZY))'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + # expect lazy fuzzy name to be accepted + $res = $imaptalk->fetch('1', '(PREVIEW (LAZY=FUZZY))'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); } sub test_preview_args - :min_version_3_5 :Conversations -{ - my ($self) = @_; + : min_version_3_5 : Conversations { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # make a message - my $msg = $self->make_message("test message"); - my $uid = $msg->{attrs}->{uid}; + # make a message + my $msg = $self->make_message("test message"); + my $uid = $msg->{attrs}->{uid}; - my $res; + my $res; - # expect no modifier to be accepted - $res = $imaptalk->fetch('1', '(PREVIEW)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + # expect no modifier to be accepted + $res = $imaptalk->fetch('1', '(PREVIEW)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - # expect empty modifier list to be rejected - $res = $imaptalk->fetch('1', '(PREVIEW ())'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # expect empty modifier list to be rejected + $res = $imaptalk->fetch('1', '(PREVIEW ())'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # expect bad modifier name to be rejected - $res = $imaptalk->fetch('1', '(PREVIEW (FOO))'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # expect bad modifier name to be rejected + $res = $imaptalk->fetch('1', '(PREVIEW (FOO))'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # expect lazy modifier to be accepted - $res = $imaptalk->fetch('1', '(PREVIEW (LAZY))'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + # expect lazy modifier to be accepted + $res = $imaptalk->fetch('1', '(PREVIEW (LAZY))'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - # expect lazy + bad modifier to be rejected - $res = $imaptalk->fetch('1', '(PREVIEW (LAZY FOO))'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # expect lazy + bad modifier to be rejected + $res = $imaptalk->fetch('1', '(PREVIEW (LAZY FOO))'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); } -sub test_unknown_cte -{ - my ($self) = @_; +sub test_unknown_cte { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $ct = "multipart/mixed"; - my $boundary = "mitomycin"; + my $ct = "multipart/mixed"; + my $boundary = "mitomycin"; - # Subpart 1 - my $body = "" + # Subpart 1 + my $body + = "" . "--$boundary\r\n" - . "Content-Type: text/plain; charset=\"utf8\"\r\n" - . "\r\n" + . "Content-Type: text/plain; charset=\"utf8\"\r\n" . "\r\n" . "The attachment to this email is meaningless gibberish\r\n"; - # Subpart 2 - $body .= "" + # Subpart 2 + $body + .= "" . "--$boundary\r\n" . "Content-Disposition: attachment; filename=\"gibberish.dat\"\r\n" . "Content-Type: application/octet-stream; name=\"gibberish.dat\"\r\n" - . "Content-Transfer-Encoding: x-no-such-encoding\r\n" - . "\r\n" + . "Content-Transfer-Encoding: x-no-such-encoding\r\n" . "\r\n" . "There'll never be a decoder for this encoding, so this\r\n" . "text can't possibly ever decode to something coherent.\r\n" . "--$boundary--\r\n"; - my $msg = $self->make_message("foo", - mime_type => $ct, - mime_boundary => $boundary, - body => $body - ); - my $uid = $msg->{attrs}->{uid}; - my $res; - - # fetch BINARY.PEEK should fail - $res = $imaptalk->fetch('1', '(BINARY.PEEK[2])'); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error()); - - # fetch BINARY.SIZE should fail - $res = $imaptalk->fetch('1', '(BINARY.SIZE[2])'); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error()); - - # enable UID mode... - $imaptalk->uid(1); - - # UID fetch BINARY.PEEK should fail - $res = $imaptalk->fetch($uid, '(BINARY.PEEK[2])'); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error()); - - # UID fetch BINARY.SIZE should fail - $res = $imaptalk->fetch($uid, '(BINARY.SIZE[2])'); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error()); + my $msg = $self->make_message( + "foo", + mime_type => $ct, + mime_boundary => $boundary, + body => $body + ); + my $uid = $msg->{attrs}->{uid}; + my $res; + + # fetch BINARY.PEEK should fail + $res = $imaptalk->fetch('1', '(BINARY.PEEK[2])'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error()); + + # fetch BINARY.SIZE should fail + $res = $imaptalk->fetch('1', '(BINARY.SIZE[2])'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error()); + + # enable UID mode... + $imaptalk->uid(1); + + # UID fetch BINARY.PEEK should fail + $res = $imaptalk->fetch($uid, '(BINARY.PEEK[2])'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error()); + + # UID fetch BINARY.SIZE should fail + $res = $imaptalk->fetch($uid, '(BINARY.SIZE[2])'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error()); } -sub test_partial -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - xlog $self, "append some messages"; - my %exp; - my $N = 10; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); - - # expunge the 1st and 6th - $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->expunge(); - - # fetch all - my $res = $imaptalk->fetch('1:*', '(UID)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals($res->{'1'}->{uid}, "2"); - $self->assert_str_equals($res->{'2'}->{uid}, "3"); - $self->assert_str_equals($res->{'3'}->{uid}, "4"); - $self->assert_str_equals($res->{'4'}->{uid}, "5"); - $self->assert_str_equals($res->{'5'}->{uid}, "7"); - $self->assert_str_equals($res->{'6'}->{uid}, "8"); - $self->assert_str_equals($res->{'7'}->{uid}, "9"); - $self->assert_str_equals($res->{'8'}->{uid}, "10"); - - # fetch first 2 - $res = $imaptalk->fetch('1:*', '(UID) (PARTIAL 1:2)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals($res->{'1'}->{uid}, "2"); - $self->assert_str_equals($res->{'2'}->{uid}, "3"); - - # fetch next 2 - $res = $imaptalk->fetch('1:*', '(UID) (PARTIAL 3:4)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals($res->{'3'}->{uid}, "4"); - $self->assert_str_equals($res->{'4'}->{uid}, "5"); - - # fetch last 2 - $res = $imaptalk->fetch('1:*', '(UID) (PARTIAL -1:-2)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals($res->{'8'}->{uid}, "10"); - $self->assert_str_equals($res->{'7'}->{uid}, "9"); - - # fetch the previous 2 - $res = $imaptalk->fetch('1:*', '(UID) (PARTIAL -3:-4)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals($res->{'6'}->{uid}, "8"); - $self->assert_str_equals($res->{'5'}->{uid}, "7"); - - # enable UID mode... - $imaptalk->uid(1); - - # fetch the middle 2 by UID - $res = $imaptalk->fetch('4:8', '(UID) (PARTIAL 2:3)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals($res->{'5'}->{uid}, "5"); - $self->assert_str_equals($res->{'7'}->{uid}, "7"); +sub test_partial { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + xlog $self, "append some messages"; + my %exp; + my $N = 10; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); + + # expunge the 1st and 6th + $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->expunge(); + + # fetch all + my $res = $imaptalk->fetch('1:*', '(UID)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals($res->{'1'}->{uid}, "2"); + $self->assert_str_equals($res->{'2'}->{uid}, "3"); + $self->assert_str_equals($res->{'3'}->{uid}, "4"); + $self->assert_str_equals($res->{'4'}->{uid}, "5"); + $self->assert_str_equals($res->{'5'}->{uid}, "7"); + $self->assert_str_equals($res->{'6'}->{uid}, "8"); + $self->assert_str_equals($res->{'7'}->{uid}, "9"); + $self->assert_str_equals($res->{'8'}->{uid}, "10"); + + # fetch first 2 + $res = $imaptalk->fetch('1:*', '(UID) (PARTIAL 1:2)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals($res->{'1'}->{uid}, "2"); + $self->assert_str_equals($res->{'2'}->{uid}, "3"); + + # fetch next 2 + $res = $imaptalk->fetch('1:*', '(UID) (PARTIAL 3:4)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals($res->{'3'}->{uid}, "4"); + $self->assert_str_equals($res->{'4'}->{uid}, "5"); + + # fetch last 2 + $res = $imaptalk->fetch('1:*', '(UID) (PARTIAL -1:-2)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals($res->{'8'}->{uid}, "10"); + $self->assert_str_equals($res->{'7'}->{uid}, "9"); + + # fetch the previous 2 + $res = $imaptalk->fetch('1:*', '(UID) (PARTIAL -3:-4)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals($res->{'6'}->{uid}, "8"); + $self->assert_str_equals($res->{'5'}->{uid}, "7"); + + # enable UID mode... + $imaptalk->uid(1); + + # fetch the middle 2 by UID + $res = $imaptalk->fetch('4:8', '(UID) (PARTIAL 2:3)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals($res->{'5'}->{uid}, "5"); + $self->assert_str_equals($res->{'7'}->{uid}, "7"); } 1; diff --git a/cassandane/Cassandane/Cyrus/Flags.pm b/cassandane/Cassandane/Cyrus/Flags.pm index 3a7b60f6ed..ffb40fcf6a 100644 --- a/cassandane/Cassandane/Cyrus/Flags.pm +++ b/cassandane/Cassandane/Cyrus/Flags.pm @@ -49,22 +49,19 @@ use Cassandane::Util::Log; use Cassandane::Util::Words; use JSON; -sub new -{ - my $class = shift; - return $class->SUPER::new({adminstore => 1}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # @@ -76,45 +73,50 @@ sub tear_down # - message numbers remain contiguous after the expunge # even when UIDs aren't contiguous anymore # -sub test_deleted -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Append 3 messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $msg{C} = $self->make_message('Message C'); - $msg{C}->set_attributes(id => 3, - uid => 3, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Mark the middle message \\Deleted"; - my $res = $talk->store('2', '+flags', '(\\Deleted)'); - $self->assert_deep_equals({ '2' => { 'flags' => [ '\\Deleted' ] }}, $res); - $msg{B}->set_attribute(flags => ['\\Deleted']); - $self->check_messages(\%msg); - - xlog $self, "Expunge the middle message"; - $talk->expunge(); - delete $msg{B}; - $msg{A}->set_attribute(id => 1); - $msg{C}->set_attribute(id => 2); - $self->check_messages(\%msg); -# -# $talk->store($seq', '+flags', '(\\flagged)') or die $@; +sub test_deleted { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Append 3 messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $msg{C} = $self->make_message('Message C'); + $msg{C}->set_attributes( + id => 3, + uid => 3, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Mark the middle message \\Deleted"; + my $res = $talk->store('2', '+flags', '(\\Deleted)'); + $self->assert_deep_equals({ '2' => { 'flags' => ['\\Deleted'] } }, $res); + $msg{B}->set_attribute(flags => ['\\Deleted']); + $self->check_messages(\%msg); + + xlog $self, "Expunge the middle message"; + $talk->expunge(); + delete $msg{B}; + $msg{A}->set_attribute(id => 1); + $msg{C}->set_attribute(id => 2); + $self->check_messages(\%msg); + # + # $talk->store($seq', '+flags', '(\\flagged)') or die $@; } # @@ -130,50 +132,53 @@ sub test_deleted # TODO: test that \Seen gets set as a side effect of # doing body fetches. # -sub test_seen -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \\Seen on message A"; - my $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Seen on message A"; - $res = $talk->store('1', '-flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \\Seen on message A again"; - $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Reconnect, \\Seen should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $self->check_messages(\%msg); +sub test_seen { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Set \\Seen on message A"; + my $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Seen on message A"; + $res = $talk->store('1', '-flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $msg{A}->set_attribute(flags => []); + $self->check_messages(\%msg); + + xlog $self, "Set \\Seen on message A again"; + $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Reconnect, \\Seen should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } # @@ -189,196 +194,201 @@ sub test_seen # TODO: test that \Seen gets set as a side effect of # doing body fetches. # -sub test_seen_otheruser -{ - my ($self) = @_; - - # no particular reason to use an admin rather than just another user, - # but it's easy - my $admintalk = $self->{adminstore}->get_client(); - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - $self->{adminstore}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - # select AFTER creating messages so we don't get \Recent - $admintalk->select('user.cassandane'); - $admintalk->unselect(); - $admintalk->select('user.cassandane'); - - xlog $self, "Set \\Seen on message A"; - my $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $self->check_messages(\%msg, store => $self->{adminstore}); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Set \\Seen on message A as admin"; - $res = $admintalk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $self->check_messages(\%msg, store => $self->{adminstore}); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Seen on message A"; - $res = $talk->store('1', '-flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $self->check_messages(\%msg, store => $self->{adminstore}); - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Seen on message A as admin"; - $res = $admintalk->store('1', '-flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $self->check_messages(\%msg, store => $self->{adminstore}); - $self->check_messages(\%msg); +sub test_seen_otheruser { + my ($self) = @_; + + # no particular reason to use an admin rather than just another user, + # but it's easy + my $admintalk = $self->{adminstore}->get_client(); + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + $self->{adminstore}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + # select AFTER creating messages so we don't get \Recent + $admintalk->select('user.cassandane'); + $admintalk->unselect(); + $admintalk->select('user.cassandane'); + + xlog $self, "Set \\Seen on message A"; + my $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $self->check_messages(\%msg, store => $self->{adminstore}); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Set \\Seen on message A as admin"; + $res = $admintalk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $self->check_messages(\%msg, store => $self->{adminstore}); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Seen on message A"; + $res = $talk->store('1', '-flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $self->check_messages(\%msg, store => $self->{adminstore}); + $msg{A}->set_attribute(flags => []); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Seen on message A as admin"; + $res = $admintalk->store('1', '-flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $self->check_messages(\%msg, store => $self->{adminstore}); + $self->check_messages(\%msg); } # https://github.com/cyrusimap/cyrus-imapd/issues/3240 sub test_seen_sharedmb_nosharedseen - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; - - my $folder = 'shared'; - - # shared mailbox with sharedseen=false - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create($folder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl($folder, 'cassandane' => 'lrswipkxtecdan'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setmetadata($folder, - '/shared/vendor/cmu/cyrus-imapd/sharedseen' => 'false' - ); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - - # add some messages - my $talk = $self->{store}->get_client(); - $self->{store}->set_folder("Shared Folders/$folder"); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - # fiddle with seen flag, making sure we get both the expected results - # and the expected untagged fetch response - xlog $self, "Set \\Seen on message A"; - my $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Seen on message A"; - $res = $talk->store('1', '-flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \\Seen on message A again"; - $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - # seen flag should survive a reconnect - xlog $self, "Reconnect, \\Seen should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $self->check_messages(\%msg); + : UnixHierarchySep : AltNamespace { + my ($self) = @_; + + my $folder = 'shared'; + + # shared mailbox with sharedseen=false + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create($folder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl($folder, 'cassandane' => 'lrswipkxtecdan'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setmetadata($folder, + '/shared/vendor/cmu/cyrus-imapd/sharedseen' => 'false'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # add some messages + my $talk = $self->{store}->get_client(); + $self->{store}->set_folder("Shared Folders/$folder"); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + # fiddle with seen flag, making sure we get both the expected results + # and the expected untagged fetch response + xlog $self, "Set \\Seen on message A"; + my $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Seen on message A"; + $res = $talk->store('1', '-flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $msg{A}->set_attribute(flags => []); + $self->check_messages(\%msg); + + xlog $self, "Set \\Seen on message A again"; + $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + # seen flag should survive a reconnect + xlog $self, "Reconnect, \\Seen should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } # https://github.com/cyrusimap/cyrus-imapd/issues/4611 sub test_seen_sharedmb_sharedseen - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; - - my $folder = 'shared'; - - # shared mailbox with sharedseen=false - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create($folder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl($folder, 'cassandane' => 'lrswipkxtecdan'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setmetadata($folder, - '/shared/vendor/cmu/cyrus-imapd/sharedseen' => 'true' - ); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - - # add some messages - my $talk = $self->{store}->get_client(); - $self->{store}->set_folder("Shared Folders/$folder"); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - # fiddle with seen flag, making sure we get both the expected results - # and the expected untagged fetch response - xlog $self, "Set \\Seen on message A"; - my $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Seen on message A"; - $res = $talk->store('1', '-flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \\Seen on message A again"; - $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - # seen flag should survive a reconnect - xlog $self, "Reconnect, \\Seen should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $self->check_messages(\%msg); + : UnixHierarchySep : AltNamespace { + my ($self) = @_; + + my $folder = 'shared'; + + # shared mailbox with sharedseen=false + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create($folder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl($folder, 'cassandane' => 'lrswipkxtecdan'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setmetadata($folder, + '/shared/vendor/cmu/cyrus-imapd/sharedseen' => 'true'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # add some messages + my $talk = $self->{store}->get_client(); + $self->{store}->set_folder("Shared Folders/$folder"); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + # fiddle with seen flag, making sure we get both the expected results + # and the expected untagged fetch response + xlog $self, "Set \\Seen on message A"; + my $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Seen on message A"; + $res = $talk->store('1', '-flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $msg{A}->set_attribute(flags => []); + $self->check_messages(\%msg); + + xlog $self, "Set \\Seen on message A again"; + $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + # seen flag should survive a reconnect + xlog $self, "Reconnect, \\Seen should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } # @@ -388,50 +398,53 @@ sub test_seen_sharedmb_sharedseen # - other messages don't get the \Flagged flag # - once set, it's persistent across sessions # -sub test_flagged -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \\Flagged on message A"; - my $res = $talk->store('1', '+flags', '(\\Flagged)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Flagged' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Flagged']); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Flagged on message A"; - $res = $talk->store('1', '-flags', '(\\Flagged)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \\Flagged on message A again"; - $res = $talk->store('1', '+flags', '(\\Flagged)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Flagged' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Flagged']); - $self->check_messages(\%msg); - - xlog $self, "Reconnect, \\Flagged should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $self->check_messages(\%msg); +sub test_flagged { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Set \\Flagged on message A"; + my $res = $talk->store('1', '+flags', '(\\Flagged)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Flagged'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Flagged']); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Flagged on message A"; + $res = $talk->store('1', '-flags', '(\\Flagged)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $msg{A}->set_attribute(flags => []); + $self->check_messages(\%msg); + + xlog $self, "Set \\Flagged on message A again"; + $res = $talk->store('1', '+flags', '(\\Flagged)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Flagged'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Flagged']); + $self->check_messages(\%msg); + + xlog $self, "Reconnect, \\Flagged should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } # @@ -444,50 +457,53 @@ sub test_flagged # This is basically the same test as for \Flagged but with a user flag, # which is an entirely different code path in the server. # -sub test_userflag -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \$Foobar on message A"; - my $res = $talk->store('1', '+flags', '($Foobar)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '$Foobar' ] }}, $res); - $msg{A}->set_attribute(flags => ['$Foobar']); - $self->check_messages(\%msg); - - xlog $self, "Clear \$Foobar on message A"; - $res = $talk->store('1', '-flags', '($Foobar)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \$Foobar on message A again"; - $res = $talk->store('1', '+flags', '($Foobar)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '$Foobar' ] }}, $res); - $msg{A}->set_attribute(flags => ['$Foobar']); - $self->check_messages(\%msg); - - xlog $self, "Reconnect, \$Foobar should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $self->check_messages(\%msg); +sub test_userflag { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Set \$Foobar on message A"; + my $res = $talk->store('1', '+flags', '($Foobar)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['$Foobar'] } }, $res); + $msg{A}->set_attribute(flags => ['$Foobar']); + $self->check_messages(\%msg); + + xlog $self, "Clear \$Foobar on message A"; + $res = $talk->store('1', '-flags', '($Foobar)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $msg{A}->set_attribute(flags => []); + $self->check_messages(\%msg); + + xlog $self, "Set \$Foobar on message A again"; + $res = $talk->store('1', '+flags', '($Foobar)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['$Foobar'] } }, $res); + $msg{A}->set_attribute(flags => ['$Foobar']); + $self->check_messages(\%msg); + + xlog $self, "Reconnect, \$Foobar should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } # @@ -497,67 +513,70 @@ sub test_userflag # - cyr_expire -t can remove the $Foobar flag from the mailbox permanentflags # # -sub test_expunge_removeflag -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - my $perm = $talk->get_response_code('permanentflags'); - my @flags = grep { !m{^\\} } @$perm; - $self->assert_deep_equals([], \@flags); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \$Foobar on message A"; - my $res = $talk->store('1', '+flags', '($Foobar)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '$Foobar' ] }}, $res); - $msg{A}->set_attribute(flags => ['$Foobar']); - $self->check_messages(\%msg); - - xlog $self, "Clear \$Foobar on message A"; - $res = $talk->store('1', '-flags', '($Foobar)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); - - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $talk = $self->{store}->get_client(); - - $self->check_messages(\%msg); - - xlog $self, "Flag is still in the mailbox"; - - $perm = $talk->get_response_code('permanentflags'); - @flags = grep { !m{^\\} } @$perm; - $self->assert_deep_equals(['$Foobar'], \@flags); - - $self->{store}->disconnect(); - - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-t'); - - $self->{store}->connect(); - $self->{store}->_select(); - $talk = $self->{store}->get_client(); - - $perm = $talk->get_response_code('permanentflags'); - @flags = grep { !m{^\\} } @$perm; - $self->assert_deep_equals([], \@flags); +sub test_expunge_removeflag { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + my $perm = $talk->get_response_code('permanentflags'); + my @flags = grep { !m{^\\} } @$perm; + $self->assert_deep_equals([], \@flags); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Set \$Foobar on message A"; + my $res = $talk->store('1', '+flags', '($Foobar)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['$Foobar'] } }, $res); + $msg{A}->set_attribute(flags => ['$Foobar']); + $self->check_messages(\%msg); + + xlog $self, "Clear \$Foobar on message A"; + $res = $talk->store('1', '-flags', '($Foobar)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $msg{A}->set_attribute(flags => []); + $self->check_messages(\%msg); + + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $talk = $self->{store}->get_client(); + + $self->check_messages(\%msg); + + xlog $self, "Flag is still in the mailbox"; + + $perm = $talk->get_response_code('permanentflags'); + @flags = grep { !m{^\\} } @$perm; + $self->assert_deep_equals(['$Foobar'], \@flags); + + $self->{store}->disconnect(); + + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-t'); + + $self->{store}->connect(); + $self->{store}->_select(); + $talk = $self->{store}->get_client(); + + $perm = $talk->get_response_code('permanentflags'); + @flags = grep { !m{^\\} } @$perm; + $self->assert_deep_equals([], \@flags); } # @@ -568,82 +587,80 @@ sub test_expunge_removeflag # - one-extra problems) # use constant MAX_USER_FLAGS => 100; -sub test_max_userflags -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - my %allflags; - for (my $i = 0 ; $i < MAX_USER_FLAGS ; $i++) - { - my $flag; - - for (;;) - { - $flag = '$' . ucfirst(random_word()); - if (!defined $allflags{$flag}) - { - $allflags{$flag} = $i; - last; - } - } - - xlog $self, "Set $flag on message A"; - my $res = $talk->store('1', '+flags', "($flag)"); - $self->assert_deep_equals({ '1' => { 'flags' => [ "$flag" ] }}, - $res); - $msg{A}->set_attribute(flags => [$flag]); - $self->check_messages(\%msg); - - xlog $self, "Clear $flag on message A"; - $res = $talk->store('1', '-flags', "($flag)"); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); +sub test_max_userflags { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + my %allflags; + for (my $i = 0; $i < MAX_USER_FLAGS; $i++) { + my $flag; + + for (;;) { + $flag = '$' . ucfirst(random_word()); + if (!defined $allflags{$flag}) { + $allflags{$flag} = $i; + last; + } } - xlog $self, "Cannot set one more wafer-thin user flag"; - my $flag = '$Farnarkle'; - $self->assert_null($allflags{$flag}); + xlog $self, "Set $flag on message A"; my $res = $talk->store('1', '+flags', "($flag)"); - my $e = $@; - $self->assert_null($res); - $self->assert_matches(qr/Too many user flags in mailbox/, $e); - - # We should have generated an IOERROR - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: out of flags/); - - xlog $self, "Can set all the flags at once"; - my @flags = sort { $allflags{$a} <=> $allflags{$b} } (keys %allflags); - xlog $self, "Set all the user flags on message A"; - $res = $talk->store('1', '+flags', '(' . join(' ',@flags) . ')'); - $self->assert_deep_equals({ '1' => { 'flags' => [ @flags ] }}, - $res); - $msg{A}->set_attribute(flags => [@flags]); + $self->assert_deep_equals({ '1' => { 'flags' => ["$flag"] } }, $res); + $msg{A}->set_attribute(flags => [$flag]); $self->check_messages(\%msg); - xlog $self, "Reconnect, all the flags should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); + xlog $self, "Clear $flag on message A"; + $res = $talk->store('1', '-flags', "($flag)"); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $msg{A}->set_attribute(flags => []); $self->check_messages(\%msg); + } + + xlog $self, "Cannot set one more wafer-thin user flag"; + my $flag = '$Farnarkle'; + $self->assert_null($allflags{$flag}); + my $res = $talk->store('1', '+flags', "($flag)"); + my $e = $@; + $self->assert_null($res); + $self->assert_matches(qr/Too many user flags in mailbox/, $e); + + # We should have generated an IOERROR + $self->assert_syslog_matches($self->{instance}, qr/IOERROR: out of flags/); + + xlog $self, "Can set all the flags at once"; + my @flags = sort { $allflags{$a} <=> $allflags{$b} } (keys %allflags); + xlog $self, "Set all the user flags on message A"; + $res = $talk->store('1', '+flags', '(' . join(' ', @flags) . ')'); + $self->assert_deep_equals({ '1' => { 'flags' => [@flags] } }, $res); + $msg{A}->set_attribute(flags => [@flags]); + $self->check_messages(\%msg); + + xlog $self, "Reconnect, all the flags should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } # @@ -651,38 +668,35 @@ sub test_max_userflags # - more than 32 flags can be searched for # - no more can be used # -sub test_search_allflags -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add messages and flags"; - - my %msg; - for (my $i = 1 ; $i <= MAX_USER_FLAGS ; $i++) - { - my $flag = "flag$i"; - $msg{$i} = $self->make_message("Message $i"); - xlog $self, "Set $flag on message $i"; - my $res = $talk->store($i, '+flags', "($flag)"); - $self->assert_deep_equals({ "$i" => { 'flags' => [ - '\\Recent', $flag - ]}}, $res); - } - - # for debugging - $talk->fetch('1:*', '(uid flags)'); - - for (my $i = 1 ; $i <= MAX_USER_FLAGS ; $i++) { - xlog $self, "Can search for flag $i"; - my $uids = $talk->search("keyword", "flag$i"); - $self->assert_equals(1, scalar(@$uids)); - $self->assert_equals($i, $uids->[0]); - } +sub test_search_allflags { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add messages and flags"; + + my %msg; + for (my $i = 1; $i <= MAX_USER_FLAGS; $i++) { + my $flag = "flag$i"; + $msg{$i} = $self->make_message("Message $i"); + xlog $self, "Set $flag on message $i"; + my $res = $talk->store($i, '+flags', "($flag)"); + $self->assert_deep_equals({ "$i" => { 'flags' => [ '\\Recent', $flag ] } }, + $res); + } + + # for debugging + $talk->fetch('1:*', '(uid flags)'); + + for (my $i = 1; $i <= MAX_USER_FLAGS; $i++) { + xlog $self, "Can search for flag $i"; + my $uids = $talk->search("keyword", "flag$i"); + $self->assert_equals(1, scalar(@$uids)); + $self->assert_equals($i, $uids->[0]); + } } # @@ -692,96 +706,103 @@ sub test_search_allflags # - other messages aren't affected by those changes # - flags are persistent across sessions # -sub test_multi_flags -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set many flags on message A"; - my $res = $talk->store('1', '+flags', - '(\\Answered \\Flagged \\Draft \\Deleted \\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ - qw(\\Answered \\Flagged \\Draft - \\Deleted \\Seen) - ]}}, $res); - $msg{A}->set_attribute(flags => [qw(\\Answered \\Flagged \\Draft \\Deleted \\Seen)]); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Flagged on message A"; - $res = $talk->store('1', '-flags', '(\\Flagged)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ - qw(\\Answered \\Draft \\Deleted \\Seen) - ]}}, $res); - $msg{A}->set_attribute(flags => [qw(\\Answered \\Draft \\Deleted \\Seen)]); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Draft and \\Deleted on message A"; - $res = $talk->store('1', '-flags', '(\\Draft \\Deleted)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ - qw(\\Answered \\Seen) - ]}}, $res); - $msg{A}->set_attribute(flags => [qw(\\Answered \\Seen)]); - $self->check_messages(\%msg); - - xlog $self, "Set \\Draft and \\Flagged on message A"; - $res = $talk->store('1', '+flags', '(\\Draft \\Flagged)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ - qw(\\Answered \\Flagged \\Draft \\Seen) - ]}}, $res); - $msg{A}->set_attribute(flags => [qw(\\Answered \\Flagged \\Draft \\Seen)]); - $self->check_messages(\%msg); - - xlog $self, "Set to just \\Answered and \\Seen on message A"; - $res = $talk->store('1', 'flags', '(\\Answered \\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ - qw(\\Answered \\Seen) - ]}}, $res); - $msg{A}->set_attribute(flags => [qw(\\Answered \\Seen)]); - $self->check_messages(\%msg); - - xlog $self, "Walk through every combination of flags"; - my %rev_map = ( - 1 => '\\Answered', - 2 => '\\Flagged', - 4 => '\\Draft', - 8 => '\\Deleted', - 16 => '\\Seen' ); - my $max = (2 ** scalar keys %rev_map) - 1; - for (my $i = 0 ; $i <= $max ; $i++) +sub test_multi_flags { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Set many flags on message A"; + my $res = $talk->store('1', '+flags', + '(\\Answered \\Flagged \\Draft \\Deleted \\Seen)'); + $self->assert_deep_equals( { - my @flags; - for (my $m = 1 ; defined($rev_map{$m}) ; $m *= 2) - { - push(@flags, $rev_map{$m}) if ($i & $m); - } - xlog $self, "Setting " . join(',',@flags) . " on message A"; - my $res = $talk->store('1', 'flags', '(' . join(' ',@flags) . ')'); - $self->assert_deep_equals({ '1' => { 'flags' => \@flags }}, $res); - $msg{A}->set_attribute(flags => \@flags); - $self->check_messages(\%msg); + '1' => { + 'flags' => [ + qw(\\Answered \\Flagged \\Draft + \\Deleted \\Seen) + ] + } + }, + $res + ); + $msg{A}->set_attribute( + flags => [qw(\\Answered \\Flagged \\Draft \\Deleted \\Seen)]); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Flagged on message A"; + $res = $talk->store('1', '-flags', '(\\Flagged)'); + $self->assert_deep_equals( + { '1' => { 'flags' => [qw(\\Answered \\Draft \\Deleted \\Seen)] } }, $res); + $msg{A}->set_attribute(flags => [qw(\\Answered \\Draft \\Deleted \\Seen)]); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Draft and \\Deleted on message A"; + $res = $talk->store('1', '-flags', '(\\Draft \\Deleted)'); + $self->assert_deep_equals({ '1' => { 'flags' => [qw(\\Answered \\Seen)] } }, + $res); + $msg{A}->set_attribute(flags => [qw(\\Answered \\Seen)]); + $self->check_messages(\%msg); + + xlog $self, "Set \\Draft and \\Flagged on message A"; + $res = $talk->store('1', '+flags', '(\\Draft \\Flagged)'); + $self->assert_deep_equals( + { '1' => { 'flags' => [qw(\\Answered \\Flagged \\Draft \\Seen)] } }, $res); + $msg{A}->set_attribute(flags => [qw(\\Answered \\Flagged \\Draft \\Seen)]); + $self->check_messages(\%msg); + + xlog $self, "Set to just \\Answered and \\Seen on message A"; + $res = $talk->store('1', 'flags', '(\\Answered \\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => [qw(\\Answered \\Seen)] } }, + $res); + $msg{A}->set_attribute(flags => [qw(\\Answered \\Seen)]); + $self->check_messages(\%msg); + + xlog $self, "Walk through every combination of flags"; + my %rev_map = ( + 1 => '\\Answered', + 2 => '\\Flagged', + 4 => '\\Draft', + 8 => '\\Deleted', + 16 => '\\Seen' + ); + my $max = (2**scalar keys %rev_map) - 1; + + for (my $i = 0; $i <= $max; $i++) { + my @flags; + for (my $m = 1; defined($rev_map{$m}); $m *= 2) { + push(@flags, $rev_map{$m}) if ($i & $m); } - - xlog $self, "Reconnect, all the flags should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); + xlog $self, "Setting " . join(',', @flags) . " on message A"; + my $res = $talk->store('1', 'flags', '(' . join(' ', @flags) . ')'); + $self->assert_deep_equals({ '1' => { 'flags' => \@flags } }, $res); + $msg{A}->set_attribute(flags => \@flags); $self->check_messages(\%msg); + } + + xlog $self, "Reconnect, all the flags should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } # Quoth RFC 4314: @@ -790,208 +811,207 @@ sub test_multi_flags # response to a STORE command is not handled very well by deployed # clients sub test_multi_flags_acl - :min_version_3_5 :NoAltNamespace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); + : min_version_3_5 : NoAltNamespace { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + my %acls = ( + '\\Seen' => 's', + '\\Deleted' => 't', + '\\Flagged' => 'w', + ); + my @flags = sort keys %acls; + + my $firsttime = 1; + while (my ($flag, $acl_bit) = each %acls) { + xlog $self, "testing flag $flag"; + # reset to no flags set + $admintalk->setacl("user.cassandane", "cassandane", "lrstw") or die; + $talk->unselect(); + $talk->select('INBOX'); + my $res = $talk->store('1', 'flags', '()'); + if ($firsttime) { + $self->assert_deep_equals({}, $res); + $firsttime = 0; + } else { + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + } + $msg{A}->set_attribute(flags => []); $self->check_messages(\%msg); - my %acls = ( - '\\Seen' => 's', - '\\Deleted' => 't', - '\\Flagged' => 'w', - ); - my @flags = sort keys %acls; - - my $firsttime = 1; - while (my ($flag, $acl_bit) = each %acls) { - xlog $self, "testing flag $flag"; - # reset to no flags set - $admintalk->setacl("user.cassandane", "cassandane", "lrstw") or die; - $talk->unselect(); - $talk->select('INBOX'); - my $res = $talk->store('1', 'flags', '()'); - if ($firsttime) { - $self->assert_deep_equals({}, $res); - $firsttime = 0; - } - else { - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - } - $msg{A}->set_attribute(flags => []); - $self->check_messages(\%msg); - - # limit user access - $admintalk->setacl("user.cassandane", "cassandane", "lr$acl_bit") - or die; - $talk->unselect(); - $talk->select('INBOX'); - - # set a bunch of flags - $res = $talk->store('1', '+flags', "(@flags)"); - - # it should work, but only the allowed flag should have been set - $self->assert_deep_equals({ '1' => { 'flags' => [ $flag ] }}, $res); - $self->assert_equals('ok', $talk->get_last_completion_response()); - $msg{A}->set_attribute(flags => [$flag]); - $self->check_messages(\%msg); - - # reset to all flags set - $admintalk->setacl("user.cassandane", "cassandane", "lrstw") or die; - $talk->unselect(); - $talk->select('INBOX'); - $res = $talk->store('1', 'flags', "(@flags)"); - $self->assert_not_null($res); - $self->assert_deep_equals([@flags], [sort @{$res->{1}->{flags}}]); - $msg{A}->set_attribute(flags => [@flags]); - $self->check_messages(\%msg); - - # limit user access - $admintalk->setacl("user.cassandane", "cassandane", "lr$acl_bit") - or die; - $talk->unselect(); - $talk->select('INBOX'); - - # remove a bunch of flags - $res = $talk->store('1', '-flags', "(@flags)"); - - # it should work, but only the allowed flag should have been changed - $self->assert_not_null($res); - $self->assert_deep_equals([ grep { $_ ne $flag } @flags ], - [ sort @{$res->{1}->{flags}} ]); - $self->assert_equals('ok', $talk->get_last_completion_response()); - $msg{A}->set_attribute(flags => [ grep { $_ ne $flag } @flags ]); - $self->check_messages(\%msg); - - # explicit set with any of them missing permission should fail - $res = $talk->store('1', 'flags', "(@flags)"); - - # nothing should have changed - $self->assert_null($res); - $self->assert_equals('no', $talk->get_last_completion_response()); - $self->check_messages(\%msg); - - # no flags we're allowed to change - $res = $talk->store('1', '+flags', - '(' . join(' ', grep { $_ ne $flag } @flags) . ')'); - - # nothing should have changed - $self->assert_null($res); - $self->assert_equals('no', $talk->get_last_completion_response()); - $self->check_messages(\%msg); - - # no flags we're allowed to change - $res = $talk->store('1', '-flags', - '(' . join(' ', grep { $_ ne $flag } @flags) . ')'); - - # nothing should have changed - $self->assert_null($res); - $self->assert_equals('no', $talk->get_last_completion_response()); - $self->check_messages(\%msg); - } -} + # limit user access + $admintalk->setacl("user.cassandane", "cassandane", "lr$acl_bit") + or die; + $talk->unselect(); + $talk->select('INBOX'); -sub test_explicit_store_acl - :NoAltNamespace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - # add a message - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); + # set a bunch of flags + $res = $talk->store('1', '+flags', "(@flags)"); + + # it should work, but only the allowed flag should have been set + $self->assert_deep_equals({ '1' => { 'flags' => [$flag] } }, $res); + $self->assert_equals('ok', $talk->get_last_completion_response()); + $msg{A}->set_attribute(flags => [$flag]); $self->check_messages(\%msg); - # set some flags on it - my $res = $talk->store('1', '+flags', '(\\Deleted \\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ qw(\\Deleted \\Seen)] }}, - $res); - $msg{A}->set_attribute(flags => [ '\\Deleted', '\\Seen' ]); + # reset to all flags set + $admintalk->setacl("user.cassandane", "cassandane", "lrstw") or die; + $talk->unselect(); + $talk->select('INBOX'); + $res = $talk->store('1', 'flags', "(@flags)"); + $self->assert_not_null($res); + $self->assert_deep_equals([@flags], [ sort @{ $res->{1}->{flags} } ]); + $msg{A}->set_attribute(flags => [@flags]); $self->check_messages(\%msg); - # remove 't' right from user - my %acl = @{ $admintalk->getacl('user.cassandane') }; - $self->assert_equals('ok', $admintalk->get_last_completion_response()); - xlog "acl: " . Dumper \%acl; - $self->assert_not_null($acl{'cassandane'}); - $acl{'cassandane'} =~ s/t//g; - $admintalk->setacl("user.cassandane", "cassandane", $acl{'cassandane'}); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); - - # try to set flags to a new set not containing \Deleted or \Seen. - # \Seen should be removed, but \Deleted must not be + # limit user access + $admintalk->setacl("user.cassandane", "cassandane", "lr$acl_bit") + or die; $talk->unselect(); $talk->select('INBOX'); - $res = $talk->store('1', 'flags', '(\\Flagged)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ - qw(\\Flagged \\Deleted) - ]}}, $res); - $msg{A}->set_attribute(flags => [ '\\Flagged', '\\Deleted' ]); + + # remove a bunch of flags + $res = $talk->store('1', '-flags', "(@flags)"); + + # it should work, but only the allowed flag should have been changed + $self->assert_not_null($res); + $self->assert_deep_equals([ grep { $_ ne $flag } @flags ], + [ sort @{ $res->{1}->{flags} } ]); + $self->assert_equals('ok', $talk->get_last_completion_response()); + $msg{A}->set_attribute(flags => [ grep { $_ ne $flag } @flags ]); + $self->check_messages(\%msg); + + # explicit set with any of them missing permission should fail + $res = $talk->store('1', 'flags', "(@flags)"); + + # nothing should have changed + $self->assert_null($res); + $self->assert_equals('no', $talk->get_last_completion_response()); + $self->check_messages(\%msg); + + # no flags we're allowed to change + $res = $talk->store('1', '+flags', + '(' . join(' ', grep { $_ ne $flag } @flags) . ')'); + + # nothing should have changed + $self->assert_null($res); + $self->assert_equals('no', $talk->get_last_completion_response()); $self->check_messages(\%msg); + + # no flags we're allowed to change + $res = $talk->store('1', '-flags', + '(' . join(' ', grep { $_ ne $flag } @flags) . ')'); + + # nothing should have changed + $self->assert_null($res); + $self->assert_equals('no', $talk->get_last_completion_response()); + $self->check_messages(\%msg); + } +} + +sub test_explicit_store_acl + : NoAltNamespace { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + # add a message + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $self->check_messages(\%msg); + + # set some flags on it + my $res = $talk->store('1', '+flags', '(\\Deleted \\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => [qw(\\Deleted \\Seen)] } }, + $res); + $msg{A}->set_attribute(flags => [ '\\Deleted', '\\Seen' ]); + $self->check_messages(\%msg); + + # remove 't' right from user + my %acl = @{ $admintalk->getacl('user.cassandane') }; + $self->assert_equals('ok', $admintalk->get_last_completion_response()); + xlog "acl: " . Dumper \%acl; + $self->assert_not_null($acl{'cassandane'}); + $acl{'cassandane'} =~ s/t//g; + $admintalk->setacl("user.cassandane", "cassandane", $acl{'cassandane'}); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); + + # try to set flags to a new set not containing \Deleted or \Seen. + # \Seen should be removed, but \Deleted must not be + $talk->unselect(); + $talk->select('INBOX'); + $res = $talk->store('1', 'flags', '(\\Flagged)'); + $self->assert_deep_equals({ '1' => { 'flags' => [qw(\\Flagged \\Deleted)] } }, + $res); + $msg{A}->set_attribute(flags => [ '\\Flagged', '\\Deleted' ]); + $self->check_messages(\%msg); } # Get the modseq of a given returned message -sub get_modseq -{ - my ($actual, $which) = @_; - - my $msl = $actual->{'Message ' . $which}->get_attribute('modseq'); - return undef unless defined $msl; - return undef unless ref $msl eq 'ARRAY'; - return undef unless scalar @$msl == 1; - return 0 + $msl->[0]; +sub get_modseq { + my ($actual, $which) = @_; + + my $msl = $actual->{ 'Message ' . $which }->get_attribute('modseq'); + return undef unless defined $msl; + return undef unless ref $msl eq 'ARRAY'; + return undef unless scalar @$msl == 1; + return 0 + $msl->[0]; } # Get the modseq from a FETCH response -sub get_modseq_from_fetch -{ - my ($fetched, $i) = @_; - - my $msl = $fetched->{$i}->{modseq}; - return undef unless defined $msl; - return undef unless ref $msl eq 'ARRAY'; - return undef unless scalar @$msl == 1; - return 0 + $msl->[0]; +sub get_modseq_from_fetch { + my ($fetched, $i) = @_; + + my $msl = $fetched->{$i}->{modseq}; + return undef unless defined $msl; + return undef unless ref $msl eq 'ARRAY'; + return undef unless scalar @$msl == 1; + return 0 + $msl->[0]; } # Get the highestmodseq of the folder -sub get_highestmodseq -{ - my ($self) = @_; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $stat = $talk->status($store->{folder}, '(highestmodseq)'); - return undef unless defined $stat; - return undef unless ref $stat eq 'HASH'; - return undef unless defined $stat->{highestmodseq}; - return 0 + $stat->{highestmodseq}; +sub get_highestmodseq { + my ($self) = @_; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $stat = $talk->status($store->{folder}, '(highestmodseq)'); + return undef unless defined $stat; + return undef unless ref $stat eq 'HASH'; + return undef unless defined $stat->{highestmodseq}; + return 0 + $stat->{highestmodseq}; } # @@ -1009,81 +1029,84 @@ sub get_highestmodseq # FETCH response. # TODO: test the .SILENT suffix # -sub test_modseq -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags modseq)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - my $act0 = $self->check_messages(\%msg); - my $hms0 = $self->get_highestmodseq(); - - xlog $self, "Set \\Flagged on message A"; - my $res = $talk->store('1', '+flags', '(\\Flagged)'); - $self->assert_not_null($res); - $self->assert_deep_equals([ '\\Flagged' ], $res->{1}->{flags}); - $msg{A}->set_attribute(flags => ['\\Flagged']); - my $act1 = $self->check_messages(\%msg); - my $hms1 = $self->get_highestmodseq(); - xlog $self, "A should have a new modseq higher than any other message"; - $self->assert(get_modseq($act1, 'A') > get_modseq($act0, 'A')); - $self->assert(get_modseq($act1, 'A') > get_modseq($act0, 'B')); - $self->assert(get_modseq($act1, 'B') == get_modseq($act0, 'B')); - $self->assert($hms1 > $hms0); - $self->assert(get_modseq($act1, 'A') == $hms1); - - xlog $self, "Set \\Flagged on message A while already set"; - $res = $talk->store('1', '+flags', '(\\Flagged)'); - $self->assert_deep_equals({}, $res); - $self->assert_equals('ok', $talk->get_last_completion_response()); - $msg{A}->set_attribute(flags => ['\\Flagged']); - my $act2 = $self->check_messages(\%msg); - my $hms2 = $self->get_highestmodseq(); - xlog $self, "A should have not changed modseq"; - $self->assert(get_modseq($act2, 'A') == get_modseq($act1, 'A')); - $self->assert(get_modseq($act2, 'B') == get_modseq($act1, 'B')); - $self->assert($hms2 == $hms1); - $self->assert(get_modseq($act2, 'A') == $hms2); - - xlog $self, "Clear \\Flagged on message A"; - $res = $talk->store('1', '-flags', '(\\Flagged)'); - $self->assert_not_null($res); - $self->assert_deep_equals([], $res->{1}->{flags}); - $msg{A}->set_attribute(flags => []); - my $act3 = $self->check_messages(\%msg); - my $hms3 = $self->get_highestmodseq(); - xlog $self, "A should have a new modseq higher than any other message"; - $self->assert(get_modseq($act3, 'A') > get_modseq($act2, 'A')); - $self->assert(get_modseq($act3, 'A') > get_modseq($act2, 'B')); - $self->assert(get_modseq($act3, 'B') == get_modseq($act2, 'B')); - $self->assert($hms3 > $hms2); - $self->assert(get_modseq($act3, 'A') == $hms3); - - xlog $self, "Clear \\Flagged on message A while already clear"; - $res = $talk->store('1', '-flags', '(\\Flagged)'); - $self->assert_deep_equals({}, $res); - $self->assert_equals('ok', $talk->get_last_completion_response()); - $msg{A}->set_attribute(flags => []); - my $act4 = $self->check_messages(\%msg); - my $hms4 = $self->get_highestmodseq(); - xlog $self, "A should have not changed modseq"; - $self->assert(get_modseq($act4, 'A') == get_modseq($act3, 'A')); - $self->assert(get_modseq($act4, 'B') == get_modseq($act3, 'B')); - $self->assert($hms4 == $hms3); - $self->assert(get_modseq($act4, 'A') == $hms4); +sub test_modseq { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags modseq)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + my $act0 = $self->check_messages(\%msg); + my $hms0 = $self->get_highestmodseq(); + + xlog $self, "Set \\Flagged on message A"; + my $res = $talk->store('1', '+flags', '(\\Flagged)'); + $self->assert_not_null($res); + $self->assert_deep_equals(['\\Flagged'], $res->{1}->{flags}); + $msg{A}->set_attribute(flags => ['\\Flagged']); + my $act1 = $self->check_messages(\%msg); + my $hms1 = $self->get_highestmodseq(); + xlog $self, "A should have a new modseq higher than any other message"; + $self->assert(get_modseq($act1, 'A') > get_modseq($act0, 'A')); + $self->assert(get_modseq($act1, 'A') > get_modseq($act0, 'B')); + $self->assert(get_modseq($act1, 'B') == get_modseq($act0, 'B')); + $self->assert($hms1 > $hms0); + $self->assert(get_modseq($act1, 'A') == $hms1); + + xlog $self, "Set \\Flagged on message A while already set"; + $res = $talk->store('1', '+flags', '(\\Flagged)'); + $self->assert_deep_equals({}, $res); + $self->assert_equals('ok', $talk->get_last_completion_response()); + $msg{A}->set_attribute(flags => ['\\Flagged']); + my $act2 = $self->check_messages(\%msg); + my $hms2 = $self->get_highestmodseq(); + xlog $self, "A should have not changed modseq"; + $self->assert(get_modseq($act2, 'A') == get_modseq($act1, 'A')); + $self->assert(get_modseq($act2, 'B') == get_modseq($act1, 'B')); + $self->assert($hms2 == $hms1); + $self->assert(get_modseq($act2, 'A') == $hms2); + + xlog $self, "Clear \\Flagged on message A"; + $res = $talk->store('1', '-flags', '(\\Flagged)'); + $self->assert_not_null($res); + $self->assert_deep_equals([], $res->{1}->{flags}); + $msg{A}->set_attribute(flags => []); + my $act3 = $self->check_messages(\%msg); + my $hms3 = $self->get_highestmodseq(); + xlog $self, "A should have a new modseq higher than any other message"; + $self->assert(get_modseq($act3, 'A') > get_modseq($act2, 'A')); + $self->assert(get_modseq($act3, 'A') > get_modseq($act2, 'B')); + $self->assert(get_modseq($act3, 'B') == get_modseq($act2, 'B')); + $self->assert($hms3 > $hms2); + $self->assert(get_modseq($act3, 'A') == $hms3); + + xlog $self, "Clear \\Flagged on message A while already clear"; + $res = $talk->store('1', '-flags', '(\\Flagged)'); + $self->assert_deep_equals({}, $res); + $self->assert_equals('ok', $talk->get_last_completion_response()); + $msg{A}->set_attribute(flags => []); + my $act4 = $self->check_messages(\%msg); + my $hms4 = $self->get_highestmodseq(); + xlog $self, "A should have not changed modseq"; + $self->assert(get_modseq($act4, 'A') == get_modseq($act3, 'A')); + $self->assert(get_modseq($act4, 'B') == get_modseq($act3, 'B')); + $self->assert($hms4 == $hms3); + $self->assert(get_modseq($act4, 'A') == $hms4); } # @@ -1105,119 +1128,119 @@ sub test_modseq # - returns an OK response # - but reports the UID in the MODIFIED response code # -sub test_unchangedsince -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags modseq)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - my $act0 = $self->check_messages(\%msg); - - my %fetched; - my $modified; - my %handlers = - ( - fetch => sub - { - my ($response, $rr, $id) = @_; - - # older versions of Mail::IMAPTalk don't have - # the 3rd argument. We can't test properly in - # those circumstances. - $self->assert_not_null($id); - - $fetched{$id} = $rr; - }, - modified => sub - { - my ($response, $rr) = @_; - # we should not get more than one of these ever - $self->assert_null($modified); - $modified = $rr; - } - ); - - # Note: Mail::IMAPTalk::store() doesn't support modifiers - # so we have to resort to the lower level interface. - - xlog $self, "Changing a flag with current modseq == UNCHANGEDSINCE"; - %fetched = (); - $modified = undef; - $talk->_imap_cmd('store', 1, \%handlers, - '1', ['unchangedsince', get_modseq($act0, 'A')], - '+flags', ['\\Flagged']); - my $res1 = $talk->get_last_completion_response(); - # - updates the flag - $msg{A}->set_attribute(flags => ['\\Flagged']); - my $act1 = $self->check_messages(\%msg); - xlog $self, "returns an OK response?"; - $self->assert_str_equals('ok', $res1); - xlog $self, "updated modseq?"; - $self->assert(get_modseq($act1, 'A') > get_modseq($act0, 'A')); - xlog $self, "returned no MODIFIED response code?"; - $self->assert_null($modified); - xlog $self, "sent an untagged FETCH response?"; - $self->assert_num_equals(1, scalar keys %fetched); - $self->assert_not_null($fetched{1}); - xlog $self, "the FETCH response has the new modseq?"; - $self->assert_num_equals(get_modseq($act1, 'A'), - get_modseq_from_fetch(\%fetched, 1)); - - xlog $self, "Changing a flag with current modseq < UNCHANGEDSINCE"; - %fetched = (); - $modified = undef; - $talk->_imap_cmd('store', 1, \%handlers, - '1', ['unchangedsince', get_modseq($act1, 'A')+1], - '-flags', ['\\Flagged']); - my $res2 = $talk->get_last_completion_response(); - # - updates the flag - $msg{A}->set_attribute(flags => []); - my $act2 = $self->check_messages(\%msg); - xlog $self, "returns an OK response?"; - $self->assert_str_equals('ok', $res2); - xlog $self, "updated modseq?"; - $self->assert(get_modseq($act2, 'A') > get_modseq($act0, 'A')); - xlog $self, "returned no MODIFIED response code?"; - $self->assert_null($modified); - xlog $self, "sent an untagged FETCH response?"; - $self->assert_num_equals(1, scalar keys %fetched); - $self->assert_not_null($fetched{1}); - xlog $self, "the FETCH response has the new modseq?"; - $self->assert_num_equals(get_modseq($act2, 'A'), - get_modseq_from_fetch(\%fetched, 1)); - - xlog $self, "Changing a flag with current modseq > UNCHANGEDSINCE"; - %fetched = (); - $modified = undef; - $talk->_imap_cmd('store', 1, \%handlers, - '1', ['unchangedsince', get_modseq($act2, 'A')-1], - '+flags', ['\\Flagged']); - my $res3 = $talk->get_last_completion_response(); - # - doesn't update the flag - $msg{A}->set_attribute(flags => []); - my $act3 = $self->check_messages(\%msg); - xlog $self, "returns an OK response?"; - $self->assert_str_equals('ok', $res3); - xlog $self, "didn't update modseq?"; - $self->assert_num_equals(get_modseq($act3, 'A'), get_modseq($act2, 'A')); - xlog $self, "reports the UID in the MODIFIED response code?"; - $self->assert_not_null($modified); - $self->assert_deep_equals($modified, [1]); - xlog $self, "sent no FETCH untagged response?"; - $self->assert_num_equals(0, scalar keys %fetched); +sub test_unchangedsince { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags modseq)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + my $act0 = $self->check_messages(\%msg); + + my %fetched; + my $modified; + my %handlers = ( + fetch => sub { + my ($response, $rr, $id) = @_; + + # older versions of Mail::IMAPTalk don't have + # the 3rd argument. We can't test properly in + # those circumstances. + $self->assert_not_null($id); + + $fetched{$id} = $rr; + }, + modified => sub { + my ($response, $rr) = @_; + # we should not get more than one of these ever + $self->assert_null($modified); + $modified = $rr; + } + ); + + # Note: Mail::IMAPTalk::store() doesn't support modifiers + # so we have to resort to the lower level interface. + + xlog $self, "Changing a flag with current modseq == UNCHANGEDSINCE"; + %fetched = (); + $modified = undef; + $talk->_imap_cmd('store', 1, \%handlers, + '1', [ 'unchangedsince', get_modseq($act0, 'A') ], + '+flags', ['\\Flagged']); + my $res1 = $talk->get_last_completion_response(); + # - updates the flag + $msg{A}->set_attribute(flags => ['\\Flagged']); + my $act1 = $self->check_messages(\%msg); + xlog $self, "returns an OK response?"; + $self->assert_str_equals('ok', $res1); + xlog $self, "updated modseq?"; + $self->assert(get_modseq($act1, 'A') > get_modseq($act0, 'A')); + xlog $self, "returned no MODIFIED response code?"; + $self->assert_null($modified); + xlog $self, "sent an untagged FETCH response?"; + $self->assert_num_equals(1, scalar keys %fetched); + $self->assert_not_null($fetched{1}); + xlog $self, "the FETCH response has the new modseq?"; + $self->assert_num_equals(get_modseq($act1, 'A'), + get_modseq_from_fetch(\%fetched, 1)); + + xlog $self, "Changing a flag with current modseq < UNCHANGEDSINCE"; + %fetched = (); + $modified = undef; + $talk->_imap_cmd('store', 1, \%handlers, + '1', [ 'unchangedsince', get_modseq($act1, 'A') + 1 ], + '-flags', ['\\Flagged']); + my $res2 = $talk->get_last_completion_response(); + # - updates the flag + $msg{A}->set_attribute(flags => []); + my $act2 = $self->check_messages(\%msg); + xlog $self, "returns an OK response?"; + $self->assert_str_equals('ok', $res2); + xlog $self, "updated modseq?"; + $self->assert(get_modseq($act2, 'A') > get_modseq($act0, 'A')); + xlog $self, "returned no MODIFIED response code?"; + $self->assert_null($modified); + xlog $self, "sent an untagged FETCH response?"; + $self->assert_num_equals(1, scalar keys %fetched); + $self->assert_not_null($fetched{1}); + xlog $self, "the FETCH response has the new modseq?"; + $self->assert_num_equals(get_modseq($act2, 'A'), + get_modseq_from_fetch(\%fetched, 1)); + + xlog $self, "Changing a flag with current modseq > UNCHANGEDSINCE"; + %fetched = (); + $modified = undef; + $talk->_imap_cmd('store', 1, \%handlers, + '1', [ 'unchangedsince', get_modseq($act2, 'A') - 1 ], + '+flags', ['\\Flagged']); + my $res3 = $talk->get_last_completion_response(); + # - doesn't update the flag + $msg{A}->set_attribute(flags => []); + my $act3 = $self->check_messages(\%msg); + xlog $self, "returns an OK response?"; + $self->assert_str_equals('ok', $res3); + xlog $self, "didn't update modseq?"; + $self->assert_num_equals(get_modseq($act3, 'A'), get_modseq($act2, 'A')); + xlog $self, "reports the UID in the MODIFIED response code?"; + $self->assert_not_null($modified); + $self->assert_deep_equals($modified, [1]); + xlog $self, "sent no FETCH untagged response?"; + $self->assert_num_equals(0, scalar keys %fetched); } # @@ -1242,365 +1265,372 @@ sub test_unchangedsince # TODO the untagged FETCH response is returned even when # .SILENT is used # -sub test_unchangedsince_multi -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags modseq)); - - xlog $self, "Add some messages"; - my %msg; - for (my $i = 1 ; $i <= 26 ; $i++) - { - my $letter = chr(64 + $i); # A ... Z - $msg{$letter} = $self->make_message('Message ' . $letter); - $msg{$letter}->set_attributes(id => $i, - uid => $i, - flags => []); - } - - xlog $self, "Bump the modseq on M,N,O"; - my $res = $talk->store('13,14,15', '+flags', '(\\Draft)'); - $self->assert_deep_equals({ - '13' => { 'flags' => [ '\\Draft' ] }, - '14' => { 'flags' => [ '\\Draft' ] }, - '15' => { 'flags' => [ '\\Draft' ] }, - }, $res); - $msg{M}->set_attribute(flags => ['\\Draft']); - $msg{N}->set_attribute(flags => ['\\Draft']); - $msg{O}->set_attribute(flags => ['\\Draft']); - - my $act0 = $self->check_messages(\%msg); +sub test_unchangedsince_multi { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags modseq)); + + xlog $self, "Add some messages"; + my %msg; + for (my $i = 1; $i <= 26; $i++) { + my $letter = chr(64 + $i); # A ... Z + $msg{$letter} = $self->make_message('Message ' . $letter); + $msg{$letter}->set_attributes( + id => $i, + uid => $i, + flags => [] + ); + } + xlog $self, "Bump the modseq on M,N,O"; + my $res = $talk->store('13,14,15', '+flags', '(\\Draft)'); + $self->assert_deep_equals( { - my $store2 = $self->{instance}->get_service('imap')->create_store(); - $store2->connect(); - $store2->_select(); - my $talk2 = $store2->get_client(); - xlog $self, "Delete and expunge D,E,F from another session"; - for (my $i = 4 ; $i <= 6 ; $i++) - { - my $letter = chr(64 + $i); # D, E, F - my $res = $talk2->store($i, '+flags', '(\\Deleted)'); - $self->assert_deep_equals({ - "$i" => { 'flags' => [ '\\Deleted' ] } - }, $res); - delete $msg{$letter}; - } - $talk2->expunge(); - $store2->disconnect(); - } - - - my %fetched; - my $modified; - my %handlers = - ( - fetch => sub + '13' => { 'flags' => ['\\Draft'] }, + '14' => { 'flags' => ['\\Draft'] }, + '15' => { 'flags' => ['\\Draft'] }, + }, + $res + ); + $msg{M}->set_attribute(flags => ['\\Draft']); + $msg{N}->set_attribute(flags => ['\\Draft']); + $msg{O}->set_attribute(flags => ['\\Draft']); + + my $act0 = $self->check_messages(\%msg); + + { + my $store2 = $self->{instance}->get_service('imap')->create_store(); + $store2->connect(); + $store2->_select(); + my $talk2 = $store2->get_client(); + xlog $self, "Delete and expunge D,E,F from another session"; + for (my $i = 4; $i <= 6; $i++) { + my $letter = chr(64 + $i); # D, E, F + my $res = $talk2->store($i, '+flags', '(\\Deleted)'); + $self->assert_deep_equals( { - my ($response, $rr, $id) = @_; - - # older versions of Mail::IMAPTalk don't have - # the 3rd argument. We can't test properly in - # those circumstances. - $self->assert_not_null($id); - - $fetched{$id} = $rr; + "$i" => { 'flags' => ['\\Deleted'] } }, - modified => sub - { - my ($response, $rr) = @_; - # we should not get more than one of these ever - $self->assert_null($modified); - $modified = $rr; - } - ); - - # Note: Mail::IMAPTalk::store() doesn't support modifiers - # so we have to resort to the lower level interface. - - xlog $self, "Changing a flag on multiple messages"; - %fetched = (); - $modified = undef; - $talk->_imap_cmd('store', 1, \%handlers, - \'1:*', ['unchangedsince', get_modseq($act0, 'Z')], - '+flags', ['\\Flagged']); - my $res1 = $talk->get_last_completion_response(); - - $msg{A}->set_attribute(flags => ['\\Flagged']); - $msg{B}->set_attribute(flags => ['\\Flagged']); - $msg{C}->set_attribute(flags => ['\\Flagged']); - # D,E,F deleted - $msg{G}->set_attribute(flags => ['\\Flagged']); - $msg{H}->set_attribute(flags => ['\\Flagged']); - $msg{I}->set_attribute(flags => ['\\Flagged']); - $msg{J}->set_attribute(flags => ['\\Flagged']); - $msg{K}->set_attribute(flags => ['\\Flagged']); - $msg{L}->set_attribute(flags => ['\\Flagged']); - # M,N,O should fail the conditional store - $msg{M}->set_attribute(flags => ['\\Draft']); - $msg{N}->set_attribute(flags => ['\\Draft']); - $msg{O}->set_attribute(flags => ['\\Draft']); - $msg{P}->set_attribute(flags => ['\\Flagged']); - $msg{Q}->set_attribute(flags => ['\\Flagged']); - $msg{R}->set_attribute(flags => ['\\Flagged']); - $msg{S}->set_attribute(flags => ['\\Flagged']); - $msg{T}->set_attribute(flags => ['\\Flagged']); - $msg{U}->set_attribute(flags => ['\\Flagged']); - $msg{V}->set_attribute(flags => ['\\Flagged']); - $msg{W}->set_attribute(flags => ['\\Flagged']); - $msg{X}->set_attribute(flags => ['\\Flagged']); - $msg{Y}->set_attribute(flags => ['\\Flagged']); - $msg{Z}->set_attribute(flags => ['\\Flagged']); - # We start a new session in check_messages, so we - # have to renumber here to account for deletion - for (my $i = 7 ; $i <= 26 ; $i++) - { - my $letter = chr(64 + $i); # G ... Z - $msg{$letter}->set_attribute(id => $i-3); + $res + ); + delete $msg{$letter}; } - my $act1 = $self->check_messages(\%msg); - -# TODO: this fails with current Cyrus code -# xlog $self, "returns a NO response?"; -# $self->assert_str_equals('NO', $res1); - - xlog $self, "updated modseq?"; - $self->assert(get_modseq($act1, 'A') > get_modseq($act0, 'A')); - $self->assert(get_modseq($act1, 'B') > get_modseq($act0, 'B')); - $self->assert(get_modseq($act1, 'C') > get_modseq($act0, 'C')); - # D,E,F deleted - $self->assert(get_modseq($act1, 'G') > get_modseq($act0, 'G')); - $self->assert(get_modseq($act1, 'H') > get_modseq($act0, 'H')); - $self->assert(get_modseq($act1, 'I') > get_modseq($act0, 'I')); - $self->assert(get_modseq($act1, 'J') > get_modseq($act0, 'J')); - $self->assert(get_modseq($act1, 'K') > get_modseq($act0, 'K')); - $self->assert(get_modseq($act1, 'L') > get_modseq($act0, 'L')); - # M,N,O have the same modseq - $self->assert(get_modseq($act1, 'M') == get_modseq($act0, 'M')); - $self->assert(get_modseq($act1, 'N') == get_modseq($act0, 'N')); - $self->assert(get_modseq($act1, 'O') == get_modseq($act0, 'O')); - $self->assert(get_modseq($act1, 'P') > get_modseq($act0, 'P')); - $self->assert(get_modseq($act1, 'Q') > get_modseq($act0, 'Q')); - $self->assert(get_modseq($act1, 'R') > get_modseq($act0, 'R')); - $self->assert(get_modseq($act1, 'S') > get_modseq($act0, 'S')); - $self->assert(get_modseq($act1, 'T') > get_modseq($act0, 'T')); - $self->assert(get_modseq($act1, 'U') > get_modseq($act0, 'U')); - $self->assert(get_modseq($act1, 'V') > get_modseq($act0, 'V')); - $self->assert(get_modseq($act1, 'W') > get_modseq($act0, 'W')); - $self->assert(get_modseq($act1, 'X') > get_modseq($act0, 'X')); - $self->assert(get_modseq($act1, 'Y') > get_modseq($act0, 'Y')); - $self->assert(get_modseq($act1, 'Z') > get_modseq($act0, 'Z')); - - xlog $self, "returned MODIFIED response code?"; - $self->assert_not_null($modified); - $self->assert_deep_equals($modified, ['13:15']); - - xlog $self, "sent untagged FETCH responses with the new modseq?"; - # also tells about the 3 messages which were deleted since - # the last command - $self->assert_num_equals(23, scalar keys %fetched); - foreach my $i (1..3, 7..12, 16..26) - { - my $letter = chr(64 + $i); - $self->assert_not_null($fetched{$i}); - $self->assert_num_equals(get_modseq($act1, $letter), - get_modseq_from_fetch(\%fetched, $i)); + $talk2->expunge(); + $store2->disconnect(); + } + + my %fetched; + my $modified; + my %handlers = ( + fetch => sub { + my ($response, $rr, $id) = @_; + + # older versions of Mail::IMAPTalk don't have + # the 3rd argument. We can't test properly in + # those circumstances. + $self->assert_not_null($id); + + $fetched{$id} = $rr; + }, + modified => sub { + my ($response, $rr) = @_; + # we should not get more than one of these ever + $self->assert_null($modified); + $modified = $rr; } + ); + + # Note: Mail::IMAPTalk::store() doesn't support modifiers + # so we have to resort to the lower level interface. + + xlog $self, "Changing a flag on multiple messages"; + %fetched = (); + $modified = undef; + $talk->_imap_cmd('store', 1, \%handlers, \'1:*', + [ 'unchangedsince', get_modseq($act0, 'Z') ], + '+flags', ['\\Flagged']); + my $res1 = $talk->get_last_completion_response(); + + $msg{A}->set_attribute(flags => ['\\Flagged']); + $msg{B}->set_attribute(flags => ['\\Flagged']); + $msg{C}->set_attribute(flags => ['\\Flagged']); + # D,E,F deleted + $msg{G}->set_attribute(flags => ['\\Flagged']); + $msg{H}->set_attribute(flags => ['\\Flagged']); + $msg{I}->set_attribute(flags => ['\\Flagged']); + $msg{J}->set_attribute(flags => ['\\Flagged']); + $msg{K}->set_attribute(flags => ['\\Flagged']); + $msg{L}->set_attribute(flags => ['\\Flagged']); + # M,N,O should fail the conditional store + $msg{M}->set_attribute(flags => ['\\Draft']); + $msg{N}->set_attribute(flags => ['\\Draft']); + $msg{O}->set_attribute(flags => ['\\Draft']); + $msg{P}->set_attribute(flags => ['\\Flagged']); + $msg{Q}->set_attribute(flags => ['\\Flagged']); + $msg{R}->set_attribute(flags => ['\\Flagged']); + $msg{S}->set_attribute(flags => ['\\Flagged']); + $msg{T}->set_attribute(flags => ['\\Flagged']); + $msg{U}->set_attribute(flags => ['\\Flagged']); + $msg{V}->set_attribute(flags => ['\\Flagged']); + $msg{W}->set_attribute(flags => ['\\Flagged']); + $msg{X}->set_attribute(flags => ['\\Flagged']); + $msg{Y}->set_attribute(flags => ['\\Flagged']); + $msg{Z}->set_attribute(flags => ['\\Flagged']); + # We start a new session in check_messages, so we + # have to renumber here to account for deletion + for (my $i = 7; $i <= 26; $i++) { + my $letter = chr(64 + $i); # G ... Z + $msg{$letter}->set_attribute(id => $i - 3); + } + my $act1 = $self->check_messages(\%msg); + + # TODO: this fails with current Cyrus code + # xlog $self, "returns a NO response?"; + # $self->assert_str_equals('NO', $res1); + + xlog $self, "updated modseq?"; + $self->assert(get_modseq($act1, 'A') > get_modseq($act0, 'A')); + $self->assert(get_modseq($act1, 'B') > get_modseq($act0, 'B')); + $self->assert(get_modseq($act1, 'C') > get_modseq($act0, 'C')); + # D,E,F deleted + $self->assert(get_modseq($act1, 'G') > get_modseq($act0, 'G')); + $self->assert(get_modseq($act1, 'H') > get_modseq($act0, 'H')); + $self->assert(get_modseq($act1, 'I') > get_modseq($act0, 'I')); + $self->assert(get_modseq($act1, 'J') > get_modseq($act0, 'J')); + $self->assert(get_modseq($act1, 'K') > get_modseq($act0, 'K')); + $self->assert(get_modseq($act1, 'L') > get_modseq($act0, 'L')); + # M,N,O have the same modseq + $self->assert(get_modseq($act1, 'M') == get_modseq($act0, 'M')); + $self->assert(get_modseq($act1, 'N') == get_modseq($act0, 'N')); + $self->assert(get_modseq($act1, 'O') == get_modseq($act0, 'O')); + $self->assert(get_modseq($act1, 'P') > get_modseq($act0, 'P')); + $self->assert(get_modseq($act1, 'Q') > get_modseq($act0, 'Q')); + $self->assert(get_modseq($act1, 'R') > get_modseq($act0, 'R')); + $self->assert(get_modseq($act1, 'S') > get_modseq($act0, 'S')); + $self->assert(get_modseq($act1, 'T') > get_modseq($act0, 'T')); + $self->assert(get_modseq($act1, 'U') > get_modseq($act0, 'U')); + $self->assert(get_modseq($act1, 'V') > get_modseq($act0, 'V')); + $self->assert(get_modseq($act1, 'W') > get_modseq($act0, 'W')); + $self->assert(get_modseq($act1, 'X') > get_modseq($act0, 'X')); + $self->assert(get_modseq($act1, 'Y') > get_modseq($act0, 'Y')); + $self->assert(get_modseq($act1, 'Z') > get_modseq($act0, 'Z')); + + xlog $self, "returned MODIFIED response code?"; + $self->assert_not_null($modified); + $self->assert_deep_equals($modified, ['13:15']); + + xlog $self, "sent untagged FETCH responses with the new modseq?"; + # also tells about the 3 messages which were deleted since + # the last command + $self->assert_num_equals(23, scalar keys %fetched); + foreach my $i (1 .. 3, 7 .. 12, 16 .. 26) { + my $letter = chr(64 + $i); + $self->assert_not_null($fetched{$i}); + $self->assert_num_equals(get_modseq($act1, $letter), + get_modseq_from_fetch(\%fetched, $i)); + } } # check that seen flags are set correctly on body fetch -sub test_setseen -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add three messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $msg{C} = $self->make_message('Message C'); - $msg{C}->set_attributes(id => 3, - uid => 3, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Fetch body of message A"; - my $res = $talk->fetch('1', '(body[])'); - $self->assert_deep_equals([ '\\Seen' ], $res->{1}->{flags}); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Fetch body.peek of message B"; - $res = $talk->fetch('2', '(body.peek[])'); - $self->assert(not exists $res->{2}->{flags}); - $self->check_messages(\%msg); - - xlog $self, "Fetch binary of message C"; - $res = $talk->fetch('3', '(binary[])'); - $self->assert_deep_equals([ '\\Seen' ], $res->{3}->{flags}); - $msg{C}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Reconnect, \\Seen should still be on messages A and C"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $self->check_messages(\%msg); +sub test_setseen { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add three messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $msg{C} = $self->make_message('Message C'); + $msg{C}->set_attributes( + id => 3, + uid => 3, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Fetch body of message A"; + my $res = $talk->fetch('1', '(body[])'); + $self->assert_deep_equals(['\\Seen'], $res->{1}->{flags}); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Fetch body.peek of message B"; + $res = $talk->fetch('2', '(body.peek[])'); + $self->assert(not exists $res->{2}->{flags}); + $self->check_messages(\%msg); + + xlog $self, "Fetch binary of message C"; + $res = $talk->fetch('3', '(binary[])'); + $self->assert_deep_equals(['\\Seen'], $res->{3}->{flags}); + $msg{C}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Reconnect, \\Seen should still be on messages A and C"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } # check that seen flags are set correctly on body fetch # even if the flag was removed in the same session -sub test_setseen_after_store -{ - my ($self) = @_; - - # need to version-gate IMAP features that aren't in 3.9... - my ($maj, $min) = Cassandane::Instance->get_version(); - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Fetch body of message A"; - $talk->fetch('1', '(body[])'); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Fetch remove the flag again, and immediately fetch the body"; - my $res = $talk->store('1', '-flags.silent', "(\\Seen)"); - if ($maj > 3 || ($maj == 3 && $min >= 9)) { - $self->assert_deep_equals({}, $res); - } - else { - # XXX flags.silent should cause there to not be an untagged response - # XXX unless the affected data was also modified by another user, but - # XXX for some reason Cyrus still returns it here? - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - } - $talk->fetch('1', '(body[])'); - $self->check_messages(\%msg); - - xlog $self, "Reconnect, \\Seen should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $self->check_messages(\%msg); +sub test_setseen_after_store { + my ($self) = @_; + + # need to version-gate IMAP features that aren't in 3.9... + my ($maj, $min) = Cassandane::Instance->get_version(); + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Fetch body of message A"; + $talk->fetch('1', '(body[])'); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Fetch remove the flag again, and immediately fetch the body"; + my $res = $talk->store('1', '-flags.silent', "(\\Seen)"); + if ($maj > 3 || ($maj == 3 && $min >= 9)) { + $self->assert_deep_equals({}, $res); + } else { + # XXX flags.silent should cause there to not be an untagged response + # XXX unless the affected data was also modified by another user, but + # XXX for some reason Cyrus still returns it here? + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + } + $talk->fetch('1', '(body[])'); + $self->check_messages(\%msg); + + xlog $self, "Reconnect, \\Seen should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); + $self->{store}->_select(); + $self->check_messages(\%msg); } sub test_setseen_notify - :Conversations :FastMailEvent :min_version_3_0 -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Throw away existing notify"; - $self->{instance}->getnotify(); - - xlog $self, "Add a messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $self->check_messages(\%msg); - - my $notify1 = $self->{instance}->getnotify(); - - $msg{A}->set_attribute(flags => ['\\Seen']); - my $res = $talk->store('1', '+flags', '\\Seen'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - - my $notify2 = $self->{instance}->getnotify(); - - my $payload1 = decode_json($notify1->[0]{MESSAGE}); - my $payload2 = decode_json($notify2->[0]{MESSAGE}); - $self->assert($payload2->{modseq} > $payload1->{modseq}, "modseq has increased: $payload2->{modseq} > $payload1->{modseq}"); + : Conversations : FastMailEvent : min_version_3_0 { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Throw away existing notify"; + $self->{instance}->getnotify(); + + xlog $self, "Add a messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $self->check_messages(\%msg); + + my $notify1 = $self->{instance}->getnotify(); + + $msg{A}->set_attribute(flags => ['\\Seen']); + my $res = $talk->store('1', '+flags', '\\Seen'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + + my $notify2 = $self->{instance}->getnotify(); + + my $payload1 = decode_json($notify1->[0]{MESSAGE}); + my $payload2 = decode_json($notify2->[0]{MESSAGE}); + $self->assert($payload2->{modseq} > $payload1->{modseq}, + "modseq has increased: $payload2->{modseq} > $payload1->{modseq}"); } sub test_userflags_crash - :Conversations - :LowEmailLimits - :min_version_3_6 -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - my $folder = 'foo'; - - # make two messages - my %msg; - $msg{1} = $self->make_message('Message 1'); - $msg{2} = $self->make_message('Message 2'); - $self->check_messages(\%msg); - - # make a second mailbox - $talk->create($folder) or die; - - # set a bunch of flags on first message - $talk->store('1', '+flags', qw(a b c d e f g h i j k l)); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - # set some different flags on second message - $talk->store('2', '+flags', qw(what the heck)); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - # duplicate messages into other folder until duplicate limit reached - my $limit = 100; # just in case - do { - $talk->copy('1:*', $folder); - } while (--$limit && 'ok' eq $talk->get_last_completion_response()); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - - # should have got a syslog message about conversations GUID limit - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: conversations GUID limit/); - - # change some flags on the first message - $talk->store('1', '-flags', qw(a b c d)); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $talk->store('1', '+flags', qw(this is new stuff)); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - # duplicate messages again + : Conversations + : LowEmailLimits + : min_version_3_6 { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + my $folder = 'foo'; + + # make two messages + my %msg; + $msg{1} = $self->make_message('Message 1'); + $msg{2} = $self->make_message('Message 2'); + $self->check_messages(\%msg); + + # make a second mailbox + $talk->create($folder) or die; + + # set a bunch of flags on first message + $talk->store('1', '+flags', qw(a b c d e f g h i j k l)); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + # set some different flags on second message + $talk->store('2', '+flags', qw(what the heck)); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + # duplicate messages into other folder until duplicate limit reached + my $limit = 100; # just in case + do { $talk->copy('1:*', $folder); - # --- better not have crashed here! --- - $self->assert_str_equals('no', $talk->get_last_completion_response()); - - # should have got a syslog message about conversations GUID limit - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: conversations GUID limit/); + } while (--$limit && 'ok' eq $talk->get_last_completion_response()); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + + # should have got a syslog message about conversations GUID limit + $self->assert_syslog_matches($self->{instance}, + qr/IOERROR: conversations GUID limit/); + + # change some flags on the first message + $talk->store('1', '-flags', qw(a b c d)); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->store('1', '+flags', qw(this is new stuff)); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + # duplicate messages again + $talk->copy('1:*', $folder); + # --- better not have crashed here! --- + $self->assert_str_equals('no', $talk->get_last_completion_response()); + + # should have got a syslog message about conversations GUID limit + $self->assert_syslog_matches($self->{instance}, + qr/IOERROR: conversations GUID limit/); } 1; diff --git a/cassandane/Cassandane/Cyrus/HTTPPTS.pm b/cassandane/Cassandane/Cyrus/HTTPPTS.pm index 9169a0204a..074760ef2c 100644 --- a/cassandane/Cassandane/Cyrus/HTTPPTS.pm +++ b/cassandane/Cassandane/Cyrus/HTTPPTS.pm @@ -49,305 +49,281 @@ use base qw(Cassandane::Cyrus::TestCase); use base qw(Cassandane::Unit::TestCase); use Cassandane::Util::Log; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set( - auth_mech => 'pts', - pts_module => 'http', - ptloader_sock => '@basedir@/conf/ptsock', - ); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set( + auth_mech => 'pts', + pts_module => 'http', + ptloader_sock => '@basedir@/conf/ptsock', + ); + + my $self = $class->SUPER::new( + { + config => $config, + adminstore => 1, + services => [qw( imap ptloader )], + start_instances => 0, + }, + @args + ); + + return $self; +} + +sub set_up { + my ($self) = @_; + + $self->SUPER::set_up(); + + $self->{server} = $self->new_test_url(sub { + my $env = shift; + my $req = Plack::Request->new($env); + + my $res; + + if ($req->method eq 'GET') { + if ($req->query_parameters->{id} eq 'cassandane') { + $res = Plack::Response->new(200); + $res->content_type('application/json'); + $res->body(encode_json({ + cassandane => [ "group:group co", "group:group c" ] })); + } elsif ($req->query_parameters->{id} eq 'otheruser') { + $res = Plack::Response->new(200); + $res->content_type('application/json'); + $res->body(encode_json({ + otheruser => [ "group:group co", "group:group c" ] })); + } else { + $res = Plack::Response->new(404); + } + } elsif ($req->method eq 'OPTIONS') { + $res = Plack::Response->new(200); + } else { + $res = Plack::Response->new(501); + } - my $self = $class->SUPER::new({ - config => $config, - adminstore => 1, - services => [qw( imap ptloader )], - start_instances => 0, - }, @args); + return $res->finalize; + }); - return $self; -} + my $uri = $self->{server}->url . "?id={groupId}"; -sub set_up -{ - my ($self) = @_; - - $self->SUPER::set_up(); - - $self->{server} = $self->new_test_url(sub { - my $env = shift; - my $req = Plack::Request->new($env); - - my $res; - - if ($req->method eq 'GET') { - if ($req->query_parameters->{id} eq 'cassandane') { - $res = Plack::Response->new(200); - $res->content_type('application/json'); - $res->body(encode_json({ cassandane => [ "group:group co", - "group:group c" ] })); - } elsif ($req->query_parameters->{id} eq 'otheruser') { - $res = Plack::Response->new(200); - $res->content_type('application/json'); - $res->body(encode_json({ otheruser => [ "group:group co", - "group:group c" ] })); - } else { - $res = Plack::Response->new(404); - } - } - elsif ($req->method eq 'OPTIONS') { - $res = Plack::Response->new(200); - } - else { - $res = Plack::Response->new(501); - } - - return $res->finalize; - }); - - my $uri = $self->{server}->url . "?id={groupId}"; - - $self->{instance}->{config}->set( - httppts_uri => $uri - ); + $self->{instance}->{config}->set(httppts_uri => $uri); - $self->_start_instances(); + $self->_start_instances(); - $self->{instance}->create_user("otheruser"); + $self->{instance}->create_user("otheruser"); } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - # clean this up as soon as we're done with it, cause it's holding a - # port open! - delete $self->{server}; + # clean this up as soon as we're done with it, cause it's holding a + # port open! + delete $self->{server}; - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } sub test_alternate_ptscache_db_path - :min_version_3_7 :AltPTSDBPath -{ - my ($self) = @_; + : min_version_3_7 : AltPTSDBPath { + my ($self) = @_; - # just interact with the store, and it should work - my $admintalk = $self->{adminstore}->get_client(); + # just interact with the store, and it should work + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->list('user.cassandane', '*'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->list('user.cassandane', '*'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $confdir = $self->{instance}->{basedir} . "/conf"; - $self->assert_file_test($confdir . "/non-default-ptscache.db"); - $self->assert_not_file_test($confdir . "/ptclient/ptscache.db"); + my $confdir = $self->{instance}->{basedir} . "/conf"; + $self->assert_file_test($confdir . "/non-default-ptscache.db"); + $self->assert_not_file_test($confdir . "/ptclient/ptscache.db"); } sub test_setacl_groupid - :min_version_3_7 -{ - my ($self) = @_; + : min_version_3_7 { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.cassandane.groupid"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->create("user.cassandane.groupid"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.groupid", - "group:foo", - "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.groupid", "group:foo", "lrswipkxtecdan"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } sub test_setacl_groupid_spaces - :min_version_3_7 -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - $admintalk->setacl("user.cassandane.groupid_spaces", - "group:this group name has spaces", - "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - my $data = $admintalk->getacl("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - $self->assert(scalar @{$data} % 2 == 0); - my %acl = @{$data}; - $self->assert_str_equals($acl{"group:this group name has spaces"}, - "lrswipkxtecdan"); - - $admintalk->select("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + : min_version_3_7 { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->create("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + $admintalk->setacl( + "user.cassandane.groupid_spaces", + "group:this group name has spaces", + "lrswipkxtecdan" + ); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + my $data = $admintalk->getacl("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + $self->assert(scalar @{$data} % 2 == 0); + my %acl = @{$data}; + $self->assert_str_equals($acl{"group:this group name has spaces"}, + "lrswipkxtecdan"); + + $admintalk->select("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } sub test_list_groupaccess_noracl - :min_version_3_7 :NoAltNamespace -{ - my ($self) = @_; + : min_version_3_7 : NoAltNamespace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $admintalk->create("user.otheruser.groupaccess"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->create("user.otheruser.groupaccess"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl("user.otheruser.groupaccess", - "group:group co", "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->setacl("user.otheruser.groupaccess", "group:group co", + "lrswipkxtecdan"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $list = $imaptalk->list("", "*"); - my @boxes = sort map { $_->[2] } @{$list}; + my $list = $imaptalk->list("", "*"); + my @boxes = sort map { $_->[2] } @{$list}; - $self->assert_deep_equals(\@boxes, - ['INBOX', 'user.otheruser.groupaccess']); + $self->assert_deep_equals(\@boxes, [ 'INBOX', 'user.otheruser.groupaccess' ]); } sub test_list_groupaccess_racl - :ReverseACLs :min_version_3_7 :NoAltNamespace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); - - $admintalk->create("user.otheruser.groupaccess"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - $admintalk->setacl("user.otheruser.groupaccess", - "group:group co", "lrswipkxtecdn"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - if (get_verbose()) { - $self->{instance}->run_command( - { cyrus => 1, }, - 'cyr_dbtool', - "$self->{instance}->{basedir}/conf/mailboxes.db", - 'twoskip', - 'show' - ); - } + : ReverseACLs : min_version_3_7 : NoAltNamespace { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + + $admintalk->create("user.otheruser.groupaccess"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + $admintalk->setacl("user.otheruser.groupaccess", "group:group co", + "lrswipkxtecdn"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $list = $imaptalk->list("", "*"); - my @boxes = sort map { $_->[2] } @{$list}; + if (get_verbose()) { + $self->{instance}->run_command( + { cyrus => 1, }, + 'cyr_dbtool', "$self->{instance}->{basedir}/conf/mailboxes.db", + 'twoskip', 'show' + ); + } + + my $list = $imaptalk->list("", "*"); + my @boxes = sort map { $_->[2] } @{$list}; - $self->assert_deep_equals(\@boxes, - ['INBOX', 'user.otheruser.groupaccess']); + $self->assert_deep_equals(\@boxes, [ 'INBOX', 'user.otheruser.groupaccess' ]); } sub do_test_list_order - :min_version_3_7 -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.zzz"); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); - - $imaptalk->create("INBOX.aaa"); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); - - my %adminfolders = ( - 'user.otheruser.order-user' => 'cassandane', - 'user.otheruser.order-co' => 'group:group co', - 'user.otheruser.order-c' => 'group:group c', - 'user.otheruser.order-o' => 'group:group o', - 'shared.order-co' => 'group:group co', - 'shared.order-c' => 'group:group c', - 'shared.order-o' => 'group:group o', + : min_version_3_7 { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.zzz"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->create("INBOX.aaa"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + my %adminfolders = ( + 'user.otheruser.order-user' => 'cassandane', + 'user.otheruser.order-co' => 'group:group co', + 'user.otheruser.order-c' => 'group:group c', + 'user.otheruser.order-o' => 'group:group o', + 'shared.order-co' => 'group:group co', + 'shared.order-c' => 'group:group c', + 'shared.order-o' => 'group:group o', + ); + + while (my ($folder, $identifier) = each %adminfolders) { + $admintalk->create($folder); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "created folder $folder successfully" ); - while (my ($folder, $identifier) = each %adminfolders) { - $admintalk->create($folder); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "created folder $folder successfully"); - - $admintalk->setacl($folder, $identifier, "lrswipkxtecdn"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "setacl folder $folder for $identifier successfully"); - - if ($folder =~ m/^shared/) { - # subvert default permissions on shared namespace for - # purpose of testing ordering - $admintalk->setacl($folder, "anyone", "p"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "setacl folder $folder for anyone successfully"); - } - } + $admintalk->setacl($folder, $identifier, "lrswipkxtecdn"); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "setacl folder $folder for $identifier successfully" + ); - if (get_verbose()) { - $self->{instance}->run_command( - { cyrus => 1, }, - 'cyr_dbtool', - "$self->{instance}->{basedir}/conf/mailboxes.db", - 'twoskip', - 'show' - ); + if ($folder =~ m/^shared/) { + # subvert default permissions on shared namespace for + # purpose of testing ordering + $admintalk->setacl($folder, "anyone", "p"); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "setacl folder $folder for anyone successfully" + ); } + } - my $list = $imaptalk->list("", "*"); - my @boxes = map { $_->[2] } @{$list}; - - # Note: order is - # * mine, alphabetically, - # * other users', alphabetically, - # * shared, alphabetically - # ... which is not the order we created them ;) - # Also, the "order-o" folders are not returned, because cassandane - # is not a member of that group - my @expect = qw( - INBOX - INBOX.aaa - INBOX.zzz - user.otheruser.order-c - user.otheruser.order-co - user.otheruser.order-user + if (get_verbose()) { + $self->{instance}->run_command( + { cyrus => 1, }, + 'cyr_dbtool', "$self->{instance}->{basedir}/conf/mailboxes.db", + 'twoskip', 'show' ); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min > 4)) { - push @expect, qw(shared); - } - push @expect, qw( shared.order-c shared.order-co ); - $self->assert_deep_equals(\@boxes, \@expect); + } + + my $list = $imaptalk->list("", "*"); + my @boxes = map { $_->[2] } @{$list}; + + # Note: order is + # * mine, alphabetically, + # * other users', alphabetically, + # * shared, alphabetically + # ... which is not the order we created them ;) + # Also, the "order-o" folders are not returned, because cassandane + # is not a member of that group + my @expect = qw( + INBOX + INBOX.aaa + INBOX.zzz + user.otheruser.order-c + user.otheruser.order-co + user.otheruser.order-user + ); + my ($maj, $min) = Cassandane::Instance->get_version(); + + if ($maj > 3 || ($maj == 3 && $min > 4)) { + push @expect, qw(shared); + } + push @expect, qw( shared.order-c shared.order-co ); + $self->assert_deep_equals(\@boxes, \@expect); } sub test_list_order_noracl - :min_version_3_7 :NoAltNamespace -{ - my $self = shift; - return $self->do_test_list_order(@_); + : min_version_3_7 : NoAltNamespace { + my $self = shift; + return $self->do_test_list_order(@_); } sub test_list_order_racl - :ReverseACLs :min_version_3_7 :NoAltNamespace -{ - my $self = shift; - return $self->do_test_list_order(@_); + : ReverseACLs : min_version_3_7 : NoAltNamespace { + my $self = shift; + return $self->do_test_list_order(@_); } 1; diff --git a/cassandane/Cassandane/Cyrus/ID.pm b/cassandane/Cassandane/Cyrus/ID.pm index b2185c3650..2063300de5 100644 --- a/cassandane/Cassandane/Cyrus/ID.pm +++ b/cassandane/Cassandane/Cyrus/ID.pm @@ -47,53 +47,49 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({ }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_cmd_id -{ - my ($self) = @_; +sub test_cmd_id { + my ($self) = @_; - # Purge any syslog lines before this test runs. - $self->{instance}->getsyslog(); + # Purge any syslog lines before this test runs. + $self->{instance}->getsyslog(); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - return if not $imaptalk->capability()->{id}; + return if not $imaptalk->capability()->{id}; - my $res = $imaptalk->id(name => "cassandane"); - xlog $self, Dumper $res; + my $res = $imaptalk->id(name => "cassandane"); + xlog $self, Dumper $res; - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - # should have logged some timer output, which should include the sess id, - # and since we sent a client id via IMAP ID, we should get that, too! - if ($self->{instance}->{have_syslog_replacement}) { - # make sure that the connection is ended so that imapd reset happens - $imaptalk->logout(); - undef $imaptalk; + # should have logged some timer output, which should include the sess id, + # and since we sent a client id via IMAP ID, we should get that, too! + if ($self->{instance}->{have_syslog_replacement}) { + # make sure that the connection is ended so that imapd reset happens + $imaptalk->logout(); + undef $imaptalk; - my @behavior_lines = $self->{instance}->getsyslog(qr/session ended/); + my @behavior_lines = $self->{instance}->getsyslog(qr/session ended/); - $self->assert_num_gte(1, scalar @behavior_lines); + $self->assert_num_gte(1, scalar @behavior_lines); - $self->assert_matches(qr/\bid\.name=/, $_) for @behavior_lines; - } + $self->assert_matches(qr/\bid\.name=/, $_) for @behavior_lines; + } } 1; diff --git a/cassandane/Cassandane/Cyrus/IMAP4rev2.pm b/cassandane/Cassandane/Cyrus/IMAP4rev2.pm index 2051f8ba68..931a8abfc8 100644 --- a/cassandane/Cassandane/Cyrus/IMAP4rev2.pm +++ b/cassandane/Cassandane/Cyrus/IMAP4rev2.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::IMAP4rev2; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use Data::Dumper; @@ -50,237 +50,229 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Util::NetString; - -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1, services => ['imap'] }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1, services => ['imap'] }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_basic - :NoAltNameSpace :min_version_3_7 -{ - my ($self) = @_; - - xlog $self, "Make some messages"; - my $uid = 1; - my %msgs; - for (1..20) + : NoAltNameSpace : min_version_3_7 { + my ($self) = @_; + + xlog $self, "Make some messages"; + my $uid = 1; + my %msgs; + for (1 .. 20) { + $msgs{$uid} = $self->make_message("Message $uid"); + $msgs{$uid}->set_attribute('uid', $uid); + $uid++; + } + + my $talk = $self->{store}->get_client(); + $talk->unselect(); + + xlog $self, "Create mailbox with mUTF7 encoded name"; + my $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.&JgA-"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "ENABLE IMAP4rev2"; + $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "IMAP4rev2"); + $self->assert_num_equals(1, $res->{imap4rev2}); + + xlog $self, "Verify that LIST responses use UTF8 mailbox names"; + $res = $talk->list("", "*"); + $self->assert_mailbox_structure( + $res, '.', { - $msgs{$uid} = $self->make_message("Message $uid"); - $msgs{$uid}->set_attribute('uid', $uid); - $uid++; + 'INBOX' => [qw( \\HasChildren )], + "INBOX.☀" => [qw( \\HasNoChildren )], } - - my $talk = $self->{store}->get_client(); - $talk->unselect(); - - xlog $self, "Create mailbox with mUTF7 encoded name"; - my $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.&JgA-"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "ENABLE IMAP4rev2"; - $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "IMAP4rev2"); - $self->assert_num_equals(1, $res->{imap4rev2}); - - xlog $self, "Verify that LIST responses use UTF8 mailbox names"; - $res = $talk->list("", "*"); - $self->assert_mailbox_structure($res, '.', { - 'INBOX' => [qw( \\HasChildren )], - "INBOX.☀" => [qw( \\HasNoChildren )], - }); - - xlog $self, "EXAMINE mailbox with UTF8 mailbox name"; - $res = $talk->_imap_cmd('EXAMINE', 0, "", "INBOX.☀"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that LIST response is returned with UTF8 mailbox name"; - my @list = $talk->get_response_code('list'); - $self->assert_str_equals("INBOX.☀", $list[0][0][2]); - - xlog $self, "Mark some messages \\Deleted"; - $talk->select('INBOX'); - $res = $talk->store('5:9', '+flags', '(\\Deleted)'); - - xlog $self, "Verify that FETCH responses include UID"; - $self->assert_str_equals("5", $res->{5}->{uid}); - $self->assert_str_equals("6", $res->{6}->{uid}); - $self->assert_str_equals("7", $res->{7}->{uid}); - $self->assert_str_equals("8", $res->{8}->{uid}); - $self->assert_str_equals("9", $res->{9}->{uid}); - - xlog $self, "Check STATUS (DELETED)"; - $res = $talk->status('INBOX', [ 'deleted' ]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals("5", $res->{deleted}); - - xlog $self, "SEARCH DELETED"; - my @results = (); - my %handlers = - ( - esearch => sub - { - my (undef, $esearch) = @_; - push(@results, $esearch); - }, - ); - $res = $talk->_imap_cmd('SEARCH', 0, \%handlers, 'DELETED'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that ESEARCH response is returned"; - $self->assert_num_equals(1, scalar @results); - $self->assert_str_equals('5:9', $results[0][2]); - - xlog $self, "COPY a deleted message to mailbox with UTF8 name"; - $res = $talk->_imap_cmd('COPY', 0, "", '5', "INBOX.☀"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "ESEARCH IN (PERSONAL) DELETED"; - @results = (); - $res = $talk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(PERSONAL)', 'DELETED'); - - xlog $self, "Verify that ESEARCH response uses UTF8 mailbox name"; - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_num_equals(2, scalar @results); - $self->assert_str_equals("INBOX", $results[0][0][3]); - $self->assert_str_equals('5:9', $results[0][3]); - $self->assert_str_equals("INBOX.☀", $results[1][0][3]); - $self->assert_str_equals('1', $results[1][3]); + ); + + xlog $self, "EXAMINE mailbox with UTF8 mailbox name"; + $res = $talk->_imap_cmd('EXAMINE', 0, "", "INBOX.☀"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that LIST response is returned with UTF8 mailbox name"; + my @list = $talk->get_response_code('list'); + $self->assert_str_equals("INBOX.☀", $list[0][0][2]); + + xlog $self, "Mark some messages \\Deleted"; + $talk->select('INBOX'); + $res = $talk->store('5:9', '+flags', '(\\Deleted)'); + + xlog $self, "Verify that FETCH responses include UID"; + $self->assert_str_equals("5", $res->{5}->{uid}); + $self->assert_str_equals("6", $res->{6}->{uid}); + $self->assert_str_equals("7", $res->{7}->{uid}); + $self->assert_str_equals("8", $res->{8}->{uid}); + $self->assert_str_equals("9", $res->{9}->{uid}); + + xlog $self, "Check STATUS (DELETED)"; + $res = $talk->status('INBOX', ['deleted']); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals("5", $res->{deleted}); + + xlog $self, "SEARCH DELETED"; + my @results = (); + my %handlers = ( + esearch => sub { + my (undef, $esearch) = @_; + push(@results, $esearch); + }, + ); + $res = $talk->_imap_cmd('SEARCH', 0, \%handlers, 'DELETED'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that ESEARCH response is returned"; + $self->assert_num_equals(1, scalar @results); + $self->assert_str_equals('5:9', $results[0][2]); + + xlog $self, "COPY a deleted message to mailbox with UTF8 name"; + $res = $talk->_imap_cmd('COPY', 0, "", '5', "INBOX.☀"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "ESEARCH IN (PERSONAL) DELETED"; + @results = (); + $res + = $talk->_imap_cmd('ESEARCH', 0, \%handlers, 'IN', '(PERSONAL)', 'DELETED'); + + xlog $self, "Verify that ESEARCH response uses UTF8 mailbox name"; + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_num_equals(2, scalar @results); + $self->assert_str_equals("INBOX", $results[0][0][3]); + $self->assert_str_equals('5:9', $results[0][3]); + $self->assert_str_equals("INBOX.☀", $results[1][0][3]); + $self->assert_str_equals('1', $results[1][3]); } sub test_oldname - :NoAltNameSpace :min_version_3_7 -{ - my ($self) = @_; - - xlog $self, "Make some messages"; - my $uid = 1; - my %msgs; - for (1..20) - { - $msgs{$uid} = $self->make_message("Message $uid"); - $msgs{$uid}->set_attribute('uid', $uid); - $uid++; - } - - my $talk = $self->{store}->get_client(); - $talk->unselect(); - - xlog $self, "ENABLE IMAP4rev2"; - my $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "IMAP4rev2"); - $self->assert_num_equals(1, $res->{imap4rev2}); - - my @results = (); - my %handlers = - ( - list => sub - { - my (undef, $list) = @_; - push(@results, $list); - }, - ); - - xlog $self, "Create a mailbox with denormalized mailbox name"; - $res = $talk->_imap_cmd('CREATE', 0, \%handlers, "INBOX.Å"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that LIST response is returned with OLDNAME"; - $self->assert_str_equals("INBOX.Å", $results[0][2]); - $self->assert_str_equals('OLDNAME', $results[0][3][0]); - $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); - - xlog $self, "Create a child mailbox with normalized mailbox name"; - @results = (); - $res = $talk->_imap_cmd('CREATE', 0, \%handlers, "INBOX.Å.B"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that there is no LIST response"; - $self->assert_null($results[0]); - - xlog $self, "Append to mailbox with denormalized mailbox name"; - my $MsgTxt = <make_message("Message $uid"); + $msgs{$uid}->set_attribute('uid', $uid); + $uid++; + } + + my $talk = $self->{store}->get_client(); + $talk->unselect(); + + xlog $self, "ENABLE IMAP4rev2"; + my $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "IMAP4rev2"); + $self->assert_num_equals(1, $res->{imap4rev2}); + + my @results = (); + my %handlers = ( + list => sub { + my (undef, $list) = @_; + push(@results, $list); + }, + ); + + xlog $self, "Create a mailbox with denormalized mailbox name"; + $res = $talk->_imap_cmd('CREATE', 0, \%handlers, "INBOX.Å"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that LIST response is returned with OLDNAME"; + $self->assert_str_equals("INBOX.Å", $results[0][2]); + $self->assert_str_equals('OLDNAME', $results[0][3][0]); + $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); + + xlog $self, "Create a child mailbox with normalized mailbox name"; + @results = (); + $res = $talk->_imap_cmd('CREATE', 0, \%handlers, "INBOX.Å.B"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that there is no LIST response"; + $self->assert_null($results[0]); + + xlog $self, "Append to mailbox with denormalized mailbox name"; + my $MsgTxt = <_imap_cmd('APPEND', 0, \%handlers, "INBOX.Å", { Literal => $MsgTxt }); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that LIST response is returned with OLDNAME"; - $self->assert_str_equals("INBOX.Å", $results[0][2]); - $self->assert_str_equals('OLDNAME', $results[0][3][0]); - $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); - - xlog $self, "EXAMINE mailbox with denormalized mailbox name"; - @results = (); - $res = $talk->_imap_cmd('EXAMINE', 0, \%handlers, "INBOX.Å"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that LIST response is returned with OLDNAME"; - $self->assert_str_equals("INBOX.Å", $results[0][2]); - $self->assert_str_equals('OLDNAME', $results[0][3][0]); - $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); - - $talk->unselect(); - - xlog $self, "RENAME mailbox with denormalized mailbox names"; - @results = (); - $res = $talk->_imap_cmd('RENAME', 0, \%handlers, "INBOX.Å", "INBOX.Ω"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that LIST responses are returned with OLDNAMEs"; - $self->assert_str_equals("\\NonExistent", $results[0][0][0]); - $self->assert_str_equals("INBOX.Å", $results[0][2]); - $self->assert_str_equals('OLDNAME', $results[0][3][0]); - $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); - $self->assert_str_equals("\\HasChildren", $results[1][0][0]); - $self->assert_str_equals("INBOX.Ω", $results[1][2]); - $self->assert_str_equals('OLDNAME', $results[1][3][0]); - $self->assert_str_equals('INBOX.Ω', $results[1][3][1][0]); - - xlog $self, "LIST renamed mailbox"; - @results = (); - $res = $talk->_imap_cmd('LIST', 0, \%handlers, "", "INBOX.Ω"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that OLDNAME appears in LIST response"; - $self->assert_str_equals('OLDNAME', $results[0][3][0]); - $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); - - xlog $self, "DELETE a child mailbox with normalized mailbox name"; - @results = (); - $res = $talk->_imap_cmd('DELETE', 0, \%handlers, "INBOX.Ω.B"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that there is no LIST response"; - $self->assert_null($results[0]); - - xlog $self, "DELETE mailbox with denormalized mailbox name"; - @results = (); - $res = $talk->_imap_cmd('DELETE', 0, \%handlers, "INBOX.Ω"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that LIST response is returned with OLDNAME"; - $self->assert_str_equals("\\NonExistent", $results[0][0][0]); - $self->assert_str_equals("INBOX.Ω", $results[0][2]); - $self->assert_str_equals('OLDNAME', $results[0][3][0]); - $self->assert_str_equals('INBOX.Ω', $results[0][3][1][0]); + $MsgTxt =~ s/\n/\015\012/g; + @results = (); + $res = $talk->_imap_cmd('APPEND', 0, \%handlers, "INBOX.Å", + { Literal => $MsgTxt }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that LIST response is returned with OLDNAME"; + $self->assert_str_equals("INBOX.Å", $results[0][2]); + $self->assert_str_equals('OLDNAME', $results[0][3][0]); + $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); + + xlog $self, "EXAMINE mailbox with denormalized mailbox name"; + @results = (); + $res = $talk->_imap_cmd('EXAMINE', 0, \%handlers, "INBOX.Å"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that LIST response is returned with OLDNAME"; + $self->assert_str_equals("INBOX.Å", $results[0][2]); + $self->assert_str_equals('OLDNAME', $results[0][3][0]); + $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); + + $talk->unselect(); + + xlog $self, "RENAME mailbox with denormalized mailbox names"; + @results = (); + $res = $talk->_imap_cmd('RENAME', 0, \%handlers, "INBOX.Å", "INBOX.Ω"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that LIST responses are returned with OLDNAMEs"; + $self->assert_str_equals("\\NonExistent", $results[0][0][0]); + $self->assert_str_equals("INBOX.Å", $results[0][2]); + $self->assert_str_equals('OLDNAME', $results[0][3][0]); + $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); + $self->assert_str_equals("\\HasChildren", $results[1][0][0]); + $self->assert_str_equals("INBOX.Ω", $results[1][2]); + $self->assert_str_equals('OLDNAME', $results[1][3][0]); + $self->assert_str_equals('INBOX.Ω', $results[1][3][1][0]); + + xlog $self, "LIST renamed mailbox"; + @results = (); + $res = $talk->_imap_cmd('LIST', 0, \%handlers, "", "INBOX.Ω"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that OLDNAME appears in LIST response"; + $self->assert_str_equals('OLDNAME', $results[0][3][0]); + $self->assert_str_equals('INBOX.Å', $results[0][3][1][0]); + + xlog $self, "DELETE a child mailbox with normalized mailbox name"; + @results = (); + $res = $talk->_imap_cmd('DELETE', 0, \%handlers, "INBOX.Ω.B"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that there is no LIST response"; + $self->assert_null($results[0]); + + xlog $self, "DELETE mailbox with denormalized mailbox name"; + @results = (); + $res = $talk->_imap_cmd('DELETE', 0, \%handlers, "INBOX.Ω"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that LIST response is returned with OLDNAME"; + $self->assert_str_equals("\\NonExistent", $results[0][0][0]); + $self->assert_str_equals("INBOX.Ω", $results[0][2]); + $self->assert_str_equals('OLDNAME', $results[0][3][0]); + $self->assert_str_equals('INBOX.Ω', $results[0][3][1][0]); } 1; diff --git a/cassandane/Cassandane/Cyrus/IMAPLimits.pm b/cassandane/Cassandane/Cyrus/IMAPLimits.pm index 563be8abdd..50967f67e6 100644 --- a/cassandane/Cassandane/Cyrus/IMAPLimits.pm +++ b/cassandane/Cassandane/Cyrus/IMAPLimits.pm @@ -60,512 +60,473 @@ $email =~ s/\r?\n/\r\n/gs; my $toobig_email = $email . "X" x 100; # Check that we got an untagged BYE [TOOBIG] response -sub assert_bye_toobig -{ - my ($self, $store) = @_; - - $store = $self->{store} if (!defined $store); - - # We want to override Mail::IMAPTalk's builtin handling of the BYE - # untagged response, as it will 'die' immediately without parsing - # the remainder of the line and especially without picking out the - # [TOOBIG] response code that we want to see. - my $got_toobig = 0; - my $handlers = - { - bye => sub - { - my (undef, $resp) = @_; - $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); - } - }; +sub assert_bye_toobig { + my ($self, $store) = @_; + + $store = $self->{store} if (!defined $store); + + # We want to override Mail::IMAPTalk's builtin handling of the BYE + # untagged response, as it will 'die' immediately without parsing + # the remainder of the line and especially without picking out the + # [TOOBIG] response code that we want to see. + my $got_toobig = 0; + my $handlers = { + bye => sub { + my (undef, $resp) = @_; + $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); + } + }; - $store->idle_response($handlers, 1); - $self->assert_num_equals(1, $got_toobig); + $store->idle_response($handlers, 1); + $self->assert_num_equals(1, $got_toobig); } # Send a command and expect an untagged BYE [TOOBIG] response -sub assert_cmd_bye_toobig -{ - my $self = shift; - my $cmd = shift; +sub assert_cmd_bye_toobig { + my $self = shift; + my $cmd = shift; - my $talk = $self->{store}->get_client(); - $talk->enable('qresync'); # IMAPTalk requires lower-case - $talk->select('INBOX'); + my $talk = $self->{store}->get_client(); + $talk->enable('qresync'); # IMAPTalk requires lower-case + $talk->select('INBOX'); - $talk->_send_cmd($cmd, @_); - $self->assert_bye_toobig(); + $talk->_send_cmd($cmd, @_); + $self->assert_bye_toobig(); } # Check that we got a tagged NO [TOOBIG] response -sub assert_no_toobig -{ - my ($self, $talk) = @_; +sub assert_no_toobig { + my ($self, $talk) = @_; - my $got_toobig = 0; - my $handlers = - { - 'no' => sub - { - my (undef, $resp) = @_; - $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); - } - }; + my $got_toobig = 0; + my $handlers = { + 'no' => sub { + my (undef, $resp) = @_; + $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); + } + }; - eval { - $talk->_parse_response($handlers); - }; + eval { $talk->_parse_response($handlers); }; - $self->assert_num_equals(1, $got_toobig); + $self->assert_num_equals(1, $got_toobig); } # Send a command and expect a tagged NO [TOOBIG] response -sub assert_cmd_no_toobig -{ - my $self = shift; - my $talk = shift; - my $cmd = shift; +sub assert_cmd_no_toobig { + my $self = shift; + my $talk = shift; + my $cmd = shift; - $talk->_send_cmd($cmd, @_); - $self->assert_no_toobig($talk); + $talk->_send_cmd($cmd, @_); + $self->assert_no_toobig($talk); } -sub new -{ - my $class = shift; +sub new { + my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(maxword => 25); - $config->set(maxquoted => 25); - $config->set(maxliteral => 25); - $config->set(literalminus => 1); - $config->set(maxargssize => 45); - $config->set(maxmessagesize => 100); - $config->set(event_groups => "message mailbox applepushservice"); - $config->set(aps_topic => "mail"); + my $config = Cassandane::Config->default()->clone(); + $config->set(maxword => 25); + $config->set(maxquoted => 25); + $config->set(maxliteral => 25); + $config->set(literalminus => 1); + $config->set(maxargssize => 45); + $config->set(maxmessagesize => 100); + $config->set(event_groups => "message mailbox applepushservice"); + $config->set(aps_topic => "mail"); - return $class->SUPER::new({ - adminstore => 1, - config => $config, - services => ['imap'], - }, @_); + return $class->SUPER::new( + { + adminstore => 1, + config => $config, + services => ['imap'], + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_maxword -{ - my ($self) = @_; +sub test_maxword { + my ($self) = @_; - # Oversized command name - $self->assert_cmd_bye_toobig("X" x 26); + # Oversized command name + $self->assert_cmd_bye_toobig("X" x 26); } -sub test_maxword_astring -{ - my ($self) = @_; +sub test_maxword_astring { + my ($self) = @_; - # Oversized mailbox name - $self->assert_cmd_bye_toobig('SELECT', "X" x 26); + # Oversized mailbox name + $self->assert_cmd_bye_toobig('SELECT', "X" x 26); } -sub test_maxquoted -{ - my ($self) = @_; +sub test_maxquoted { + my ($self) = @_; - # Oversized mailbox name - $self->assert_cmd_bye_toobig('SELECT', { Quote => "X" x 26 }); + # Oversized mailbox name + $self->assert_cmd_bye_toobig('SELECT', { Quote => "X" x 26 }); } -sub test_maxliteral_nosync -{ - my ($self) = @_; +sub test_maxliteral_nosync { + my ($self) = @_; - my $talk = $self->{store}->get_client(); - # Do this by brute force until we have IMAPTalk v4.06+ - $talk->_imap_socket_out($talk->{CmdId}++ . " SELECT {26+}\015\012"); - $self->assert_bye_toobig(); + my $talk = $self->{store}->get_client(); + # Do this by brute force until we have IMAPTalk v4.06+ + $talk->_imap_socket_out($talk->{CmdId}++ . " SELECT {26+}\015\012"); + $self->assert_bye_toobig(); } -sub test_maxliteral_sync -{ - my ($self) = @_; +sub test_maxliteral_sync { + my ($self) = @_; - # Unlike oversized non-sync literals which fatal() in one central location, - # oversized sync literals fail with a NO response in multiple places, - # so we test as many of those places as possible. - # Having said that, arguments parsed in cmdloop() or in get_search_criterion() - # are mostly handled centrally. + # Unlike oversized non-sync literals which fatal() in one central location, + # oversized sync literals fail with a NO response in multiple places, + # so we test as many of those places as possible. + # Having said that, arguments parsed in cmdloop() or in get_search_criterion() + # are mostly handled centrally. - # Authenticated State + # Authenticated State - # Synchronizing literals are the default in IMAPTalk v4.05 (and earlier) - my $talk = $self->{store}->get_client(NoLiteralPlus => 1); + # Synchronizing literals are the default in IMAPTalk v4.05 (and earlier) + my $talk = $self->{store}->get_client(NoLiteralPlus => 1); - $self->assert_cmd_no_toobig($talk, 'SELECT', - { Literal => "X" x 26 }); + $self->assert_cmd_no_toobig($talk, 'SELECT', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'ID', - [ { Literal => "X" x 26 } ]); + $self->assert_cmd_no_toobig($talk, 'ID', [ { Literal => "X" x 26 } ]); - $self->assert_cmd_no_toobig($talk, 'ID', - [ { Quote => 'foo' }, { Literal => "X" x 26 } ] ); + $self->assert_cmd_no_toobig($talk, 'ID', + [ { Quote => 'foo' }, { Literal => "X" x 26 } ]); - $self->assert_cmd_no_toobig($talk, 'LIST', - { Literal => "X" x 26 }); + $self->assert_cmd_no_toobig($talk, 'LIST', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'LIST', - { Quote => '' }, { Literal => "X" x 26 }); + $self->assert_cmd_no_toobig( + $talk, 'LIST', + { Quote => '' }, + { Literal => "X" x 26 } + ); - $self->assert_cmd_no_toobig($talk, 'NOTIFY', - 'SET', [ 'MAILBOXES', [ { Literal => "X" x 26 } ] ] ); + $self->assert_cmd_no_toobig($talk, 'NOTIFY', + 'SET', [ 'MAILBOXES', [ { Literal => "X" x 26 } ] ]); - $self->assert_cmd_no_toobig($talk, 'LISTRIGHTS', - 'INBOX', { Literal => "X" x 26 }); + $self->assert_cmd_no_toobig($talk, 'LISTRIGHTS', + 'INBOX', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'SETACL', - 'INBOX', 'anyone', { Literal => "X" x 26 }); + $self->assert_cmd_no_toobig($talk, 'SETACL', + 'INBOX', 'anyone', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'GETMETADATA', - 'INBOX', { Literal => "X" x 26 } ); + $self->assert_cmd_no_toobig($talk, 'GETMETADATA', + 'INBOX', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'GETMETADATA', - 'INBOX', [ { Literal => "X" x 26 } ] ); + $self->assert_cmd_no_toobig($talk, 'GETMETADATA', + 'INBOX', [ { Literal => "X" x 26 } ]); - $self->assert_cmd_no_toobig($talk, 'SETMETADATA', - 'INBOX', [ { Literal => "X" x 26 } ] ); + $self->assert_cmd_no_toobig($talk, 'SETMETADATA', + 'INBOX', [ { Literal => "X" x 26 } ]); - $self->assert_cmd_no_toobig($talk, 'SETMETADATA', - 'INBOX', [ '/comment', { Literal => "X" x 26 } ] ); + $self->assert_cmd_no_toobig($talk, 'SETMETADATA', + 'INBOX', [ '/comment', { Literal => "X" x 26 } ]); - $self->assert_cmd_no_toobig($talk, 'XAPPLEPUSHSERVICE', - { Literal => "X" x 26 }); + $self->assert_cmd_no_toobig($talk, 'XAPPLEPUSHSERVICE', + { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'XAPPLEPUSHSERVICE', - 'FOO', { Literal => "X" x 26 }); + $self->assert_cmd_no_toobig($talk, 'XAPPLEPUSHSERVICE', + 'FOO', { Literal => "X" x 26 }); - # Selected State - $talk->select('INBOX'); + # Selected State + $talk->select('INBOX'); - $self->assert_cmd_no_toobig($talk, 'FETCH', - '1', [ 'ANNOTATION', - [ { Literal => "X" x 26 } ] ] ); + $self->assert_cmd_no_toobig($talk, 'FETCH', + '1', [ 'ANNOTATION', [ { Literal => "X" x 26 } ] ]); - $self->assert_cmd_no_toobig($talk, 'FETCH', - '1', [ 'BODY[HEADER.FIELDS', - [ { Literal => "X" x 26 } ] ] ); + $self->assert_cmd_no_toobig($talk, 'FETCH', + '1', [ 'BODY[HEADER.FIELDS', [ { Literal => "X" x 26 } ] ]); - $self->assert_cmd_no_toobig($talk, 'FETCH', - '1', [ 'RFC822.HEADER.LINES', - [ { Literal => "X" x 26 } ] ] ); + $self->assert_cmd_no_toobig($talk, 'FETCH', + '1', [ 'RFC822.HEADER.LINES', [ { Literal => "X" x 26 } ] ]); - $self->assert_cmd_no_toobig($talk, 'STORE', - '1', 'ANNOTATION', [ { Literal => "X" x 26 } ] ); + $self->assert_cmd_no_toobig($talk, 'STORE', + '1', 'ANNOTATION', [ { Literal => "X" x 26 } ]); - $self->assert_cmd_no_toobig($talk, 'STORE', - '1', 'ANNOTATION', - [ { Quote => '/comment' }, - [ { Literal => "X" x 26 } ] ] ); + $self->assert_cmd_no_toobig($talk, 'STORE', '1', 'ANNOTATION', + [ { Quote => '/comment' }, [ { Literal => "X" x 26 } ] ]); - $self->assert_cmd_no_toobig($talk, 'STORE', - '1', 'ANNOTATION', - [ { Quote => '/comment' }, - [ { Quote => 'value' }, - { Literal => "X" x 26 } ] ] ); + $self->assert_cmd_no_toobig( + $talk, 'STORE', '1', + 'ANNOTATION', + [ + { Quote => '/comment' }, + [ { Quote => 'value' }, { Literal => "X" x 26 } ] + ] + ); - $self->assert_cmd_no_toobig($talk, 'SEARCH', - 'HEADER', { Literal => "X" x 26 } ); + $self->assert_cmd_no_toobig($talk, 'SEARCH', + 'HEADER', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'SEARCH', - 'HEADER', 'SUBJECT', { Literal => "X" x 26 } ); + $self->assert_cmd_no_toobig($talk, 'SEARCH', + 'HEADER', 'SUBJECT', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'SEARCH', - 'ANNOTATION', { Literal => "X" x 26 } ); + $self->assert_cmd_no_toobig($talk, 'SEARCH', + 'ANNOTATION', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'SEARCH', - 'ANNOTATION', '/comment', { Literal => "X" x 26 } ); + $self->assert_cmd_no_toobig($talk, 'SEARCH', + 'ANNOTATION', '/comment', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'SEARCH', - 'ANNOTATION', '/comment', - 'value', { Literal => "X" x 26 } ); + $self->assert_cmd_no_toobig($talk, 'SEARCH', 'ANNOTATION', '/comment', + 'value', { Literal => "X" x 26 }); - $self->assert_cmd_no_toobig($talk, 'ESEARCH', - 'IN', [ 'MAILBOXES', { Literal => "X" x 26 } ] ); + $self->assert_cmd_no_toobig($talk, 'ESEARCH', + 'IN', [ 'MAILBOXES', { Literal => "X" x 26 } ]); } -sub test_maxargssize_append_flags -{ - my ($self) = @_; +sub test_maxargssize_append_flags { + my ($self) = @_; - $self->assert_cmd_bye_toobig('APPEND', 'INBOX', - [ "X" x 25, "X" x 25 ], { Literal => $email } ); + $self->assert_cmd_bye_toobig( + 'APPEND', 'INBOX', + [ "X" x 25, "X" x 25 ], + { Literal => $email } + ); } -sub test_maxargssize_append_annot -{ - my ($self) = @_; +sub test_maxargssize_append_annot { + my ($self) = @_; - # Use MULTIAPPEND, fail the second - $self->assert_cmd_bye_toobig('APPEND', 'INBOX', - { Literal => $email }, - 'ANNOTATION', - [ "X" x 25, [ 'VALUE', { Quote => "X" x 25 } ] ], - { Literal => $email } ); + # Use MULTIAPPEND, fail the second + $self->assert_cmd_bye_toobig( + 'APPEND', 'INBOX', { Literal => $email }, + 'ANNOTATION', + [ "X" x 25, [ 'VALUE', { Quote => "X" x 25 } ] ], + { Literal => $email } + ); } -sub test_maxargssize_create -{ - my ($self) = @_; +sub test_maxargssize_create { + my ($self) = @_; - $self->assert_cmd_bye_toobig('CREATE', "X" x 25, [ "X" x 25 ] ); + $self->assert_cmd_bye_toobig('CREATE', "X" x 25, [ "X" x 25 ]); } -sub test_maxargssize_create_ext -{ - my ($self) = @_; +sub test_maxargssize_create_ext { + my ($self) = @_; - $self->assert_cmd_bye_toobig('CREATE', - "X" x 5, [ "X" x 5, [ "X" x 25, "X" x 25 ] ] ); + $self->assert_cmd_bye_toobig('CREATE', + "X" x 5, [ "X" x 5, [ "X" x 25, "X" x 25 ] ]); } -sub test_maxargssize_fetch -{ - my ($self) = @_; +sub test_maxargssize_fetch { + my ($self) = @_; - $self->assert_cmd_bye_toobig('FETCH', '1', - [ 'BODY', 'ENVELOPE', 'FLAGS', - 'INTERNALDATE', 'RFC822.SIZE' ]); + $self->assert_cmd_bye_toobig('FETCH', '1', + [ 'BODY', 'ENVELOPE', 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ]); } -sub test_maxargssize_fetch_annot -{ - my ($self) = @_; +sub test_maxargssize_fetch_annot { + my ($self) = @_; - $self->assert_cmd_bye_toobig('FETCH', '1', - [ 'ANNOTATION', - [ [ "X" x 25, "X" x 25 ] ], "X" x 5 ] ); + $self->assert_cmd_bye_toobig('FETCH', '1', + [ 'ANNOTATION', [ [ "X" x 25, "X" x 25 ] ], "X" x 5 ]); } -sub test_maxargssize_fetch_annot2 -{ - my ($self) = @_; +sub test_maxargssize_fetch_annot2 { + my ($self) = @_; - $self->assert_cmd_bye_toobig('FETCH', '1', - [ 'ANNOTATION', - [ "X" x 5, [ "X" x 25, "X" x 25 ] ] ] ); + $self->assert_cmd_bye_toobig('FETCH', '1', + [ 'ANNOTATION', [ "X" x 5, [ "X" x 25, "X" x 25 ] ] ]); } -sub test_maxargssize_fetch_headers -{ - my ($self) = @_; +sub test_maxargssize_fetch_headers { + my ($self) = @_; - $self->assert_cmd_bye_toobig('FETCH', '1', - [ 'BODY[HEADER.FIELDS', [ "X" x 25, "X" x 25 ] ] ); + $self->assert_cmd_bye_toobig('FETCH', '1', + [ 'BODY[HEADER.FIELDS', [ "X" x 25, "X" x 25 ] ]); } -sub test_maxargssize_getmetadata -{ - my ($self) = @_; +sub test_maxargssize_getmetadata { + my ($self) = @_; - $self->assert_cmd_bye_toobig('GETMETADATA', 'INBOX', [ "X" x 25, "X" x 25 ] ); + $self->assert_cmd_bye_toobig('GETMETADATA', 'INBOX', [ "X" x 25, "X" x 25 ]); } -sub test_maxargssize_list_multi -{ - my ($self) = @_; +sub test_maxargssize_list_multi { + my ($self) = @_; - $self->assert_cmd_bye_toobig('LIST', { Quote => '' }, [ "X" x 25, "X" x 25 ]); + $self->assert_cmd_bye_toobig('LIST', { Quote => '' }, [ "X" x 25, "X" x 25 ]); } -sub test_maxargssize_list_select -{ - my ($self) = @_; +sub test_maxargssize_list_select { + my ($self) = @_; - $self->assert_cmd_bye_toobig('LIST', - [ 'SUBSCRIBED', 'REMOTE', - 'RECURSIVEMATCH', 'SPECIAL-USE' ], - { Quote => '' }, '*'); + $self->assert_cmd_bye_toobig( + 'LIST', + [ 'SUBSCRIBED', 'REMOTE', 'RECURSIVEMATCH', 'SPECIAL-USE' ], + { Quote => '' }, '*' + ); } -sub test_maxargssize_list_return -{ - my ($self) = @_; +sub test_maxargssize_list_return { + my ($self) = @_; - $self->assert_cmd_bye_toobig('LIST', - { Quote => '' }, '*', 'RETURN', - [ 'SUBSCRIBED', 'CHILDREN', - 'MYRIGHTS', 'SPECIAL-USE' ] ); + $self->assert_cmd_bye_toobig('LIST', { Quote => '' }, + '*', 'RETURN', [ 'SUBSCRIBED', 'CHILDREN', 'MYRIGHTS', 'SPECIAL-USE' ]); } -sub test_maxargssize_notify_events -{ - my ($self) = @_; +sub test_maxargssize_notify_events { + my ($self) = @_; - $self->assert_cmd_bye_toobig('NOTIFY', 'SET', - [ 'SELECTED', - [ 'MessageNew', 'MessageExpunge', 'FlagChange' ] ] ); + $self->assert_cmd_bye_toobig('NOTIFY', 'SET', + [ 'SELECTED', [ 'MessageNew', 'MessageExpunge', 'FlagChange' ] ]); } -sub test_maxargssize_notify_multi -{ - my ($self) = @_; +sub test_maxargssize_notify_multi { + my ($self) = @_; - $self->assert_cmd_bye_toobig('NOTIFY', 'SET', - [ 'PERSONAL', 'NONE' ], - [ 'SELECTED', 'NONE' ], - [ 'SUBSCRIBED', 'NONE' ] ); + $self->assert_cmd_bye_toobig( + 'NOTIFY', 'SET', + [ 'PERSONAL', 'NONE' ], + [ 'SELECTED', 'NONE' ], + [ 'SUBSCRIBED', 'NONE' ] + ); } -sub test_maxargssize_notify_subtree -{ - my ($self) = @_; +sub test_maxargssize_notify_subtree { + my ($self) = @_; - $self->assert_cmd_bye_toobig('NOTIFY', 'SET', - [ 'SUBTREE', [ "X" x 25, "X" x 25 ] ] ); + $self->assert_cmd_bye_toobig('NOTIFY', 'SET', + [ 'SUBTREE', [ "X" x 25, "X" x 25 ] ]); } -sub test_maxargssize_search -{ - my ($self) = @_; +sub test_maxargssize_search { + my ($self) = @_; - $self->assert_cmd_bye_toobig('SEARCH', - 'TEXT', "X" x 25, 'TEXT', { Quote => "X" x 25 } ); + $self->assert_cmd_bye_toobig('SEARCH', + 'TEXT', "X" x 25, 'TEXT', { Quote => "X" x 25 }); } -sub test_maxargssize_multisearch -{ - my ($self) = @_; +sub test_maxargssize_multisearch { + my ($self) = @_; - $self->assert_cmd_bye_toobig('ESEARCH', - 'IN', [ 'MAILBOXES', [ "X" x 25, "X" x 25 ] ]); + $self->assert_cmd_bye_toobig('ESEARCH', + 'IN', [ 'MAILBOXES', [ "X" x 25, "X" x 25 ] ]); } -sub test_maxargssize_select -{ - my ($self) = @_; +sub test_maxargssize_select { + my ($self) = @_; - $self->assert_cmd_bye_toobig('SELECT', 'INBOX', - [ 'QRESYNC', [ '1234567890', '1234567890' ], - 'ANNOTATE' ] ); + $self->assert_cmd_bye_toobig('SELECT', 'INBOX', + [ 'QRESYNC', [ '1234567890', '1234567890' ], 'ANNOTATE' ]); } -sub test_maxargssize_setmetadata -{ - my ($self) = @_; +sub test_maxargssize_setmetadata { + my ($self) = @_; - $self->assert_cmd_bye_toobig('SETMETADATA', 'INBOX', - [ "X" x 25, { Quote => "X" x 25 } ] ); + $self->assert_cmd_bye_toobig('SETMETADATA', 'INBOX', + [ "X" x 25, { Quote => "X" x 25 } ]); } -sub test_maxargssize_setmetadata2 -{ - my ($self) = @_; +sub test_maxargssize_setmetadata2 { + my ($self) = @_; - $self->assert_cmd_bye_toobig('SETMETADATA', 'INBOX', - [ '/shared', { Quote => "X" x 25 }, - '/shared', { Quote => "X" x 25 } ] ); + $self->assert_cmd_bye_toobig('SETMETADATA', 'INBOX', + [ '/shared', { Quote => "X" x 25 }, '/shared', { Quote => "X" x 25 } ]); } -sub test_maxargssize_setquota -{ - my ($self) = @_; +sub test_maxargssize_setquota { + my ($self) = @_; - my $store = $self->{adminstore}; - my $talk = $store->get_client(); + my $store = $self->{adminstore}; + my $talk = $store->get_client(); - $talk->_send_cmd('SETQUOTA', 'user.cassandane', - [ 'STORAGE', '1234567890', - 'MESSAGE', '1234567890', - 'MAILBOX', '1234567890' ] ); - $self->assert_bye_toobig($store); + $talk->_send_cmd( + 'SETQUOTA', + 'user.cassandane', + [ + 'STORAGE', '1234567890', 'MESSAGE', '1234567890', + 'MAILBOX', '1234567890' + ] + ); + $self->assert_bye_toobig($store); } -sub test_maxargssize_sort -{ - my ($self) = @_; +sub test_maxargssize_sort { + my ($self) = @_; - $self->assert_cmd_bye_toobig('SORT', - [ 'ARRIVAL', 'CC', 'DATE', - 'FROM', 'REVERSE', 'SIZE', 'TO' ], - 'UTF-8', 'ALL'); + $self->assert_cmd_bye_toobig('SORT', + [ 'ARRIVAL', 'CC', 'DATE', 'FROM', 'REVERSE', 'SIZE', 'TO' ], + 'UTF-8', 'ALL'); } -sub test_maxargssize_status -{ - my ($self) = @_; +sub test_maxargssize_status { + my ($self) = @_; - $self->assert_cmd_bye_toobig('STATUS', 'INBOX', - [ 'MESSAGES', 'UIDNEXT', - 'UIDVALIDITY', 'UNSEEN', 'SIZE' ] ); + $self->assert_cmd_bye_toobig('STATUS', 'INBOX', + [ 'MESSAGES', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN', 'SIZE' ]); } -sub test_maxargssize_store_annot -{ - my ($self) = @_; +sub test_maxargssize_store_annot { + my ($self) = @_; - $self->assert_cmd_bye_toobig('STORE', '1', 'ANNOTATION', - [ "X" x 25, [ 'VALUE', { Quote => "X" x 25 } ] ] ); + $self->assert_cmd_bye_toobig('STORE', '1', 'ANNOTATION', + [ "X" x 25, [ 'VALUE', { Quote => "X" x 25 } ] ]); } -sub test_maxargssize_store_annot2 -{ - my ($self) = @_; +sub test_maxargssize_store_annot2 { + my ($self) = @_; - $self->assert_cmd_bye_toobig('STORE', '1', 'ANNOTATION', - [ "X" x 5, [ 'VALUE', { Quote => "X" x 25 } ], - "X" x 5, [ 'VALUE', { Quote => "X" x 25 } ] ] ); + $self->assert_cmd_bye_toobig( + 'STORE', '1', + 'ANNOTATION', + [ + "X" x 5, + [ 'VALUE', { Quote => "X" x 25 } ], + "X" x 5, + [ 'VALUE', { Quote => "X" x 25 } ] + ] + ); } -sub test_append_zero -{ - my ($self) = @_; +sub test_append_zero { + my ($self) = @_; - my $talk = $self->{store}->get_client(); - $talk->_imap_cmd('APPEND', 0, '', 'INBOX', { Literal => '' } ); - $self->assert_str_equals('no', $talk->get_last_completion_response()); + my $talk = $self->{store}->get_client(); + $talk->_imap_cmd('APPEND', 0, '', 'INBOX', { Literal => '' }); + $self->assert_str_equals('no', $talk->get_last_completion_response()); } -sub test_maxmessagesize_sync_literal -{ - my ($self) = @_; +sub test_maxmessagesize_sync_literal { + my ($self) = @_; - # Synchronizing literals are the default in IMAPTalk v4.05 (and earlier) - my $talk = $self->{store}->get_client(NoLiteralPlus => 1); + # Synchronizing literals are the default in IMAPTalk v4.05 (and earlier) + my $talk = $self->{store}->get_client(NoLiteralPlus => 1); - $self->assert_cmd_no_toobig($talk, 'APPEND', - 'INBOX', { Literal => $toobig_email } ); + $self->assert_cmd_no_toobig($talk, 'APPEND', + 'INBOX', { Literal => $toobig_email }); } -sub test_maxmessagesize_nosync_literal -{ - my ($self) = @_; +sub test_maxmessagesize_nosync_literal { + my ($self) = @_; - my $talk = $self->{store}->get_client(); - # Do this by brute force until we have IMAPTalk v4.06+ - $talk->_imap_socket_out($talk->{CmdId}++ . " APPEND INBOX {101+}\015\012"); - $self->assert_no_toobig($talk); - $self->assert_bye_toobig(); + my $talk = $self->{store}->get_client(); + # Do this by brute force until we have IMAPTalk v4.06+ + $talk->_imap_socket_out($talk->{CmdId}++ . " APPEND INBOX {101+}\015\012"); + $self->assert_no_toobig($talk); + $self->assert_bye_toobig(); } -sub test_literal_minus -{ - my ($self) = @_; +sub test_literal_minus { + my ($self) = @_; - my $talk = $self->{store}->get_client(); - $talk->_imap_socket_out($talk->{CmdId}++ . " APPEND INBOX {4097+}\015\012"); - $self->assert_no_toobig($talk); - $self->assert_bye_toobig(); + my $talk = $self->{store}->get_client(); + $talk->_imap_socket_out($talk->{CmdId}++ . " APPEND INBOX {4097+}\015\012"); + $self->assert_no_toobig($talk); + $self->assert_bye_toobig(); } 1; diff --git a/cassandane/Cassandane/Cyrus/Idle.pm b/cassandane/Cassandane/Cyrus/Idle.pm index 9ce685eb27..3c710cf178 100644 --- a/cassandane/Cassandane/Cyrus/Idle.pm +++ b/cassandane/Cassandane/Cyrus/Idle.pm @@ -46,565 +46,557 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(imapidlepoll => 2); - return $class->SUPER::new({ - config => $config, - deliver => 1, - start_instances => 0, - }, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config->default()->clone(); + $config->set(imapidlepoll => 2); + return $class->SUPER::new( + { + config => $config, + deliver => 1, + start_instances => 0, + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub start_and_abort_idled -{ - my ($self) = @_; - - # We don't start idled via the START section in cyrus.conf, - # because master would restart it when we kill it, and we - # want to test that the fallback to polling mode works even - # when it's not restarted. - # - # Also note, one of the effects of the -d option is to prevent - # idled forking, which lets us predict which pid to kill. - - my $pid = $self->{instance}->run_command({ - cyrus => 1, - background => 1 - }, 'idled', '-d'); - xlog $self, "pid of idled should be $pid"; - - xlog $self, "giving idled some time to start up"; - my $tries = 60; - my $idle_sock = $self->{instance}->{basedir} . "/conf/socket/idle"; - while ($tries--) { - last if -S $idle_sock; - sleep 1; - } - $self->assert($tries > 0, "idled started successfully"); - - xlog $self, "bring idled's reign to an abrupt and brutal end"; - kill('KILL', $pid) - or die "Failed to kill idled $pid: $!"; - - # reap_command will 'die' because the process terminated - # on SIGKILL. We need to avoid that stopping the test. - # But we still need to waitpid() to avoid zombies. - xlog $self, "reaping pid $pid"; - eval { $self->{instance}->reap_command($pid); }; - - # Now, no idled is running, but any state created by idled in the - # filesystem is still present. In particular, the idle socket. - # Let's check that our assumption is correct. - xlog $self, "check that idle left a socket lying around"; - $self->assert( -S $idle_sock, "$idle_sock exists and is a socket"); +sub start_and_abort_idled { + my ($self) = @_; + + # We don't start idled via the START section in cyrus.conf, + # because master would restart it when we kill it, and we + # want to test that the fallback to polling mode works even + # when it's not restarted. + # + # Also note, one of the effects of the -d option is to prevent + # idled forking, which lets us predict which pid to kill. + + my $pid = $self->{instance}->run_command( + { + cyrus => 1, + background => 1 + }, + 'idled', '-d' + ); + xlog $self, "pid of idled should be $pid"; + + xlog $self, "giving idled some time to start up"; + my $tries = 60; + my $idle_sock = $self->{instance}->{basedir} . "/conf/socket/idle"; + while ($tries--) { + last if -S $idle_sock; + sleep 1; + } + $self->assert($tries > 0, "idled started successfully"); + + xlog $self, "bring idled's reign to an abrupt and brutal end"; + kill('KILL', $pid) + or die "Failed to kill idled $pid: $!"; + + # reap_command will 'die' because the process terminated + # on SIGKILL. We need to avoid that stopping the test. + # But we still need to waitpid() to avoid zombies. + xlog $self, "reaping pid $pid"; + eval { $self->{instance}->reap_command($pid); }; + + # Now, no idled is running, but any state created by idled in the + # filesystem is still present. In particular, the idle socket. + # Let's check that our assumption is correct. + xlog $self, "check that idle left a socket lying around"; + $self->assert(-S $idle_sock, "$idle_sock exists and is a socket"); } -sub test_disabled -{ - my ($self) = @_; +sub test_disabled { + my ($self) = @_; - xlog $self, "Test that the IDLE command can be disabled in"; - xlog $self, "imapd.conf by setting imapidlepoll = 0"; + xlog $self, "Test that the IDLE command can be disabled in"; + xlog $self, "imapd.conf by setting imapidlepoll = 0"; - xlog $self, "Starting up the instance"; - $self->{instance}->{config}->set(imapidlepoll => '0'); - $self->{instance}->start(); - my $svc = $self->{instance}->get_service('imap'); + xlog $self, "Starting up the instance"; + $self->{instance}->{config}->set(imapidlepoll => '0'); + $self->{instance}->start(); + my $svc = $self->{instance}->get_service('imap'); - my $store = $svc->create_store(folder => 'INBOX'); - my $talk = $store->get_client(); + my $store = $svc->create_store(folder => 'INBOX'); + my $talk = $store->get_client(); - xlog $self, "The server should not report the IDLE capability"; - $self->assert(!$talk->capability()->{idle}); + xlog $self, "The server should not report the IDLE capability"; + $self->assert(!$talk->capability()->{idle}); - xlog $self, "The IDLE command should not be recognised"; - # Note that we don't use idle_begin() because that will get - # upset if we get "tag BAD ..." back instead of "+ something". - my $r = $talk->_imap_cmd('idle', 0, ''); - $self->assert_null($r); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - $self->assert_matches(qr/Unrecognized command/, $talk->get_last_error()); + xlog $self, "The IDLE command should not be recognised"; + # Note that we don't use idle_begin() because that will get + # upset if we get "tag BAD ..." back instead of "+ something". + my $r = $talk->_imap_cmd('idle', 0, ''); + $self->assert_null($r); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + $self->assert_matches(qr/Unrecognized command/, $talk->get_last_error()); } -sub common_basic -{ - my ($self) = @_; +sub common_basic { + my ($self) = @_; - my $svc = $self->{instance}->get_service('imap'); + my $svc = $self->{instance}->get_service('imap'); - my $store = $svc->create_store(folder => 'INBOX'); - my $talk = $store->get_client(); - $store->_select(); + my $store = $svc->create_store(folder => 'INBOX'); + my $talk = $store->get_client(); + $store->_select(); - xlog $self, "The server should report the IDLE capability"; - $self->assert($talk->capability()->{idle}); + xlog $self, "The server should report the IDLE capability"; + $self->assert($talk->capability()->{idle}); - xlog $self, "Sending the IDLE command"; - $store->idle_begin() - or die "IDLE failed: $@"; + xlog $self, "Sending the IDLE command"; + $store->idle_begin() + or die "IDLE failed: $@"; - xlog $self, "Poll for any unsolicited response - should be none"; - my $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); + xlog $self, "Poll for any unsolicited response - should be none"; + my $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); - xlog $self, "Sending DONE continuation"; - $store->idle_end({}); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "Sending DONE continuation"; + $store->idle_end({}); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "Testing that normal IMAP commands still work"; - my $res = $talk->status('INBOX', '(messages unseen)'); - $self->assert_deep_equals({ messages => 0, unseen => 0 }, $res); + xlog $self, "Testing that normal IMAP commands still work"; + my $res = $talk->status('INBOX', '(messages unseen)'); + $self->assert_deep_equals({ messages => 0, unseen => 0 }, $res); } sub test_basic_idled - :needs_component_idled -{ - my ($self) = @_; - - xlog $self, "Basic test of the IDLE command, idled started"; - - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - $self->common_basic(); + : needs_component_idled { + my ($self) = @_; + + xlog $self, "Basic test of the IDLE command, idled started"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + $self->common_basic(); } -sub test_basic_noidled -{ - my ($self) = @_; +sub test_basic_noidled { + my ($self) = @_; - xlog $self, "Basic test of the IDLE command, no idled started"; + xlog $self, "Basic test of the IDLE command, no idled started"; - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->start(); - $self->common_basic(); + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->start(); + $self->common_basic(); } sub test_basic_abortedidled - :needs_component_idled -{ - my ($self) = @_; + : needs_component_idled { + my ($self) = @_; - xlog $self, "Basic test of the IDLE command, idled started but aborted"; + xlog $self, "Basic test of the IDLE command, idled started but aborted"; - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->start(); - $self->start_and_abort_idled(); + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->start(); + $self->start_and_abort_idled(); - $self->common_basic(); + $self->common_basic(); } -sub common_delivery -{ - my ($self) = @_; +sub common_delivery { + my ($self) = @_; - xlog $self, "Starting up the instance"; - my $svc = $self->{instance}->get_service('imap'); + xlog $self, "Starting up the instance"; + my $svc = $self->{instance}->get_service('imap'); - my $store = $svc->create_store(folder => 'INBOX'); - my $talk = $store->get_client(); - $store->_select(); + my $store = $svc->create_store(folder => 'INBOX'); + my $talk = $store->get_client(); + $store->_select(); - xlog $self, "Sending the IDLE command"; - $store->idle_begin() - or die "IDLE failed: $@"; + xlog $self, "Sending the IDLE command"; + $store->idle_begin() + or die "IDLE failed: $@"; - xlog $self, "Poll for any unsolicited response - should be none"; - my $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); + xlog $self, "Poll for any unsolicited response - should be none"; + my $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); - xlog $self, "sleeping for 3 seconds"; - sleep(3); + xlog $self, "sleeping for 3 seconds"; + sleep(3); - xlog $self, "Poll for any unsolicited response - should be none"; - $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); + xlog $self, "Poll for any unsolicited response - should be none"; + $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); - - $r = $store->idle_response({}, 5); - $self->assert($r, "received an unsolicited response"); - $r = $store->idle_response({}, 5); - $self->assert($r, "received an unsolicited response"); - $r = $store->idle_response({}, 1); - $self->assert(!$r, "no more unsolicited responses"); - $self->assert_num_equals(1, $talk->get_response_code('exists')); - $self->assert_num_equals(1, $talk->get_response_code('recent')); - - xlog $self, "Sending DONE continuation"; - $store->idle_end({}); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); -} + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); -sub test_delivery_idled - :needs_component_idled -{ - my ($self) = @_; + $r = $store->idle_response({}, 5); + $self->assert($r, "received an unsolicited response"); + $r = $store->idle_response({}, 5); + $self->assert($r, "received an unsolicited response"); + $r = $store->idle_response({}, 1); + $self->assert(!$r, "no more unsolicited responses"); + $self->assert_num_equals(1, $talk->get_response_code('exists')); + $self->assert_num_equals(1, $talk->get_response_code('recent')); - xlog $self, "Test the IDLE command vs local delivery, idled started"; + xlog $self, "Sending DONE continuation"; + $store->idle_end({}); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); +} - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - $self->common_delivery(); +sub test_delivery_idled + : needs_component_idled { + my ($self) = @_; + + xlog $self, "Test the IDLE command vs local delivery, idled started"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + $self->common_delivery(); } -sub test_delivery_noidled -{ - my ($self) = @_; +sub test_delivery_noidled { + my ($self) = @_; - xlog $self, "Test the IDLE command vs local delivery, no idled started"; + xlog $self, "Test the IDLE command vs local delivery, no idled started"; - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->start(); - $self->common_delivery(); + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->start(); + $self->common_delivery(); } sub test_delivery_abortedidled - :needs_component_idled -{ - my ($self) = @_; + : needs_component_idled { + my ($self) = @_; - xlog $self, "Test the IDLE command vs local delivery, idled started but aborted"; + xlog $self, + "Test the IDLE command vs local delivery, idled started but aborted"; - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->start(); - $self->start_and_abort_idled(); + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->start(); + $self->start_and_abort_idled(); - $self->common_delivery(); + $self->common_delivery(); } -sub common_shutdownfile -{ - my ($self) = @_; - - xlog $self, "Starting up the instance"; - my $svc = $self->{instance}->get_service('imap'); - - my $store = $svc->create_store(folder => 'INBOX'); - my $talk = $store->get_client(); - $store->_select(); - - xlog $self, "Sending the IDLE command"; - $store->idle_begin() - or die "IDLE failed: $@"; - - xlog $self, "Poll for any unsolicited response - should be none"; - my $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); - - xlog $self, "sleeping for 3 seconds"; - sleep(3); - - xlog $self, "Poll for any unsolicited response - should be none"; - $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); - - $self->assert_null($talk->get_response_code('alert')); - - xlog $self, "Write some text to the shutdown file"; - my $admin_store = $svc->create_store(folder => 'user.cassandane', - username => 'admin'); - my $shut_message = "The Mayans were right"; - $admin_store->get_client()->setmetadata("", - "/shared/vendor/cmu/cyrus-imapd/shutdown", $shut_message); - $admin_store->disconnect(); - $admin_store = undef; - - # We want to override Mail::IMAPTalk's builtin handling of the BYE - # untagged response, as it will 'die' immediately without parsing - # the remainder of the line and especially without picking out the - # [ALERT] message that we want to see. - my $got_bye_alert; - my $handlers = - { - bye => sub - { - my ($response, $rr) = @_; - if (lc($rr->[0]) eq '[alert]') - { - # Arguments to [ALERT] is the rest of the line - # Sadly we've already split on whitespace but lets - # hope the original message only had single spaces - $got_bye_alert = join(' ', splice(@$rr, 1)); - } - } - }; - - xlog $self, "Check that we got a BYE [ALERT] response with the message"; - $r = $store->idle_response($handlers, 5); - $self->assert($r, "Got an unsolicited response"); - $self->assert_not_null($got_bye_alert); - $self->assert_str_equals($shut_message, $got_bye_alert); - - xlog $self, "Check that the server disconnected"; - eval - { - # We use _send_cmd() and _next_atom() rather the normal path - # through _imap_cmd() because the latter will warn() to stderr - # about the exception we're about to generate, which is - # downright untidy. - $talk->_send_cmd('status', 'INBOX', '(messages unseen)'); - $talk->_parse_response({}); - }; - my $mm = $@; # this doesn't survive unless we save it - $self->assert_matches(qr/IMAP Connection closed by other end/, $mm); +sub common_shutdownfile { + my ($self) = @_; + + xlog $self, "Starting up the instance"; + my $svc = $self->{instance}->get_service('imap'); + + my $store = $svc->create_store(folder => 'INBOX'); + my $talk = $store->get_client(); + $store->_select(); + + xlog $self, "Sending the IDLE command"; + $store->idle_begin() + or die "IDLE failed: $@"; + + xlog $self, "Poll for any unsolicited response - should be none"; + my $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); + + xlog $self, "sleeping for 3 seconds"; + sleep(3); + + xlog $self, "Poll for any unsolicited response - should be none"; + $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); + + $self->assert_null($talk->get_response_code('alert')); + + xlog $self, "Write some text to the shutdown file"; + my $admin_store = $svc->create_store( + folder => 'user.cassandane', + username => 'admin' + ); + my $shut_message = "The Mayans were right"; + $admin_store->get_client() + ->setmetadata("", "/shared/vendor/cmu/cyrus-imapd/shutdown", $shut_message); + $admin_store->disconnect(); + $admin_store = undef; + + # We want to override Mail::IMAPTalk's builtin handling of the BYE + # untagged response, as it will 'die' immediately without parsing + # the remainder of the line and especially without picking out the + # [ALERT] message that we want to see. + my $got_bye_alert; + my $handlers = { + bye => sub { + my ($response, $rr) = @_; + if (lc($rr->[0]) eq '[alert]') { + # Arguments to [ALERT] is the rest of the line + # Sadly we've already split on whitespace but lets + # hope the original message only had single spaces + $got_bye_alert = join(' ', splice(@$rr, 1)); + } + } + }; + + xlog $self, "Check that we got a BYE [ALERT] response with the message"; + $r = $store->idle_response($handlers, 5); + $self->assert($r, "Got an unsolicited response"); + $self->assert_not_null($got_bye_alert); + $self->assert_str_equals($shut_message, $got_bye_alert); + + xlog $self, "Check that the server disconnected"; + eval { + # We use _send_cmd() and _next_atom() rather the normal path + # through _imap_cmd() because the latter will warn() to stderr + # about the exception we're about to generate, which is + # downright untidy. + $talk->_send_cmd('status', 'INBOX', '(messages unseen)'); + $talk->_parse_response({}); + }; + my $mm = $@; # this doesn't survive unless we save it + $self->assert_matches(qr/IMAP Connection closed by other end/, $mm); } sub test_shutdownfile_idled - :needs_component_idled -{ - my ($self) = @_; - - xlog $self, "Test the IDLE command vs the shutdownfile, idled started"; - - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - $self->common_shutdownfile(); + : needs_component_idled { + my ($self) = @_; + + xlog $self, "Test the IDLE command vs the shutdownfile, idled started"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + $self->common_shutdownfile(); } -sub test_shutdownfile_noidled -{ - my ($self) = @_; +sub test_shutdownfile_noidled { + my ($self) = @_; - xlog $self, "Test the IDLE command vs the shutdownfile"; + xlog $self, "Test the IDLE command vs the shutdownfile"; - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->start(); - $self->common_shutdownfile(); + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->start(); + $self->common_shutdownfile(); } sub test_shutdownfile_abortedidled - :needs_component_idled -{ - my ($self) = @_; + : needs_component_idled { + my ($self) = @_; - xlog $self, "Test the IDLE command vs the shutdownfile, idled started but aborted"; + xlog $self, + "Test the IDLE command vs the shutdownfile, idled started but aborted"; - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->start(); - $self->start_and_abort_idled(); + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->start(); + $self->start_and_abort_idled(); - $self->common_shutdownfile(); + $self->common_shutdownfile(); } sub test_sigterm - :needs_component_idled -{ - my ($self) = @_; + : needs_component_idled { + my ($self) = @_; + + xlog $self, "Test that an imapd can be killed with SIGTERM"; + xlog $self, "while executing an IDLE command"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + xlog $self, "Starting up the instance"; + $self->{instance}->start(); + + my $svc = $self->{instance}->get_service('imap'); + + my $store = $svc->create_store(folder => 'INBOX'); + my $talk = $store->get_client(); + + $store->_select(); + + xlog $self, "Sending the IDLE command"; + $store->idle_begin() + or die "IDLE failed: $@"; + + # procinfo: pid SP servicename SP host [SP user] [SP mailbox] [SP cmdname] + my $procinfo = join '', $self->{instance}->run_cyr_info('proc'); + my ($imapd_pid, undef) + = ($procinfo =~ m/^(\d+) imap (.+) cassandane user.cassandane Idle$/); + $self->assert_not_null($imapd_pid); + $imapd_pid = 0 + $imapd_pid; + $self->assert($imapd_pid > 1); + xlog $self, "PID of imapd process is $imapd_pid"; + + xlog $self, "Poll for any unsolicited response - should be none"; + my $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); + + xlog $self, "sleeping for 3 seconds"; + sleep(3); + + xlog $self, "Poll for any unsolicited response - should be none"; + $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); + + $self->assert_null($talk->get_response_code('alert')); + + xlog $self, "Send SIGQUIT (or worse) to the imapd"; + $r = Cassandane::Instance::_stop_pid($imapd_pid); + $self->assert($r == 1, "shutdown required brute force"); + + xlog $self, "Check that the server disconnected"; + eval { + # We use _send_cmd() and _next_atom() rather the normal path + # through _imap_cmd() because the latter will warn() to stderr + # about the exception we're about to generate, which is + # downright untidy. + $talk->_send_cmd('status', 'INBOX', '(messages unseen)'); + $talk->_parse_response({}); + }; + my $mm = $@; # this doesn't survive unless we save it + $self->assert_matches(qr/IMAP Connection closed by other end/, $mm); +} - xlog $self, "Test that an imapd can be killed with SIGTERM"; - xlog $self, "while executing an IDLE command"; +sub test_sigterm_many + : needs_component_idled { + my ($self) = @_; - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - xlog $self, "Starting up the instance"; - $self->{instance}->start(); + xlog $self, "Test that the Cyrus instance can be cleanly shut"; + xlog $self, "down with SIGTERM while many imapds execute an"; + xlog $self, "IDLE command"; - my $svc = $self->{instance}->get_service('imap'); + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + xlog $self, "Starting up the instance"; + $self->{instance}->start(); + my $svc = $self->{instance}->get_service('imap'); + + my $N = 16; + my @stores; + my $r; + + for (my $i = 0; $i < $N; $i++) { my $store = $svc->create_store(folder => 'INBOX'); + push(@stores, $store); my $talk = $store->get_client(); $store->_select(); xlog $self, "Sending the IDLE command"; $store->idle_begin() - or die "IDLE failed: $@"; - - # procinfo: pid SP servicename SP host [SP user] [SP mailbox] [SP cmdname] - my $procinfo = join '', $self->{instance}->run_cyr_info('proc'); - my ($imapd_pid, undef) = - ($procinfo =~ m/^(\d+) imap (.+) cassandane user.cassandane Idle$/); - $self->assert_not_null($imapd_pid); - $imapd_pid = 0 + $imapd_pid; - $self->assert($imapd_pid > 1); - xlog $self, "PID of imapd process is $imapd_pid"; + or die "IDLE failed: $@"; xlog $self, "Poll for any unsolicited response - should be none"; - my $r = $store->idle_response({}, 0); + $r = $store->idle_response({}, 0); $self->assert(!$r, "No unsolicted response"); + } - xlog $self, "sleeping for 3 seconds"; - sleep(3); + xlog $self, "sleeping for 3 seconds"; + sleep(3); + foreach my $store (@stores) { xlog $self, "Poll for any unsolicited response - should be none"; $r = $store->idle_response({}, 0); $self->assert(!$r, "No unsolicted response"); - $self->assert_null($talk->get_response_code('alert')); + $self->assert_null($store->get_client()->get_response_code('alert')); + } - xlog $self, "Send SIGQUIT (or worse) to the imapd"; - $r = Cassandane::Instance::_stop_pid($imapd_pid); - $self->assert($r == 1, "shutdown required brute force"); + xlog $self, "Shut down the instance"; + $self->{instance}->stop(); + # $self->assert($r == 1, "shutdown required brute force"); - xlog $self, "Check that the server disconnected"; - eval - { - # We use _send_cmd() and _next_atom() rather the normal path - # through _imap_cmd() because the latter will warn() to stderr - # about the exception we're about to generate, which is - # downright untidy. - $talk->_send_cmd('status', 'INBOX', '(messages unseen)'); - $talk->_parse_response({}); + xlog $self, "Check that the server disconnected"; + + foreach my $store (@stores) { + eval { + # We use _send_cmd() and _next_atom() rather the normal path + # through _imap_cmd() because the latter will warn() to stderr + # about the exception we're about to generate, which is + # downright untidy. + my $talk = $store->get_client(); + $talk->_send_cmd('status', 'INBOX', '(messages unseen)'); + $talk->_parse_response({}); }; - my $mm = $@; # this doesn't survive unless we save it + my $mm = $@; # this doesn't survive unless we save it $self->assert_matches(qr/IMAP Connection closed by other end/, $mm); -} - -sub test_sigterm_many - :needs_component_idled -{ - my ($self) = @_; - - xlog $self, "Test that the Cyrus instance can be cleanly shut"; - xlog $self, "down with SIGTERM while many imapds execute an"; - xlog $self, "IDLE command"; - - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - xlog $self, "Starting up the instance"; - $self->{instance}->start(); - - my $svc = $self->{instance}->get_service('imap'); - - my $N = 16; - my @stores; - my $r; - - for (my $i = 0 ; $i < $N ; $i++) - { - my $store = $svc->create_store(folder => 'INBOX'); - push(@stores, $store); - my $talk = $store->get_client(); - - $store->_select(); - - xlog $self, "Sending the IDLE command"; - $store->idle_begin() - or die "IDLE failed: $@"; - - xlog $self, "Poll for any unsolicited response - should be none"; - $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); - } - - xlog $self, "sleeping for 3 seconds"; - sleep(3); - - foreach my $store (@stores) - { - xlog $self, "Poll for any unsolicited response - should be none"; - $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); - - $self->assert_null($store->get_client()->get_response_code('alert')); - } - - xlog $self, "Shut down the instance"; - $self->{instance}->stop(); -# $self->assert($r == 1, "shutdown required brute force"); - - xlog $self, "Check that the server disconnected"; - - foreach my $store (@stores) - { - eval - { - # We use _send_cmd() and _next_atom() rather the normal path - # through _imap_cmd() because the latter will warn() to stderr - # about the exception we're about to generate, which is - # downright untidy. - my $talk = $store->get_client(); - $talk->_send_cmd('status', 'INBOX', '(messages unseen)'); - $talk->_parse_response({}); - }; - my $mm = $@; # this doesn't survive unless we save it - $self->assert_matches(qr/IMAP Connection closed by other end/, $mm); - } + } } sub test_idled_default_timeout - :needs_component_idled -{ - my ($self) = @_; - - # The default timeout if `imapidlepoll` isn't set in imapd.conf - # is set to 60 seconds. If idled is not broken, then we should - # return immediately(pretty much), instead of having to wait all - # of 60 seconds. - xlog $self, "Set idle poll timeout 60 seconds"; - $self->{instance}->{config}->set(imapidlepoll => '60'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - - xlog $self, "Starting up the instance"; - my $svc = $self->{instance}->get_service('imap'); - - my $store = $svc->create_store(folder => 'INBOX'); - my $talk = $store->get_client(); - $store->_select(); - - xlog $self, "Sending the IDLE command"; - $store->idle_begin() - or die "IDLE failed: $@"; - - my $date1 = DateTime->from_epoch(epoch => time()); - - xlog $self, "Poll for any unsolicited response - should be none"; - my $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); - - xlog $self, "Poll for any unsolicited response - should be none"; - $r = $store->idle_response({}, 0); - $self->assert(!$r, "No unsolicted response"); - - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); - - $r = $store->idle_response({}, 5); - $self->assert($r, "received an unsolicited response"); - $r = $store->idle_response({}, 5); - $self->assert($r, "received an unsolicited response"); - $r = $store->idle_response({}, 1); - $self->assert(!$r, "no more unsolicited responses"); - $self->assert_num_equals(1, $talk->get_response_code('exists')); - $self->assert_num_equals(1, $talk->get_response_code('recent')); - - xlog $self, "Sending DONE continuation"; - $store->idle_end({}); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - my $date2 = DateTime->from_epoch(epoch => time()); - - my $dur = $date2->epoch - $date1->epoch; - $self->assert($dur < 15, "IDLE took longer than expected"); + : needs_component_idled { + my ($self) = @_; + + # The default timeout if `imapidlepoll` isn't set in imapd.conf + # is set to 60 seconds. If idled is not broken, then we should + # return immediately(pretty much), instead of having to wait all + # of 60 seconds. + xlog $self, "Set idle poll timeout 60 seconds"; + $self->{instance}->{config}->set(imapidlepoll => '60'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + + xlog $self, "Starting up the instance"; + my $svc = $self->{instance}->get_service('imap'); + + my $store = $svc->create_store(folder => 'INBOX'); + my $talk = $store->get_client(); + $store->_select(); + + xlog $self, "Sending the IDLE command"; + $store->idle_begin() + or die "IDLE failed: $@"; + + my $date1 = DateTime->from_epoch(epoch => time()); + + xlog $self, "Poll for any unsolicited response - should be none"; + my $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); + + xlog $self, "Poll for any unsolicited response - should be none"; + $r = $store->idle_response({}, 0); + $self->assert(!$r, "No unsolicted response"); + + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); + + $r = $store->idle_response({}, 5); + $self->assert($r, "received an unsolicited response"); + $r = $store->idle_response({}, 5); + $self->assert($r, "received an unsolicited response"); + $r = $store->idle_response({}, 1); + $self->assert(!$r, "no more unsolicited responses"); + $self->assert_num_equals(1, $talk->get_response_code('exists')); + $self->assert_num_equals(1, $talk->get_response_code('recent')); + + xlog $self, "Sending DONE continuation"; + $store->idle_end({}); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + my $date2 = DateTime->from_epoch(epoch => time()); + + my $dur = $date2->epoch - $date1->epoch; + $self->assert($dur < 15, "IDLE took longer than expected"); } - 1; diff --git a/cassandane/Cassandane/Cyrus/ImapTest.pm b/cassandane/Cassandane/Cyrus/ImapTest.pm index 6595806f9e..ae146f737e 100644 --- a/cassandane/Cassandane/Cyrus/ImapTest.pm +++ b/cassandane/Cassandane/Cyrus/ImapTest.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::ImapTest; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; @@ -54,137 +54,129 @@ my $binary; my $testdir; my %suppressed; -sub init -{ - my $cassini = Cassandane::Cassini->instance(); - $basedir = $cassini->val('imaptest', 'basedir'); - return unless defined $basedir; - $basedir = abs_path($basedir); +sub init { + my $cassini = Cassandane::Cassini->instance(); + $basedir = $cassini->val('imaptest', 'basedir'); + return unless defined $basedir; + $basedir = abs_path($basedir); - my $supp = $cassini->val('imaptest', 'suppress', - 'listext subscribe'); - map { $suppressed{$_} = 1; } split(/\s+/, $supp); + my $supp = $cassini->val('imaptest', 'suppress', 'listext subscribe'); + map { $suppressed{$_} = 1; } split(/\s+/, $supp); - $binary = "$basedir/src/imaptest"; - $testdir = "$basedir/src/tests"; + $binary = "$basedir/src/imaptest"; + $testdir = "$basedir/src/tests"; } init; -sub new -{ - my $class = shift; +sub new { + my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(servername => "127.0.0.1"); # urlauth needs matching servername - $config->set(virtdomains => 'userid'); - $config->set(unixhierarchysep => 'on'); - $config->set(altnamespace => 'yes'); + my $config = Cassandane::Config->default()->clone(); + $config->set(servername => "127.0.0.1"); # urlauth needs matching servername + $config->set(virtdomains => 'userid'); + $config->set(unixhierarchysep => 'on'); + $config->set(altnamespace => 'yes'); - return $class->SUPER::new({ config => $config }, @_); + return $class->SUPER::new({ config => $config }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - $self->{instance}->create_user('user2', subdirs => ['imaptest']); + $self->{instance}->create_user('user2', subdirs => ['imaptest']); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub list_tests -{ - my @tests; +sub list_tests { + my @tests; + + if (!defined $basedir) { + return ('test_warning_imaptest_is_not_installed'); + } + + opendir TESTS, $testdir + or die "Cannot open directory $testdir: $!"; + while (my $e = readdir TESTS) { + next if $e =~ m/^\./; + next if $e =~ m/\.mbox$/; + next if $suppressed{$e}; + next if (!-f "$testdir/$e"); + push(@tests, "test_$e"); + } + closedir TESTS; + + return @tests; +} - if (!defined $basedir) - { - return ( 'test_warning_imaptest_is_not_installed' ); - } +sub run_test { + my ($self) = @_; - opendir TESTS, $testdir - or die "Cannot open directory $testdir: $!"; - while (my $e = readdir TESTS) - { - next if $e =~ m/^\./; - next if $e =~ m/\.mbox$/; - next if $suppressed{$e}; - next if ( ! -f "$testdir/$e" ); - push(@tests, "test_$e"); - } - closedir TESTS; + if (!defined $basedir) { + xlog $self, "ImapTests are not enabled. To enabled them, please"; + xlog $self, "install ImapTest from http://www.imapwiki.org/ImapTest/"; + xlog $self, "and edit [imaptest]basedir in cassandane.ini"; + xlog $self, "This is not a failure"; + return; + } - return @tests; -} + my $name = $self->name(); + $name =~ s/^test_//; -sub run_test -{ - my ($self) = @_; + my $logdir = "$self->{instance}->{basedir}/rawlog/"; + mkdir($logdir); - if (!defined $basedir) + my $svc = $self->{instance}->get_service('imap'); + my $params = $svc->store_params(); + + my $errfile = $self->{instance}->{basedir} . "/$name.errors"; + my $status; + $self->{instance}->run_command( { - xlog $self, "ImapTests are not enabled. To enabled them, please"; - xlog $self, "install ImapTest from http://www.imapwiki.org/ImapTest/"; - xlog $self, "and edit [imaptest]basedir in cassandane.ini"; - xlog $self, "This is not a failure"; - return; + redirects => { stderr => $errfile }, + workingdir => $logdir, + handlers => { + exited_normally => sub { $status = 1; }, + exited_abnormally => sub { $status = 0; }, + }, + }, + $binary, + "host=" . $params->{host}, + "port=" . $params->{port}, + "user=" . $params->{username}, + "user2=" . "user2", + "pass=" . $params->{password}, + "rawlog", + "test=$testdir/$name" + ); + + if ((!$status || get_verbose)) { + if (-f $errfile) { + open FH, '<', $errfile + or die "Cannot open $errfile for reading: $!"; + while (readline FH) { + xlog $self, $_; + } + close FH; } - - my $name = $self->name(); - $name =~ s/^test_//; - - my $logdir = "$self->{instance}->{basedir}/rawlog/"; - mkdir($logdir); - - my $svc = $self->{instance}->get_service('imap'); - my $params = $svc->store_params(); - - my $errfile = $self->{instance}->{basedir} . "/$name.errors"; - my $status; - $self->{instance}->run_command({ - redirects => { stderr => $errfile }, - workingdir => $logdir, - handlers => { - exited_normally => sub { $status = 1; }, - exited_abnormally => sub { $status = 0; }, - }, - }, - $binary, - "host=" . $params->{host}, - "port=" . $params->{port}, - "user=" . $params->{username}, - "user2=" . "user2", - "pass=" . $params->{password}, - "rawlog", - "test=$testdir/$name"); - - if ((!$status || get_verbose)) { - if (-f $errfile) { - open FH, '<', $errfile - or die "Cannot open $errfile for reading: $!"; - while (readline FH) { - xlog $self, $_; - } - close FH; - } - opendir(DH, $logdir) or die "Can't open logdir $logdir"; - while (my $item = readdir(DH)) { - next unless $item =~ m/^rawlog\./; - print "============> $item <=============\n"; - open(FH, '<', "$logdir/$item") or die "Can't open $logdir/$item"; - while (readline FH) { - print $_; - } - close(FH); - } + opendir(DH, $logdir) or die "Can't open logdir $logdir"; + while (my $item = readdir(DH)) { + next unless $item =~ m/^rawlog\./; + print "============> $item <=============\n"; + open(FH, '<', "$logdir/$item") or die "Can't open $logdir/$item"; + while (readline FH) { + print $_; + } + close(FH); } + } - $self->assert($status); + $self->assert($status); } 1; diff --git a/cassandane/Cassandane/Cyrus/InProgress.pm b/cassandane/Cassandane/Cyrus/InProgress.pm index 3433ca5cc7..1e6f6e9d3e 100644 --- a/cassandane/Cassandane/Cyrus/InProgress.pm +++ b/cassandane/Cassandane/Cyrus/InProgress.pm @@ -56,310 +56,287 @@ use Cassandane::Util::Log; use charnames ':full'; -sub new -{ - my $class = shift; - - my $config = Cassandane::Config->default()->clone(); - $config->set(mailbox_legacy_dirs => 'yes'); - $config->set(singleinstancestore => 'no'); - $config->set(imap_inprogress_interval => '1s'); - - return $class->SUPER::new({ - adminstore => 1, - config => $config, - services => ['imap'], - }, @_); +sub new { + my $class = shift; + + my $config = Cassandane::Config->default()->clone(); + $config->set(mailbox_legacy_dirs => 'yes'); + $config->set(singleinstancestore => 'no'); + $config->set(imap_inprogress_interval => '1s'); + + return $class->SUPER::new( + { + adminstore => 1, + config => $config, + services => ['imap'], + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_xrename - :NoAltNameSpace -{ - my ($self) = @_; - - my @resp; - my %handlers = - ( - ok => sub - { - my (undef, $ok) = @_; - push(@resp, $ok); - }, - ); - - xlog $self, "Create some personal folders"; - my $talk = $self->{store}->get_client(); - $self->setup_mailbox_structure($talk, [ - [ 'create' => [qw( INBOX.src INBOX.src.child INBOX.src.child.grand)] ], - ]); - - xlog $self, "rename mailbox tree"; - @resp = (); - $talk->_imap_cmd('XRENAME', 0, \%handlers, "INBOX.src", "INBOX.dst"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we shouldn't have a count or total - $self->assert_null($resp[0][1][1]); - $self->assert_null($resp[0][1][2]); - $self->assert_str_equals('rename', $resp[0][3]); - $self->assert_str_equals('INBOX.src', $resp[0][4]); - $self->assert_str_equals('INBOX.dst', $resp[0][5]); - $self->assert_str_equals('INBOX.src.child', $resp[1][4]); - $self->assert_str_equals('INBOX.dst.child', $resp[1][5]); - $self->assert_str_equals('INBOX.src.child.grand', $resp[2][4]); - $self->assert_str_equals('INBOX.dst.child.grand', $resp[2][5]); + : NoAltNameSpace { + my ($self) = @_; + + my @resp; + my %handlers = ( + ok => sub { + my (undef, $ok) = @_; + push(@resp, $ok); + }, + ); + + xlog $self, "Create some personal folders"; + my $talk = $self->{store}->get_client(); + $self->setup_mailbox_structure($talk, + [ [ 'create' => [qw( INBOX.src INBOX.src.child INBOX.src.child.grand)] ], ] + ); + + xlog $self, "rename mailbox tree"; + @resp = (); + $talk->_imap_cmd('XRENAME', 0, \%handlers, "INBOX.src", "INBOX.dst"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we shouldn't have a count or total + $self->assert_null($resp[0][1][1]); + $self->assert_null($resp[0][1][2]); + $self->assert_str_equals('rename', $resp[0][3]); + $self->assert_str_equals('INBOX.src', $resp[0][4]); + $self->assert_str_equals('INBOX.dst', $resp[0][5]); + $self->assert_str_equals('INBOX.src.child', $resp[1][4]); + $self->assert_str_equals('INBOX.dst.child', $resp[1][5]); + $self->assert_str_equals('INBOX.src.child.grand', $resp[2][4]); + $self->assert_str_equals('INBOX.dst.child.grand', $resp[2][5]); } sub test_copy - :SlowIO :needs_component_slowio :NoAltNameSpace -{ - my ($self) = @_; - - my @resp; - my %handlers = - ( - ok => sub - { - my (undef, $ok) = @_; - push(@resp, $ok); - }, - ); - - xlog "generate some test messages"; - foreach (1..100) { - $self->make_message("Message $_", size => 128_000); - } - - xlog $self, "Create another folder"; - my $talk = $self->{store}->get_client(); - $talk->create("INBOX.dst"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "copy messages"; - @resp = (); - $talk->select("INBOX"); - $talk->_imap_cmd('COPY', 0, \%handlers, '1:100', 'INBOX.dst'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we don't know what the exact count will be, be we know the total - $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); - $self->assert_str_equals('100', $resp[0][1][2]); + : SlowIO : needs_component_slowio : NoAltNameSpace { + my ($self) = @_; + + my @resp; + my %handlers = ( + ok => sub { + my (undef, $ok) = @_; + push(@resp, $ok); + }, + ); + + xlog "generate some test messages"; + foreach (1 .. 100) { + $self->make_message("Message $_", size => 128_000); + } + + xlog $self, "Create another folder"; + my $talk = $self->{store}->get_client(); + $talk->create("INBOX.dst"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "copy messages"; + @resp = (); + $talk->select("INBOX"); + $talk->_imap_cmd('COPY', 0, \%handlers, '1:100', 'INBOX.dst'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we don't know what the exact count will be, be we know the total + $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); + $self->assert_str_equals('100', $resp[0][1][2]); } sub test_search - :SlowIO :needs_component_slowio :NoAltNameSpace -{ - my ($self) = @_; - - my @resp; - my %handlers = - ( - ok => sub - { - my (undef, $ok) = @_; - push(@resp, $ok); - }, - ); - - xlog "generate some test messages"; - foreach (1..100) { - $self->make_message("Message $_", size => 128_000); - } - - xlog $self, "search messages"; - my $talk = $self->{store}->get_client(); - @resp = (); - $talk->_imap_cmd('SEARCH', 0, \%handlers, 'RETURN', '(PARTIAL -1:-500)', '1:100', 'BODY', 'needle'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we don't know what the exact count will be, be we know the total - $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); - $self->assert_str_equals('100', $resp[0][1][2]); + : SlowIO : needs_component_slowio : NoAltNameSpace { + my ($self) = @_; + + my @resp; + my %handlers = ( + ok => sub { + my (undef, $ok) = @_; + push(@resp, $ok); + }, + ); + + xlog "generate some test messages"; + foreach (1 .. 100) { + $self->make_message("Message $_", size => 128_000); + } + + xlog $self, "search messages"; + my $talk = $self->{store}->get_client(); + @resp = (); + $talk->_imap_cmd('SEARCH', 0, \%handlers, 'RETURN', '(PARTIAL -1:-500)', + '1:100', 'BODY', 'needle'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we don't know what the exact count will be, be we know the total + $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); + $self->assert_str_equals('100', $resp[0][1][2]); } sub test_esearch_selected - :SlowIO :needs_component_slowio :NoAltNameSpace -{ - my ($self) = @_; - - my @resp; - my %handlers = - ( - ok => sub - { - my (undef, $ok) = @_; - push(@resp, $ok); - }, - ); - - xlog "generate some test messages"; - foreach (1..100) { - $self->make_message("Message $_", size => 128_000); - } - - xlog $self, "esearch selected mailbox"; - my $talk = $self->{store}->get_client(); - @resp = (); - $talk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(SELECTED)', '1:100', 'BODY', 'needle'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we don't know what the exact count will be, be we know the total - $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); - $self->assert_str_equals('100', $resp[0][1][2]); + : SlowIO : needs_component_slowio : NoAltNameSpace { + my ($self) = @_; + + my @resp; + my %handlers = ( + ok => sub { + my (undef, $ok) = @_; + push(@resp, $ok); + }, + ); + + xlog "generate some test messages"; + foreach (1 .. 100) { + $self->make_message("Message $_", size => 128_000); + } + + xlog $self, "esearch selected mailbox"; + my $talk = $self->{store}->get_client(); + @resp = (); + $talk->_imap_cmd('ESEARCH', 0, \%handlers, + 'IN', '(SELECTED)', '1:100', 'BODY', 'needle'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we don't know what the exact count will be, be we know the total + $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); + $self->assert_str_equals('100', $resp[0][1][2]); } sub test_esearch_multiple - :SlowIO :needs_component_slowio :NoAltNameSpace -{ - my ($self) = @_; - - my @resp; - my %handlers = - ( - ok => sub - { - my (undef, $ok) = @_; - push(@resp, $ok); - }, - ); - - xlog "generate some test messages"; - foreach (1..100) { - $self->make_message("Message $_", size => 128_000); - } - - xlog $self, "Create another folder"; - my $talk = $self->{store}->get_client(); - $talk->create("INBOX.dst"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "copy messages"; - @resp = (); - $talk->select("INBOX"); - $talk->_imap_cmd('COPY', 0, \%handlers, '1:100', 'INBOX.dst'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we don't know what the exact count will be, be we know the total - $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); - $self->assert_str_equals('100', $resp[0][1][2]); - - xlog $self, "esearch multiple mailboxes"; - @resp = (); - $talk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(PERSONAL)', '1:100', 'BODY', 'needle'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we shouldn't have a count or total - $self->assert_null($resp[0][1][1]); - $self->assert_null($resp[0][1][2]); + : SlowIO : needs_component_slowio : NoAltNameSpace { + my ($self) = @_; + + my @resp; + my %handlers = ( + ok => sub { + my (undef, $ok) = @_; + push(@resp, $ok); + }, + ); + + xlog "generate some test messages"; + foreach (1 .. 100) { + $self->make_message("Message $_", size => 128_000); + } + + xlog $self, "Create another folder"; + my $talk = $self->{store}->get_client(); + $talk->create("INBOX.dst"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "copy messages"; + @resp = (); + $talk->select("INBOX"); + $talk->_imap_cmd('COPY', 0, \%handlers, '1:100', 'INBOX.dst'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we don't know what the exact count will be, be we know the total + $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); + $self->assert_str_equals('100', $resp[0][1][2]); + + xlog $self, "esearch multiple mailboxes"; + @resp = (); + $talk->_imap_cmd('ESEARCH', 0, \%handlers, + 'IN', '(PERSONAL)', '1:100', 'BODY', 'needle'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we shouldn't have a count or total + $self->assert_null($resp[0][1][1]); + $self->assert_null($resp[0][1][2]); } sub test_sort - :SlowIO :needs_component_slowio :NoAltNameSpace -{ - my ($self) = @_; - - my @resp; - my %handlers = - ( - ok => sub - { - my (undef, $ok) = @_; - push(@resp, $ok); - }, - ); - - xlog "generate some test messages"; - foreach (1..100) { - $self->make_message("Message $_", size => 128_000); - } - - xlog $self, "sort messages"; - my $talk = $self->{store}->get_client(); - @resp = (); - $talk->_imap_cmd('SORT', 0, \%handlers, - '(ARRIVAL)', 'US-ASCII', '1:100', 'BODY', 'needle'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we don't know what the exact count will be, be we know the total - $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); - $self->assert_str_equals('100', $resp[0][1][2]); + : SlowIO : needs_component_slowio : NoAltNameSpace { + my ($self) = @_; + + my @resp; + my %handlers = ( + ok => sub { + my (undef, $ok) = @_; + push(@resp, $ok); + }, + ); + + xlog "generate some test messages"; + foreach (1 .. 100) { + $self->make_message("Message $_", size => 128_000); + } + + xlog $self, "sort messages"; + my $talk = $self->{store}->get_client(); + @resp = (); + $talk->_imap_cmd('SORT', 0, \%handlers, + '(ARRIVAL)', 'US-ASCII', '1:100', 'BODY', 'needle'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we don't know what the exact count will be, be we know the total + $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); + $self->assert_str_equals('100', $resp[0][1][2]); } sub test_thread - :SlowIO :needs_component_slowio :NoAltNameSpace -{ - my ($self) = @_; - - my @resp; - my %handlers = - ( - ok => sub - { - my (undef, $ok) = @_; - push(@resp, $ok); - }, - ); - - xlog "generate some test messages"; - foreach (1..100) { - $self->make_message("Message $_", size => 128_000); - } - - xlog $self, "thread messages"; - my $talk = $self->{store}->get_client(); - @resp = (); - $talk->_imap_cmd('THREAD', 0, \%handlers, - 'REFERENCES', 'US-ASCII', '1:100', 'BODY', 'needle'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we don't know what the exact count will be, be we know the total - $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); - $self->assert_str_equals('100', $resp[0][1][2]); + : SlowIO : needs_component_slowio : NoAltNameSpace { + my ($self) = @_; + + my @resp; + my %handlers = ( + ok => sub { + my (undef, $ok) = @_; + push(@resp, $ok); + }, + ); + + xlog "generate some test messages"; + foreach (1 .. 100) { + $self->make_message("Message $_", size => 128_000); + } + + xlog $self, "thread messages"; + my $talk = $self->{store}->get_client(); + @resp = (); + $talk->_imap_cmd('THREAD', 0, \%handlers, + 'REFERENCES', 'US-ASCII', '1:100', 'BODY', 'needle'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we don't know what the exact count will be, be we know the total + $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); + $self->assert_str_equals('100', $resp[0][1][2]); } sub test_rename - :SlowIO :needs_component_slowio :NoAltNameSpace -{ - my ($self) = @_; - - my @resp; - my %handlers = - ( - ok => sub - { - my (undef, $ok) = @_; - push(@resp, $ok); - }, - ); - - xlog "generate some test messages"; - foreach (1..100) { - $self->make_message("Message $_", size => 128_000); - } - - xlog $self, "rename INBOX"; - my $talk = $self->{store}->get_client(); - @resp = (); - $talk->_imap_cmd('RENAME', 0, \%handlers, 'INBOX', 'INBOX.Archive'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_str_equals('[INPROGRESS', $resp[0][0]); - # we don't know what the exact count will be, be we know the total - $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); - $self->assert_str_equals('100', $resp[0][1][2]); + : SlowIO : needs_component_slowio : NoAltNameSpace { + my ($self) = @_; + + my @resp; + my %handlers = ( + ok => sub { + my (undef, $ok) = @_; + push(@resp, $ok); + }, + ); + + xlog "generate some test messages"; + foreach (1 .. 100) { + $self->make_message("Message $_", size => 128_000); + } + + xlog $self, "rename INBOX"; + my $talk = $self->{store}->get_client(); + @resp = (); + $talk->_imap_cmd('RENAME', 0, \%handlers, 'INBOX', 'INBOX.Archive'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_str_equals('[INPROGRESS', $resp[0][0]); + # we don't know what the exact count will be, be we know the total + $self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]); + $self->assert_str_equals('100', $resp[0][1][2]); } 1; diff --git a/cassandane/Cassandane/Cyrus/Info.pm b/cassandane/Cassandane/Cyrus/Info.pm index b8d89d5169..d79a0ac9c5 100644 --- a/cassandane/Cassandane/Cyrus/Info.pm +++ b/cassandane/Cassandane/Cyrus/Info.pm @@ -49,395 +49,382 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_conf -{ - my ($self) = @_; - - my %imapd_conf; - my $filename = $self->{instance}->_imapd_conf(); - open my $fh, '<', $filename - or die "Cannot open $filename for reading: $!"; - while (my $line = <$fh>) { - chomp $line; - my ($name, $value) = split /\s*:\s*/, $line, 2; - if (Cassandane::Config::is_bitfield($name)) { - my @values = split /\s+/, $value; - $imapd_conf{$name} = join q{ }, sort @values; - } - else { - $imapd_conf{$name} = $value; - } +sub test_conf { + my ($self) = @_; + + my %imapd_conf; + my $filename = $self->{instance}->_imapd_conf(); + open my $fh, '<', $filename + or die "Cannot open $filename for reading: $!"; + while (my $line = <$fh>) { + chomp $line; + my ($name, $value) = split /\s*:\s*/, $line, 2; + if (Cassandane::Config::is_bitfield($name)) { + my @values = split /\s+/, $value; + $imapd_conf{$name} = join q{ }, sort @values; + } else { + $imapd_conf{$name} = $value; } - close $fh; - - my %cyr_info_conf; - foreach my $line ($self->{instance}->run_cyr_info('conf')) { - chomp $line; - my ($name, $value) = split /\s*:\s*/, $line, 2; - if (Cassandane::Config::is_bitfield($name)) { - my @values = split /\s+/, $value; - $cyr_info_conf{$name} = join q{ }, sort @values; - } - else { - $cyr_info_conf{$name} = $value; - } + } + close $fh; + + my %cyr_info_conf; + foreach my $line ($self->{instance}->run_cyr_info('conf')) { + chomp $line; + my ($name, $value) = split /\s*:\s*/, $line, 2; + if (Cassandane::Config::is_bitfield($name)) { + my @values = split /\s+/, $value; + $cyr_info_conf{$name} = join q{ }, sort @values; + } else { + $cyr_info_conf{$name} = $value; } + } - $self->assert_deep_equals(\%imapd_conf, \%cyr_info_conf); + $self->assert_deep_equals(\%imapd_conf, \%cyr_info_conf); } -sub test_conf_all -{ - my ($self) = @_; - - my %imapd_conf; - my $filename = $self->{instance}->_imapd_conf(); - open my $fh, '<', $filename - or die "Cannot open $filename for reading: $!"; - while (my $line = <$fh>) { - chomp $line; - my ($name, $value) = split /\s*:\s*/, $line, 2; - if (Cassandane::Config::is_bitfield($name)) { - my @values = split /\s+/, $value; - $imapd_conf{$name} = join q{ }, sort @values; - } - else { - $imapd_conf{$name} = $value; - } +sub test_conf_all { + my ($self) = @_; + + my %imapd_conf; + my $filename = $self->{instance}->_imapd_conf(); + open my $fh, '<', $filename + or die "Cannot open $filename for reading: $!"; + while (my $line = <$fh>) { + chomp $line; + my ($name, $value) = split /\s*:\s*/, $line, 2; + if (Cassandane::Config::is_bitfield($name)) { + my @values = split /\s+/, $value; + $imapd_conf{$name} = join q{ }, sort @values; + } else { + $imapd_conf{$name} = $value; } - close $fh; - - my %cyr_info_conf; - foreach my $line ($self->{instance}->run_cyr_info('conf-all')) { - chomp $line; - my ($name, $value) = split /\s*:\s*/, $line, 2; - - # conf-all outputs ALL configured values (including defaults) - # but we can only really test for the ones we know we put there - next if not exists $imapd_conf{$name}; - - if (Cassandane::Config::is_bitfield($name)) { - my @values = split /\s+/, $value; - $cyr_info_conf{$name} = join q{ }, sort @values; - } - else { - $cyr_info_conf{$name} = $value; - } + } + close $fh; + + my %cyr_info_conf; + foreach my $line ($self->{instance}->run_cyr_info('conf-all')) { + chomp $line; + my ($name, $value) = split /\s*:\s*/, $line, 2; + + # conf-all outputs ALL configured values (including defaults) + # but we can only really test for the ones we know we put there + next if not exists $imapd_conf{$name}; + + if (Cassandane::Config::is_bitfield($name)) { + my @values = split /\s+/, $value; + $cyr_info_conf{$name} = join q{ }, sort @values; + } else { + $cyr_info_conf{$name} = $value; } + } - $self->assert_deep_equals(\%imapd_conf, \%cyr_info_conf); + $self->assert_deep_equals(\%imapd_conf, \%cyr_info_conf); } -sub test_conf_default -{ - my ($self) = @_; +sub test_conf_default { + my ($self) = @_; - # conf-default spits out all the defaults. can't do much to - # check the actual contents, short of duplicating lib/imapoptions - # in here, but we can at least make sure it runs without crashing - # and its output looks reasonably sane + # conf-default spits out all the defaults. can't do much to + # check the actual contents, short of duplicating lib/imapoptions + # in here, but we can at least make sure it runs without crashing + # and its output looks reasonably sane - foreach my $line ($self->{instance}->run_cyr_info('conf-default')) { - chomp $line; - my ($name, $value) = split /\s*:\s*/, $line, 2; + foreach my $line ($self->{instance}->run_cyr_info('conf-default')) { + chomp $line; + my ($name, $value) = split /\s*:\s*/, $line, 2; - $self->assert_not_null($name); - $self->assert_not_null($value); + $self->assert_not_null($name); + $self->assert_not_null($value); - if (Cassandane::Config::is_bitfield($name)) { - foreach my $v (split /\s+/, $value) { - $self->assert_not_null(Cassandane::Config::is_bitfield_bit($name, $v)); - } - } + if (Cassandane::Config::is_bitfield($name)) { + foreach my $v (split /\s+/, $value) { + $self->assert_not_null(Cassandane::Config::is_bitfield_bit($name, $v)); + } } + } } -sub test_lint -{ - my ($self) = @_; +sub test_lint { + my ($self) = @_; - xlog $self, "test 'cyr_info conf-lint' in the simplest case"; + xlog $self, "test 'cyr_info conf-lint' in the simplest case"; - my @output = $self->{instance}->run_cyr_info('conf-lint'); - $self->assert_deep_equals([], \@output); + my @output = $self->{instance}->run_cyr_info('conf-lint'); + $self->assert_deep_equals([], \@output); } -Cassandane::Cyrus::TestCase::magic(ConfigJunk => sub { +Cassandane::Cyrus::TestCase::magic( + ConfigJunk => sub { shift->config_set(trust_fund => 'street art'); -}); + } +); sub test_lint_junk - :ConfigJunk -{ - my ($self) = @_; + : ConfigJunk { + my ($self) = @_; - xlog $self, "test 'cyr_info conf-lint' with junk in the config"; + xlog $self, "test 'cyr_info conf-lint' with junk in the config"; - my @output = $self->{instance}->run_cyr_info('conf-lint'); - $self->assert_deep_equals(["trust_fund: street art\n"], \@output); + my @output = $self->{instance}->run_cyr_info('conf-lint'); + $self->assert_deep_equals(["trust_fund: street art\n"], \@output); } sub test_lint_channels - :min_version_3_2 :NoStartInstances -{ - my ($self) = @_; - - $self->config_set( - 'sync_log_channels' => 'banana', - 'banana_sync_host' => 'banana.internal', - 'banana_sync_trust_fund' => 'street art', - 'banana_tcp_keepalive' => 'yes', - ); - - $self->_start_instances(); - - xlog $self, "test 'cyr_info conf-lint' with channel-specific sync config"; - - my @output = $self->{instance}->run_cyr_info('conf-lint'); - - $self->assert_deep_equals( - [ sort( - "banana_sync_trust_fund: street art\n", - "banana_tcp_keepalive: yes\n", - ) ], - [ sort @output ] - ); + : min_version_3_2 : NoStartInstances { + my ($self) = @_; + + $self->config_set( + 'sync_log_channels' => 'banana', + 'banana_sync_host' => 'banana.internal', + 'banana_sync_trust_fund' => 'street art', + 'banana_tcp_keepalive' => 'yes', + ); + + $self->_start_instances(); + + xlog $self, "test 'cyr_info conf-lint' with channel-specific sync config"; + + my @output = $self->{instance}->run_cyr_info('conf-lint'); + + $self->assert_deep_equals( + [ + sort("banana_sync_trust_fund: street art\n", + "banana_tcp_keepalive: yes\n", + ) + ], + [ sort @output ] + ); } sub test_lint_partitions - :min_version_3_0 :NoStartInstances -{ - my ($self) = @_; - - $self->config_set( - # metapartition-, archivepartition- and searchpartition- must - # correspond with an extant partition- - # - # backuppartition- is independent - 'partition-good' => '/tmp/pgood', - 'metapartition-good' => '/tmp/mgood', - 'archivepartition-good' => '/tmp/agood', - 'foosearchpartition-good' => '/tmp/sgood', - 'backuppartition-good' => '/tmp/bgood', - - 'metapartition-bad' => '/tmp/mbad', - 'archivepartition-bad' => '/tmp/abad', - 'foosearchpartition-bad' => '/tmp/sbad', - - # not actually bad - 'backuppartition-bad' => '/tmp/bbad', - ); - - $self->_start_instances(); - - xlog $self, "test 'cyr_info conf-lint' with partitions configured"; + : min_version_3_0 : NoStartInstances { + my ($self) = @_; + + $self->config_set( + # metapartition-, archivepartition- and searchpartition- must + # correspond with an extant partition- + # + # backuppartition- is independent + 'partition-good' => '/tmp/pgood', + 'metapartition-good' => '/tmp/mgood', + 'archivepartition-good' => '/tmp/agood', + 'foosearchpartition-good' => '/tmp/sgood', + 'backuppartition-good' => '/tmp/bgood', + + 'metapartition-bad' => '/tmp/mbad', + 'archivepartition-bad' => '/tmp/abad', + 'foosearchpartition-bad' => '/tmp/sbad', + + # not actually bad + 'backuppartition-bad' => '/tmp/bbad', + ); + + $self->_start_instances(); + + xlog $self, "test 'cyr_info conf-lint' with partitions configured"; + + my @output = $self->{instance}->run_cyr_info('conf-lint'); + + $self->assert_deep_equals( + [ + sort("archivepartition-bad: /tmp/abad\n", + "foosearchpartition-bad: /tmp/sbad\n", + "metapartition-bad: /tmp/mbad\n", + ) + ], + [ sort @output ] + ); +} - my @output = $self->{instance}->run_cyr_info('conf-lint'); +sub test_proc_services { + my ($self) = @_; - $self->assert_deep_equals( - [ sort( - "archivepartition-bad: /tmp/abad\n", - "foosearchpartition-bad: /tmp/sbad\n", - "metapartition-bad: /tmp/mbad\n", - ) ], - [ sort @output ] - ); -} + # no clients => no service daemons => no processes + my @output = $self->{instance}->run_cyr_info('proc'); + $self->assert_num_equals(0, scalar @output); -sub test_proc_services -{ - my ($self) = @_; + # master spawns service processes when clients connect to them + my $imap_svc = $self->{instance}->get_service('imap'); + my @clients; + foreach (1 .. 5) { + # five concurrent connections for a single user is normal, + # e.g. thunderbird does this + my $store = $imap_svc->create_store(username => 'cassandane'); + my $imaptalk = $store->get_client(); + push @clients, $imaptalk if $imaptalk; + } - # no clients => no service daemons => no processes - my @output = $self->{instance}->run_cyr_info('proc'); - $self->assert_num_equals(0, scalar @output); + # better have got some clients from that! + $self->assert_num_gte(1, scalar @clients); - # master spawns service processes when clients connect to them - my $imap_svc = $self->{instance}->get_service('imap'); - my @clients; - foreach (1..5) { - # five concurrent connections for a single user is normal, - # e.g. thunderbird does this - my $store = $imap_svc->create_store(username => 'cassandane'); - my $imaptalk = $store->get_client(); - push @clients, $imaptalk if $imaptalk; - } + # five clients => five service daemons => five processes + @output = $self->{instance}->run_cyr_info('proc'); + $self->assert_num_equals(scalar @clients, scalar @output); - # better have got some clients from that! - $self->assert_num_gte(1, scalar @clients); + # log clients out one at a time, expect proc count to decrease + while (scalar @clients) { + my $old = shift @clients; + $old->logout(); - # five clients => five service daemons => five processes @output = $self->{instance}->run_cyr_info('proc'); $self->assert_num_equals(scalar @clients, scalar @output); - - # log clients out one at a time, expect proc count to decrease - while (scalar @clients) { - my $old = shift @clients; - $old->logout(); - - @output = $self->{instance}->run_cyr_info('proc'); - $self->assert_num_equals(scalar @clients, scalar @output); - } + } } sub test_proc_starts - :NoStartInstances -{ - my ($self) = @_; - - # we used to recommend starting idled from START, and it will - # still work like that, so using it here saves me mocking something - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - - # entries listed in START run to completion before master fully - # starts up. if they fork themselves and hang around (like idled - # does) then that's their business, but master can't and doesn't - # track them - my @output = $self->{instance}->run_cyr_info('proc'); - - $self->assert_num_equals(0, scalar @output); + : NoStartInstances { + my ($self) = @_; + + # we used to recommend starting idled from START, and it will + # still work like that, so using it here saves me mocking something + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + + # entries listed in START run to completion before master fully + # starts up. if they fork themselves and hang around (like idled + # does) then that's their business, but master can't and doesn't + # track them + my @output = $self->{instance}->run_cyr_info('proc'); + + $self->assert_num_equals(0, scalar @output); } sub test_proc_periodic_events_slow - :NoStartInstances -{ - my ($self) = @_; - - my $sleeper_time = 10; # seconds - - # periodic events first fire immediately at startup, and then every - # 'period' minutes thereafter. the fastest we can schedule them is - # every 1 minute, so this test must run for at least several real - # minutes - $self->{instance}->add_event( - name => 'sleeper', - argv => [ realpath('utils/sleeper'), $sleeper_time ], - period => 1, - ); - $self->{instance}->start(); - - sleep 2; # offset our checks a little to avoid races - - # observe for three cycles - my $observations = 3; - while ($observations > 0) { - # event should have fired and be running - my @output = $self->{instance}->run_cyr_info('proc'); - $self->assert_num_equals(1, scalar @output); - - # wait for it to finish and check again - sleep $sleeper_time; - @output = $self->{instance}->run_cyr_info('proc'); - $self->assert_num_equals(0, scalar @output); - - # skip final wait if we're done - $observations--; - last if $observations == 0; - - # wait until next period - sleep 60 - $sleeper_time; - } -} - -sub test_proc_scheduled_events - :NoStartInstances -{ - my ($self) = @_; - - my $sleeper_time = 10; - - # schedule an event to fire at the next minute boundary that is at - # least ten seconds away - my $at = time + 70; - $at -= ($at % 60); - my $at_hm = time2str('%H%M', $at); - xlog $self, "scheduling event to run at $at_hm ($at)"; - $self->{instance}->add_event( - name => 'sleeper', - argv => [ realpath('utils/sleeper'), $sleeper_time ], - at => $at_hm, - ); - $self->{instance}->start(); - - # event process should not be running at startup + : NoStartInstances { + my ($self) = @_; + + my $sleeper_time = 10; # seconds + + # periodic events first fire immediately at startup, and then every + # 'period' minutes thereafter. the fastest we can schedule them is + # every 1 minute, so this test must run for at least several real + # minutes + $self->{instance}->add_event( + name => 'sleeper', + argv => [ realpath('utils/sleeper'), $sleeper_time ], + period => 1, + ); + $self->{instance}->start(); + + sleep 2; # offset our checks a little to avoid races + + # observe for three cycles + my $observations = 3; + while ($observations > 0) { + # event should have fired and be running my @output = $self->{instance}->run_cyr_info('proc'); - $self->assert_num_equals(0, scalar @output); - - # should be running at the scheduled time (with a little slop) - sleep 2 + $at - time; - @output = $self->{instance}->run_cyr_info('proc'); $self->assert_num_equals(1, scalar @output); - # should not be running after we expect it to have finished + # wait for it to finish and check again sleep $sleeper_time; @output = $self->{instance}->run_cyr_info('proc'); $self->assert_num_equals(0, scalar @output); + + # skip final wait if we're done + $observations--; + last if $observations == 0; + + # wait until next period + sleep 60 - $sleeper_time; + } +} + +sub test_proc_scheduled_events + : NoStartInstances { + my ($self) = @_; + + my $sleeper_time = 10; + + # schedule an event to fire at the next minute boundary that is at + # least ten seconds away + my $at = time + 70; + $at -= ($at % 60); + my $at_hm = time2str('%H%M', $at); + xlog $self, "scheduling event to run at $at_hm ($at)"; + $self->{instance}->add_event( + name => 'sleeper', + argv => [ realpath('utils/sleeper'), $sleeper_time ], + at => $at_hm, + ); + $self->{instance}->start(); + + # event process should not be running at startup + my @output = $self->{instance}->run_cyr_info('proc'); + $self->assert_num_equals(0, scalar @output); + + # should be running at the scheduled time (with a little slop) + sleep 2 + $at - time; + @output = $self->{instance}->run_cyr_info('proc'); + $self->assert_num_equals(1, scalar @output); + + # should not be running after we expect it to have finished + sleep $sleeper_time; + @output = $self->{instance}->run_cyr_info('proc'); + $self->assert_num_equals(0, scalar @output); } sub test_proc_daemons - :NoStartInstances -{ - my ($self) = @_; - - my $sleeper_time = 10; # seconds - my $daemons = 3; - - for my $i (1 .. $daemons) { - # you wouldn't usually run a daemon that exits and needs to be - # restarted every ten seconds, but it's useful for testing - # that cyr_info proc notices the pid changing - $self->{instance}->add_daemon( - name => "sleeper$i", - argv => [ realpath('utils/sleeper'), $sleeper_time ], - ); - } - $self->{instance}->start(); + : NoStartInstances { + my ($self) = @_; + + my $sleeper_time = 10; # seconds + my $daemons = 3; + + for my $i (1 .. $daemons) { + # you wouldn't usually run a daemon that exits and needs to be + # restarted every ten seconds, but it's useful for testing + # that cyr_info proc notices the pid changing + $self->{instance}->add_daemon( + name => "sleeper$i", + argv => [ realpath('utils/sleeper'), $sleeper_time ], + ); + } + $self->{instance}->start(); - sleep 2; # offset our checks a little to avoid races + sleep 2; # offset our checks a little to avoid races - my $observations = 3; - my %lastpid = map {; "sleeper$_" => 0 } (1 .. $daemons); - while ($observations > 0) { - my @output = $self->{instance}->run_cyr_info('proc'); + my $observations = 3; + my %lastpid = map { ; "sleeper$_" => 0 } (1 .. $daemons); + while ($observations > 0) { + my @output = $self->{instance}->run_cyr_info('proc'); - # always exactly one process per daemon - $self->assert_num_equals($daemons, scalar @output); + # always exactly one process per daemon + $self->assert_num_equals($daemons, scalar @output); - # expect a new pid for each daemon each time - foreach my $line (@output) { - my ($pid, $servicename, $host, $user, $mailbox, $cmd) - = split /\s/, $line, 6; - $self->assert_num_not_equals($lastpid{$servicename}, $pid); - $lastpid{$servicename} = $pid; - } + # expect a new pid for each daemon each time + foreach my $line (@output) { + my ($pid, $servicename, $host, $user, $mailbox, $cmd) = split /\s/, + $line, 6; + $self->assert_num_not_equals($lastpid{$servicename}, $pid); + $lastpid{$servicename} = $pid; + } - # skip final wait if we're done - $observations--; - last if $observations == 0; + # skip final wait if we're done + $observations--; + last if $observations == 0; - # wait for next restart - sleep $sleeper_time; - } + # wait for next restart + sleep $sleeper_time; + } } 1; diff --git a/cassandane/Cassandane/Cyrus/JMAPBackup.pm b/cassandane/Cassandane/Cyrus/JMAPBackup.pm index 9c94aee1ac..7adf442416 100644 --- a/cassandane/Cassandane/Cyrus/JMAPBackup.pm +++ b/cassandane/Cassandane/Cyrus/JMAPBackup.pm @@ -56,41 +56,44 @@ use Cassandane::Util::Log; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; +sub new { + my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane', - caldav_historical_age => -1, - conversations => 'yes', - httpmodules => 'carddav caldav jmap', - httpallowcompress => 'no', - notesmailbox => 'Notes', - jmap_nonstandard_extensions => 'yes'); + my $config = Cassandane::Config->default()->clone(); + $config->set( + caldav_realm => 'Cassandane', + caldav_historical_age => -1, + conversations => 'yes', + httpmodules => 'carddav caldav jmap', + httpallowcompress => 'no', + notesmailbox => 'Notes', + jmap_nonstandard_extensions => 'yes' + ); - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - services => [ 'imap', 'http' ] - }, @args); + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + services => [ 'imap', 'http' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/backup', - 'https://cyrusimap.org/ns/jmap/contacts', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/notes', - ]); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/backup', + 'https://cyrusimap.org/ns/jmap/contacts', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/notes', + ]); } use Cassandane::Tiny::Loader 'tiny-tests/JMAPBackup'; diff --git a/cassandane/Cassandane/Cyrus/JMAPCalendars.pm b/cassandane/Cassandane/Cyrus/JMAPCalendars.pm index ffdc769fd1..6cd25b590a 100644 --- a/cassandane/Cassandane/Cyrus/JMAPCalendars.pm +++ b/cassandane/Cassandane/Cyrus/JMAPCalendars.pm @@ -61,547 +61,557 @@ use Cassandane::Util::Slurp; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); - - $config->set(caldav_realm => 'Cassandane', - caldav_historical_age => -1, - conversations => 'yes', - httpmodules => 'carddav caldav jmap', - httpallowcompress => 'no', - sync_log => 'yes', - jmap_nonstandard_extensions => 'yes', - defaultdomain => 'example.com'); - - # Configure Sieve iMIP delivery - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj == 3 && $min == 0) { - # need to explicitly add 'body' to sieve_extensions for 3.0 - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags mailbox mboxmetadata servermetadata variables " . - "body"); - } - elsif ($maj < 3) { - # also for 2.5 (the earliest Cyrus that Cassandane can test) - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags body"); - } - $config->set(sievenotifier => 'mailto'); - $config->set(calendar_user_address_set => 'example.com'); - $config->set(caldav_historical_age => -1); - $config->set(virtdomains => 'no'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - deliver => 1, - services => [ 'imap', 'sieve', 'http' ], - }, @args); +sub new { + my ($class, @args) = @_; + my $config = Cassandane::Config->default()->clone(); + + $config->set( + caldav_realm => 'Cassandane', + caldav_historical_age => -1, + conversations => 'yes', + httpmodules => 'carddav caldav jmap', + httpallowcompress => 'no', + sync_log => 'yes', + jmap_nonstandard_extensions => 'yes', + defaultdomain => 'example.com' + ); + + # Configure Sieve iMIP delivery + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj == 3 && $min == 0) { + # need to explicitly add 'body' to sieve_extensions for 3.0 + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags mailbox mboxmetadata servermetadata variables " + . "body"); + } elsif ($maj < 3) { + # also for 2.5 (the earliest Cyrus that Cassandane can test) + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags body"); + } + $config->set(sievenotifier => 'mailto'); + $config->set(calendar_user_address_set => 'example.com'); + $config->set(caldav_historical_age => -1); + $config->set(virtdomains => 'no'); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + deliver => 1, + services => [ 'imap', 'sieve', 'http' ], + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - if ($self->{_want}->{start_instances}) { - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'urn:ietf:params:jmap:calendars:preferences', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/debug', - ]); - } +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + if ($self->{_want}->{start_instances}) { + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'urn:ietf:params:jmap:calendars:preferences', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/debug', + ]); + } } -sub encode_eventid -{ - # This function hard-codes the event id format. - # It might break if we change the id scheme. - my ($uid, $recurid) = @_; - my $eid = 'E'; - if ($recurid) { - $eid .= 'R' - } - if ($uid =~ /[^0-9A-Za-z\-_]/) { - $eid .= 'B'; - } - $eid .= '-'; - if ($recurid) { - $eid .= $recurid . '-'; - } - if ($uid =~ /[^0-9A-Za-z\-_]/) { - $eid .= encode_base64url($uid); - } - else { - $eid .= $uid; - } - return $eid; +sub encode_eventid { + # This function hard-codes the event id format. + # It might break if we change the id scheme. + my ($uid, $recurid) = @_; + my $eid = 'E'; + if ($recurid) { + $eid .= 'R'; + } + if ($uid =~ /[^0-9A-Za-z\-_]/) { + $eid .= 'B'; + } + $eid .= '-'; + if ($recurid) { + $eid .= $recurid . '-'; + } + if ($uid =~ /[^0-9A-Za-z\-_]/) { + $eid .= encode_base64url($uid); + } else { + $eid .= $uid; + } + return $eid; } -sub normalize_event -{ - my ($event) = @_; - - if (not exists $event->{q{@type}}) { - $event->{q{@type}} = 'Event'; - } - if (not exists $event->{freeBusyStatus}) { - $event->{freeBusyStatus} = 'busy'; - } - if (not exists $event->{priority}) { - $event->{priority} = 0; - } - if (not exists $event->{title}) { - $event->{title} = ''; - } - if (not exists $event->{description}) { - $event->{description} = ''; - } - if (not exists $event->{descriptionContentType}) { - $event->{descriptionContentType} = 'text/plain'; - } - if (not exists $event->{showWithoutTime}) { - $event->{showWithoutTime} = JSON::false; - } - if (not exists $event->{locations}) { - $event->{locations} = undef; - } elsif (defined $event->{locations}) { - foreach my $loc (values %{$event->{locations}}) { - if (not exists $loc->{name}) { - $loc->{name} = ''; - } - if (not exists $loc->{q{@type}}) { - $loc->{q{@type}} = 'Location'; - } - foreach my $link (values %{$loc->{links}}) { - if (not exists $link->{q{@type}}) { - $link->{q{@type}} = 'Link'; - } - } +sub normalize_event { + my ($event) = @_; + + if (not exists $event->{q{@type}}) { + $event->{q{@type}} = 'Event'; + } + if (not exists $event->{freeBusyStatus}) { + $event->{freeBusyStatus} = 'busy'; + } + if (not exists $event->{priority}) { + $event->{priority} = 0; + } + if (not exists $event->{title}) { + $event->{title} = ''; + } + if (not exists $event->{description}) { + $event->{description} = ''; + } + if (not exists $event->{descriptionContentType}) { + $event->{descriptionContentType} = 'text/plain'; + } + if (not exists $event->{showWithoutTime}) { + $event->{showWithoutTime} = JSON::false; + } + if (not exists $event->{locations}) { + $event->{locations} = undef; + } elsif (defined $event->{locations}) { + foreach my $loc (values %{ $event->{locations} }) { + if (not exists $loc->{name}) { + $loc->{name} = ''; + } + if (not exists $loc->{q{@type}}) { + $loc->{q{@type}} = 'Location'; + } + foreach my $link (values %{ $loc->{links} }) { + if (not exists $link->{q{@type}}) { + $link->{q{@type}} = 'Link'; } - } - if (not exists $event->{virtualLocations}) { - $event->{virtualLocations} = undef; - } elsif (defined $event->{virtualLocations}) { - foreach my $loc (values %{$event->{virtualLocations}}) { - if (not exists $loc->{name}) { - $loc->{name} = '' - } - if (not exists $loc->{description}) { - $loc->{description} = undef; - } - if (not exists $loc->{uri}) { - $loc->{uri} = undef; - } - if (not exists $loc->{q{@type}}) { - $loc->{q{@type}} = 'VirtualLocation'; - } - } - } - if (not exists $event->{keywords}) { - $event->{keywords} = undef; - } - if (not exists $event->{locale}) { - $event->{locale} = undef; - } - if (not exists $event->{links}) { - $event->{links} = undef; - } elsif (defined $event->{links}) { - foreach my $link (values %{$event->{links}}) { - if (not exists $link->{q{@type}}) { - $link->{q{@type}} = 'Link'; - } - } - } - if (not exists $event->{relatedTo}) { - $event->{relatedTo} = undef; - } elsif (defined $event->{relatedTo}) { - foreach my $rel (values %{$event->{relatedTo}}) { - if (not exists $rel->{q{@type}}) { - $rel->{q{@type}} = 'Relation'; - } - } - } - if (not exists $event->{participants}) { - $event->{participants} = undef; - } elsif (defined $event->{participants}) { - foreach my $p (values %{$event->{participants}}) { - if (not exists $p->{linkIds}) { - $p->{linkIds} = undef; - } - if (not exists $p->{participationStatus}) { - $p->{participationStatus} = 'needs-action'; - } - if (not exists $p->{expectReply}) { - $p->{expectReply} = JSON::false; - } - if (not exists $p->{scheduleSequence}) { - $p->{scheduleSequence} = 0; - } - if (not exists $p->{q{@type}}) { - $p->{q{@type}} = 'Participant'; - } - foreach my $link (values %{$p->{links}}) { - if (not exists $link->{q{@type}}) { - $link->{q{@type}} = 'Link'; - } - } + } + } + } + if (not exists $event->{virtualLocations}) { + $event->{virtualLocations} = undef; + } elsif (defined $event->{virtualLocations}) { + foreach my $loc (values %{ $event->{virtualLocations} }) { + if (not exists $loc->{name}) { + $loc->{name} = ''; + } + if (not exists $loc->{description}) { + $loc->{description} = undef; + } + if (not exists $loc->{uri}) { + $loc->{uri} = undef; + } + if (not exists $loc->{q{@type}}) { + $loc->{q{@type}} = 'VirtualLocation'; + } + } + } + if (not exists $event->{keywords}) { + $event->{keywords} = undef; + } + if (not exists $event->{locale}) { + $event->{locale} = undef; + } + if (not exists $event->{links}) { + $event->{links} = undef; + } elsif (defined $event->{links}) { + foreach my $link (values %{ $event->{links} }) { + if (not exists $link->{q{@type}}) { + $link->{q{@type}} = 'Link'; + } + } + } + if (not exists $event->{relatedTo}) { + $event->{relatedTo} = undef; + } elsif (defined $event->{relatedTo}) { + foreach my $rel (values %{ $event->{relatedTo} }) { + if (not exists $rel->{q{@type}}) { + $rel->{q{@type}} = 'Relation'; + } + } + } + if (not exists $event->{participants}) { + $event->{participants} = undef; + } elsif (defined $event->{participants}) { + foreach my $p (values %{ $event->{participants} }) { + if (not exists $p->{linkIds}) { + $p->{linkIds} = undef; + } + if (not exists $p->{participationStatus}) { + $p->{participationStatus} = 'needs-action'; + } + if (not exists $p->{expectReply}) { + $p->{expectReply} = JSON::false; + } + if (not exists $p->{scheduleSequence}) { + $p->{scheduleSequence} = 0; + } + if (not exists $p->{q{@type}}) { + $p->{q{@type}} = 'Participant'; + } + foreach my $link (values %{ $p->{links} }) { + if (not exists $link->{q{@type}}) { + $link->{q{@type}} = 'Link'; } - } - if (not exists $event->{replyTo}) { - $event->{replyTo} = undef; - } - if (not exists $event->{recurrenceRules}) { - $event->{recurrenceRules} = undef; - } elsif (defined $event->{recurrenceRules}) { - foreach my $rrule (@{$event->{recurrenceRules}}) { - if (not exists $rrule->{interval}) { - $rrule->{interval} = 1; - } - if (not exists $rrule->{firstDayOfWeek}) { - $rrule->{firstDayOfWeek} = 'mo'; - } - if (not exists $rrule->{rscale}) { - $rrule->{rscale} = 'gregorian'; - } - if (not exists $rrule->{skip}) { - $rrule->{skip} = 'omit'; - } - if (not exists $rrule->{byDay}) { - $rrule->{byDay} = undef; - } elsif (defined $rrule->{byDay}) { - foreach my $nday (@{$rrule->{byDay}}) { - if (not exists $nday->{q{@type}}) { - $nday->{q{@type}} = 'NDay'; - } - } - } - if (not exists $rrule->{q{@type}}) { - $rrule->{q{@type}} = 'RecurrenceRule'; - } + } + } + } + if (not exists $event->{replyTo}) { + $event->{replyTo} = undef; + } + if (not exists $event->{recurrenceRules}) { + $event->{recurrenceRules} = undef; + } elsif (defined $event->{recurrenceRules}) { + foreach my $rrule (@{ $event->{recurrenceRules} }) { + if (not exists $rrule->{interval}) { + $rrule->{interval} = 1; + } + if (not exists $rrule->{firstDayOfWeek}) { + $rrule->{firstDayOfWeek} = 'mo'; + } + if (not exists $rrule->{rscale}) { + $rrule->{rscale} = 'gregorian'; + } + if (not exists $rrule->{skip}) { + $rrule->{skip} = 'omit'; + } + if (not exists $rrule->{byDay}) { + $rrule->{byDay} = undef; + } elsif (defined $rrule->{byDay}) { + foreach my $nday (@{ $rrule->{byDay} }) { + if (not exists $nday->{q{@type}}) { + $nday->{q{@type}} = 'NDay'; + } } - } - if (not exists $event->{excludedRecurrenceRules}) { - $event->{excludedRecurrenceRules} = undef; - } elsif (defined $event->{excludedRecurrenceRules}) { - foreach my $exrule (@{$event->{excludedRecurrenceRules}}) { - if (not exists $exrule->{interval}) { - $exrule->{interval} = 1; - } - if (not exists $exrule->{firstDayOfWeek}) { - $exrule->{firstDayOfWeek} = 'mo'; - } - if (not exists $exrule->{rscale}) { - $exrule->{rscale} = 'gregorian'; - } - if (not exists $exrule->{skip}) { - $exrule->{skip} = 'omit'; - } - if (not exists $exrule->{byDay}) { - $exrule->{byDay} = undef; - } elsif (defined $exrule->{byDay}) { - foreach my $nday (@{$exrule->{byDay}}) { - if (not exists $nday->{q{@type}}) { - $nday->{q{@type}} = 'NDay'; - } - } - } - if (not exists $exrule->{q{@type}}) { - $exrule->{q{@type}} = 'RecurrenceRule'; - } + } + if (not exists $rrule->{q{@type}}) { + $rrule->{q{@type}} = 'RecurrenceRule'; + } + } + } + if (not exists $event->{excludedRecurrenceRules}) { + $event->{excludedRecurrenceRules} = undef; + } elsif (defined $event->{excludedRecurrenceRules}) { + foreach my $exrule (@{ $event->{excludedRecurrenceRules} }) { + if (not exists $exrule->{interval}) { + $exrule->{interval} = 1; + } + if (not exists $exrule->{firstDayOfWeek}) { + $exrule->{firstDayOfWeek} = 'mo'; + } + if (not exists $exrule->{rscale}) { + $exrule->{rscale} = 'gregorian'; + } + if (not exists $exrule->{skip}) { + $exrule->{skip} = 'omit'; + } + if (not exists $exrule->{byDay}) { + $exrule->{byDay} = undef; + } elsif (defined $exrule->{byDay}) { + foreach my $nday (@{ $exrule->{byDay} }) { + if (not exists $nday->{q{@type}}) { + $nday->{q{@type}} = 'NDay'; + } } - } - if (not exists $event->{recurrenceOverrides}) { - $event->{recurrenceOverrides} = undef; - } - if (not exists $event->{alerts}) { - $event->{alerts} = undef; - } - elsif (defined $event->{alerts}) { - foreach my $alert (values %{$event->{alerts}}) { - if (not exists $alert->{action}) { - $alert->{action} = 'display'; - } - if (not exists $alert->{q{@type}}) { - $alert->{q{@type}} = 'Alert'; - } - if (not exists $alert->{relatedTo}) { - $alert->{relatedTo} = undef; - } elsif (defined $alert->{relatedTo}) { - foreach my $rel (values %{$alert->{relatedTo}}) { - if (not exists $rel->{q{@type}}) { - $rel->{q{@type}} = 'Relation'; - } - } - } - if ($alert->{trigger} and $alert->{trigger}{q{@type}} eq 'OffsetTrigger') { - if (not exists $alert->{trigger}{relativeTo}) { - $alert->{trigger}{relativeTo} = 'start'; - } - } + } + if (not exists $exrule->{q{@type}}) { + $exrule->{q{@type}} = 'RecurrenceRule'; + } + } + } + if (not exists $event->{recurrenceOverrides}) { + $event->{recurrenceOverrides} = undef; + } + if (not exists $event->{alerts}) { + $event->{alerts} = undef; + } elsif (defined $event->{alerts}) { + foreach my $alert (values %{ $event->{alerts} }) { + if (not exists $alert->{action}) { + $alert->{action} = 'display'; + } + if (not exists $alert->{q{@type}}) { + $alert->{q{@type}} = 'Alert'; + } + if (not exists $alert->{relatedTo}) { + $alert->{relatedTo} = undef; + } elsif (defined $alert->{relatedTo}) { + foreach my $rel (values %{ $alert->{relatedTo} }) { + if (not exists $rel->{q{@type}}) { + $rel->{q{@type}} = 'Relation'; + } } - } - if (not exists $event->{useDefaultAlerts}) { - $event->{useDefaultAlerts} = JSON::false; - } - if (not exists $event->{prodId}) { - $event->{prodId} = undef; - } - if (not exists $event->{links}) { - $event->{links} = undef; - } elsif (defined $event->{links}) { - foreach my $link (values %{$event->{links}}) { - if (not exists $link->{cid}) { - $link->{cid} = undef; - } - if (not exists $link->{contentType}) { - $link->{contentType} = undef; - } - if (not exists $link->{size}) { - $link->{size} = undef; - } - if (not exists $link->{title}) { - $link->{title} = undef; - } - if (not exists $link->{q{@type}}) { - $link->{q{@type}} = 'Link'; - } + } + if ($alert->{trigger} and $alert->{trigger}{q{@type}} eq 'OffsetTrigger') + { + if (not exists $alert->{trigger}{relativeTo}) { + $alert->{trigger}{relativeTo} = 'start'; } + } } - if (not exists $event->{status}) { - $event->{status} = "confirmed"; - } - if (not exists $event->{privacy}) { - $event->{privacy} = "public"; - } - if (not exists $event->{isDraft}) { - $event->{isDraft} = JSON::false; - } - if (not exists $event->{excluded}) { - $event->{excluded} = JSON::false, - } - - if (not exists $event->{calendarIds}) { - $event->{calendarIds} = undef; - } - if (not exists $event->{timeZone}) { - $event->{timeZone} = undef; - } - - if (not exists $event->{mayInviteSelf}) { - $event->{mayInviteSelf} = JSON::false, - } - - # undefine dynamically generated values - $event->{created} = undef; - $event->{updated} = undef; - $event->{uid} = undef; - $event->{id} = undef; - $event->{"x-href"} = undef; - $event->{sequence} = 0; + } + if (not exists $event->{useDefaultAlerts}) { + $event->{useDefaultAlerts} = JSON::false; + } + if (not exists $event->{prodId}) { $event->{prodId} = undef; - $event->{isOrigin} = undef; - delete($event->{blobId}); - delete($event->{debugBlobId}); + } + if (not exists $event->{links}) { + $event->{links} = undef; + } elsif (defined $event->{links}) { + foreach my $link (values %{ $event->{links} }) { + if (not exists $link->{cid}) { + $link->{cid} = undef; + } + if (not exists $link->{contentType}) { + $link->{contentType} = undef; + } + if (not exists $link->{size}) { + $link->{size} = undef; + } + if (not exists $link->{title}) { + $link->{title} = undef; + } + if (not exists $link->{q{@type}}) { + $link->{q{@type}} = 'Link'; + } + } + } + if (not exists $event->{status}) { + $event->{status} = "confirmed"; + } + if (not exists $event->{privacy}) { + $event->{privacy} = "public"; + } + if (not exists $event->{isDraft}) { + $event->{isDraft} = JSON::false; + } + if (not exists $event->{excluded}) { + $event->{excluded} = JSON::false,; + } + + if (not exists $event->{calendarIds}) { + $event->{calendarIds} = undef; + } + if (not exists $event->{timeZone}) { + $event->{timeZone} = undef; + } + + if (not exists $event->{mayInviteSelf}) { + $event->{mayInviteSelf} = JSON::false,; + } + + # undefine dynamically generated values + $event->{created} = undef; + $event->{updated} = undef; + $event->{uid} = undef; + $event->{id} = undef; + $event->{"x-href"} = undef; + $event->{sequence} = 0; + $event->{prodId} = undef; + $event->{isOrigin} = undef; + delete($event->{blobId}); + delete($event->{debugBlobId}); } -sub assert_normalized_event_equals -{ - my ($self, $a, $b) = @_; - my $copyA = dclone($a); - my $copyB = dclone($b); - normalize_event($copyA); - normalize_event($copyB); - return $self->assert_deep_equals($copyA, $copyB); +sub assert_normalized_event_equals { + my ($self, $a, $b) = @_; + my $copyA = dclone($a); + my $copyB = dclone($b); + normalize_event($copyA); + normalize_event($copyB); + return $self->assert_deep_equals($copyA, $copyB); } -sub putandget_vevent -{ - my ($self, $id, $ical, $props) = @_; +sub putandget_vevent { + my ($self, $id, $ical, $props) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "get default calendar id"; - my $res = $jmap->CallMethods([['Calendar/get', {ids => ["Default"]}, "R1"]]); - $self->assert_str_equals("Default", $res->[0][1]{list}[0]{id}); - my $calid = $res->[0][1]{list}[0]{id}; - my $xhref = $res->[0][1]{list}[0]{"x-href"}; + xlog $self, "get default calendar id"; + my $res + = $jmap->CallMethods([ [ 'Calendar/get', { ids => ["Default"] }, "R1" ] ]); + $self->assert_str_equals("Default", $res->[0][1]{list}[0]{id}); + my $calid = $res->[0][1]{list}[0]{id}; + my $xhref = $res->[0][1]{list}[0]{"x-href"}; - # Create event via CalDAV to test CalDAV/JMAP interop. - xlog $self, "create event (via CalDAV)"; - my $href = "$xhref/$id.ics"; + # Create event via CalDAV to test CalDAV/JMAP interop. + xlog $self, "create event (via CalDAV)"; + my $href = "$xhref/$id.ics"; - $caldav->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); - xlog $self, "get event $id"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$id], properties => $props}, "R1"]]); + xlog $self, "get event $id"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/get', { ids => [$id], properties => $props }, "R1" ] ]); - my $event = $res->[0][1]{list}[0]; - $self->assert_not_null($event); - return $event; + my $event = $res->[0][1]{list}[0]; + $self->assert_not_null($event); + return $event; } -sub icalfile -{ - my ($self, $name) = @_; +sub icalfile { + my ($self, $name) = @_; - my $path = abs_path("data/icalendar/$name.ics"); - $self->assert_file_test($path, '-f'); - my $data = slurp_file($path); + my $path = abs_path("data/icalendar/$name.ics"); + $self->assert_file_test($path, '-f'); + my $data = slurp_file($path); - my ($id) = ($data =~ m/^UID:(\S+)\r?$/m); - $self->assert($id); - return ($id, $data); + my ($id) = ($data =~ m/^UID:(\S+)\r?$/m); + $self->assert($id); + return ($id, $data); } -sub createandget_event -{ - my ($self, $event, %params) = @_; - - my $jmap = $self->{jmap}; - my $accountId = $params{accountId} || 'cassandane'; - - xlog $self, "create event"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { - accountId => $accountId, - create => {"1" => $event}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - my $id = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get calendar event $id"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$id]}, "R1"]]); - my $ret = $res->[0][1]{list}[0]; - return $ret; +sub createandget_event { + my ($self, $event, %params) = @_; + + my $jmap = $self->{jmap}; + my $accountId = $params{accountId} || 'cassandane'; + + xlog $self, "create event"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => $accountId, + create => { "1" => $event } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $id = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get calendar event $id"; + $res + = $jmap->CallMethods([ [ 'CalendarEvent/get', { ids => [$id] }, "R1" ] ]); + my $ret = $res->[0][1]{list}[0]; + return $ret; } -sub updateandget_event -{ - my ($self, $event) = @_; +sub updateandget_event { + my ($self, $event) = @_; - my $jmap = $self->{jmap}; - my $id = $event->{id}; + my $jmap = $self->{jmap}; + my $id = $event->{id}; - xlog $self, "update event $id"; - my $res = $jmap->CallMethods([['CalendarEvent/set', {update => {$id => $event}}, "R1"]]); - $self->assert_not_null($res->[0][1]{updated}); + xlog $self, "update event $id"; + my $res = $jmap->CallMethods( + [ [ 'CalendarEvent/set', { update => { $id => $event } }, "R1" ] ]); + $self->assert_not_null($res->[0][1]{updated}); - xlog $self, "get calendar event $id"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$id]}, "R1"]]); - my $ret = $res->[0][1]{list}[0]; - return $ret; + xlog $self, "get calendar event $id"; + $res + = $jmap->CallMethods([ [ 'CalendarEvent/get', { ids => [$id] }, "R1" ] ]); + my $ret = $res->[0][1]{list}[0]; + return $ret; } -sub createcalendar -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", color => "coral", sortOrder => 1, isVisible => \1 - }}}, "R1"] - ]); - $self->assert_not_null($res->[0][1]{created}); - return $res->[0][1]{created}{"1"}{id}; +sub createcalendar { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 1, + isVisible => \1 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + return $res->[0][1]{created}{"1"}{id}; } sub assert_rewrite_webdav_attachment_url_itip - :min_version_3_5 :needs_component_jmap -{ - my ($self, $eventHref) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - - xlog "Assert ATTACH in iTIP message is a BINARY value"; - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); - - my $ical = Data::ICal->new(data => $payload->{ical}); - my %entries = map { $_->ical_entry_type() => $_ } @{$ical->entries()}; - my $event = $entries{'VEVENT'}; - $self->assert_not_null($event); - - my $attach = $event->property('ATTACH'); - $self->assert_num_equals(1, scalar @{$attach}); - $self->assert_null($attach->[0]->parameters()->{'MANAGED-ID'}); - $self->assert_str_equals('BINARY', $attach->[0]->parameters()->{VALUE}); - $self->assert_str_equals('c29tZWJsb2I=', $attach->[0]->value()); # 'someblob' in base64 - - xlog "Assert ATTACH on server is a WebDAV attachment URI"; - my $caldavResponse = $caldav->Request('GET', $eventHref); - $ical = Data::ICal->new(data => $caldavResponse->{content}); - %entries = map { $_->ical_entry_type() => $_ } @{$ical->entries()}; - $event = $entries{'VEVENT'}; - $self->assert_not_null($event); - - $attach = $event->property('ATTACH'); - $self->assert_num_equals(1, scalar @{$attach}); - $self->assert_not_null($attach->[0]->parameters()->{'MANAGED-ID'}); - $self->assert_null($attach->[0]->parameters()->{VALUE}); - my $webdavAttachURI = - $self->{instance}->{config}->get('webdav_attachments_baseurl') . - '/dav/calendars/user/cassandane/Attachments/'; - $self->assert($attach->[0]->value() =~ /^$webdavAttachURI.+/); + : min_version_3_5 : needs_component_jmap { + my ($self, $eventHref) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + + xlog "Assert ATTACH in iTIP message is a BINARY value"; + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); + my $payload = decode_json($imip->{MESSAGE}); + + my $ical = Data::ICal->new(data => $payload->{ical}); + my %entries = map { $_->ical_entry_type() => $_ } @{ $ical->entries() }; + my $event = $entries{'VEVENT'}; + $self->assert_not_null($event); + + my $attach = $event->property('ATTACH'); + $self->assert_num_equals(1, scalar @{$attach}); + $self->assert_null($attach->[0]->parameters()->{'MANAGED-ID'}); + $self->assert_str_equals('BINARY', $attach->[0]->parameters()->{VALUE}); + $self->assert_str_equals('c29tZWJsb2I=', $attach->[0]->value()) + ; # 'someblob' in base64 + + xlog "Assert ATTACH on server is a WebDAV attachment URI"; + my $caldavResponse = $caldav->Request('GET', $eventHref); + $ical = Data::ICal->new(data => $caldavResponse->{content}); + %entries = map { $_->ical_entry_type() => $_ } @{ $ical->entries() }; + $event = $entries{'VEVENT'}; + $self->assert_not_null($event); + + $attach = $event->property('ATTACH'); + $self->assert_num_equals(1, scalar @{$attach}); + $self->assert_not_null($attach->[0]->parameters()->{'MANAGED-ID'}); + $self->assert_null($attach->[0]->parameters()->{VALUE}); + my $webdavAttachURI + = $self->{instance}->{config}->get('webdav_attachments_baseurl') + . '/dav/calendars/user/cassandane/Attachments/'; + $self->assert($attach->[0]->value() =~ /^$webdavAttachURI.+/); } -sub create_user -{ - my ($self, $username) = @_; - - xlog $self, "create user $username"; - my $admin = $self->{adminstore}->get_client(); - $admin->create("user.$username"); - $admin->setacl("user.$username", admin => 'lrswipkxtecdan') or die; - $admin->setacl("user.$username", $username => 'lrswipkxtecdn') or die; - - my $http = $self->{instance}->get_service("http"); - my $userJmap = Mail::JMAPTalk->new( - user => $username, - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $userJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - - my $userCalDAV = Net::CalDAVTalk->new( - user => $username, - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - return ($userJmap, $userCalDAV); +sub create_user { + my ($self, $username) = @_; + + xlog $self, "create user $username"; + my $admin = $self->{adminstore}->get_client(); + $admin->create("user.$username"); + $admin->setacl("user.$username", admin => 'lrswipkxtecdan') or die; + $admin->setacl("user.$username", $username => 'lrswipkxtecdn') or die; + + my $http = $self->{instance}->get_service("http"); + my $userJmap = Mail::JMAPTalk->new( + user => $username, + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $userJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); + + my $userCalDAV = Net::CalDAVTalk->new( + user => $username, + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + return ($userJmap, $userCalDAV); } sub deliver_imip { - my ($self) = @_; + my ($self) = @_; - my $uuid = guid_string(); - my $imip = <<"EOF"; + my $uuid = guid_string(); + my $imip = <<"EOF"; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -628,9 +638,9 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); -}; + xlog $self, "Deliver iMIP invite"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); +} use Cassandane::Tiny::Loader 'tiny-tests/JMAPCalendars'; diff --git a/cassandane/Cassandane/Cyrus/JMAPContacts.pm b/cassandane/Cassandane/Cyrus/JMAPContacts.pm index be65c2b2e1..6f07de2a70 100644 --- a/cassandane/Cassandane/Cyrus/JMAPContacts.pm +++ b/cassandane/Cassandane/Cyrus/JMAPContacts.pm @@ -58,82 +58,81 @@ use Cassandane::Util::Slurp; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set(carddav_realm => 'Cassandane', - conversations => 'yes', - httpmodules => 'carddav jmap', - httpallowcompress => 'no', - vcard_max_size => 100000, - jmap_nonstandard_extensions => 'yes'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - services => [ 'imap', 'http' ] - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set( + carddav_realm => 'Cassandane', + conversations => 'yes', + httpmodules => 'carddav jmap', + httpallowcompress => 'no', + vcard_max_size => 100000, + jmap_nonstandard_extensions => 'yes' + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + services => [ 'imap', 'http' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/contacts', - 'https://cyrusimap.org/ns/jmap/debug', - ]); - - my $buildinfo = Cassandane::BuildInfo->new(); - if ($buildinfo->get('dependency', 'icalvcard')) { - $self->{jmap}->AddUsing('urn:ietf:params:jmap:contacts'); - } +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'https://cyrusimap.org/ns/jmap/contacts', + 'https://cyrusimap.org/ns/jmap/debug', + ]); + + my $buildinfo = Cassandane::BuildInfo->new(); + if ($buildinfo->get('dependency', 'icalvcard')) { + $self->{jmap}->AddUsing('urn:ietf:params:jmap:contacts'); + } } -sub normalize_jscard -{ - my ($jscard) = @_; +sub normalize_jscard { + my ($jscard) = @_; - if ($jscard->{vCardProps}) { - my @sorted = sort { $a->[0] cmp $b->[0] } @{$jscard->{vCardProps}}; - $jscard->{vCardProps} = \@sorted; - } + if ($jscard->{vCardProps}) { + my @sorted = sort { $a->[0] cmp $b->[0] } @{ $jscard->{vCardProps} }; + $jscard->{vCardProps} = \@sorted; + } - if (not exists $jscard->{kind}) { - $jscard->{kind} = 'individual'; - } + if (not exists $jscard->{kind}) { + $jscard->{kind} = 'individual'; + } - if (not exists $jscard->{'cyrusimap.org:importance'}) { - $jscard->{'cyrusimap.org:importance'} = '0'; - } + if (not exists $jscard->{'cyrusimap.org:importance'}) { + $jscard->{'cyrusimap.org:importance'} = '0'; + } } -sub _set_quotaroot -{ - my ($self, $quotaroot) = @_; - $self->{quotaroot} = $quotaroot; +sub _set_quotaroot { + my ($self, $quotaroot) = @_; + $self->{quotaroot} = $quotaroot; } -sub _set_quotalimits -{ - my ($self, %resources) = @_; - my $admintalk = $self->{adminstore}->get_client(); - - my $quotaroot = delete $resources{quotaroot} || $self->{quotaroot}; - my @quotalist; - foreach my $resource (keys %resources) - { - my $limit = $resources{$resource} - or die "No limit specified for $resource"; - push(@quotalist, uc($resource), $limit); - } - $self->{limits}->{$quotaroot} = { @quotalist }; - $admintalk->setquota($quotaroot, \@quotalist); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); +sub _set_quotalimits { + my ($self, %resources) = @_; + my $admintalk = $self->{adminstore}->get_client(); + + my $quotaroot = delete $resources{quotaroot} || $self->{quotaroot}; + my @quotalist; + foreach my $resource (keys %resources) { + my $limit = $resources{$resource} + or die "No limit specified for $resource"; + push(@quotalist, uc($resource), $limit); + } + $self->{limits}->{$quotaroot} = {@quotalist}; + $admintalk->setquota($quotaroot, \@quotalist); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } use Cassandane::Tiny::Loader 'tiny-tests/JMAPContacts'; diff --git a/cassandane/Cassandane/Cyrus/JMAPCore.pm b/cassandane/Cassandane/Cyrus/JMAPCore.pm index ea69efb7b2..c1188c83bd 100644 --- a/cassandane/Cassandane/Cyrus/JMAPCore.pm +++ b/cassandane/Cassandane/Cyrus/JMAPCore.pm @@ -48,8 +48,8 @@ use Mail::JMAPTalk 0.15; use Data::Dumper; use Storable 'dclone'; use MIME::Base64 qw(encode_base64); -use Encode qw(decode_utf8); -use Cwd qw(abs_path getcwd); +use Encode qw(decode_utf8); +use Cwd qw(abs_path getcwd); use lib '.'; use base qw(Cassandane::Cyrus::TestCase); @@ -58,995 +58,1134 @@ use Cassandane::Util::Slurp; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane', - conversations => 'yes', - httpmodules => 'carddav caldav jmap', - jmap_max_size_upload => '1k', - httpallowcompress => 'no'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - services => [ 'imap', 'http' ] - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set( + caldav_realm => 'Cassandane', + conversations => 'yes', + httpmodules => 'carddav caldav jmap', + jmap_max_size_upload => '1k', + httpallowcompress => 'no' + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + services => [ 'imap', 'http' ] + }, + @args + ); } sub test_capabilities - :min_version_3_1 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - $self->{instance}->create_user("other"); - $admintalk->create("user.other.box1") or die; - $admintalk->setacl("user.other.box1", "cassandane", "lrswp") or die; - - # Missing capability in 'using' - my $res = $jmap->CallMethods([ - ['Core/echo', { hello => 'world' }, "R1"] - ], []); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('unknownMethod', $res->[0][1]{type}); - - # Missing capability in account capabilities - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'other' - }, "R1"] - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('accountNotSupportedByMethod', $res->[0][1]{type}); + : min_version_3_1 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + $self->{instance}->create_user("other"); + $admintalk->create("user.other.box1") or die; + $admintalk->setacl("user.other.box1", "cassandane", "lrswp") or die; + + # Missing capability in 'using' + my $res + = $jmap->CallMethods([ [ 'Core/echo', { hello => 'world' }, "R1" ] ], []); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('unknownMethod', $res->[0][1]{type}); + + # Missing capability in account capabilities + $res = $jmap->CallMethods( + [ [ + 'CalendarEvent/get', + { + accountId => 'other' + }, + "R1" + ] ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + ] + ); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('accountNotSupportedByMethod', $res->[0][1]{type}); } sub test_get_session - :min_version_3_1 :needs_component_jmap :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; - - # need to version-gate jmap features that aren't in 3.2... - my ($maj, $min) = Cassandane::Instance->get_version(); - - my $buildinfo = Cassandane::BuildInfo->new(); - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - xlog $self, "setup shared accounts"; - $self->{instance}->create_user("account1"); - $self->{instance}->create_user("account2"); - $self->{instance}->create_user("account3"); - $self->{instance}->create_user("account4"); - - # Account 1: read-only mail, calendars. No contacts. - my $httpService = $self->{instance}->get_service("http"); - my $account1CalDAVTalk = Net::CalDAVTalk->new( - user => "account1", - password => 'pass', - host => $httpService->host(), - port => $httpService->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $account1CalendarId = $account1CalDAVTalk->NewCalendar({name => 'calendar1'}); - $admintalk->setacl("user.account1", "cassandane", "lr") or die; - $admintalk->setacl("user.account1.#calendars.Default", "cassandane" => 'lr') or die; - $admintalk->setacl("user.account1.#addressbooks.Default", "cassandane" => '') or die; - # Account 2: read/write mail - $admintalk->setacl("user.account2", "cassandane", "lrswipkxtecdn") or die; - # Account 3: no access - - # GET session - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - my $session = eval { decode_json($RawResponse->{content}) }; - $self->assert_not_null($session); - - # Validate session - $self->assert_not_null($session->{username}); - $self->assert_not_null($session->{apiUrl}); - $self->assert_not_null($session->{downloadUrl}); - $self->assert_not_null($session->{uploadUrl}); + : min_version_3_1 : needs_component_jmap : JMAPExtensions : NoAltNameSpace { + my ($self) = @_; + + # need to version-gate jmap features that aren't in 3.2... + my ($maj, $min) = Cassandane::Instance->get_version(); + + my $buildinfo = Cassandane::BuildInfo->new(); + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + xlog $self, "setup shared accounts"; + $self->{instance}->create_user("account1"); + $self->{instance}->create_user("account2"); + $self->{instance}->create_user("account3"); + $self->{instance}->create_user("account4"); + + # Account 1: read-only mail, calendars. No contacts. + my $httpService = $self->{instance}->get_service("http"); + my $account1CalDAVTalk = Net::CalDAVTalk->new( + user => "account1", + password => 'pass', + host => $httpService->host(), + port => $httpService->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $account1CalendarId + = $account1CalDAVTalk->NewCalendar({ name => 'calendar1' }); + $admintalk->setacl("user.account1", "cassandane", "lr") or die; + $admintalk->setacl("user.account1.#calendars.Default", "cassandane" => 'lr') + or die; + $admintalk->setacl("user.account1.#addressbooks.Default", "cassandane" => '') + or die; + # Account 2: read/write mail + $admintalk->setacl("user.account2", "cassandane", "lrswipkxtecdn") or die; + # Account 3: no access + + # GET session + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + my $session = eval { decode_json($RawResponse->{content}) }; + $self->assert_not_null($session); + + # Validate session + $self->assert_not_null($session->{username}); + $self->assert_not_null($session->{apiUrl}); + $self->assert_not_null($session->{downloadUrl}); + $self->assert_not_null($session->{uploadUrl}); + if ($maj > 3 || ($maj == 3 && $min >= 3)) { + $self->assert_not_null($session->{eventSourceUrl}); + } + $self->assert_not_null($session->{state}); + + # Validate server capabilities + my $capabilities = $session->{capabilities}; + $self->assert_not_null($capabilities); + my $coreCapability = $capabilities->{'urn:ietf:params:jmap:core'}; + $self->assert_not_null($coreCapability); + $self->assert($coreCapability->{maxSizeUpload} > 0); + $self->assert($coreCapability->{maxConcurrentUpload} > 0); + $self->assert($coreCapability->{maxSizeRequest} > 0); + $self->assert($coreCapability->{maxConcurrentRequests} > 0); + $self->assert($coreCapability->{maxCallsInRequest} > 0); + $self->assert($coreCapability->{maxObjectsInGet} > 0); + $self->assert($coreCapability->{maxObjectsInSet} > 0); + $self->assert(exists $coreCapability->{collationAlgorithms}); + $self->assert_deep_equals({}, $capabilities->{'urn:ietf:params:jmap:mail'}); + $self->assert_deep_equals({}, + $capabilities->{'urn:ietf:params:jmap:submission'}); + $self->assert_deep_equals({}, + $capabilities->{'urn:ietf:params:jmap:calendars'}); + $self->assert_deep_equals({}, + $capabilities->{'https://cyrusimap.org/ns/jmap/contacts'}); + $self->assert_deep_equals({ isRFC => JSON::true }, + $capabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); + + if ($buildinfo->get('component', 'sieve')) { + $self->assert_deep_equals({}, + $capabilities->{'urn:ietf:params:jmap:vacationresponse'}); if ($maj > 3 || ($maj == 3 && $min >= 3)) { - $self->assert_not_null($session->{eventSourceUrl}); - } - $self->assert_not_null($session->{state}); - - # Validate server capabilities - my $capabilities = $session->{capabilities}; - $self->assert_not_null($capabilities); - my $coreCapability = $capabilities->{'urn:ietf:params:jmap:core'}; - $self->assert_not_null($coreCapability); - $self->assert($coreCapability->{maxSizeUpload} > 0); - $self->assert($coreCapability->{maxConcurrentUpload} > 0); - $self->assert($coreCapability->{maxSizeRequest} > 0); - $self->assert($coreCapability->{maxConcurrentRequests} > 0); - $self->assert($coreCapability->{maxCallsInRequest} > 0); - $self->assert($coreCapability->{maxObjectsInGet} > 0); - $self->assert($coreCapability->{maxObjectsInSet} > 0); - $self->assert(exists $coreCapability->{collationAlgorithms}); - $self->assert_deep_equals({}, $capabilities->{'urn:ietf:params:jmap:mail'}); - $self->assert_deep_equals({}, $capabilities->{'urn:ietf:params:jmap:submission'}); - $self->assert_deep_equals({}, $capabilities->{'urn:ietf:params:jmap:calendars'}); - $self->assert_deep_equals({}, $capabilities->{'https://cyrusimap.org/ns/jmap/contacts'}); - $self->assert_deep_equals({ isRFC => JSON::true }, - , $capabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); - if ($buildinfo->get('component', 'sieve')) { - $self->assert_deep_equals({}, $capabilities->{'urn:ietf:params:jmap:vacationresponse'}); - if ($maj > 3 || ($maj == 3 && $min >= 3)) { - # jmap sieve added in 3.3 - $self->assert_deep_equals({}, $capabilities->{'https://cyrusimap.org/ns/jmap/sieve'}); - } + # jmap sieve added in 3.3 + $self->assert_deep_equals({}, + $capabilities->{'https://cyrusimap.org/ns/jmap/sieve'}); } - if ($buildinfo->get('dependency', 'icalvcard')) { - $self->assert_deep_equals({}, $capabilities->{'urn:ietf:params:jmap:contacts'}); - } - - # primaryAccounts - my $expect_primaryAccounts = { - 'urn:ietf:params:jmap:mail' => 'cassandane', - 'urn:ietf:params:jmap:submission' => 'cassandane', - 'urn:ietf:params:jmap:calendars' => 'cassandane', - 'urn:ietf:params:jmap:principals' => 'cassandane', - 'https://cyrusimap.org/ns/jmap/contacts' => 'cassandane', - 'https://cyrusimap.org/ns/jmap/calendars' => 'cassandane', - }; + } + if ($buildinfo->get('dependency', 'icalvcard')) { + $self->assert_deep_equals({}, + $capabilities->{'urn:ietf:params:jmap:contacts'}); + } + + # primaryAccounts + my $expect_primaryAccounts = { + 'urn:ietf:params:jmap:mail' => 'cassandane', + 'urn:ietf:params:jmap:submission' => 'cassandane', + 'urn:ietf:params:jmap:calendars' => 'cassandane', + 'urn:ietf:params:jmap:principals' => 'cassandane', + 'https://cyrusimap.org/ns/jmap/contacts' => 'cassandane', + 'https://cyrusimap.org/ns/jmap/calendars' => 'cassandane', + }; + if ($maj > 3 || ($maj == 3 && $min >= 3)) { + # jmap backup and sieve added in 3.3 + $expect_primaryAccounts->{'https://cyrusimap.org/ns/jmap/backup'} + = 'cassandane'; + } + if ($buildinfo->get('component', 'sieve')) { + $expect_primaryAccounts->{'urn:ietf:params:jmap:vacationresponse'} + = 'cassandane'; if ($maj > 3 || ($maj == 3 && $min >= 3)) { - # jmap backup and sieve added in 3.3 - $expect_primaryAccounts->{'https://cyrusimap.org/ns/jmap/backup'} - = 'cassandane'; - } - if ($buildinfo->get('component', 'sieve')) { - $expect_primaryAccounts->{'urn:ietf:params:jmap:vacationresponse'} - = 'cassandane'; - if ($maj > 3 || ($maj == 3 && $min >= 3)) { - # jmap sieve added in 3.3 - $expect_primaryAccounts->{'https://cyrusimap.org/ns/jmap/sieve'} - = 'cassandane'; - } + # jmap sieve added in 3.3 + $expect_primaryAccounts->{'https://cyrusimap.org/ns/jmap/sieve'} + = 'cassandane'; } - if ($buildinfo->get('dependency', 'icalvcard')) { - $expect_primaryAccounts->{'urn:ietf:params:jmap:contacts'} - = 'cassandane'; - } - $self->assert_deep_equals($expect_primaryAccounts, - $session->{primaryAccounts}); - - $self->assert_num_equals(3, scalar keys %{$session->{accounts}}); - $self->assert_not_null($session->{accounts}{cassandane}); - - my $primaryAccount = $session->{accounts}{cassandane}; - $self->assert_not_null($primaryAccount); - my $account1 = $session->{accounts}{account1}; - $self->assert_not_null($account1); - my $account2 = $session->{accounts}{account2}; - $self->assert_not_null($account2); - - $self->assert_str_equals('cassandane', $primaryAccount->{name}); - $self->assert_equals(JSON::false, $primaryAccount->{isReadOnly}); - $self->assert_equals(JSON::true, $primaryAccount->{isPersonal}); - my $accountCapabilities = $primaryAccount->{accountCapabilities}; - $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:mail'}); - $self->assert_equals(JSON::true, $accountCapabilities->{'urn:ietf:params:jmap:mail'}{mayCreateTopLevelMailbox}); - $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:submission'}); - if ($buildinfo->get('component', 'sieve')) { - $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:vacationresponse'}); - } - $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:calendars'}); - $self->assert_not_null($accountCapabilities->{'https://cyrusimap.org/ns/jmap/contacts'}); - $self->assert_not_null($accountCapabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); - - # Account 1: read-only mail, calendars. No contacts. - $self->assert_str_equals('account1', $account1->{name}); - $self->assert_equals(JSON::true, $account1->{isReadOnly}); - $self->assert_equals(JSON::false, $account1->{isPersonal}); - $accountCapabilities = $account1->{accountCapabilities}; - $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:mail'}); - $self->assert_equals(JSON::false, $accountCapabilities->{'urn:ietf:params:jmap:mail'}{mayCreateTopLevelMailbox}); - $self->assert_null($accountCapabilities->{'urn:ietf:params:jmap:submission'}); - if ($buildinfo->get('component', 'sieve')) { - $self->assert_null($accountCapabilities->{'urn:ietf:params:jmap:vacationresponse'}); - } - $self->assert_null($accountCapabilities->{'https://cyrusimap.org/ns/jmap/contacts'}); - $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:calendars'}); - $self->assert_not_null($accountCapabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); - - # Account 2: read/write mail - $self->assert_str_equals('account2', $account2->{name}); - $self->assert_equals(JSON::false, $account2->{isReadOnly}); - $self->assert_equals(JSON::false, $account2->{isPersonal}); - $accountCapabilities = $account2->{accountCapabilities}; - $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:mail'}); - $self->assert_equals(JSON::true, $accountCapabilities->{'urn:ietf:params:jmap:mail'}{mayCreateTopLevelMailbox}); - $self->assert_null($accountCapabilities->{'urn:ietf:params:jmap:submission'}); - if ($buildinfo->get('component', 'sieve')) { - $self->assert_null($accountCapabilities->{'urn:ietf:params:jmap:vacationresponse'}); - } - $self->assert_null($accountCapabilities->{'urn:ietf:params:jmap:calendars'}); - $self->assert_null($accountCapabilities->{'https://cyrusimap.org/ns/jmap/contacts'}); - $self->assert_null($accountCapabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); + } + if ($buildinfo->get('dependency', 'icalvcard')) { + $expect_primaryAccounts->{'urn:ietf:params:jmap:contacts'} = 'cassandane'; + } + $self->assert_deep_equals($expect_primaryAccounts, + $session->{primaryAccounts}); + + $self->assert_num_equals(3, scalar keys %{ $session->{accounts} }); + $self->assert_not_null($session->{accounts}{cassandane}); + + my $primaryAccount = $session->{accounts}{cassandane}; + $self->assert_not_null($primaryAccount); + my $account1 = $session->{accounts}{account1}; + $self->assert_not_null($account1); + my $account2 = $session->{accounts}{account2}; + $self->assert_not_null($account2); + + $self->assert_str_equals('cassandane', $primaryAccount->{name}); + $self->assert_equals(JSON::false, $primaryAccount->{isReadOnly}); + $self->assert_equals(JSON::true, $primaryAccount->{isPersonal}); + my $accountCapabilities = $primaryAccount->{accountCapabilities}; + $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:mail'}); + $self->assert_equals(JSON::true, + $accountCapabilities->{'urn:ietf:params:jmap:mail'} + {mayCreateTopLevelMailbox}); + $self->assert_not_null( + $accountCapabilities->{'urn:ietf:params:jmap:submission'}); + + if ($buildinfo->get('component', 'sieve')) { + $self->assert_not_null( + $accountCapabilities->{'urn:ietf:params:jmap:vacationresponse'}); + } + $self->assert_not_null( + $accountCapabilities->{'urn:ietf:params:jmap:calendars'}); + $self->assert_not_null( + $accountCapabilities->{'https://cyrusimap.org/ns/jmap/contacts'}); + $self->assert_not_null( + $accountCapabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); + + # Account 1: read-only mail, calendars. No contacts. + $self->assert_str_equals('account1', $account1->{name}); + $self->assert_equals(JSON::true, $account1->{isReadOnly}); + $self->assert_equals(JSON::false, $account1->{isPersonal}); + $accountCapabilities = $account1->{accountCapabilities}; + $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:mail'}); + $self->assert_equals(JSON::false, + $accountCapabilities->{'urn:ietf:params:jmap:mail'} + {mayCreateTopLevelMailbox}); + $self->assert_null($accountCapabilities->{'urn:ietf:params:jmap:submission'}); + + if ($buildinfo->get('component', 'sieve')) { + $self->assert_null( + $accountCapabilities->{'urn:ietf:params:jmap:vacationresponse'}); + } + $self->assert_null( + $accountCapabilities->{'https://cyrusimap.org/ns/jmap/contacts'}); + $self->assert_not_null( + $accountCapabilities->{'urn:ietf:params:jmap:calendars'}); + $self->assert_not_null( + $accountCapabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); + + # Account 2: read/write mail + $self->assert_str_equals('account2', $account2->{name}); + $self->assert_equals(JSON::false, $account2->{isReadOnly}); + $self->assert_equals(JSON::false, $account2->{isPersonal}); + $accountCapabilities = $account2->{accountCapabilities}; + $self->assert_not_null($accountCapabilities->{'urn:ietf:params:jmap:mail'}); + $self->assert_equals(JSON::true, + $accountCapabilities->{'urn:ietf:params:jmap:mail'} + {mayCreateTopLevelMailbox}); + $self->assert_null($accountCapabilities->{'urn:ietf:params:jmap:submission'}); + + if ($buildinfo->get('component', 'sieve')) { + $self->assert_null( + $accountCapabilities->{'urn:ietf:params:jmap:vacationresponse'}); + } + $self->assert_null($accountCapabilities->{'urn:ietf:params:jmap:calendars'}); + $self->assert_null( + $accountCapabilities->{'https://cyrusimap.org/ns/jmap/contacts'}); + $self->assert_null( + $accountCapabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); } sub test_blob_download - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $data = $jmap->Upload("some test", "text/plain"); - - my $resp = $jmap->Download('cassandane', $data->{blobId}); - - $self->assert_str_equals('application/octet-stream', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); - - $resp = $jmap->Download({ accept => 'text/plain' }, 'cassandane', $data->{blobId}); - $self->assert_str_equals('text/plain', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); - - $resp = $jmap->Download({ accept => 'text/plain;q=0.9, text/html' }, 'cassandane', $data->{blobId}); - $self->assert_str_equals('text/html', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); - - $resp = $jmap->Download({ accept => '*/*' }, 'cassandane', $data->{blobId}); - $self->assert_str_equals('application/octet-stream', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); - - $resp = $jmap->Download({ accept => 'foo' }, 'cassandane', $data->{blobId}); - $self->assert_str_equals('application/octet-stream', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); - - $resp = $jmap->Download({ accept => 'foo*/bar' }, 'cassandane', $data->{blobId}); - $self->assert_str_equals('application/octet-stream', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); - - $resp = $jmap->Download({ accept => 'foo/(bar)' }, 'cassandane', $data->{blobId}); - $self->assert_str_equals('application/octet-stream', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $data = $jmap->Upload("some test", "text/plain"); + + my $resp = $jmap->Download('cassandane', $data->{blobId}); + + $self->assert_str_equals('application/octet-stream', + $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); + + $resp = $jmap->Download({ accept => 'text/plain' }, 'cassandane', + $data->{blobId}); + $self->assert_str_equals('text/plain', $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); + + $resp = $jmap->Download({ accept => 'text/plain;q=0.9, text/html' }, + 'cassandane', $data->{blobId}); + $self->assert_str_equals('text/html', $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); + + $resp = $jmap->Download({ accept => '*/*' }, 'cassandane', $data->{blobId}); + $self->assert_str_equals('application/octet-stream', + $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); + + $resp = $jmap->Download({ accept => 'foo' }, 'cassandane', $data->{blobId}); + $self->assert_str_equals('application/octet-stream', + $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); + + $resp + = $jmap->Download({ accept => 'foo*/bar' }, 'cassandane', $data->{blobId}); + $self->assert_str_equals('application/octet-stream', + $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); + + $resp + = $jmap->Download({ accept => 'foo/(bar)' }, 'cassandane', $data->{blobId}); + $self->assert_str_equals('application/octet-stream', + $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); } sub test_blob_download_name - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $data = $jmap->Upload("some test", "text/plain"); + my $data = $jmap->Upload("some test", "text/plain"); - my $resp = $jmap->Download('cassandane', $data->{blobId}, 'foo'); - $self->assert_str_equals('attachment; filename="foo"', - $resp->{headers}{'content-disposition'}); + my $resp = $jmap->Download('cassandane', $data->{blobId}, 'foo'); + $self->assert_str_equals('attachment; filename="foo"', + $resp->{headers}{'content-disposition'}); - $resp = $jmap->Download('cassandane', $data->{blobId}, decode_utf8('тест.txt')); - $self->assert_str_equals("attachment; filename*=utf-8''%D1%82%D0%B5%D1%81%D1%82.txt", - $resp->{headers}{'content-disposition'}); + $resp + = $jmap->Download('cassandane', $data->{blobId}, decode_utf8('тест.txt')); + $self->assert_str_equals( + "attachment; filename*=utf-8''%D1%82%D0%B5%D1%81%D1%82.txt", + $resp->{headers}{'content-disposition'}); } sub test_created_ids - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - xlog $self, "send bogus creation ids map"; - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - }, - content => encode_json({ - using => ['urn:ietf:params:jmap:mail'], - methodCalls => [['Identity/get', {}, 'R1']], - createdIds => 'bogus', - }), - }; - my $RawResponse = $jmap->ua->post($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('400', $RawResponse->{status}); - - xlog $self, "create a mailbox without any client-supplied creation ids"; - my $JMAPRequest = { - using => ['urn:ietf:params:jmap:mail'], - methodCalls => [['Mailbox/set', { - create => { - "1" => { - name => "foo", - parentId => undef, - role => undef - } - } - }, "R1"]], - }; - my $JMAPResponse = $jmap->Request($JMAPRequest); - my $mboxid1 = $JMAPResponse->{methodResponses}->[0][1]{created}{1}{id}; - $self->assert_not_null($mboxid1); - $self->assert_null($JMAPResponse->{createdIds}); - - xlog $self, "get mailbox using client-supplied creation id"; - $JMAPRequest = { - using => ['urn:ietf:params:jmap:mail'], - methodCalls => [['Mailbox/get', { ids => ['#1'] }, 'R1']], - createdIds => { 1 => $mboxid1 }, - }; - $JMAPResponse = $jmap->Request($JMAPRequest); - $self->assert_str_equals($mboxid1, $JMAPResponse->{methodResponses}->[0][1]{list}[0]{id}); - $self->assert_not_null($JMAPResponse->{createdIds}); - $self->assert_str_equals($mboxid1, $JMAPResponse->{createdIds}{1}); - - xlog $self, "create a mailbox with empty client-supplied creation ids"; - $JMAPRequest = { - using => ['urn:ietf:params:jmap:mail'], - methodCalls => [['Mailbox/set', { - create => { - "2" => { - name => "bar", - parentId => undef, - role => undef - } - } - }, "R1"]], - createdIds => {}, - }; - $JMAPResponse = $jmap->Request($JMAPRequest); - my $mboxid2 = $JMAPResponse->{methodResponses}->[0][1]{created}{2}{id}; - $self->assert_str_equals($mboxid2, $JMAPResponse->{createdIds}{2}); - - xlog $self, "create a mailbox with client-supplied creation ids"; - $JMAPRequest = { - using => ['urn:ietf:params:jmap:mail'], - methodCalls => [['Mailbox/set', { - create => { - "3" => { - name => "baz", - parentId => "#2", - role => undef - } - } - }, "R1"]], - createdIds => { - 1 => $mboxid1, - 2 => $mboxid2, - }, - }; - $JMAPResponse = $jmap->Request($JMAPRequest); - my $mboxid3 = $JMAPResponse->{methodResponses}->[0][1]{created}{3}{id}; - $self->assert_str_equals($mboxid1, $JMAPResponse->{createdIds}{1}); - $self->assert_str_equals($mboxid2, $JMAPResponse->{createdIds}{2}); - $self->assert_str_equals($mboxid3, $JMAPResponse->{createdIds}{3}); - - xlog $self, "get mailbox and check parentid"; - $JMAPRequest = { - using => ['urn:ietf:params:jmap:mail'], - methodCalls => [['Mailbox/get', { ids => [$mboxid3], properties => ['parentId'] }, 'R1']], - }; - $JMAPResponse = $jmap->Request($JMAPRequest); - $self->assert_str_equals($mboxid3, $JMAPResponse->{methodResponses}->[0][1]{list}[0]{id}); - $self->assert_str_equals($mboxid2, $JMAPResponse->{methodResponses}->[0][1]{list}[0]{parentId}); - $self->assert_null($JMAPResponse->{createdIds}); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + xlog $self, "send bogus creation ids map"; + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + }, + content => encode_json({ + using => ['urn:ietf:params:jmap:mail'], + methodCalls => [ [ 'Identity/get', {}, 'R1' ] ], + createdIds => 'bogus', + }), + }; + my $RawResponse = $jmap->ua->post($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('400', $RawResponse->{status}); + + xlog $self, "create a mailbox without any client-supplied creation ids"; + my $JMAPRequest = { + using => ['urn:ietf:params:jmap:mail'], + methodCalls => [ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + parentId => undef, + role => undef + } + } + }, + "R1" + ] ], + }; + my $JMAPResponse = $jmap->Request($JMAPRequest); + my $mboxid1 = $JMAPResponse->{methodResponses}->[0][1]{created}{1}{id}; + $self->assert_not_null($mboxid1); + $self->assert_null($JMAPResponse->{createdIds}); + + xlog $self, "get mailbox using client-supplied creation id"; + $JMAPRequest = { + using => ['urn:ietf:params:jmap:mail'], + methodCalls => [ [ 'Mailbox/get', { ids => ['#1'] }, 'R1' ] ], + createdIds => { 1 => $mboxid1 }, + }; + $JMAPResponse = $jmap->Request($JMAPRequest); + $self->assert_str_equals($mboxid1, + $JMAPResponse->{methodResponses}->[0][1]{list}[0]{id}); + $self->assert_not_null($JMAPResponse->{createdIds}); + $self->assert_str_equals($mboxid1, $JMAPResponse->{createdIds}{1}); + + xlog $self, "create a mailbox with empty client-supplied creation ids"; + $JMAPRequest = { + using => ['urn:ietf:params:jmap:mail'], + methodCalls => [ [ + 'Mailbox/set', + { + create => { + "2" => { + name => "bar", + parentId => undef, + role => undef + } + } + }, + "R1" + ] ], + createdIds => {}, + }; + $JMAPResponse = $jmap->Request($JMAPRequest); + my $mboxid2 = $JMAPResponse->{methodResponses}->[0][1]{created}{2}{id}; + $self->assert_str_equals($mboxid2, $JMAPResponse->{createdIds}{2}); + + xlog $self, "create a mailbox with client-supplied creation ids"; + $JMAPRequest = { + using => ['urn:ietf:params:jmap:mail'], + methodCalls => [ [ + 'Mailbox/set', + { + create => { + "3" => { + name => "baz", + parentId => "#2", + role => undef + } + } + }, + "R1" + ] ], + createdIds => { + 1 => $mboxid1, + 2 => $mboxid2, + }, + }; + $JMAPResponse = $jmap->Request($JMAPRequest); + my $mboxid3 = $JMAPResponse->{methodResponses}->[0][1]{created}{3}{id}; + $self->assert_str_equals($mboxid1, $JMAPResponse->{createdIds}{1}); + $self->assert_str_equals($mboxid2, $JMAPResponse->{createdIds}{2}); + $self->assert_str_equals($mboxid3, $JMAPResponse->{createdIds}{3}); + + xlog $self, "get mailbox and check parentid"; + $JMAPRequest = { + using => ['urn:ietf:params:jmap:mail'], + methodCalls => [ [ + 'Mailbox/get', { ids => [$mboxid3], properties => ['parentId'] }, + 'R1' + ] ], + }; + $JMAPResponse = $jmap->Request($JMAPRequest); + $self->assert_str_equals($mboxid3, + $JMAPResponse->{methodResponses}->[0][1]{list}[0]{id}); + $self->assert_str_equals($mboxid2, + $JMAPResponse->{methodResponses}->[0][1]{list}[0]{parentId}); + $self->assert_null($JMAPResponse->{createdIds}); } sub test_echo - :min_version_3_1 :needs_component_jmap -{ + : min_version_3_1 : needs_component_jmap { - my ($self) = @_; + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $req = { - hello => JSON::true, - max => 5, - stuff => { foo => "bar", empty => JSON::null } - }; + my $req = { + hello => JSON::true, + max => 5, + stuff => { foo => "bar", empty => JSON::null } + }; - xlog $self, "send ping"; - my $res = $jmap->CallMethods([['Core/echo', $req, "R1"]]); + xlog $self, "send ping"; + my $res = $jmap->CallMethods([ [ 'Core/echo', $req, "R1" ] ]); - xlog $self, "check pong"; - $self->assert_not_null($res); - $self->assert_str_equals('Core/echo', $res->[0][0]); - $self->assert_deep_equals($req, $res->[0][1]); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "check pong"; + $self->assert_not_null($res); + $self->assert_str_equals('Core/echo', $res->[0][0]); + $self->assert_deep_equals($req, $res->[0][1]); + $self->assert_str_equals('R1', $res->[0][2]); } sub test_identity_get - :min_version_3_1 :needs_component_jmap -{ + : min_version_3_1 : needs_component_jmap { - my ($self) = @_; + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:submission', - ]; + my $using = [ 'urn:ietf:params:jmap:submission', ]; - my $res = $jmap->CallMethods([ - ['Identity/get', { }, 'R1'], - ['Identity/get', { ids => undef }, 'R2'], - ['Identity/get', { ids => [] }, 'R3'], - ], $using); + my $res = $jmap->CallMethods( + [ + [ 'Identity/get', {}, 'R1' ], + [ 'Identity/get', { ids => undef }, 'R2' ], + [ 'Identity/get', { ids => [] }, 'R3' ], + ], + $using + ); - $self->assert_str_equals('Identity/get', $res->[0][0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('cassandane', $res->[0][1]{list}[0]{id}); - $self->assert_not_null($res->[0][1]->{state}); - $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_equals('Identity/get', $res->[0][0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('cassandane', $res->[0][1]{list}[0]{id}); + $self->assert_not_null($res->[0][1]->{state}); + $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_str_equals('cassandane', $res->[1][1]{list}[0]{id}); - $self->assert_not_null($res->[1][1]->{state}); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_str_equals('cassandane', $res->[1][1]{list}[0]{id}); + $self->assert_not_null($res->[1][1]->{state}); - $self->assert_deep_equals([], $res->[2][1]{list}); - $self->assert_not_null($res->[2][1]->{state}); + $self->assert_deep_equals([], $res->[2][1]{list}); + $self->assert_not_null($res->[2][1]->{state}); } sub test_sessionstate - :min_version_3_1 :needs_component_jmap :ReverseACLs -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - $self->{instance}->create_user("other"); - - # Fetch sessionState - my $JMAPRequest = { - using => ['urn:ietf:params:jmap:core'], - methodCalls => [['Core/echo', { }, 'R1']], - }; - my $JMAPResponse = $jmap->Request($JMAPRequest); - $self->assert_not_null($JMAPResponse->{sessionState}); - my $sessionState = $JMAPResponse->{sessionState}; - - # Update ACL - $admintalk->setacl("user.other", "cassandane", "lr") or die; - - # Fetch sessionState - $JMAPResponse = $jmap->Request($JMAPRequest); - $self->assert_str_not_equals($sessionState, $JMAPResponse->{sessionState}); - $sessionState = $JMAPResponse->{sessionState}; - - # Update ACL - $admintalk->setacl("user.other", "cassandane", "") or die; - - # Fetch sessionState - $JMAPResponse = $jmap->Request($JMAPRequest); - $self->assert_str_not_equals($sessionState, $JMAPResponse->{sessionState}); - $sessionState = $JMAPResponse->{sessionState}; + : min_version_3_1 : needs_component_jmap : ReverseACLs { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + $self->{instance}->create_user("other"); + + # Fetch sessionState + my $JMAPRequest = { + using => ['urn:ietf:params:jmap:core'], + methodCalls => [ [ 'Core/echo', {}, 'R1' ] ], + }; + my $JMAPResponse = $jmap->Request($JMAPRequest); + $self->assert_not_null($JMAPResponse->{sessionState}); + my $sessionState = $JMAPResponse->{sessionState}; + + # Update ACL + $admintalk->setacl("user.other", "cassandane", "lr") or die; + + # Fetch sessionState + $JMAPResponse = $jmap->Request($JMAPRequest); + $self->assert_str_not_equals($sessionState, $JMAPResponse->{sessionState}); + $sessionState = $JMAPResponse->{sessionState}; + + # Update ACL + $admintalk->setacl("user.other", "cassandane", "") or die; + + # Fetch sessionState + $JMAPResponse = $jmap->Request($JMAPRequest); + $self->assert_str_not_equals($sessionState, $JMAPResponse->{sessionState}); + $sessionState = $JMAPResponse->{sessionState}; } sub test_using_unknown_capability - :min_version_3_1 :needs_component_jmap -{ - - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - }, - content => encode_json({ - using => [ - 'urn:ietf:params:jmap:core', - 'urn:foo' # Unknown capability - ], - methodCalls => [['Core/echo', { hello => JSON::true }, 'R1']], - }), - }; - my $RawResponse = $jmap->ua->post($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('400', $RawResponse->{status}); - - my $Response = eval { decode_json($RawResponse->{content}) }; - $self->assert_str_equals('urn:ietf:params:jmap:error:unknownCapability', $Response->{type}); + : min_version_3_1 : needs_component_jmap { + + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + }, + content => encode_json({ + using => [ + 'urn:ietf:params:jmap:core', + 'urn:foo' # Unknown capability + ], + methodCalls => [ [ 'Core/echo', { hello => JSON::true }, 'R1' ] ], + }), + }; + my $RawResponse = $jmap->ua->post($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('400', $RawResponse->{status}); + + my $Response = eval { decode_json($RawResponse->{content}) }; + $self->assert_str_equals('urn:ietf:params:jmap:error:unknownCapability', + $Response->{type}); } sub test_require_conversations - :min_version_3_1 :needs_component_jmap :NoStartInstances -{ - my ($self) = @_; - - my $instance = $self->{instance}; - $instance->{config}->set(conversations => 'no'); - - $self->_start_instances(); - $self->_setup_http_service_objects(); - - my $jmap = $self->{jmap}; - my $JMAPRequest = { - using => ['urn:ietf:params:jmap:core'], - methodCalls => [['Core/echo', { }, 'R1']], - }; - - # request should fail - my ($response, undef) = $jmap->Request($JMAPRequest); - $self->assert(not $response->{success}); - - # httpd should syslog an error - $self->assert_syslog_matches( - $self->{instance}, - qr/ERROR: cannot enable \w+ module with conversations disabled/, - ); + : min_version_3_1 : needs_component_jmap : NoStartInstances { + my ($self) = @_; + + my $instance = $self->{instance}; + $instance->{config}->set(conversations => 'no'); + + $self->_start_instances(); + $self->_setup_http_service_objects(); + + my $jmap = $self->{jmap}; + my $JMAPRequest = { + using => ['urn:ietf:params:jmap:core'], + methodCalls => [ [ 'Core/echo', {}, 'R1' ] ], + }; + + # request should fail + my ($response, undef) = $jmap->Request($JMAPRequest); + $self->assert(not $response->{success}); + + # httpd should syslog an error + $self->assert_syslog_matches($self->{instance}, + qr/ERROR: cannot enable \w+ module with conversations disabled/, + ); } sub test_eventsource - :min_version_3_5 :needs_component_jmap :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $http = $self->{instance}->get_service("http"); - - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - my $session = eval { decode_json($RawResponse->{content}) }; - $self->assert_not_null($session); - my $url = $session->{eventSourceUrl}; - $self->assert_not_null($url); - - $self->assert_num_equals(1, $url =~ s/\{types\}/Email/g); - $self->assert_num_equals(1, $url =~ s/\{closeafter\}/state/g); - $self->assert_num_equals(1, $url =~ s/\{ping\}/0/g); - - if (not $url =~ /^http/) { - $url = "http://".$http->host().":".$http->port().$url; - } - - $RawRequest->{headers}->{'Last-Event-Id'} = '0'; - $RawResponse = $jmap->ua->get($url, $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - $self->assert_str_equals('text/event-stream', - $RawResponse->{headers}{'content-type'}); - $self->assert_null($RawResponse->{headers}{'content-length'}); - - my %event = $RawResponse->{content} =~ /^(\w+): ?(.*)$/mg; - $self->assert_not_null($event{id}); - $self->assert_str_equals('state', $event{event}); - - my $data = eval { decode_json($event{data}) }; - $self->assert_not_null($data); - $self->assert_str_equals('StateChange', $data->{'@type'}); - $self->assert_not_null($data->{changed}); - $self->assert_not_null($data->{changed}->{cassandane}); - $self->assert_not_null($data->{changed}->{cassandane}->{Email}); + : min_version_3_5 : needs_component_jmap : JMAPExtensions : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $http = $self->{instance}->get_service("http"); + + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + my $session = eval { decode_json($RawResponse->{content}) }; + $self->assert_not_null($session); + my $url = $session->{eventSourceUrl}; + $self->assert_not_null($url); + + $self->assert_num_equals(1, $url =~ s/\{types\}/Email/g); + $self->assert_num_equals(1, $url =~ s/\{closeafter\}/state/g); + $self->assert_num_equals(1, $url =~ s/\{ping\}/0/g); + + if (not $url =~ /^http/) { + $url = "http://" . $http->host() . ":" . $http->port() . $url; + } + + $RawRequest->{headers}->{'Last-Event-Id'} = '0'; + $RawResponse = $jmap->ua->get($url, $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + $self->assert_str_equals('text/event-stream', + $RawResponse->{headers}{'content-type'}); + $self->assert_null($RawResponse->{headers}{'content-length'}); + + my %event = $RawResponse->{content} =~ /^(\w+): ?(.*)$/mg; + $self->assert_not_null($event{id}); + $self->assert_str_equals('state', $event{event}); + + my $data = eval { decode_json($event{data}) }; + $self->assert_not_null($data); + $self->assert_str_equals('StateChange', $data->{'@type'}); + $self->assert_not_null($data->{changed}); + $self->assert_not_null($data->{changed}->{cassandane}); + $self->assert_not_null($data->{changed}->{cassandane}->{Email}); } sub test_bearer_auth_jwt - :min_version_3_5 :needs_component_jmap :NoAltNameSpace :HttpJWTAuthRSA -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $http = $self->{instance}->get_service("http"); - - my $token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjYXNzYW5kYW5lIn0.Eoa-9imqmFVYKU19yMaHZGEwiOWE3rSKQDw598rZYJvLqjrF8bG2fvMAUB6VeXxoJLca-uXAtTNHKBWYye9uvzTO3e8VMQOHHIb2RbBVyC7UxUEkbN8KC8YVrMNQoJDuugxeANKSrbmL8l6AtGEBK8iCoBnedleCzQ-nE7KtnwD356F63teK6jIoGW9KI0zNIeTe1k5Wh6NM3hZKC12mfU2JsOHTes-XH8lig2RQraBmdR1t9EKMTVztq-hXiVxvYtc3eIghdz5Ss52qr3VaCJJXExOXbnp0LwbUNUOFn1GCPfhRyEZdQxhGV19cO-RceIV1aawZnegdQS_kWERQNg"; - - xlog "Use valid RS256 token"; - my $RawRequest = { - headers => { - 'Authorization' => 'Bearer ' . $token, - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - - xlog "Use invalid RS256 token"; - $RawRequest = { - headers => { - 'Authorization' => 'Bearer ' . substr $token, 0, -3 - }, - content => '', - }; - $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('401', $RawResponse->{status}); + : min_version_3_5 : needs_component_jmap : NoAltNameSpace : HttpJWTAuthRSA { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $http = $self->{instance}->get_service("http"); + + my $token + = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjYXNzYW5kYW5lIn0.Eoa-9imqmFVYKU19yMaHZGEwiOWE3rSKQDw598rZYJvLqjrF8bG2fvMAUB6VeXxoJLca-uXAtTNHKBWYye9uvzTO3e8VMQOHHIb2RbBVyC7UxUEkbN8KC8YVrMNQoJDuugxeANKSrbmL8l6AtGEBK8iCoBnedleCzQ-nE7KtnwD356F63teK6jIoGW9KI0zNIeTe1k5Wh6NM3hZKC12mfU2JsOHTes-XH8lig2RQraBmdR1t9EKMTVztq-hXiVxvYtc3eIghdz5Ss52qr3VaCJJXExOXbnp0LwbUNUOFn1GCPfhRyEZdQxhGV19cO-RceIV1aawZnegdQS_kWERQNg"; + + xlog "Use valid RS256 token"; + my $RawRequest = { + headers => { + 'Authorization' => 'Bearer ' . $token, + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + + xlog "Use invalid RS256 token"; + $RawRequest = { + headers => { + 'Authorization' => 'Bearer ' . substr $token, + 0, -3 + }, + content => '', + }; + $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('401', $RawResponse->{status}); } sub test_blob_set_basic - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my $self = shift; - my $instance = $self->{instance}; - - xlog "Test without capability"; - my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([['Blob/upload', { create => { b1 => { data => [{'data:asText' => 'hello world'}] } } }, 'R1']]); - $self->assert_str_equals($res->[0][0], 'error'); - - # XXX: this will be replaced with the upstream one - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); - - xlog "Regular Blob/upload works and returns a blobId"; - $res = $jmap->CallMethods([['Blob/upload', { create => { b1 => { data => [{'data:asText' => 'hello world'}] } } }, 'R1']]); - $self->assert_str_equals('Blob/upload', $res->[0][0]); - $self->assert_not_null($res->[0][1]{created}{b1}{id}); + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my $self = shift; + my $instance = $self->{instance}; + + xlog "Test without capability"; + my $jmap = $self->{jmap}; + my $res = $jmap->CallMethods( + [ [ + 'Blob/upload', + { + create => { b1 => { data => [ { 'data:asText' => 'hello world' } ] } } + }, + 'R1' + ] ] + ); + $self->assert_str_equals($res->[0][0], 'error'); + + # XXX: this will be replaced with the upstream one + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); + + xlog "Regular Blob/upload works and returns a blobId"; + $res = $jmap->CallMethods( + [ [ + 'Blob/upload', + { + create => { b1 => { data => [ { 'data:asText' => 'hello world' } ] } } + }, + 'R1' + ] ] + ); + $self->assert_str_equals('Blob/upload', $res->[0][0]); + $self->assert_not_null($res->[0][1]{created}{b1}{id}); } sub test_blob_lookup - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my $self = shift; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); - - my $body = "A plain text email."; - $exp_sub{A} = $self->make_message("foo", - body => $body - ); - - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; - - xlog $self, "get emails"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; - - my $blobId = $msg->{textBody}[0]{blobId}; - $self->assert_not_null($blobId); - my $emailId = $msg->{id}; - $self->assert_not_null($emailId); - my $threadId = $msg->{threadId}; - $self->assert_not_null($threadId); - my $mailboxIds = $msg->{mailboxIds}; - my ($mailboxId) = keys %$mailboxIds; - $self->assert_not_null($mailboxId); - - xlog "Test without capability"; - $res = $jmap->CallMethods([['Blob/lookup', { ids => [$blobId, 'unknown'], typeNames => ['Mailbox', 'Thread', 'Email'] }, 'R1']]); - $self->assert_str_equals($res->[0][0], 'error'); - - # XXX: this will be replaced with the upstream one - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); - - xlog "Regular Blob/lookup works"; - $res = $jmap->CallMethods([['Blob/lookup', { ids => [$blobId, 'unknown'], typeNames => ['Mailbox', 'Thread', 'Email'] }, 'R1']]); - $self->assert_str_equals($res->[0][0], 'Blob/lookup'); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals($blobId, $res->[0][1]{list}[0]{id}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}[0]{matchedIds}{Mailbox}}); - $self->assert_str_equals($mailboxId, $res->[0][1]{list}[0]{matchedIds}{Mailbox}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}[0]{matchedIds}{Thread}}); - $self->assert_str_equals($threadId, $res->[0][1]{list}[0]{matchedIds}{Thread}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}[0]{matchedIds}{Email}}); - $self->assert_str_equals($emailId, $res->[0][1]{list}[0]{matchedIds}{Email}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{notFound}}); - $self->assert_str_equals('unknown', $res->[0][1]{notFound}[0]); + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my $self = shift; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); + + my $body = "A plain text email."; + $exp_sub{A} = $self->make_message("foo", body => $body); + + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; + + xlog $self, "get emails"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => $ids }, "R1" ] ]); + my $msg = $res->[0][1]{list}[0]; + + my $blobId = $msg->{textBody}[0]{blobId}; + $self->assert_not_null($blobId); + my $emailId = $msg->{id}; + $self->assert_not_null($emailId); + my $threadId = $msg->{threadId}; + $self->assert_not_null($threadId); + my $mailboxIds = $msg->{mailboxIds}; + my ($mailboxId) = keys %$mailboxIds; + $self->assert_not_null($mailboxId); + + xlog "Test without capability"; + $res = $jmap->CallMethods( + [ [ + 'Blob/lookup', + { + ids => [ $blobId, 'unknown' ], + typeNames => [ 'Mailbox', 'Thread', 'Email' ] + }, + 'R1' + ] ] + ); + $self->assert_str_equals($res->[0][0], 'error'); + + # XXX: this will be replaced with the upstream one + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); + + xlog "Regular Blob/lookup works"; + $res = $jmap->CallMethods( + [ [ + 'Blob/lookup', + { + ids => [ $blobId, 'unknown' ], + typeNames => [ 'Mailbox', 'Thread', 'Email' ] + }, + 'R1' + ] ] + ); + $self->assert_str_equals($res->[0][0], 'Blob/lookup'); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals($blobId, $res->[0][1]{list}[0]{id}); + $self->assert_num_equals(1, + scalar @{ $res->[0][1]{list}[0]{matchedIds}{Mailbox} }); + $self->assert_str_equals($mailboxId, + $res->[0][1]{list}[0]{matchedIds}{Mailbox}[0]); + $self->assert_num_equals(1, + scalar @{ $res->[0][1]{list}[0]{matchedIds}{Thread} }); + $self->assert_str_equals($threadId, + $res->[0][1]{list}[0]{matchedIds}{Thread}[0]); + $self->assert_num_equals(1, + scalar @{ $res->[0][1]{list}[0]{matchedIds}{Email} }); + $self->assert_str_equals($emailId, + $res->[0][1]{list}[0]{matchedIds}{Email}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{notFound} }); + $self->assert_str_equals('unknown', $res->[0][1]{notFound}[0]); } sub test_blob_get - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my $self = shift; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); - - my $body = "A plain text email."; - $exp_sub{A} = $self->make_message("foo", - body => $body - ); - - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; - - xlog $self, "get emails"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; - - my $blobId = $msg->{textBody}[0]{blobId}; - $self->assert_not_null($blobId); - - xlog "Test without capability"; - $res = $jmap->CallMethods([['Blob/get', { ids => [$blobId], properties => [ 'data:asText', 'size' ] }, 'R1']]); - $self->assert_str_equals($res->[0][0], 'error'); - - # XXX: this will be replaced with the upstream one - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); - - xlog "Regular Blob/get works and returns a blobId"; - $res = $jmap->CallMethods([['Blob/get', { ids => [$blobId], properties => [ 'data:asText', 'data:asBase64', 'size' ] }, 'R1']]); - $self->assert_str_equals($res->[0][0], 'Blob/get'); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals($blobId, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals($body, $res->[0][1]{list}[0]{'data:asText'}); - $self->assert_str_equals(encode_base64($body, ''), $res->[0][1]{list}[0]{'data:asBase64'}); - $self->assert_num_equals(length($body), $res->[0][1]{list}[0]{'size'}); + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my $self = shift; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); + + my $body = "A plain text email."; + $exp_sub{A} = $self->make_message("foo", body => $body); + + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; + + xlog $self, "get emails"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => $ids }, "R1" ] ]); + my $msg = $res->[0][1]{list}[0]; + + my $blobId = $msg->{textBody}[0]{blobId}; + $self->assert_not_null($blobId); + + xlog "Test without capability"; + $res = $jmap->CallMethods( + [ [ + 'Blob/get', + { ids => [$blobId], properties => [ 'data:asText', 'size' ] }, 'R1' + ] ] + ); + $self->assert_str_equals($res->[0][0], 'error'); + + # XXX: this will be replaced with the upstream one + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); + + xlog "Regular Blob/get works and returns a blobId"; + $res = $jmap->CallMethods( + [ [ + 'Blob/get', + { + ids => [$blobId], + properties => [ 'data:asText', 'data:asBase64', 'size' ] + }, + 'R1' + ] ] + ); + $self->assert_str_equals($res->[0][0], 'Blob/get'); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals($blobId, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals($body, $res->[0][1]{list}[0]{'data:asText'}); + $self->assert_str_equals(encode_base64($body, ''), + $res->[0][1]{list}[0]{'data:asBase64'}); + $self->assert_num_equals(length($body), $res->[0][1]{list}[0]{'size'}); } sub test_blob_set_complex - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my $self = shift; - my $jmap = $self->{jmap}; - - # GET supported digest algorithms from session object - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - my $session = eval { decode_json($RawResponse->{content}) }; - my %algs = map { $_ => 1 } @{$session->{capabilities}->{'https://cyrusimap.org/ns/jmap/blob'}->{supportedDigestAlgorithms}}; - - # XXX: this will be replaced with the upstream one - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); - - my $data = "The quick brown fox jumped over the lazy dog."; - my $bdata = encode_base64($data, ''); - - my $res; - - xlog "Regular Blob/upload works and returns the right data"; - $res = $jmap->CallMethods([ - ['Blob/upload', { create => { b1 => { data => [{'data:asText' => $data}] } } }, 'S1'], - ['Blob/get', { ids => ['#b1'], properties => [ 'data:asText', 'size' ] }, 'G1'], - ]); - $self->assert_str_equals('Blob/upload', $res->[0][0]); - $self->assert_str_equals('Blob/get', $res->[1][0]); - $self->assert_str_equals($data, $res->[1][1]{list}[0]{'data:asText'}); - $self->assert_num_equals(length $data, $res->[1][1]{list}[0]{size}); - - xlog "Base64 Blob/upload works and returns the right data"; - my $props = [ 'data:asText', 'size' ]; - if ($algs{'md5'}) { - push @{$props}, 'digest:md5'; - } - if ($algs{'sha'}) { - push @{$props}, 'digest:sha'; - } - if ($algs{'sha-256'}) { - push @{$props}, 'digest:sha-256'; - } - - $res = $jmap->CallMethods([ - ['Blob/upload', { create => { b2 => { data => [{'data:asBase64' => $bdata}] } } }, 'S2'], - ['Blob/get', { ids => ['#b2'], properties => $props }, 'G2'], - ['Blob/get', { ids => ['#b2'], offset => 4, length => 9, properties => $props }, 'G2'], - ]); - $self->assert_str_equals('Blob/upload', $res->[0][0]); - $self->assert_str_equals('Blob/get', $res->[1][0]); - $self->assert_str_equals($data, $res->[1][1]{list}[0]{'data:asText'}); - $self->assert_num_equals(length $data, $res->[1][1]{list}[0]{size}); - $self->assert_str_equals("quick bro", $res->[2][1]{list}[0]{'data:asText'}); - if ($algs{'md5'}) { - $self->assert_str_equals("tTNHgg3iNoIdHFn81iQD9A==", $res->[2][1]{list}[0]{'digest:md5'}); - } - if ($algs{'sha'}) { - $self->assert_str_equals("QiRAPtfyX8K6tm1iOAtZ87Xj3Ww=", $res->[2][1]{list}[0]{'digest:sha'}); - } - if ($algs{'sha-256'}) { - $self->assert_str_equals("gdg9INW7lwHK6OQ9u0dwDz2ZY/gubi0En0xlFpKt0OA=", $res->[2][1]{list}[0]{'digest:sha-256'}); - } - - xlog "Complex expression works and returns the right data"; - my $target = "How quick was that?"; - $res = $jmap->CallMethods([ - ['Blob/upload', { create => { b4 => { data => [{'data:asText' => $data}] } } }, 'S4'], - ['Blob/upload', { create => { mult => { data => [ - { 'data:asText' => 'How' }, # 'How' - { 'blobId' => '#b4', offset => 3, length => 7 }, # ' quick ' - { 'data:asText' => "was t" }, # 'was t' - { 'blobId' => '#b4', offset => 1, length => 1 }, # 'h' - { 'data:asBase64' => encode_base64('at?', '') }, # 'at?' - ] } } }, 'CAT'], - ['Blob/get', { ids => ['#mult'], properties => [ 'data:asText', 'size' ] }, 'G4'], - ]); - $self->assert_str_equals('Blob/upload', $res->[0][0]); - $self->assert_str_equals('Blob/upload', $res->[1][0]); - $self->assert_str_equals('Blob/get', $res->[2][0]); - $self->assert_str_equals($target, $res->[2][1]{list}[0]{'data:asText'}); - $self->assert_num_equals(length $target, $res->[2][1]{list}[0]{size}); + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my $self = shift; + my $jmap = $self->{jmap}; + + # GET supported digest algorithms from session object + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + my $session = eval { decode_json($RawResponse->{content}) }; + my %algs + = map { $_ => 1 } + @{ $session->{capabilities}->{'https://cyrusimap.org/ns/jmap/blob'} + ->{supportedDigestAlgorithms} }; + + # XXX: this will be replaced with the upstream one + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); + + my $data = "The quick brown fox jumped over the lazy dog."; + my $bdata = encode_base64($data, ''); + + my $res; + + xlog "Regular Blob/upload works and returns the right data"; + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { create => { b1 => { data => [ { 'data:asText' => $data } ] } } }, 'S1' + ], + [ + 'Blob/get', { ids => ['#b1'], properties => [ 'data:asText', 'size' ] }, + 'G1' + ], + ]); + $self->assert_str_equals('Blob/upload', $res->[0][0]); + $self->assert_str_equals('Blob/get', $res->[1][0]); + $self->assert_str_equals($data, $res->[1][1]{list}[0]{'data:asText'}); + $self->assert_num_equals(length $data, $res->[1][1]{list}[0]{size}); + + xlog "Base64 Blob/upload works and returns the right data"; + my $props = [ 'data:asText', 'size' ]; + if ($algs{'md5'}) { + push @{$props}, 'digest:md5'; + } + if ($algs{'sha'}) { + push @{$props}, 'digest:sha'; + } + if ($algs{'sha-256'}) { + push @{$props}, 'digest:sha-256'; + } + + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { create => { b2 => { data => [ { 'data:asBase64' => $bdata } ] } } }, + 'S2' + ], + [ 'Blob/get', { ids => ['#b2'], properties => $props }, 'G2' ], + [ + 'Blob/get', + { ids => ['#b2'], offset => 4, length => 9, properties => $props }, 'G2' + ], + ]); + $self->assert_str_equals('Blob/upload', $res->[0][0]); + $self->assert_str_equals('Blob/get', $res->[1][0]); + $self->assert_str_equals($data, $res->[1][1]{list}[0]{'data:asText'}); + $self->assert_num_equals(length $data, $res->[1][1]{list}[0]{size}); + $self->assert_str_equals("quick bro", $res->[2][1]{list}[0]{'data:asText'}); + if ($algs{'md5'}) { + $self->assert_str_equals("tTNHgg3iNoIdHFn81iQD9A==", + $res->[2][1]{list}[0]{'digest:md5'}); + } + if ($algs{'sha'}) { + $self->assert_str_equals("QiRAPtfyX8K6tm1iOAtZ87Xj3Ww=", + $res->[2][1]{list}[0]{'digest:sha'}); + } + if ($algs{'sha-256'}) { + $self->assert_str_equals( + "gdg9INW7lwHK6OQ9u0dwDz2ZY/gubi0En0xlFpKt0OA=", + $res->[2][1]{list}[0]{'digest:sha-256'} + ); + } + + xlog "Complex expression works and returns the right data"; + my $target = "How quick was that?"; + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { create => { b4 => { data => [ { 'data:asText' => $data } ] } } }, 'S4' + ], + [ + 'Blob/upload', + { + create => { + mult => { + data => [ + { 'data:asText' => 'How' }, # 'How' + { 'blobId' => '#b4', offset => 3, length => 7 }, # ' quick ' + { 'data:asText' => "was t" }, # 'was t' + { 'blobId' => '#b4', offset => 1, length => 1 }, # 'h' + { 'data:asBase64' => encode_base64('at?', '') }, # 'at?' + ] + } + } + }, + 'CAT' + ], + [ + 'Blob/get', + { ids => ['#mult'], properties => [ 'data:asText', 'size' ] }, + 'G4' + ], + ]); + $self->assert_str_equals('Blob/upload', $res->[0][0]); + $self->assert_str_equals('Blob/upload', $res->[1][0]); + $self->assert_str_equals('Blob/get', $res->[2][0]); + $self->assert_str_equals($target, $res->[2][1]{list}[0]{'data:asText'}); + $self->assert_num_equals(length $target, $res->[2][1]{list}[0]{size}); } sub test_blob_upload_repair_acl - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my $self = shift; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); - - $jmap->Upload("hello", "application/data"); - - my $binary = slurp_file(abs_path('data/mime/repair_acl.eml')); - - xlog "Assert that uploading duplicates does not fail"; - $admin->setacl("user.cassandane.#jmap", "cassandane", "lrswkcni") or die; - my $res = $jmap->Upload($binary); - my $blobId = $res->{blobId}; - $res = $jmap->Upload($binary, "message/rfc822"); - $self->assert_str_equals($blobId, $res->{blobId}); - - xlog "Assert ACLs got repaired"; - my %acl = @{$admin->getacl("user.cassandane.#jmap")}; - $self->assert_str_equals("lrswitedn", $acl{cassandane}); + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my $self = shift; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); + + $jmap->Upload("hello", "application/data"); + + my $binary = slurp_file(abs_path('data/mime/repair_acl.eml')); + + xlog "Assert that uploading duplicates does not fail"; + $admin->setacl("user.cassandane.#jmap", "cassandane", "lrswkcni") or die; + my $res = $jmap->Upload($binary); + my $blobId = $res->{blobId}; + $res = $jmap->Upload($binary, "message/rfc822"); + $self->assert_str_equals($blobId, $res->{blobId}); + + xlog "Assert ACLs got repaired"; + my %acl = @{ $admin->getacl("user.cassandane.#jmap") }; + $self->assert_str_equals("lrswitedn", $acl{cassandane}); } sub test_blob_upload_type - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my $self = shift; - my $jmap = $self->{jmap}; - - xlog "Assert client-supplied type is returned"; - my $res = $jmap->Upload("blob1", "text/plain"); - $self->assert_str_equals("text/plain", $res->{type}); - - xlog "Assert client-supplied type is normalized"; - $res = $jmap->Upload("blob1", "text/plain;charset=latin1"); - $self->assert_str_equals("text/plain", $res->{type}); - - xlog "Assert default server type"; - my $httpReq = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => 'blob2', - }; - my $httpRes = $jmap->ua->post($jmap->uploaduri('cassandane'), $httpReq); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($httpReq, $httpRes); - } - $res = eval { decode_json($httpRes->{content}) }; - $self->assert_str_equals("application/octet-stream", $res->{type}); + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my $self = shift; + my $jmap = $self->{jmap}; + + xlog "Assert client-supplied type is returned"; + my $res = $jmap->Upload("blob1", "text/plain"); + $self->assert_str_equals("text/plain", $res->{type}); + + xlog "Assert client-supplied type is normalized"; + $res = $jmap->Upload("blob1", "text/plain;charset=latin1"); + $self->assert_str_equals("text/plain", $res->{type}); + + xlog "Assert default server type"; + my $httpReq = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => 'blob2', + }; + my $httpRes = $jmap->ua->post($jmap->uploaduri('cassandane'), $httpReq); + + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($httpReq, $httpRes); + } + $res = eval { decode_json($httpRes->{content}) }; + $self->assert_str_equals("application/octet-stream", $res->{type}); } sub test_get_session_rename_race - :AllowMoves :min_version_3_7 :needs_component_jmap -{ - # Test fetching the JMAP session during/after a user has been renamed - # but before the authentication credentials have been changed. - # In this case, we should return 503 rather than 500. - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); - - # GET session - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - my $session = eval { decode_json($RawResponse->{content}) }; - $self->assert_not_null($session); - - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert(not $admintalk->get_last_error()); - - # Try to GET session - $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('503', $RawResponse->{status}); - $self->assert_not_null($RawResponse->{headers}{'retry-after'}); + : AllowMoves : min_version_3_7 : needs_component_jmap { + # Test fetching the JMAP session during/after a user has been renamed + # but before the authentication credentials have been changed. + # In this case, we should return 503 rather than 500. + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); + + # GET session + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + my $session = eval { decode_json($RawResponse->{content}) }; + $self->assert_not_null($session); + + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert(not $admintalk->get_last_error()); + + # Try to GET session + $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('503', $RawResponse->{status}); + $self->assert_not_null($RawResponse->{headers}{'retry-after'}); } sub test_blob_upload_too_large - :min_version_3_9 :needs_component_jmap :JMAPExtensions -{ - my $self = shift; - my $jmap = $self->{jmap}; - - xlog "Assert Problem Details report"; - my $httpReq = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => 'X' x 1025, - }; - my $httpRes = $jmap->ua->post($jmap->uploaduri('cassandane'), $httpReq); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($httpReq, $httpRes); - } - $self->assert_str_equals("413", $httpRes->{status}); - my $res = eval { decode_json($httpRes->{content}) }; - $self->assert_str_equals("413", $res->{status}); - $self->assert_str_equals("Content Too Large", $res->{title}); - $self->assert_str_equals("urn:ietf:params:jmap:error:limit", $res->{type}); - $self->assert_str_equals("maxSizeUpload", $res->{limit}); + : min_version_3_9 : needs_component_jmap : JMAPExtensions { + my $self = shift; + my $jmap = $self->{jmap}; + + xlog "Assert Problem Details report"; + my $httpReq = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => 'X' x 1025, + }; + my $httpRes = $jmap->ua->post($jmap->uploaduri('cassandane'), $httpReq); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($httpReq, $httpRes); + } + $self->assert_str_equals("413", $httpRes->{status}); + my $res = eval { decode_json($httpRes->{content}) }; + $self->assert_str_equals("413", $res->{status}); + $self->assert_str_equals("Content Too Large", $res->{title}); + $self->assert_str_equals("urn:ietf:params:jmap:error:limit", $res->{type}); + $self->assert_str_equals("maxSizeUpload", $res->{limit}); } sub test_blob_upload_bad_url - :min_version_3_9 :needs_component_jmap :JMAPExtensions -{ - my $self = shift; - my $jmap = $self->{jmap}; - - xlog "Assert Problem Details report"; - my $httpReq = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => 'Hello World', - }; - my $httpRes = $jmap->ua->post($jmap->uploaduri('cassandane') . 'X', $httpReq); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($httpReq, $httpRes); - } - $self->assert_str_equals("404", $httpRes->{status}); - my $res = eval { decode_json($httpRes->{content}) }; - $self->assert_str_equals("404", $res->{status}); - $self->assert_str_equals("Not Found", $res->{title}); - $self->assert_str_equals("about:blank", $res->{type}); - $self->assert_str_equals("unknown uploadUrl", $res->{detail}); + : min_version_3_9 : needs_component_jmap : JMAPExtensions { + my $self = shift; + my $jmap = $self->{jmap}; + + xlog "Assert Problem Details report"; + my $httpReq = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => 'Hello World', + }; + my $httpRes = $jmap->ua->post($jmap->uploaduri('cassandane') . 'X', $httpReq); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($httpReq, $httpRes); + } + $self->assert_str_equals("404", $httpRes->{status}); + my $res = eval { decode_json($httpRes->{content}) }; + $self->assert_str_equals("404", $res->{status}); + $self->assert_str_equals("Not Found", $res->{title}); + $self->assert_str_equals("about:blank", $res->{type}); + $self->assert_str_equals("unknown uploadUrl", $res->{detail}); } 1; diff --git a/cassandane/Cassandane/Cyrus/JMAPEmail.pm b/cassandane/Cassandane/Cyrus/JMAPEmail.pm index 3a8ee2fdbe..fed0c29514 100644 --- a/cassandane/Cassandane/Cyrus/JMAPEmail.pm +++ b/cassandane/Cassandane/Cyrus/JMAPEmail.pm @@ -48,7 +48,7 @@ use Mail::JMAPTalk 0.13; use Data::Dumper; use Storable 'dclone'; use MIME::Base64 qw(encode_base64); -use Cwd qw(abs_path getcwd); +use Cwd qw(abs_path getcwd); use URI; use URI::Escape; @@ -59,326 +59,367 @@ use Cassandane::Util::Slurp; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_historical_age => -1, - caldav_realm => 'Cassandane', - conversations => 'yes', - conversations_counted_flags => "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", - defaultdomain => 'example.com', - httpallowcompress => 'no', - httpmodules => 'carddav caldav jmap', - icalendar_max_size => 100000, - jmap_nonstandard_extensions => 'yes', - jmapsubmission_deleteonsend => 'no', - notesmailbox => 'Notes', - sync_log => 'yes'); - - # setup sieve - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj == 3 && $min == 0) { - # need to explicitly add 'body' to sieve_extensions for 3.0 - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags mailbox mboxmetadata servermetadata variables " . - "body"); - } - elsif ($maj < 3) { - # also for 2.5 (the earliest Cyrus that Cassandane can test) - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags body"); - } - $config->set(sievenotifier => 'mailto'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - deliver => 1, - adminstore => 1, - services => [ 'imap', 'http', 'sieve' ] - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set( + caldav_historical_age => -1, + caldav_realm => 'Cassandane', + conversations => 'yes', + conversations_counted_flags => + "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", + defaultdomain => 'example.com', + httpallowcompress => 'no', + httpmodules => 'carddav caldav jmap', + icalendar_max_size => 100000, + jmap_nonstandard_extensions => 'yes', + jmapsubmission_deleteonsend => 'no', + notesmailbox => 'Notes', + sync_log => 'yes' + ); + + # setup sieve + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj == 3 && $min == 0) { + # need to explicitly add 'body' to sieve_extensions for 3.0 + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags mailbox mboxmetadata servermetadata variables " + . "body"); + } elsif ($maj < 3) { + # also for 2.5 (the earliest Cyrus that Cassandane can test) + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags body"); + } + $config->set(sievenotifier => 'mailto'); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + deliver => 1, + adminstore => 1, + services => [ 'imap', 'http', 'sieve' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - ]); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + ]); } -sub getinbox -{ - my ($self, $args) = @_; +sub getinbox { + my ($self, $args) = @_; - $args = {} unless $args; + $args = {} unless $args; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get existing mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', $args, "R1"]]); - $self->assert_not_null($res); + xlog $self, "get existing mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', $args, "R1" ] ]); + $self->assert_not_null($res); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - return $m{"Inbox"}; + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + return $m{"Inbox"}; } sub get_account_capabilities - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - my $Request; - my $Response; - - xlog $self, "get session"; - $Request = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - $Response = $jmap->ua->get($jmap->uri(), $Request); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($Request, $Response); - } - $self->assert_str_equals('200', $Response->{status}); - - my $session; - $session = eval { decode_json($Response->{content}) } if $Response->{success}; - - return $session->{accounts}{cassandane}{accountCapabilities}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + my $Request; + my $Response; + + xlog $self, "get session"; + $Request = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + $Response = $jmap->ua->get($jmap->uri(), $Request); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($Request, $Response); + } + $self->assert_str_equals('200', $Response->{status}); + + my $session; + $session = eval { decode_json($Response->{content}) } if $Response->{success}; + + return $session->{accounts}{cassandane}{accountCapabilities}; } -sub defaultprops_for_email_get -{ - return ( "id", "blobId", "threadId", "mailboxIds", "keywords", "size", "receivedAt", "messageId", "inReplyTo", "references", "sender", "from", "to", "cc", "bcc", "replyTo", "subject", "sentAt", "hasAttachment", "preview", "bodyValues", "textBody", "htmlBody", "attachments" ); +sub defaultprops_for_email_get { + return ( + "id", "blobId", "threadId", "mailboxIds", + "keywords", "size", "receivedAt", "messageId", + "inReplyTo", "references", "sender", "from", + "to", "cc", "bcc", "replyTo", + "subject", "sentAt", "hasAttachment", "preview", + "bodyValues", "textBody", "htmlBody", "attachments" + ); } -sub download -{ - my ($self, $accountid, $blobid) = @_; - my $jmap = $self->{jmap}; - - my $uri = $jmap->downloaduri($accountid, $blobid); - my %Headers; - $Headers{'Authorization'} = $jmap->auth_header(); - my %getopts = (headers => \%Headers); - my $res = $jmap->ua->get($uri, \%getopts); - xlog $self, "JMAP DOWNLOAD @_ " . Dumper($res); - return $res; +sub download { + my ($self, $accountid, $blobid) = @_; + my $jmap = $self->{jmap}; + + my $uri = $jmap->downloaduri($accountid, $blobid); + my %Headers; + $Headers{'Authorization'} = $jmap->auth_header(); + my %getopts = (headers => \%Headers); + my $res = $jmap->ua->get($uri, \%getopts); + xlog $self, "JMAP DOWNLOAD @_ " . Dumper($res); + return $res; } -sub email_query_window_internal -{ - my ($self, %params) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; - - $params{filter} //= undef; - $params{wantGuidSearch} //= JSON::false; - $params{calculateTotal} //= JSON::true; - - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; - - my $imaptalk = $self->{store}->get_client(); - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - xlog $self, "generating email A"; - $exp{A} = $self->make_message("Email A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "generating email C referencing A"; - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ]); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "generating email D"; - $exp{D} = $self->make_message("Email D"); - $exp{D}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "list all emails"; - $res = $jmap->CallMethods([['Email/query', { - calculateTotal => JSON::true, - }, "R1"]]); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{ids}}); +sub email_query_window_internal { + my ($self, %params) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; + + $params{filter} //= undef; + $params{wantGuidSearch} //= JSON::false; + $params{calculateTotal} //= JSON::true; + + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; + + my $imaptalk = $self->{store}->get_client(); + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + xlog $self, "generating email A"; + $exp{A} = $self->make_message("Email A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "generating email C referencing A"; + $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ]); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "generating email D"; + $exp{D} = $self->make_message("Email D"); + $exp{D}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "list all emails"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + calculateTotal => JSON::true, + }, + "R1" + ] ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{ids} }); + $self->assert_num_equals(4, $res->[0][1]->{total}); + + my $ids = $res->[0][1]->{ids}; + my @subids; + + xlog $self, "list emails from position 1"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + position => 1, + filter => $params{filter}, + calculateTotal => $params{calculateTotal}, + }, + "R1" + ] ], + $using + ); + $self->assert_equals($params{wantGuidSearch}, + $res->[0][1]{performance}{details}{isGuidSearch}); + @subids = @{$ids}[ 1 .. 3 ]; + $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); + + if ($params{calculateTotal}) { $self->assert_num_equals(4, $res->[0][1]->{total}); - - my $ids = $res->[0][1]->{ids}; - my @subids; - - xlog $self, "list emails from position 1"; - $res = $jmap->CallMethods([ - ['Email/query', { - position => 1, - filter => $params{filter}, - calculateTotal => $params{calculateTotal}, - }, "R1"] - ], $using); - $self->assert_equals($params{wantGuidSearch}, - $res->[0][1]{performance}{details}{isGuidSearch}); - @subids = @{$ids}[1..3]; - $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); - if ($params{calculateTotal}) { - $self->assert_num_equals(4, $res->[0][1]->{total}); - } - - xlog $self, "list emails from position 4"; - $res = $jmap->CallMethods([ - ['Email/query', { - position => 4, - filter => $params{filter}, - calculateTotal => $params{calculateTotal}, - }, "R1"] - ], $using); - $self->assert_equals($params{wantGuidSearch}, - $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - if ($params{calculateTotal}) { - $self->assert_num_equals(4, $res->[0][1]->{total}); - } - - xlog $self, "limit emails from position 1 to one email"; - $res = $jmap->CallMethods([ - ['Email/query', { - position => 1, - limit => 1, - filter => $params{filter}, - calculateTotal => $params{calculateTotal}, - }, "R1"] - ], $using); - $self->assert_equals($params{wantGuidSearch}, - $res->[0][1]{performance}{details}{isGuidSearch}); - @subids = @{$ids}[1..1]; - $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); - $self->assert_num_equals(1, $res->[0][1]->{position}); - if ($params{calculateTotal}) { - $self->assert_num_equals(4, $res->[0][1]->{total}); - } - - xlog $self, "anchor at 2nd email"; - $res = $jmap->CallMethods([ - ['Email/query', { - anchor => @{$ids}[1], - filter => $params{filter}, - calculateTotal => $params{calculateTotal}, - }, "R1"] - ], $using); - $self->assert_equals($params{wantGuidSearch}, - $res->[0][1]{performance}{details}{isGuidSearch}); - @subids = @{$ids}[1..3]; - $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); - $self->assert_num_equals(1, $res->[0][1]->{position}); - if ($params{calculateTotal}) { - $self->assert_num_equals(4, $res->[0][1]->{total}); - } - - xlog $self, "anchor at 2nd email and offset 1"; - $res = $jmap->CallMethods([ - ['Email/query', { - anchor => @{$ids}[1], - anchorOffset => 1, - filter => $params{filter}, - calculateTotal => $params{calculateTotal}, - }, "R1"] - ], $using); - $self->assert_equals($params{wantGuidSearch}, - $res->[0][1]{performance}{details}{isGuidSearch}); - @subids = @{$ids}[2..3]; - $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); - $self->assert_num_equals(2, $res->[0][1]->{position}); - if ($params{calculateTotal}) { - $self->assert_num_equals(4, $res->[0][1]->{total}); - } - - xlog $self, "anchor at 3rd email and offset -1"; - $res = $jmap->CallMethods([ - ['Email/query', { - anchor => @{$ids}[2], - anchorOffset => -1, - filter => $params{filter}, - calculateTotal => $params{calculateTotal}, - }, "R1"] - ], $using); - $self->assert_equals($params{wantGuidSearch}, - $res->[0][1]{performance}{details}{isGuidSearch}); - @subids = @{$ids}[1..3]; - $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); - $self->assert_num_equals(1, $res->[0][1]->{position}); - if ($params{calculateTotal}) { - $self->assert_num_equals(4, $res->[0][1]->{total}); - } - - xlog $self, "anchor at 1st email offset 1 and limit 2"; - $res = $jmap->CallMethods([ - ['Email/query', { - anchor => @{$ids}[0], - anchorOffset => 1, - limit => 2, - filter => $params{filter}, - calculateTotal => $params{calculateTotal}, - }, "R1"] - ], $using); - $self->assert_equals($params{wantGuidSearch}, - $res->[0][1]{performance}{details}{isGuidSearch}); - @subids = @{$ids}[1..2]; - $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); - $self->assert_num_equals(1, $res->[0][1]->{position}); - if ($params{calculateTotal}) { - $self->assert_num_equals(4, $res->[0][1]->{total}); - } + } + + xlog $self, "list emails from position 4"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + position => 4, + filter => $params{filter}, + calculateTotal => $params{calculateTotal}, + }, + "R1" + ] ], + $using + ); + $self->assert_equals($params{wantGuidSearch}, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + if ($params{calculateTotal}) { + $self->assert_num_equals(4, $res->[0][1]->{total}); + } + + xlog $self, "limit emails from position 1 to one email"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + position => 1, + limit => 1, + filter => $params{filter}, + calculateTotal => $params{calculateTotal}, + }, + "R1" + ] ], + $using + ); + $self->assert_equals($params{wantGuidSearch}, + $res->[0][1]{performance}{details}{isGuidSearch}); + @subids = @{$ids}[ 1 .. 1 ]; + $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); + $self->assert_num_equals(1, $res->[0][1]->{position}); + if ($params{calculateTotal}) { + $self->assert_num_equals(4, $res->[0][1]->{total}); + } + + xlog $self, "anchor at 2nd email"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + anchor => @{$ids}[1], + filter => $params{filter}, + calculateTotal => $params{calculateTotal}, + }, + "R1" + ] ], + $using + ); + $self->assert_equals($params{wantGuidSearch}, + $res->[0][1]{performance}{details}{isGuidSearch}); + @subids = @{$ids}[ 1 .. 3 ]; + $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); + $self->assert_num_equals(1, $res->[0][1]->{position}); + if ($params{calculateTotal}) { + $self->assert_num_equals(4, $res->[0][1]->{total}); + } + + xlog $self, "anchor at 2nd email and offset 1"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + anchor => @{$ids}[1], + anchorOffset => 1, + filter => $params{filter}, + calculateTotal => $params{calculateTotal}, + }, + "R1" + ] ], + $using + ); + $self->assert_equals($params{wantGuidSearch}, + $res->[0][1]{performance}{details}{isGuidSearch}); + @subids = @{$ids}[ 2 .. 3 ]; + $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); + $self->assert_num_equals(2, $res->[0][1]->{position}); + if ($params{calculateTotal}) { + $self->assert_num_equals(4, $res->[0][1]->{total}); + } + + xlog $self, "anchor at 3rd email and offset -1"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + anchor => @{$ids}[2], + anchorOffset => -1, + filter => $params{filter}, + calculateTotal => $params{calculateTotal}, + }, + "R1" + ] ], + $using + ); + $self->assert_equals($params{wantGuidSearch}, + $res->[0][1]{performance}{details}{isGuidSearch}); + @subids = @{$ids}[ 1 .. 3 ]; + $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); + $self->assert_num_equals(1, $res->[0][1]->{position}); + if ($params{calculateTotal}) { + $self->assert_num_equals(4, $res->[0][1]->{total}); + } + + xlog $self, "anchor at 1st email offset 1 and limit 2"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + anchor => @{$ids}[0], + anchorOffset => 1, + limit => 2, + filter => $params{filter}, + calculateTotal => $params{calculateTotal}, + }, + "R1" + ] ], + $using + ); + $self->assert_equals($params{wantGuidSearch}, + $res->[0][1]{performance}{details}{isGuidSearch}); + @subids = @{$ids}[ 1 .. 2 ]; + $self->assert_deep_equals(\@subids, $res->[0][1]->{ids}); + $self->assert_num_equals(1, $res->[0][1]->{position}); + if ($params{calculateTotal}) { + $self->assert_num_equals(4, $res->[0][1]->{total}); + } } -sub _set_quotaroot -{ - my ($self, $quotaroot) = @_; - $self->{quotaroot} = $quotaroot; +sub _set_quotaroot { + my ($self, $quotaroot) = @_; + $self->{quotaroot} = $quotaroot; } -sub _set_quotalimits -{ - my ($self, %resources) = @_; - my $admintalk = $self->{adminstore}->get_client(); - - my $quotaroot = delete $resources{quotaroot} || $self->{quotaroot}; - my @quotalist; - foreach my $resource (keys %resources) - { - my $limit = $resources{$resource} - or die "No limit specified for $resource"; - push(@quotalist, uc($resource), $limit); - } - $self->{limits}->{$quotaroot} = { @quotalist }; - $admintalk->setquota($quotaroot, \@quotalist); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); +sub _set_quotalimits { + my ($self, %resources) = @_; + my $admintalk = $self->{adminstore}->get_client(); + + my $quotaroot = delete $resources{quotaroot} || $self->{quotaroot}; + my @quotalist; + foreach my $resource (keys %resources) { + my $limit = $resources{$resource} + or die "No limit specified for $resource"; + push(@quotalist, uc($resource), $limit); + } + $self->{limits}->{$quotaroot} = {@quotalist}; + $admintalk->setquota($quotaroot, \@quotalist); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } use Cassandane::Tiny::Loader 'tiny-tests/JMAPEmail'; diff --git a/cassandane/Cassandane/Cyrus/JMAPEmailSubmission.pm b/cassandane/Cassandane/Cyrus/JMAPEmailSubmission.pm index 10810d8c1f..2ce2d85798 100644 --- a/cassandane/Cassandane/Cyrus/JMAPEmailSubmission.pm +++ b/cassandane/Cassandane/Cyrus/JMAPEmailSubmission.pm @@ -48,7 +48,7 @@ use Mail::JMAPTalk 0.13; use Data::Dumper; use Storable 'dclone'; use MIME::Base64 qw(encode_base64); -use Cwd qw(abs_path getcwd); +use Cwd qw(abs_path getcwd); use URI; use lib '.'; @@ -57,67 +57,68 @@ use Cassandane::Util::Log; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane', - conversations => 'yes', - conversations_counted_flags => "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", - event_groups => 'mailbox message flags calendar applepushservice jmap', - jmapsubmission_deleteonsend => 'no', - httpmodules => 'carddav caldav jmap', - httpallowcompress => 'no'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - services => [ 'imap', 'http' ] - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set( + caldav_realm => 'Cassandane', + conversations => 'yes', + conversations_counted_flags => + "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", + event_groups => 'mailbox message flags calendar applepushservice jmap', + jmapsubmission_deleteonsend => 'no', + httpmodules => 'carddav caldav jmap', + httpallowcompress => 'no' + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + services => [ 'imap', 'http' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - ]); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + ]); } -sub skip_check -{ - my ($self) = @_; +sub skip_check { + my ($self) = @_; - # XXX skip tests that would hang in verbose mode for now -- see - # XXX detailed comment at MaxMessages::put_submission - if (get_verbose() - && $self->{_name} eq 'test_emailsubmission_set_futurerelease') - { - return 'test would hang in verbose mode'; - } + # XXX skip tests that would hang in verbose mode for now -- see + # XXX detailed comment at MaxMessages::put_submission + if (get_verbose() + && $self->{_name} eq 'test_emailsubmission_set_futurerelease') + { + return 'test would hang in verbose mode'; + } - return undef; + return undef; } -sub getinbox -{ - my ($self, $args) = @_; +sub getinbox { + my ($self, $args) = @_; - $args = {} unless $args; + $args = {} unless $args; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get existing mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', $args, "R1"]]); - $self->assert_not_null($res); + xlog $self, "get existing mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', $args, "R1" ] ]); + $self->assert_not_null($res); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - return $m{"Inbox"}; + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + return $m{"Inbox"}; } use Cassandane::Tiny::Loader 'tiny-tests/JMAPEmailSubmission'; diff --git a/cassandane/Cassandane/Cyrus/JMAPMailbox.pm b/cassandane/Cassandane/Cyrus/JMAPMailbox.pm index f3e43926a2..2093ac8e49 100644 --- a/cassandane/Cassandane/Cyrus/JMAPMailbox.pm +++ b/cassandane/Cassandane/Cyrus/JMAPMailbox.pm @@ -48,7 +48,7 @@ use Mail::JMAPTalk 0.13; use Data::Dumper; use Storable 'dclone'; use MIME::Base64 qw(encode_base64); -use Cwd qw(abs_path getcwd); +use Cwd qw(abs_path getcwd); use lib '.'; use base qw(Cassandane::Cyrus::TestCase); @@ -59,95 +59,110 @@ use Cyrus::DList; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set(caldav_realm => 'Cassandane', - conversations => 'yes', - conversations_counted_flags => "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", - httpmodules => 'carddav caldav jmap', - specialuse_extra => '\\XSpecialUse \\XChats \\XTemplates \\XNotes', - notesmailbox => 'Notes', - httpallowcompress => 'no'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - adminstore => 1, - services => [ 'imap', 'http' ] - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set( + caldav_realm => 'Cassandane', + conversations => 'yes', + conversations_counted_flags => + "\\Draft \\Flagged \$IsMailingList \$IsNotification \$HasAttachment", + httpmodules => 'carddav caldav jmap', + specialuse_extra => '\\XSpecialUse \\XChats \\XTemplates \\XNotes', + notesmailbox => 'Notes', + httpallowcompress => 'no' + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + adminstore => 1, + services => [ 'imap', 'http' ] + }, + @args + ); } -sub setup_default_using -{ - my ($self) = @_; - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]); +sub setup_default_using { + my ($self) = @_; + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', ]); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - if ($self->{jmap}) { - $self->setup_default_using(); - } - # n.b. tests that use :NoStartInstances will need to call - # $self->setup_default_using() themselves! + if ($self->{jmap}) { + $self->setup_default_using(); + } + # n.b. tests that use :NoStartInstances will need to call + # $self->setup_default_using() themselves! } -sub getinbox -{ - my ($self, $args) = @_; +sub getinbox { + my ($self, $args) = @_; - $args = {} unless $args; + $args = {} unless $args; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get existing mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', $args, "R1"]]); - $self->assert_not_null($res); + xlog $self, "get existing mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', $args, "R1" ] ]); + $self->assert_not_null($res); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - return $m{"Inbox"}; + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + return $m{"Inbox"}; } sub _check_one_count { - my $self = shift; - my $want = shift; - my $have = shift; - my $name = shift; - $self->assert_num_equals($want, $have); + my $self = shift; + my $want = shift; + my $have = shift; + my $name = shift; + $self->assert_num_equals($want, $have); } -sub _check_counts -{ - my $self = shift; - my $name = shift; - my %expect = @_; - - my $jmap = $self->{jmap}; - - my $res = $jmap->CallMethods([['Mailbox/get', {}, 'R']]); - - # "totalEmails": 3, - # "unreadEmails": 1, - # "totalThreads": 3, - # "unreadThreads": 1, - - for my $folder (@{$res->[0][1]{list}}) { - my $want = $expect{$folder->{name}}; - next unless $want; - $self->_check_one_count($want->[0], $folder->{totalEmails}, "$folder->{name} totalEmails"); - $self->_check_one_count($want->[1], $folder->{unreadEmails}, "$folder->{name} unreadEmails"); - $self->_check_one_count($want->[2], $folder->{totalThreads}, "$folder->{name} totalThreads"); - $self->_check_one_count($want->[3], $folder->{unreadThreads}, "$folder->{name} unreadThreads"); - } +sub _check_counts { + my $self = shift; + my $name = shift; + my %expect = @_; + + my $jmap = $self->{jmap}; + + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, 'R' ] ]); + + # "totalEmails": 3, + # "unreadEmails": 1, + # "totalThreads": 3, + # "unreadThreads": 1, + + for my $folder (@{ $res->[0][1]{list} }) { + my $want = $expect{ $folder->{name} }; + next unless $want; + $self->_check_one_count( + $want->[0], + $folder->{totalEmails}, + "$folder->{name} totalEmails" + ); + $self->_check_one_count( + $want->[1], + $folder->{unreadEmails}, + "$folder->{name} unreadEmails" + ); + $self->_check_one_count( + $want->[2], + $folder->{totalThreads}, + "$folder->{name} totalThreads" + ); + $self->_check_one_count( + $want->[3], + $folder->{unreadThreads}, + "$folder->{name} unreadThreads" + ); + } } use Cassandane::Tiny::Loader 'tiny-tests/JMAPMailbox'; diff --git a/cassandane/Cassandane/Cyrus/JMAPSieve.pm b/cassandane/Cassandane/Cyrus/JMAPSieve.pm index 3f91975338..852b381dc1 100644 --- a/cassandane/Cassandane/Cyrus/JMAPSieve.pm +++ b/cassandane/Cassandane/Cyrus/JMAPSieve.pm @@ -56,803 +56,965 @@ use Cassandane::Util::Log; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj == 3 && $min == 0) { - # need to explicitly add 'body' to sieve_extensions for 3.0 - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags mailbox mboxmetadata servermetadata variables " . - "body"); - } - elsif ($maj < 3) { - # also for 2.5 (the earliest Cyrus that Cassandane can test) - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags body"); - } - - $config->set(caldav_realm => 'Cassandane', - conversations => 'yes', - httpmodules => 'carddav caldav jmap', - httpallowcompress => 'no', - jmap_nonstandard_extensions => 'yes'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - deliver => 1, - adminstore => 1, - services => [ 'imap', 'sieve', 'http' ] - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj == 3 && $min == 0) { + # need to explicitly add 'body' to sieve_extensions for 3.0 + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags mailbox mboxmetadata servermetadata variables " + . "body"); + } elsif ($maj < 3) { + # also for 2.5 (the earliest Cyrus that Cassandane can test) + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags body"); + } + + $config->set( + caldav_realm => 'Cassandane', + conversations => 'yes', + httpmodules => 'carddav caldav jmap', + httpallowcompress => 'no', + jmap_nonstandard_extensions => 'yes' + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + deliver => 1, + adminstore => 1, + services => [ 'imap', 'sieve', 'http' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/sieve', - 'https://cyrusimap.org/ns/jmap/blob', - ]); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/sieve', + 'https://cyrusimap.org/ns/jmap/blob', + ]); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub download -{ - my ($self, $accountid, $blobid) = @_; - my $jmap = $self->{jmap}; - - my $uri = $jmap->downloaduri($accountid, $blobid); - my %Headers; - $Headers{'Authorization'} = $jmap->auth_header(); - my %getopts = (headers => \%Headers); - my $res = $jmap->ua->get($uri, \%getopts); - xlog $self, "JMAP DOWNLOAD @_ " . Dumper($res); - return $res; +sub download { + my ($self, $accountid, $blobid) = @_; + my $jmap = $self->{jmap}; + + my $uri = $jmap->downloaduri($accountid, $blobid); + my %Headers; + $Headers{'Authorization'} = $jmap->auth_header(); + my %getopts = (headers => \%Headers); + my $res = $jmap->ua->get($uri, \%getopts); + xlog $self, "JMAP DOWNLOAD @_ " . Dumper($res); + return $res; } sub test_sieve_get - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $target = "INBOX.target"; + my $target = "INBOX.target"; - xlog $self, "Install a sieve script filing all mail into a folder"; - my $script = <{instance}->install_sieve_script($script); - - xlog "get all scripts"; - my $res = $jmap->CallMethods([ - ['SieveScript/get', { - properties => ['name', 'isActive'], - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('SieveScript/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('test1', $res->[0][1]{list}[0]{name}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isActive}); - - my $id = $res->[0][1]{list}[0]{id}; - - xlog "get script by id"; - $res = $jmap->CallMethods([ - ['SieveScript/get', { - ids => [$id], - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('SieveScript/get', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('test1', $res->[0][1]{list}[0]{name}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isActive}); - - my $blobId = $res->[0][1]{list}[0]{blobId}; - - xlog $self, "download script blob"; - $res = $self->download('cassandane', $blobId); - $self->assert_str_equals('200', $res->{status}); - $self->assert_str_equals($script, $res->{content}); + $self->{instance}->install_sieve_script($script); + + xlog "get all scripts"; + my $res = $jmap->CallMethods([ [ + 'SieveScript/get', + { + properties => [ 'name', 'isActive' ], + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('SieveScript/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('test1', $res->[0][1]{list}[0]{name}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isActive}); + + my $id = $res->[0][1]{list}[0]{id}; + + xlog "get script by id"; + $res = $jmap->CallMethods([ [ + 'SieveScript/get', + { + ids => [$id], + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('SieveScript/get', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('test1', $res->[0][1]{list}[0]{name}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isActive}); + + my $blobId = $res->[0][1]{list}[0]{blobId}; + + xlog $self, "download script blob"; + $res = $self->download('cassandane', $blobId); + $self->assert_str_equals('200', $res->{status}); + $self->assert_str_equals($script, $res->{content}); } sub test_sieve_set - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; - my $script1 = <{jmap}; - - my $res = $jmap->Upload($script1, "application/sieve"); - my $blobid = $res->{blobId}; - - xlog "create script"; - $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => $script2}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => "foo", - blobId => $blobid - }, - "2" => { - name => JSON::null, - blobId => "#A" - } - }, - onSuccessActivateScript => "#1" - }, "R1"], - ['SieveScript/get', { - 'ids' => [ '#1', '#2' ] - }, "R2"] - ]); - $self->assert_not_null($res); - $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); - $self->assert_equals(JSON::false, $res->[1][1]{created}{2}{isActive}); - - my $id1 = $res->[1][1]{created}{"1"}{id}; - my $id2 = $res->[1][1]{created}{"2"}{id}; - - $self->assert_num_equals(2, scalar @{$res->[2][1]{list}}); - $self->assert_str_equals('foo', $res->[2][1]{list}[0]{name}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{isActive}); - $self->assert_str_equals($id2, $res->[2][1]{list}[1]{name}); - $self->assert_equals(JSON::false, $res->[2][1]{list}[1]{isActive}); - - xlog "attempt to create script with same name"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - create => { - "1" => { - name => "foo", - blobId => $blobid - } - }, - }, "R1"], - ['SieveScript/get', { - }, "R2"] - ]); - $self->assert_not_null($res); - $self->assert_null($res->[0][1]{created}); - $self->assert_str_equals('alreadyExists', $res->[0][1]{notCreated}{1}{type}); - $self->assert_num_equals(2, scalar @{$res->[1][1]{list}}); - - xlog "rename and deactivate script"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - update => { - $id1 => { - name => "bar" - } - }, - onSuccessActivateScript => JSON::null - }, "R3"] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id1}{isActive}); - - xlog "rewrite one script and activate another"; - $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "B" => { data => [{'data:asText' => $script3}] } - } - }, "R0"], - ['SieveScript/set', { - update => { - $id1 => { - blobId => "#B", - } - }, - onSuccessActivateScript => $id2 - }, "R4"], - ]); - $self->assert_not_null($res->[1][1]{updated}); - $self->assert_not_null($res->[1][1]{updated}{$id1}{blobId}); - $self->assert_equals(JSON::true, $res->[1][1]{updated}{$id2}{isActive}); - $self->assert_null($res->[1][1]{notUpdated}); - - xlog "change active script"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - onSuccessActivateScript => $id1 - }, "R4"], - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_equals(JSON::true, $res->[0][1]{updated}{$id1}{isActive}); - $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id2}{isActive}); - $self->assert_null($res->[0][1]{notUpdated}); - - xlog "attempt to delete active script"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - destroy => [ $id1 ], - }, "R6"], - ['SieveScript/get', { - }, "R7"] - ]); - $self->assert_null($res->[0][1]{destroyed}); - $self->assert_not_null($res->[0][1]{notDestroyed}); - $self->assert_num_equals(2, scalar @{$res->[1][1]{list}}); - - xlog "delete active script"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - onSuccessActivateScript => JSON::null - }, "R8"], - ['SieveScript/set', { - destroy => [ $id1 ], - }, "R8.5"], - ['SieveScript/get', { - }, "R9"] - ]); - $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id1}{isActive}); - $self->assert_not_null($res->[1][1]{destroyed}); - $self->assert_null($res->[1][1]{notDestroyed}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{list}}); + $script3 =~ s/\r?\n/\r\n/gs; + + my $jmap = $self->{jmap}; + + my $res = $jmap->Upload($script1, "application/sieve"); + my $blobid = $res->{blobId}; + + xlog "create script"; + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => $script2 } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => "foo", + blobId => $blobid + }, + "2" => { + name => JSON::null, + blobId => "#A" + } + }, + onSuccessActivateScript => "#1" + }, + "R1" + ], + [ + 'SieveScript/get', + { + 'ids' => [ '#1', '#2' ] + }, + "R2" + ] + ]); + $self->assert_not_null($res); + $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); + $self->assert_equals(JSON::false, $res->[1][1]{created}{2}{isActive}); + + my $id1 = $res->[1][1]{created}{"1"}{id}; + my $id2 = $res->[1][1]{created}{"2"}{id}; + + $self->assert_num_equals(2, scalar @{ $res->[2][1]{list} }); + $self->assert_str_equals('foo', $res->[2][1]{list}[0]{name}); + $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{isActive}); + $self->assert_str_equals($id2, $res->[2][1]{list}[1]{name}); + $self->assert_equals(JSON::false, $res->[2][1]{list}[1]{isActive}); + + xlog "attempt to create script with same name"; + $res = $jmap->CallMethods([ + [ + 'SieveScript/set', + { + create => { + "1" => { + name => "foo", + blobId => $blobid + } + }, + }, + "R1" + ], + [ 'SieveScript/get', {}, "R2" ] + ]); + $self->assert_not_null($res); + $self->assert_null($res->[0][1]{created}); + $self->assert_str_equals('alreadyExists', $res->[0][1]{notCreated}{1}{type}); + $self->assert_num_equals(2, scalar @{ $res->[1][1]{list} }); + + xlog "rename and deactivate script"; + $res = $jmap->CallMethods([ [ + 'SieveScript/set', + { + update => { + $id1 => { + name => "bar" + } + }, + onSuccessActivateScript => JSON::null + }, + "R3" + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id1}{isActive}); + + xlog "rewrite one script and activate another"; + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "B" => { data => [ { 'data:asText' => $script3 } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + update => { + $id1 => { + blobId => "#B", + } + }, + onSuccessActivateScript => $id2 + }, + "R4" + ], + ]); + $self->assert_not_null($res->[1][1]{updated}); + $self->assert_not_null($res->[1][1]{updated}{$id1}{blobId}); + $self->assert_equals(JSON::true, $res->[1][1]{updated}{$id2}{isActive}); + $self->assert_null($res->[1][1]{notUpdated}); + + xlog "change active script"; + $res = $jmap->CallMethods([ + [ + 'SieveScript/set', + { + onSuccessActivateScript => $id1 + }, + "R4" + ], + ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_equals(JSON::true, $res->[0][1]{updated}{$id1}{isActive}); + $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id2}{isActive}); + $self->assert_null($res->[0][1]{notUpdated}); + + xlog "attempt to delete active script"; + $res = $jmap->CallMethods([ + [ + 'SieveScript/set', + { + destroy => [$id1], + }, + "R6" + ], + [ 'SieveScript/get', {}, "R7" ] + ]); + $self->assert_null($res->[0][1]{destroyed}); + $self->assert_not_null($res->[0][1]{notDestroyed}); + $self->assert_num_equals(2, scalar @{ $res->[1][1]{list} }); + + xlog "delete active script"; + $res = $jmap->CallMethods([ + [ + 'SieveScript/set', + { + onSuccessActivateScript => JSON::null + }, + "R8" + ], + [ + 'SieveScript/set', + { + destroy => [$id1], + }, + "R8.5" + ], + [ 'SieveScript/get', {}, "R9" ] + ]); + $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id1}{isActive}); + $self->assert_not_null($res->[1][1]{destroyed}); + $self->assert_null($res->[1][1]{notDestroyed}); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{list} }); } sub test_sieve_set_replication - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; - my $script1 = <{jmap}; - - my $res = $jmap->Upload($script1, "application/sieve"); - my $blobid = $res->{blobId}; - - xlog "create script"; - $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => $script2}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => "foo", - blobId => $blobid - }, - "2" => { - name => JSON::null, - blobId => "#A" - } - }, - onSuccessActivateScript => "#1" - }, "R1"], - ['SieveScript/get', { - 'ids' => [ '#1', '#2' ] - }, "R2"] - ]); - $self->assert_not_null($res); - $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); - $self->assert_equals(JSON::false, $res->[1][1]{created}{2}{isActive}); - - my $id1 = $res->[1][1]{created}{"1"}{id}; - my $id2 = $res->[1][1]{created}{"2"}{id}; - - $self->assert_num_equals(2, scalar @{$res->[2][1]{list}}); - $self->assert_str_equals('foo', $res->[2][1]{list}[0]{name}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{isActive}); - $self->assert_str_equals($id2, $res->[2][1]{list}[1]{name}); - $self->assert_equals(JSON::false, $res->[2][1]{list}[1]{isActive}); - - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog "attempt to create script with same name"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - create => { - "1" => { - name => "foo", - blobId => $blobid - } - }, - }, "R1"], - ['SieveScript/get', { - }, "R2"] - ]); - $self->assert_not_null($res); - $self->assert_null($res->[0][1]{created}); - $self->assert_str_equals('alreadyExists', $res->[0][1]{notCreated}{1}{type}); - $self->assert_num_equals(2, scalar @{$res->[1][1]{list}}); - - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog "rename and deactivate script"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - update => { - $id1 => { - name => "bar" - } - }, - onSuccessActivateScript => JSON::null - }, "R3"] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id1}{isActive}); - - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog "rewrite one script and activate another"; - $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "B" => { data => [{'data:asText' => $script3}] } - } - }, "R0"], - ['SieveScript/set', { - update => { - $id1 => { - blobId => "#B", - } - }, - onSuccessActivateScript => $id2 - }, "R4"], - ]); - $self->assert_not_null($res->[1][1]{updated}); - $self->assert_not_null($res->[1][1]{updated}{$id1}{blobId}); - $self->assert_equals(JSON::true, $res->[1][1]{updated}{$id2}{isActive}); - $self->assert_null($res->[1][1]{notUpdated}); - - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog "change active script"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - onSuccessActivateScript => $id1 - }, "R4"], - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_equals(JSON::true, $res->[0][1]{updated}{$id1}{isActive}); - $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id2}{isActive}); - $self->assert_null($res->[0][1]{notUpdated}); - - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog "attempt to delete active script"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - destroy => [ $id1 ], - }, "R6"], - ['SieveScript/get', { - }, "R7"] - ]); - $self->assert_null($res->[0][1]{destroyed}); - $self->assert_not_null($res->[0][1]{notDestroyed}); - $self->assert_num_equals(2, scalar @{$res->[1][1]{list}}); - - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog "delete active script"; - $res = $jmap->CallMethods([ - ['SieveScript/set', { - onSuccessActivateScript => JSON::null - }, "R8"], - ['SieveScript/set', { - destroy => [ $id1 ], - }, "R8.5"], - ['SieveScript/get', { - }, "R9"] - ]); - $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id1}{isActive}); - $self->assert_not_null($res->[1][1]{destroyed}); - $self->assert_null($res->[1][1]{notDestroyed}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{list}}); - - $self->run_replication(); - $self->check_replication('cassandane'); + $script3 =~ s/\r?\n/\r\n/gs; + + my $jmap = $self->{jmap}; + + my $res = $jmap->Upload($script1, "application/sieve"); + my $blobid = $res->{blobId}; + + xlog "create script"; + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => $script2 } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => "foo", + blobId => $blobid + }, + "2" => { + name => JSON::null, + blobId => "#A" + } + }, + onSuccessActivateScript => "#1" + }, + "R1" + ], + [ + 'SieveScript/get', + { + 'ids' => [ '#1', '#2' ] + }, + "R2" + ] + ]); + $self->assert_not_null($res); + $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); + $self->assert_equals(JSON::false, $res->[1][1]{created}{2}{isActive}); + + my $id1 = $res->[1][1]{created}{"1"}{id}; + my $id2 = $res->[1][1]{created}{"2"}{id}; + + $self->assert_num_equals(2, scalar @{ $res->[2][1]{list} }); + $self->assert_str_equals('foo', $res->[2][1]{list}[0]{name}); + $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{isActive}); + $self->assert_str_equals($id2, $res->[2][1]{list}[1]{name}); + $self->assert_equals(JSON::false, $res->[2][1]{list}[1]{isActive}); + + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog "attempt to create script with same name"; + $res = $jmap->CallMethods([ + [ + 'SieveScript/set', + { + create => { + "1" => { + name => "foo", + blobId => $blobid + } + }, + }, + "R1" + ], + [ 'SieveScript/get', {}, "R2" ] + ]); + $self->assert_not_null($res); + $self->assert_null($res->[0][1]{created}); + $self->assert_str_equals('alreadyExists', $res->[0][1]{notCreated}{1}{type}); + $self->assert_num_equals(2, scalar @{ $res->[1][1]{list} }); + + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog "rename and deactivate script"; + $res = $jmap->CallMethods([ [ + 'SieveScript/set', + { + update => { + $id1 => { + name => "bar" + } + }, + onSuccessActivateScript => JSON::null + }, + "R3" + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id1}{isActive}); + + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog "rewrite one script and activate another"; + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "B" => { data => [ { 'data:asText' => $script3 } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + update => { + $id1 => { + blobId => "#B", + } + }, + onSuccessActivateScript => $id2 + }, + "R4" + ], + ]); + $self->assert_not_null($res->[1][1]{updated}); + $self->assert_not_null($res->[1][1]{updated}{$id1}{blobId}); + $self->assert_equals(JSON::true, $res->[1][1]{updated}{$id2}{isActive}); + $self->assert_null($res->[1][1]{notUpdated}); + + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog "change active script"; + $res = $jmap->CallMethods([ + [ + 'SieveScript/set', + { + onSuccessActivateScript => $id1 + }, + "R4" + ], + ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_equals(JSON::true, $res->[0][1]{updated}{$id1}{isActive}); + $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id2}{isActive}); + $self->assert_null($res->[0][1]{notUpdated}); + + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog "attempt to delete active script"; + $res = $jmap->CallMethods([ + [ + 'SieveScript/set', + { + destroy => [$id1], + }, + "R6" + ], + [ 'SieveScript/get', {}, "R7" ] + ]); + $self->assert_null($res->[0][1]{destroyed}); + $self->assert_not_null($res->[0][1]{notDestroyed}); + $self->assert_num_equals(2, scalar @{ $res->[1][1]{list} }); + + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog "delete active script"; + $res = $jmap->CallMethods([ + [ + 'SieveScript/set', + { + onSuccessActivateScript => JSON::null + }, + "R8" + ], + [ + 'SieveScript/set', + { + destroy => [$id1], + }, + "R8.5" + ], + [ 'SieveScript/get', {}, "R9" ] + ]); + $self->assert_equals(JSON::false, $res->[0][1]{updated}{$id1}{isActive}); + $self->assert_not_null($res->[1][1]{destroyed}); + $self->assert_null($res->[1][1]{notDestroyed}); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{list} }); + + $self->run_replication(); + $self->check_replication('cassandane'); } sub test_sieve_set_bad_script - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "create bad script"; - my $res = $jmap->Upload("keepme;", "application/sieve"); - my $blobid = $res->{blobId}; - - $res = $jmap->CallMethods([ - ['SieveScript/set', { - create => { - "1" => { - name => "foo", - blobId => $blobid - } - } - }, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_null($res->[0][1]{created}); - $self->assert_str_equals('invalidScript', $res->[0][1]{notCreated}{1}{type}); - - xlog "update bad script"; - $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => "keep;"}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => "foo", - blobId => "#A" - } - }, - update => { - "#1" => { - blobId => $blobid - } - }, - destroy => [ "#1" ] - }, "R2"] - ]); - $self->assert_not_null($res); - - my $id = $res->[1][1]{created}{"1"}{id}; - - $self->assert_null($res->[1][1]{updated}); - $self->assert_str_equals('invalidScript', $res->[1][1]{notUpdated}{$id}{type}); - $self->assert_not_null($res->[1][1]{destroyed}); - $self->assert_str_equals($id, $res->[1][1]{destroyed}[0]); - $self->assert_null($res->[1][1]{notDestroyed}); + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "create bad script"; + my $res = $jmap->Upload("keepme;", "application/sieve"); + my $blobid = $res->{blobId}; + + $res = $jmap->CallMethods([ [ + 'SieveScript/set', + { + create => { + "1" => { + name => "foo", + blobId => $blobid + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_null($res->[0][1]{created}); + $self->assert_str_equals('invalidScript', $res->[0][1]{notCreated}{1}{type}); + + xlog "update bad script"; + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => "keep;" } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => "foo", + blobId => "#A" + } + }, + update => { + "#1" => { + blobId => $blobid + } + }, + destroy => ["#1"] + }, + "R2" + ] + ]); + $self->assert_not_null($res); + + my $id = $res->[1][1]{created}{"1"}{id}; + + $self->assert_null($res->[1][1]{updated}); + $self->assert_str_equals('invalidScript', + $res->[1][1]{notUpdated}{$id}{type}); + $self->assert_not_null($res->[1][1]{destroyed}); + $self->assert_str_equals($id, $res->[1][1]{destroyed}[0]); + $self->assert_null($res->[1][1]{notDestroyed}); } sub test_sieve_query - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "create script"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => "keep;"}] }, - "B" => { data => [{'data:asText' => "discard;"}] }, - "C" => { data => [{'data:asText' => "redirect \"test\@example.com\";"}] }, - "D" => { data => [{'data:asText' => "stop;"}] }, - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => "foo", - blobId => "#A" - }, - "2" => { - name => "bar", - blobId => "#B" - }, - "3" => { - name => "pooh", - blobId => "#C" - }, - "4" => { - name => "abc", - blobId => "#D" - } - }, - onSuccessActivateScript => "#1" - }, "R1"], - ]); - $self->assert_not_null($res); - my $id1 = $res->[1][1]{created}{"1"}{id}; - my $id2 = $res->[1][1]{created}{"2"}{id}; - my $id3 = $res->[1][1]{created}{"3"}{id}; - my $id4 = $res->[1][1]{created}{"4"}{id}; - - xlog $self, "get unfiltered list"; - $res = $jmap->CallMethods([ ['SieveScript/query', { }, "R1"] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - - xlog $self, "sort by name"; - $res = $jmap->CallMethods([ ['SieveScript/query', { - sort => [{ - property => 'name', - }] - }, "R1"] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); - $self->assert_str_equals($id2, $res->[0][1]{ids}[1]); - $self->assert_str_equals($id1, $res->[0][1]{ids}[2]); - $self->assert_str_equals($id3, $res->[0][1]{ids}[3]); - - xlog $self, "filter by isActive"; - $res = $jmap->CallMethods([ ['SieveScript/query', { - filter => { - isActive => JSON::true, - } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - - xlog $self, "filter by not isActive"; - $res = $jmap->CallMethods([ ['SieveScript/query', { - filter => { - isActive => JSON::false, - } - }, "R1"] ]); - $self->assert_num_equals(3, $res->[0][1]{total}); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - my %scriptIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_not_null($scriptIds{$id2}); - $self->assert_not_null($scriptIds{$id3}); - $self->assert_not_null($scriptIds{$id4}); - - xlog $self, "filter by name containing 'oo', sorted descending"; - $res = $jmap->CallMethods([ ['SieveScript/query', { - filter => { - name => 'oo', - }, - sort => [{ - property => 'name', - isAscending => JSON::false, - }] - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); - $self->assert_str_equals($id1, $res->[0][1]{ids}[1]); - - xlog $self, "filter by name not containing 'oo'"; - $res = $jmap->CallMethods([ ['SieveScript/query', { - filter => { - operator => 'NOT', - conditions => [{ - name => 'oo', - }] - }, - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - %scriptIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_not_null($scriptIds{$id2}); - $self->assert_not_null($scriptIds{$id4}); - - xlog $self, "filter by name containing 'oo' and inactive"; - $res = $jmap->CallMethods([ ['SieveScript/query', { - filter => { - operator => 'AND', - conditions => [{ - name => 'oo', - isActive => JSON::false, - }] - }, - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); - - xlog $self, "filter by name not containing 'oo' or active"; - $res = $jmap->CallMethods([ ['SieveScript/query', { - filter => { - operator => 'OR', - conditions => [ - { - operator => 'NOT', - conditions => [{ - name => 'oo', - }] - }, - { - isActive => JSON::true, - }] - }, - sort => [{ - property => 'name', - isAscending => JSON::true, - }] - }, "R1"] ]); - $self->assert_num_equals(3, $res->[0][1]{total}); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - %scriptIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_not_null($scriptIds{$id1}); - $self->assert_not_null($scriptIds{$id2}); - $self->assert_not_null($scriptIds{$id4}); -} - -sub test_sieve_validate - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "validating scripts"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => "keepme;"}] }, - "B" => { data => [{'data:asText' => "keep;"}] } - } - }, "R0"], - ['SieveScript/validate', { - blobId => JSON::null - }, "R1"], - ['SieveScript/validate', { - blobId => "#A", - blobId => JSON::null - }, "R2"], - ['SieveScript/validate', { + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "create script"; + my $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => "keep;" } ] }, + "B" => { data => [ { 'data:asText' => "discard;" } ] }, + "C" => { + data => [ { 'data:asText' => "redirect \"test\@example.com\";" } ] + }, + "D" => { data => [ { 'data:asText' => "stop;" } ] }, + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => "foo", blobId => "#A" - }, "R3"], - ['SieveScript/validate', { + }, + "2" => { + name => "bar", blobId => "#B" - }, "R4"], - ]); - $self->assert_not_null($res); - - $self->assert_str_equals("error", $res->[1][0]); - $self->assert_str_equals("invalidArguments", $res->[1][1]{type}); - - $self->assert_str_equals("error", $res->[2][0]); - $self->assert_str_equals("invalidArguments", $res->[2][1]{type}); - - $self->assert_str_equals("SieveScript/validate", $res->[3][0]); - $self->assert_str_equals("invalidScript", $res->[3][1]{error}{type}); + }, + "3" => { + name => "pooh", + blobId => "#C" + }, + "4" => { + name => "abc", + blobId => "#D" + } + }, + onSuccessActivateScript => "#1" + }, + "R1" + ], + ]); + $self->assert_not_null($res); + my $id1 = $res->[1][1]{created}{"1"}{id}; + my $id2 = $res->[1][1]{created}{"2"}{id}; + my $id3 = $res->[1][1]{created}{"3"}{id}; + my $id4 = $res->[1][1]{created}{"4"}{id}; + + xlog $self, "get unfiltered list"; + $res = $jmap->CallMethods([ [ 'SieveScript/query', {}, "R1" ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "sort by name"; + $res = $jmap->CallMethods([ [ + 'SieveScript/query', + { + sort => [ { + property => 'name', + } ] + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); + $self->assert_str_equals($id2, $res->[0][1]{ids}[1]); + $self->assert_str_equals($id1, $res->[0][1]{ids}[2]); + $self->assert_str_equals($id3, $res->[0][1]{ids}[3]); + + xlog $self, "filter by isActive"; + $res = $jmap->CallMethods([ [ + 'SieveScript/query', + { + filter => { + isActive => JSON::true, + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + + xlog $self, "filter by not isActive"; + $res = $jmap->CallMethods([ [ + 'SieveScript/query', + { + filter => { + isActive => JSON::false, + } + }, + "R1" + ] ]); + $self->assert_num_equals(3, $res->[0][1]{total}); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + my %scriptIds = map { $_ => 1 } @{ $res->[0][1]{ids} }; + $self->assert_not_null($scriptIds{$id2}); + $self->assert_not_null($scriptIds{$id3}); + $self->assert_not_null($scriptIds{$id4}); + + xlog $self, "filter by name containing 'oo', sorted descending"; + $res = $jmap->CallMethods([ [ + 'SieveScript/query', + { + filter => { + name => 'oo', + }, + sort => [ { + property => 'name', + isAscending => JSON::false, + } ] + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); + $self->assert_str_equals($id1, $res->[0][1]{ids}[1]); + + xlog $self, "filter by name not containing 'oo'"; + $res = $jmap->CallMethods([ [ + 'SieveScript/query', + { + filter => { + operator => 'NOT', + conditions => [ { + name => 'oo', + } ] + }, + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + %scriptIds = map { $_ => 1 } @{ $res->[0][1]{ids} }; + $self->assert_not_null($scriptIds{$id2}); + $self->assert_not_null($scriptIds{$id4}); + + xlog $self, "filter by name containing 'oo' and inactive"; + $res = $jmap->CallMethods([ [ + 'SieveScript/query', + { + filter => { + operator => 'AND', + conditions => [ { + name => 'oo', + isActive => JSON::false, + } ] + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); + + xlog $self, "filter by name not containing 'oo' or active"; + $res = $jmap->CallMethods([ [ + 'SieveScript/query', + { + filter => { + operator => 'OR', + conditions => [ + { + operator => 'NOT', + conditions => [ { + name => 'oo', + } ] + }, + { + isActive => JSON::true, + } + ] + }, + sort => [ { + property => 'name', + isAscending => JSON::true, + } ] + }, + "R1" + ] ]); + $self->assert_num_equals(3, $res->[0][1]{total}); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + %scriptIds = map { $_ => 1 } @{ $res->[0][1]{ids} }; + $self->assert_not_null($scriptIds{$id1}); + $self->assert_not_null($scriptIds{$id2}); + $self->assert_not_null($scriptIds{$id4}); +} - $self->assert_str_equals("SieveScript/validate", $res->[4][0]); - $self->assert_null($res->[4][1]{error}); +sub test_sieve_validate + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "validating scripts"; + my $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => "keepme;" } ] }, + "B" => { data => [ { 'data:asText' => "keep;" } ] } + } + }, + "R0" + ], + [ + 'SieveScript/validate', + { + blobId => JSON::null + }, + "R1" + ], + [ + 'SieveScript/validate', + { + blobId => "#A", + blobId => JSON::null + }, + "R2" + ], + [ + 'SieveScript/validate', + { + blobId => "#A" + }, + "R3" + ], + [ + 'SieveScript/validate', + { + blobId => "#B" + }, + "R4" + ], + ]); + $self->assert_not_null($res); + + $self->assert_str_equals("error", $res->[1][0]); + $self->assert_str_equals("invalidArguments", $res->[1][1]{type}); + + $self->assert_str_equals("error", $res->[2][0]); + $self->assert_str_equals("invalidArguments", $res->[2][1]{type}); + + $self->assert_str_equals("SieveScript/validate", $res->[3][0]); + $self->assert_str_equals("invalidScript", $res->[3][1]{error}{type}); + + $self->assert_str_equals("SieveScript/validate", $res->[4][0]); + $self->assert_null($res->[4][1]{error}); } sub test_sieve_test - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; - my $script = <{jmap}; - - xlog "create script"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => $script}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => "foo", - blobId => "#A" - } - } - }, "R1"] - ]); - $self->assert_not_null($res); - - my $scriptid = $res->[1][1]{created}{"1"}{blobId}; - - xlog "create email"; - $res = $jmap->CallMethods([['Mailbox/get', { properties => ["id"] }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; - - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "Whoa!" }} - }; - - $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R2"], - ]); - - my $emailid = $res->[0][1]{created}{"1"}{blobId}; - - xlog "test script"; - $res = $jmap->CallMethods([ - ['SieveScript/test', { - scriptBlobId => "$scriptid", - emailBlobIds => [ "$emailid" ], - envelope => JSON::null, - lastVacationResponse => JSON::null - }, "R3"] - ]); - $self->assert_not_null($res); - $self->assert_not_null($res->[0][1]{completed}); - $self->assert_str_equals('fileinto', - $res->[0][1]{completed}{$emailid}[0][0]); - $self->assert_str_equals('keep', - $res->[0][1]{completed}{$emailid}[1][0]); - $self->assert_null($res->[0][1]{notCompleted}); + $script =~ s/\r?\n/\r\n/gs; + $script =~ s/\\/\\\\/gs; + + my $jmap = $self->{jmap}; + + xlog "create script"; + my $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => $script } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => "foo", + blobId => "#A" + } + } + }, + "R1" + ] + ]); + $self->assert_not_null($res); + + my $scriptid = $res->[1][1]{created}{"1"}{blobId}; + + xlog "create email"; + $res + = $jmap->CallMethods([ [ 'Mailbox/get', { properties => ["id"] }, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; + + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "Whoa!" } } + }; + + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R2" ], ]); + + my $emailid = $res->[0][1]{created}{"1"}{blobId}; + + xlog "test script"; + $res = $jmap->CallMethods([ [ + 'SieveScript/test', + { + scriptBlobId => "$scriptid", + emailBlobIds => ["$emailid"], + envelope => JSON::null, + lastVacationResponse => JSON::null + }, + "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_not_null($res->[0][1]{completed}); + $self->assert_str_equals('fileinto', $res->[0][1]{completed}{$emailid}[0][0]); + $self->assert_str_equals('keep', $res->[0][1]{completed}{$emailid}[1][0]); + $self->assert_null($res->[0][1]{notCompleted}); } sub test_sieve_test_upload - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; - my $email1 = <<'EOF'; + my $email1 = <<'EOF'; From: "Some Example Sender" To: cassandane@example.com Subject: test email @@ -863,9 +1025,9 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email1 =~ s/\r?\n/\r\n/gs; + $email1 =~ s/\r?\n/\r\n/gs; - my $email2 = <<'EOF'; + my $email2 = <<'EOF'; From: "Some Example Sender" To: cassandane@example.com Subject: Hello! @@ -876,9 +1038,9 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email2 =~ s/\r?\n/\r\n/gs; + $email2 =~ s/\r?\n/\r\n/gs; - my $script = <{jmap}; - - my $res = $jmap->Upload($email1, "message/rfc822"); - my $emailid1 = $res->{blobId}; - - $res = $jmap->Upload($email2, "message/rfc822"); - my $emailid2 = $res->{blobId}; - - $res = $jmap->Upload($script, "application/sieve"); - my $scriptid = $res->{blobId}; - - xlog "test script"; - $res = $jmap->CallMethods([ - ['SieveScript/test', { - emailBlobIds => [ $emailid1, 'foobar', $emailid2 ], - scriptBlobId => $scriptid, - envelope => { - mailFrom => { - email => 'foo@example.com', - parameters => JSON::null - }, - rcptTo => [ { - email => 'cassandane@example.com', - parameters => JSON::null - } ] - }, - lastVacationResponse => JSON::null - }, "R1"] - ]); - $self->assert_not_null($res); - - $self->assert_not_null($res->[0][1]{completed}); - $self->assert_str_equals('fileinto', - $res->[0][1]{completed}{$emailid1}[0][0]); - $self->assert_str_equals('keep', - $res->[0][1]{completed}{$emailid1}[1][0]); - $self->assert_str_equals('vacation', - $res->[0][1]{completed}{$emailid2}[0][0]); - $self->assert_str_equals('keep', - $res->[0][1]{completed}{$emailid2}[1][0]); - - $self->assert_not_null($res->[0][1]{notCompleted}); - $self->assert_str_equals('blobNotFound', - $res->[0][1]{notCompleted}{foobar}{type}); + $script =~ s/\r?\n/\r\n/gs; + + my $jmap = $self->{jmap}; + + my $res = $jmap->Upload($email1, "message/rfc822"); + my $emailid1 = $res->{blobId}; + + $res = $jmap->Upload($email2, "message/rfc822"); + my $emailid2 = $res->{blobId}; + + $res = $jmap->Upload($script, "application/sieve"); + my $scriptid = $res->{blobId}; + + xlog "test script"; + $res = $jmap->CallMethods([ [ + 'SieveScript/test', + { + emailBlobIds => [ $emailid1, 'foobar', $emailid2 ], + scriptBlobId => $scriptid, + envelope => { + mailFrom => { + email => 'foo@example.com', + parameters => JSON::null + }, + rcptTo => [ { + email => 'cassandane@example.com', + parameters => JSON::null + } ] + }, + lastVacationResponse => JSON::null + }, + "R1" + ] ]); + $self->assert_not_null($res); + + $self->assert_not_null($res->[0][1]{completed}); + $self->assert_str_equals('fileinto', + $res->[0][1]{completed}{$emailid1}[0][0]); + $self->assert_str_equals('keep', $res->[0][1]{completed}{$emailid1}[1][0]); + $self->assert_str_equals('vacation', + $res->[0][1]{completed}{$emailid2}[0][0]); + $self->assert_str_equals('keep', $res->[0][1]{completed}{$emailid2}[1][0]); + + $self->assert_not_null($res->[0][1]{notCompleted}); + $self->assert_str_equals('blobNotFound', + $res->[0][1]{notCompleted}{foobar}{type}); } sub test_sieve_test_singlecommand - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; - my $email1 = <<'EOF'; + my $email1 = <<'EOF'; From: "Some Example Sender" To: cassandane@example.com Subject: test email @@ -952,9 +1114,9 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email1 =~ s/\r?\n/\r\n/gs; + $email1 =~ s/\r?\n/\r\n/gs; - my $email2 = <<'EOF'; + my $email2 = <<'EOF'; From: "Some Example Sender" To: cassandane@example.com Subject: Hello! @@ -965,9 +1127,9 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email2 =~ s/\r?\n/\r\n/gs; + $email2 =~ s/\r?\n/\r\n/gs; - my $script = <{jmap}; - - xlog "test script"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "1" => { data => [{'data:asText' => $email1}] }, - "3" => { data => [{'data:asText' => $email2}] }, - "2" => { data => [{'data:asText' => $script}] }, - }}, 'R0'], - ['SieveScript/test', { - emailBlobIds => [ '#1', 'foobar', '#3' ], - scriptBlobId => '#2', - envelope => { - mailFrom => { - email => 'foo@example.com', - parameters => JSON::null - }, - rcptTo => [ { - email => 'cassandane@example.com', - parameters => JSON::null - } ] - }, - lastVacationResponse => JSON::null - }, "R1"] - ]); - $self->assert_not_null($res); - - my $emailid1 = $res->[0][1]{created}{1}{blobId}; - my $emailid2 = $res->[0][1]{created}{3}{blobId}; - - $self->assert_not_null($res->[1][1]{completed}); - $self->assert_str_equals('fileinto', - $res->[1][1]{completed}{$emailid1}[0][0]); - $self->assert_str_equals('keep', - $res->[1][1]{completed}{$emailid1}[1][0]); - $self->assert_str_equals('vacation', - $res->[1][1]{completed}{$emailid2}[0][0]); - $self->assert_str_equals('keep', - $res->[1][1]{completed}{$emailid2}[1][0]); - - $self->assert_not_null($res->[1][1]{notCompleted}); - $self->assert_str_equals('blobNotFound', - $res->[1][1]{notCompleted}{foobar}{type}); + $script =~ s/\r?\n/\r\n/gs; + $script =~ s/\\/\\\\/gs; + + my $jmap = $self->{jmap}; + + xlog "test script"; + my $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "1" => { data => [ { 'data:asText' => $email1 } ] }, + "3" => { data => [ { 'data:asText' => $email2 } ] }, + "2" => { data => [ { 'data:asText' => $script } ] }, + } + }, + 'R0' + ], + [ + 'SieveScript/test', + { + emailBlobIds => [ '#1', 'foobar', '#3' ], + scriptBlobId => '#2', + envelope => { + mailFrom => { + email => 'foo@example.com', + parameters => JSON::null + }, + rcptTo => [ { + email => 'cassandane@example.com', + parameters => JSON::null + } ] + }, + lastVacationResponse => JSON::null + }, + "R1" + ] + ]); + $self->assert_not_null($res); + + my $emailid1 = $res->[0][1]{created}{1}{blobId}; + my $emailid2 = $res->[0][1]{created}{3}{blobId}; + + $self->assert_not_null($res->[1][1]{completed}); + $self->assert_str_equals('fileinto', + $res->[1][1]{completed}{$emailid1}[0][0]); + $self->assert_str_equals('keep', $res->[1][1]{completed}{$emailid1}[1][0]); + $self->assert_str_equals('vacation', + $res->[1][1]{completed}{$emailid2}[0][0]); + $self->assert_str_equals('keep', $res->[1][1]{completed}{$emailid2}[1][0]); + + $self->assert_not_null($res->[1][1]{notCompleted}); + $self->assert_str_equals('blobNotFound', + $res->[1][1]{notCompleted}{foobar}{type}); } sub test_sieve_blind_replace_active - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "create initial script"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => "keep;"}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => JSON::null, - blobId => "#A" - } - }, - onSuccessActivateScript => "#1" - }, "R1"], - ['SieveScript/query', { - filter => { - isActive => JSON::false, - } - }, "R2"], - ['SieveScript/set', { - '#destroy' => { - resultOf => 'R2', - name => 'SieveScript/query', - path => '/ids' - } - }, "R3"], - ['SieveScript/get', { - }, "R4"] - ]); - $self->assert_not_null($res); - $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); - $self->assert_null($res->[1][1]{updated}); - $self->assert_null($res->[1][1]{destroyed}); - - my $id1 = $res->[1][1]{created}{"1"}{id}; - - $self->assert_deep_equals([], $res->[2][1]{ids}); - - $self->assert_null($res->[3][1]{created}); - $self->assert_null($res->[3][1]{updated}); - $self->assert_null($res->[3][1]{destroyed}); - - $self->assert_num_equals(1, scalar @{$res->[4][1]{list}}); - $self->assert_str_equals($id1, $res->[4][1]{list}[0]{name}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[0]{isActive}); - - my $blobId = $res->[4][1]{list}[0]{blobId}; - - xlog $self, "download script blob"; - $res = $self->download('cassandane', $blobId); - $self->assert_str_equals('200', $res->{status}); - $self->assert_str_equals('keep;', $res->{content}); - - xlog "replace active script"; - $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "B" => { data => [{'data:asText' => "discard;"}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "2" => { - name => JSON::null, - blobId => "#B" - } - }, - onSuccessActivateScript => "#2" - }, "R1"], - ['SieveScript/query', { - filter => { - isActive => JSON::false, - } - }, "R2"], - ['SieveScript/set', { - '#destroy' => { - resultOf => 'R2', - name => 'SieveScript/query', - path => '/ids' - } - }, "R3"], - ['SieveScript/get', { - }, "R4"] - ]); - $self->assert_not_null($res); - $self->assert_equals(JSON::true, $res->[1][1]{created}{2}{isActive}); - $self->assert_equals(JSON::false, $res->[1][1]{updated}{$id1}{isActive}); - $self->assert_null($res->[1][1]{destroyed}); - - my $id2 = $res->[1][1]{created}{"2"}{id}; - - $self->assert_deep_equals([$id1], $res->[2][1]{ids}); - - $self->assert_null($res->[3][1]{created}); - $self->assert_null($res->[3][1]{updated}); - $self->assert_deep_equals([$id1], $res->[3][1]{destroyed}); - - $self->assert_num_equals(1, scalar @{$res->[4][1]{list}}); - $self->assert_str_equals($id2, $res->[4][1]{list}[0]{name}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[0]{isActive}); - - $blobId = $res->[4][1]{list}[0]{blobId}; - - xlog $self, "download script blob"; - $res = $self->download('cassandane', $blobId); - $self->assert_str_equals('200', $res->{status}); - $self->assert_str_equals('discard;', $res->{content}); + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "create initial script"; + my $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => "keep;" } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => JSON::null, + blobId => "#A" + } + }, + onSuccessActivateScript => "#1" + }, + "R1" + ], + [ + 'SieveScript/query', + { + filter => { + isActive => JSON::false, + } + }, + "R2" + ], + [ + 'SieveScript/set', + { + '#destroy' => { + resultOf => 'R2', + name => 'SieveScript/query', + path => '/ids' + } + }, + "R3" + ], + [ 'SieveScript/get', {}, "R4" ] + ]); + $self->assert_not_null($res); + $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); + $self->assert_null($res->[1][1]{updated}); + $self->assert_null($res->[1][1]{destroyed}); + + my $id1 = $res->[1][1]{created}{"1"}{id}; + + $self->assert_deep_equals([], $res->[2][1]{ids}); + + $self->assert_null($res->[3][1]{created}); + $self->assert_null($res->[3][1]{updated}); + $self->assert_null($res->[3][1]{destroyed}); + + $self->assert_num_equals(1, scalar @{ $res->[4][1]{list} }); + $self->assert_str_equals($id1, $res->[4][1]{list}[0]{name}); + $self->assert_equals(JSON::true, $res->[4][1]{list}[0]{isActive}); + + my $blobId = $res->[4][1]{list}[0]{blobId}; + + xlog $self, "download script blob"; + $res = $self->download('cassandane', $blobId); + $self->assert_str_equals('200', $res->{status}); + $self->assert_str_equals('keep;', $res->{content}); + + xlog "replace active script"; + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "B" => { data => [ { 'data:asText' => "discard;" } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "2" => { + name => JSON::null, + blobId => "#B" + } + }, + onSuccessActivateScript => "#2" + }, + "R1" + ], + [ + 'SieveScript/query', + { + filter => { + isActive => JSON::false, + } + }, + "R2" + ], + [ + 'SieveScript/set', + { + '#destroy' => { + resultOf => 'R2', + name => 'SieveScript/query', + path => '/ids' + } + }, + "R3" + ], + [ 'SieveScript/get', {}, "R4" ] + ]); + $self->assert_not_null($res); + $self->assert_equals(JSON::true, $res->[1][1]{created}{2}{isActive}); + $self->assert_equals(JSON::false, $res->[1][1]{updated}{$id1}{isActive}); + $self->assert_null($res->[1][1]{destroyed}); + + my $id2 = $res->[1][1]{created}{"2"}{id}; + + $self->assert_deep_equals([$id1], $res->[2][1]{ids}); + + $self->assert_null($res->[3][1]{created}); + $self->assert_null($res->[3][1]{updated}); + $self->assert_deep_equals([$id1], $res->[3][1]{destroyed}); + + $self->assert_num_equals(1, scalar @{ $res->[4][1]{list} }); + $self->assert_str_equals($id2, $res->[4][1]{list}[0]{name}); + $self->assert_equals(JSON::true, $res->[4][1]{list}[0]{isActive}); + + $blobId = $res->[4][1]{list}[0]{blobId}; + + xlog $self, "download script blob"; + $res = $self->download('cassandane', $blobId); + $self->assert_str_equals('200', $res->{status}); + $self->assert_str_equals('discard;', $res->{content}); } sub test_deliver_compile - :min_version_3_3 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - my $target = "INBOX.target"; - - xlog $self, "Create the target folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; - $self->{store}->set_fetch_attributes('uid'); - - xlog $self, "Install a sieve script filing all mail into the target folder"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => "require [\"fileinto\"];\r\nfileinto \"$target\";\r\n"}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => JSON::null, - blobId => "#A" - } - }, - onSuccessActivateScript => "#1" - }, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); - $self->assert_null($res->[1][1]{updated}); - $self->assert_null($res->[1][1]{destroyed}); - - my $id = $res->[1][1]{created}{"1"}{id}; - - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); - - xlog $self, "Delete the compiled bytecode"; - my $sieve_dir = $self->{instance}->get_sieve_script_dir('cassandane'); - my $fname = "$sieve_dir/$id.bc"; - unlink $fname or die "Cannot unlink $fname: $!"; - - sleep 1; # so the two deliveries get different syslog timestamps - - xlog $self, "Deliver another message - lmtpd should rebuild the missing bytecode"; - my $msg2 = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg2); - - xlog $self, "Check that both messages made it to the target"; - $self->{store}->set_folder($target); - $self->check_messages({ 1 => $msg1, 2 => $msg2 }, check_guid => 0); + : min_version_3_3 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + my $target = "INBOX.target"; + + xlog $self, "Create the target folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($target) + or die "Cannot create $target: $@"; + $self->{store}->set_fetch_attributes('uid'); + + xlog $self, "Install a sieve script filing all mail into the target folder"; + my $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { + data => [ { + 'data:asText' => + "require [\"fileinto\"];\r\nfileinto \"$target\";\r\n" + } ] + } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => JSON::null, + blobId => "#A" + } + }, + onSuccessActivateScript => "#1" + }, + "R1" + ] + ]); + $self->assert_not_null($res); + $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); + $self->assert_null($res->[1][1]{updated}); + $self->assert_null($res->[1][1]{destroyed}); + + my $id = $res->[1][1]{created}{"1"}{id}; + + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); + + xlog $self, "Delete the compiled bytecode"; + my $sieve_dir = $self->{instance}->get_sieve_script_dir('cassandane'); + my $fname = "$sieve_dir/$id.bc"; + unlink $fname or die "Cannot unlink $fname: $!"; + + sleep 1; # so the two deliveries get different syslog timestamps + + xlog $self, + "Deliver another message - lmtpd should rebuild the missing bytecode"; + my $msg2 = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg2); + + xlog $self, "Check that both messages made it to the target"; + $self->{store}->set_folder($target); + $self->check_messages({ 1 => $msg1, 2 => $msg2 }, check_guid => 0); } sub test_getmetadata - :min_version_3_6 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "Install a no-op sieve script"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => "keep;\r\n"}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => JSON::null, - blobId => "#A" - } - }, - onSuccessActivateScript => "#1" - }, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); - $self->assert_null($res->[1][1]{updated}); - $self->assert_null($res->[1][1]{destroyed}); - - my $id = $res->[1][1]{created}{"1"}{id}; - - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); - - my $imaptalk = $self->{store}->get_client(); - - # LIST doesn't show DAV mailboxes - $res = $imaptalk->list('', '*'); - $self->assert_mailbox_structure($res, '.', { - 'INBOX' => [ '\\HasNoChildren' ], - }); - - # better not see any DAV mailboxes via GETMETADATA either - $res = $imaptalk->getmetadata('*', '/private/comment'); - $self->assert_deep_equals({ 'INBOX' => { '/private/comment' => undef } }, - $res); -} - -sub test_legacy_sieve_replication - :min_version_3_9 :MailboxLegacyDirs :ImmediateDelete -{ - my ($self) = @_; - - # can't do anything without captured syslog - if (!$self->{instance}->{have_syslog_replacement}) { - xlog $self, "can't examine syslog, test is useless"; - return; + : min_version_3_6 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "Install a no-op sieve script"; + my $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => "keep;\r\n" } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => JSON::null, + blobId => "#A" + } + }, + onSuccessActivateScript => "#1" + }, + "R1" + ] + ]); + $self->assert_not_null($res); + $self->assert_equals(JSON::true, $res->[1][1]{created}{1}{isActive}); + $self->assert_null($res->[1][1]{updated}); + $self->assert_null($res->[1][1]{destroyed}); + + my $id = $res->[1][1]{created}{"1"}{id}; + + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); + + my $imaptalk = $self->{store}->get_client(); + + # LIST doesn't show DAV mailboxes + $res = $imaptalk->list('', '*'); + $self->assert_mailbox_structure( + $res, '.', + { + 'INBOX' => ['\\HasNoChildren'], } + ); - # create #sieve mailbox - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->_imap_cmd('CREATE', 0, '', - "user.cassandane.#sieve", [ 'TYPE', 'SIEVE' ]); - - # extract #sieve mailbox containing legacy Sieve code - $self->{instance}->unpackfile(abs_path('data/cyrus/legacy_sieve.tar.gz'), - 'data/user/cassandane'); - - # this will fail due to replica being unable to compile the legacy Sieve - eval { - $self->run_replication(); - }; - - # sync_client should have logged the failure - if ($self->{instance}->{have_syslog_replacement}) { - my @mlines = $self->{instance}->getsyslog(); - $self->assert_matches(qr/IOERROR: user replication failed/, "@mlines"); - $self->assert_matches(qr/MAILBOX received NO response: IMAP_SYNC_BADSIEVE/, "@mlines"); - } + # better not see any DAV mailboxes via GETMETADATA either + $res = $imaptalk->getmetadata('*', '/private/comment'); + $self->assert_deep_equals({ 'INBOX' => { '/private/comment' => undef } }, + $res); +} - # immediately delete the #sieve mailbox to prevent _check_sanity() - # from complaining about INCONSISTENCIES and failing the test - $admintalk = $self->{adminstore}->get_client(); - $admintalk->delete("user.cassandane.#sieve"); +sub test_legacy_sieve_replication + : min_version_3_9 : MailboxLegacyDirs : ImmediateDelete { + my ($self) = @_; + + # can't do anything without captured syslog + if (!$self->{instance}->{have_syslog_replacement}) { + xlog $self, "can't examine syslog, test is useless"; + return; + } + + # create #sieve mailbox + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->_imap_cmd('CREATE', 0, '', + "user.cassandane.#sieve", [ 'TYPE', 'SIEVE' ]); + + # extract #sieve mailbox containing legacy Sieve code + $self->{instance}->unpackfile(abs_path('data/cyrus/legacy_sieve.tar.gz'), + 'data/user/cassandane'); + + # this will fail due to replica being unable to compile the legacy Sieve + eval { $self->run_replication(); }; + + # sync_client should have logged the failure + if ($self->{instance}->{have_syslog_replacement}) { + my @mlines = $self->{instance}->getsyslog(); + $self->assert_matches(qr/IOERROR: user replication failed/, "@mlines"); + $self->assert_matches(qr/MAILBOX received NO response: IMAP_SYNC_BADSIEVE/, + "@mlines"); + } + + # immediately delete the #sieve mailbox to prevent _check_sanity() + # from complaining about INCONSISTENCIES and failing the test + $admintalk = $self->{adminstore}->get_client(); + $admintalk->delete("user.cassandane.#sieve"); } 1; diff --git a/cassandane/Cassandane/Cyrus/JMAPTestSuite.pm b/cassandane/Cassandane/Cyrus/JMAPTestSuite.pm index 609471c86c..1aed3ea302 100644 --- a/cassandane/Cassandane/Cyrus/JMAPTestSuite.pm +++ b/cassandane/Cassandane/Cyrus/JMAPTestSuite.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::JMAPTestSuite; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use JSON::XS qw(encode_json); @@ -63,258 +63,250 @@ my %suppressed; # Same as the :min_version annotations on native Cassandane tests, but # dot-separated. See skip_before() below for implementation details. my %notbefore = ( - 't:Email:get:header-header-field-name' => '3.5', - 't:Email:import:good-imports' => '3.8', - 't:Email:import:one-fails-another-succeeds' => '3.8', + 't:Email:get:header-header-field-name' => '3.5', + 't:Email:import:good-imports' => '3.8', + 't:Email:import:one-fails-another-succeeds' => '3.8', ); # Tests which JMAP-TestSuite will skip anyway when it detects it's # talking to Cyrus. my %notcyrus = map { $_ => 1 } ( - 't:Mailbox:get:no-existing-entities', - 't:Mailbox:query:no-existing-entities', + 't:Mailbox:get:no-existing-entities', + 't:Mailbox:query:no-existing-entities', ); -sub cyrus_version_supports_jmap -{ - my ($maj, $min) = Cassandane::Instance->get_version(); +sub cyrus_version_supports_jmap { + my ($maj, $min) = Cassandane::Instance->get_version(); - return 0 if ($maj < 3); # not supported before 3.x - return 0 if ($maj == 3 && $min == 0); # not supported in 3.0.x + return 0 if ($maj < 3); # not supported before 3.x + return 0 if ($maj == 3 && $min == 0); # not supported in 3.0.x - # not supported if configured out - my $buildinfo = Cassandane::BuildInfo->new(); - return 0 if not $buildinfo->get('component', 'jmap'); + # not supported if configured out + my $buildinfo = Cassandane::BuildInfo->new(); + return 0 if not $buildinfo->get('component', 'jmap'); - return 1; # supported in everything newer + return 1; # supported in everything newer } -sub init -{ - my $cassini = Cassandane::Cassini->instance(); - $basedir = $cassini->val('jmaptestsuite', 'basedir'); - return unless defined $basedir; - $basedir = abs_path($basedir); +sub init { + my $cassini = Cassandane::Cassini->instance(); + $basedir = $cassini->val('jmaptestsuite', 'basedir'); + return unless defined $basedir; + $basedir = abs_path($basedir); - my $supp = $cassini->val('jmaptestsuite', 'suppress', ''); - map { $suppressed{$_} = 1; } split(/\s+/, $supp); + my $supp = $cassini->val('jmaptestsuite', 'suppress', ''); + map { $suppressed{$_} = 1; } split(/\s+/, $supp); - $testdir = "$basedir/t"; - $authortestdir = "$basedir/xt"; + $testdir = "$basedir/t"; + $authortestdir = "$basedir/xt"; } init; -sub new -{ - my $class = shift; - - my $config = Cassandane::Config->default()->clone(); - $config->set(servername => "127.0.0.1"); # urlauth needs matching servername - $config->set(virtdomains => 'userid'); - $config->set(caldav_realm => 'Cassandane'); - $config->set(httpallowcompress => 'no'); - $config->set(conversations => 'yes'); - - $config->set(search_engine => 'xapian'); - $config->set(search_index_headers => 'no'); - $config->set(search_batchsize => 8192); - $config->set(defaultpartition => 'default'); - $config->set(defaultsearchtier => 't1'); - - $config->set('sync_log' => 'on'); - $config->set('sync_log_channels' => 'squatter'); - - if (cyrus_version_supports_jmap()) { - $config->set(httpmodules => 'jmap'); - - return $class->SUPER::new({ - config => $config, - adminstore => 1, - squatter => 1, - services => ['imap', 'http'], - }, @_); - } - else { - return $class->SUPER::new({}, @_); - } +sub new { + my $class = shift; + + my $config = Cassandane::Config->default()->clone(); + $config->set(servername => "127.0.0.1"); # urlauth needs matching servername + $config->set(virtdomains => 'userid'); + $config->set(caldav_realm => 'Cassandane'); + $config->set(httpallowcompress => 'no'); + $config->set(conversations => 'yes'); + + $config->set(search_engine => 'xapian'); + $config->set(search_index_headers => 'no'); + $config->set(search_batchsize => 8192); + $config->set(defaultpartition => 'default'); + $config->set(defaultsearchtier => 't1'); + + $config->set('sync_log' => 'on'); + $config->set('sync_log_channels' => 'squatter'); + + if (cyrus_version_supports_jmap()) { + $config->set(httpmodules => 'jmap'); + + return $class->SUPER::new( + { + config => $config, + adminstore => 1, + squatter => 1, + services => [ 'imap', 'http' ], + }, + @_ + ); + } else { + return $class->SUPER::new({}, @_); + } } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # n.b. similar to _skip_version() in Cassandane::Unit::TestCase -sub skip_before -{ - my ($lim) = @_; +sub skip_before { + my ($lim) = @_; - my ($lim_major, $lim_minor, $lim_revision, $lim_commits) - = map { 0 + $_ } split /\./, $lim; - return if not defined $lim_major; + my ($lim_major, $lim_minor, $lim_revision, $lim_commits) + = map { 0 + $_ } split /\./, $lim; + return if not defined $lim_major; - my ($major, $minor, $revision, $commits) = - Cassandane::Instance->get_version(); + my ($major, $minor, $revision, $commits) + = Cassandane::Instance->get_version(); - return 1 if $major < $lim_major; # too old, skip! - return if $major > $lim_major; # definitely new enough + return 1 if $major < $lim_major; # too old, skip! + return if $major > $lim_major; # definitely new enough - return if not defined $lim_minor; # don't check deeper if caller doesn't care - return 1 if $minor < $lim_minor; - return if $minor > $lim_minor; + return if not defined $lim_minor; # don't check deeper if caller doesn't care + return 1 if $minor < $lim_minor; + return if $minor > $lim_minor; - return if not defined $lim_revision; - return 1 if $revision < $lim_revision; + return if not defined $lim_revision; + return 1 if $revision < $lim_revision; - return if not defined $lim_commits; - return 1 if $commits < $lim_commits; + return if not defined $lim_commits; + return 1 if $commits < $lim_commits; - return; + return; } -sub find_tests -{ - my ($dir) = @_; - - my @tests; - - find( - sub { - my $file = $File::Find::name; - - return unless $file =~ s/\.t$//; - return unless -f "$file.t"; - $file =~ s/^$basedir\/?//; - $file =~ s{/}{:}g; - return if $notcyrus{$file}; - return if $suppressed{$file}; - if (exists $notbefore{$file} and skip_before($notbefore{$file})) { - return; - } - push @tests, "test_$file"; - }, - $dir, - ); +sub find_tests { + my ($dir) = @_; + + my @tests; - return @tests; + find( + sub { + my $file = $File::Find::name; + + return unless $file =~ s/\.t$//; + return unless -f "$file.t"; + $file =~ s/^$basedir\/?//; + $file =~ s{/}{:}g; + return if $notcyrus{$file}; + return if $suppressed{$file}; + if (exists $notbefore{$file} and skip_before($notbefore{$file})) { + return; + } + push @tests, "test_$file"; + }, + $dir, + ); + + return @tests; } -sub list_tests -{ - my @tests; +sub list_tests { + my @tests; - if (!cyrus_version_supports_jmap()) - { - return ( 'test_jmaptest_disabled' ); - } + if (!cyrus_version_supports_jmap()) { + return ('test_jmaptest_disabled'); + } - if (!defined $basedir) - { - return ( 'test_warning_jmaptestsuite_is_not_installed' ); - } + if (!defined $basedir) { + return ('test_warning_jmaptestsuite_is_not_installed'); + } - @tests = find_tests($testdir); + @tests = find_tests($testdir); - if ($ENV{AUTHOR_TESTING}) { - push @tests, find_tests($authortestdir); - } + if ($ENV{AUTHOR_TESTING}) { + push @tests, find_tests($authortestdir); + } - return @tests; + return @tests; } -sub run_test -{ - my ($self) = @_; +sub run_test { + my ($self) = @_; - if (!defined $basedir) - { - xlog $self, "JMAP Tests are not enabled. To enabled them, please"; - xlog $self, "install JMAP-TestSuite from https://github.com/fastmail/JMAP-TestSuite"; - xlog $self, "and edit [jmaptestsuite]basedir in cassandane.ini"; - xlog $self, "This is not a failure"; - return; - } + if (!defined $basedir) { + xlog $self, "JMAP Tests are not enabled. To enabled them, please"; + xlog $self, + "install JMAP-TestSuite from https://github.com/fastmail/JMAP-TestSuite"; + xlog $self, "and edit [jmaptestsuite]basedir in cassandane.ini"; + xlog $self, "This is not a failure"; + return; + } - if (!cyrus_version_supports_jmap()) + if (!cyrus_version_supports_jmap()) { + xlog $self, "The version of Cyrus being tested does not support JMAP"; + xlog $self, "JMAP-TestSuite tests skipped"; + return; + } + + my $name = $self->name(); + $name =~ s/^test_//; + + my $configfile = "$self->{instance}->{basedir}/testerconfig.json"; + my $errfile = $self->{instance}->{basedir} . "/$name.errors"; + my $outfile = $self->{instance}->{basedir} . "/$name.stdout"; + + my $service = $self->{instance}->get_service("http"); + my $imap = $self->{instance}->get_service("imap"); + + local $ENV{JMAP_SERVER_ADAPTER_FILE} = $configfile; + + open(FH, ">$configfile"); + + print FH encode_json({ + adapter => 'Cyrus', + base_uri => 'http://' . $service->host() . ':' . $service->port() . '/', + cyrus_host => $imap->host(), + cyrus_port => $imap->port(), + cyrus_admin_user => 'admin', + cyrus_admin_pass => 'testpw', + no_sasl => 1, + credentials => [ + { + username => 'cassandane', + password => 'pass', + }, + ], + cyrus_hierarchy_separator => '.', + cyrus_prefix => $self->{instance}->{cyrus_prefix} + }); + close(FH); + + my $status = 0; + + $name =~ s{:}{/}g; + + local $ENV{JMTS_TEST_OUTPUT_TO_STDERR} = 1 if get_verbose; + local $ENV{JMTS_TELEMETRY} = 1 if get_verbose >= 3; + local $ENV{JMTS_USE_WEBSOCKETS} = 0; + + $self->{instance}->run_command( { - xlog $self, "The version of Cyrus being tested does not support JMAP"; - xlog $self, "JMAP-TestSuite tests skipped"; - return; - } - - my $name = $self->name(); - $name =~ s/^test_//; - - my $configfile = "$self->{instance}->{basedir}/testerconfig.json"; - my $errfile = $self->{instance}->{basedir} . "/$name.errors"; - my $outfile = $self->{instance}->{basedir} . "/$name.stdout"; - - my $service = $self->{instance}->get_service("http"); - my $imap = $self->{instance}->get_service("imap"); - - local $ENV{JMAP_SERVER_ADAPTER_FILE} = $configfile; - - open(FH, ">$configfile"); - - print FH encode_json({ - adapter => 'Cyrus', - base_uri => 'http://' . $service->host() . ':' . $service->port() . '/', - cyrus_host => $imap->host(), - cyrus_port => $imap->port(), - cyrus_admin_user => 'admin', - cyrus_admin_pass => 'testpw', - no_sasl => 1, - credentials => [ - { - username => 'cassandane', - password => 'pass', - }, - ], - cyrus_hierarchy_separator => '.', - cyrus_prefix => $self->{instance}->{cyrus_prefix} - }); - close(FH); - - my $status = 0; - - $name =~ s{:}{/}g; - - local $ENV{JMTS_TEST_OUTPUT_TO_STDERR} = 1 if get_verbose; - local $ENV{JMTS_TELEMETRY} = 1 if get_verbose >= 3; - local $ENV{JMTS_USE_WEBSOCKETS} = 0; - - $self->{instance}->run_command({ - redirects => { stderr => $errfile, stdout => $outfile }, - workingdir => $basedir, - handlers => { - exited_normally => sub { $status = 1; }, - exited_abnormally => sub { $status = 0; }, - }, - }, - "perl", '-I' => "$basedir/lib", - "$basedir/$name.t", - ); - - if ((!$status || get_verbose)) { - if (-f $errfile) { - open FH, '<', $errfile - or die "Cannot open $errfile for reading: $!"; - while (readline FH) { - chomp; - xlog $_; - } - close FH; - } + redirects => { stderr => $errfile, stdout => $outfile }, + workingdir => $basedir, + handlers => { + exited_normally => sub { $status = 1; }, + exited_abnormally => sub { $status = 0; }, + }, + }, + "perl", + '-I' => "$basedir/lib", + "$basedir/$name.t", + ); + + if ((!$status || get_verbose)) { + if (-f $errfile) { + open FH, '<', $errfile + or die "Cannot open $errfile for reading: $!"; + while (readline FH) { + chomp; + xlog $_; + } + close FH; } + } - $self->assert($status); + $self->assert($status); } 1; diff --git a/cassandane/Cassandane/Cyrus/JMAPTestSuiteWS.pm b/cassandane/Cassandane/Cyrus/JMAPTestSuiteWS.pm index 72f4a621df..cf04b2764b 100644 --- a/cassandane/Cassandane/Cyrus/JMAPTestSuiteWS.pm +++ b/cassandane/Cassandane/Cyrus/JMAPTestSuiteWS.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::JMAPTestSuiteWS; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use JSON::XS qw(encode_json); @@ -64,274 +64,264 @@ my %suppressed; # Same as the :min_version annotations on native Cassandane tests, but # dot-separated. See skip_before() below for implementation details. my %notbefore = ( - 't:Email:get:header-header-field-name' => '3.5', - 't:Email:import:good-imports' => '3.8', - 't:Email:import:one-fails-another-succeeds' => '3.8', + 't:Email:get:header-header-field-name' => '3.5', + 't:Email:import:good-imports' => '3.8', + 't:Email:import:one-fails-another-succeeds' => '3.8', ); # Tests which JMAPTestSuite will skip anyway when it detects it's # talking to Cyrus. my %notcyrus = map { $_ => 1 } ( - 't:Mailbox:get:no-existing-entities', - 't:Mailbox:query:no-existing-entities', + 't:Mailbox:get:no-existing-entities', + 't:Mailbox:query:no-existing-entities', ); -sub cyrus_version_supports_jmap -{ - my ($maj, $min) = Cassandane::Instance->get_version(); +sub cyrus_version_supports_jmap { + my ($maj, $min) = Cassandane::Instance->get_version(); - return 0 if ($maj < 3); # not supported before 3.x - return 0 if ($maj == 3 && $min == 0); # not supported in 3.0.x + return 0 if ($maj < 3); # not supported before 3.x + return 0 if ($maj == 3 && $min == 0); # not supported in 3.0.x - # not supported if configured out - my $buildinfo = Cassandane::BuildInfo->new(); - return 0 if not $buildinfo->get('component', 'jmap'); + # not supported if configured out + my $buildinfo = Cassandane::BuildInfo->new(); + return 0 if not $buildinfo->get('component', 'jmap'); - return 1; # supported in everything newer + return 1; # supported in everything newer } -sub have_jmap_tester_websocket -{ - # not supported if feature wasn't compiled in - my $buildinfo = Cassandane::BuildInfo->new(); - return 0 if not $buildinfo->get('dependency', 'wslay'); +sub have_jmap_tester_websocket { + # not supported if feature wasn't compiled in + my $buildinfo = Cassandane::BuildInfo->new(); + return 0 if not $buildinfo->get('dependency', 'wslay'); - return defined check_install(module => 'JMAP::Tester::WebSocket'); + return defined check_install(module => 'JMAP::Tester::WebSocket'); } -sub init -{ - my $cassini = Cassandane::Cassini->instance(); - $basedir = $cassini->val('jmaptestsuite', 'basedir'); - return unless defined $basedir; - $basedir = abs_path($basedir); +sub init { + my $cassini = Cassandane::Cassini->instance(); + $basedir = $cassini->val('jmaptestsuite', 'basedir'); + return unless defined $basedir; + $basedir = abs_path($basedir); - my $supp = $cassini->val('jmaptestsuite', 'suppress', ''); - map { $suppressed{$_} = 1; } split(/\s+/, $supp); + my $supp = $cassini->val('jmaptestsuite', 'suppress', ''); + map { $suppressed{$_} = 1; } split(/\s+/, $supp); - $testdir = "$basedir/t"; - $authortestdir = "$basedir/xt"; + $testdir = "$basedir/t"; + $authortestdir = "$basedir/xt"; } init; -sub new -{ - my $class = shift; - - my $config = Cassandane::Config->default()->clone(); - $config->set(servername => "127.0.0.1"); # urlauth needs matching servername - $config->set(virtdomains => 'userid'); - $config->set(caldav_realm => 'Cassandane'); - $config->set(httpallowcompress => 'no'); - $config->set(conversations => 'yes'); - - $config->set(search_engine => 'xapian'); - $config->set(search_index_headers => 'no'); - $config->set(search_batchsize => 8192); - $config->set(defaultpartition => 'default'); - $config->set(defaultsearchtier => 't1'); - - $config->set('sync_log' => 'on'); - $config->set('sync_log_channels' => 'squatter'); - - if (cyrus_version_supports_jmap()) { - $config->set(httpmodules => 'jmap'); - - return $class->SUPER::new({ - config => $config, - adminstore => 1, - squatter => 1, - services => ['imap', 'http'], - }, @_); - } - else { - return $class->SUPER::new({}, @_); - } +sub new { + my $class = shift; + + my $config = Cassandane::Config->default()->clone(); + $config->set(servername => "127.0.0.1"); # urlauth needs matching servername + $config->set(virtdomains => 'userid'); + $config->set(caldav_realm => 'Cassandane'); + $config->set(httpallowcompress => 'no'); + $config->set(conversations => 'yes'); + + $config->set(search_engine => 'xapian'); + $config->set(search_index_headers => 'no'); + $config->set(search_batchsize => 8192); + $config->set(defaultpartition => 'default'); + $config->set(defaultsearchtier => 't1'); + + $config->set('sync_log' => 'on'); + $config->set('sync_log_channels' => 'squatter'); + + if (cyrus_version_supports_jmap()) { + $config->set(httpmodules => 'jmap'); + + return $class->SUPER::new( + { + config => $config, + adminstore => 1, + squatter => 1, + services => [ 'imap', 'http' ], + }, + @_ + ); + } else { + return $class->SUPER::new({}, @_); + } } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # n.b. similar to _skip_version() in Cassandane::Unit::TestCase -sub skip_before -{ - my ($lim) = @_; +sub skip_before { + my ($lim) = @_; - my ($lim_major, $lim_minor, $lim_revision, $lim_commits) - = map { 0 + $_ } split /\./, $lim; - return if not defined $lim_major; + my ($lim_major, $lim_minor, $lim_revision, $lim_commits) + = map { 0 + $_ } split /\./, $lim; + return if not defined $lim_major; - my ($major, $minor, $revision, $commits) = - Cassandane::Instance->get_version(); + my ($major, $minor, $revision, $commits) + = Cassandane::Instance->get_version(); - return 1 if $major < $lim_major; # too old, skip! - return if $major > $lim_major; # definitely new enough + return 1 if $major < $lim_major; # too old, skip! + return if $major > $lim_major; # definitely new enough - return if not defined $lim_minor; # don't check deeper if caller doesn't care - return 1 if $minor < $lim_minor; - return if $minor > $lim_minor; + return if not defined $lim_minor; # don't check deeper if caller doesn't care + return 1 if $minor < $lim_minor; + return if $minor > $lim_minor; - return if not defined $lim_revision; - return 1 if $revision < $lim_revision; + return if not defined $lim_revision; + return 1 if $revision < $lim_revision; - return if not defined $lim_commits; - return 1 if $commits < $lim_commits; + return if not defined $lim_commits; + return 1 if $commits < $lim_commits; - return; + return; } -sub find_tests -{ - my ($dir) = @_; - - my @tests; - - find( - sub { - my $file = $File::Find::name; - - return unless $file =~ s/\.t$//; - return unless -f "$file.t"; - $file =~ s/^$basedir\/?//; - $file =~ s{/}{:}g; - return if $notcyrus{$file}; - return if $suppressed{$file}; - if (exists $notbefore{$file} and skip_before($notbefore{$file})) { - return; - } - push @tests, "test_$file"; - }, - $dir, - ); +sub find_tests { + my ($dir) = @_; + + my @tests; + + find( + sub { + my $file = $File::Find::name; - return @tests; + return unless $file =~ s/\.t$//; + return unless -f "$file.t"; + $file =~ s/^$basedir\/?//; + $file =~ s{/}{:}g; + return if $notcyrus{$file}; + return if $suppressed{$file}; + if (exists $notbefore{$file} and skip_before($notbefore{$file})) { + return; + } + push @tests, "test_$file"; + }, + $dir, + ); + + return @tests; } -sub list_tests -{ - my @tests; +sub list_tests { + my @tests; - if (!cyrus_version_supports_jmap() || !have_jmap_tester_websocket()) - { - return ( 'test_jmaptest_websocket_disabled' ); - } + if (!cyrus_version_supports_jmap() || !have_jmap_tester_websocket()) { + return ('test_jmaptest_websocket_disabled'); + } - if (!defined $basedir) - { - return ( 'test_warning_jmaptestsuite_is_not_installed' ); - } + if (!defined $basedir) { + return ('test_warning_jmaptestsuite_is_not_installed'); + } - @tests = find_tests($testdir); + @tests = find_tests($testdir); - if ($ENV{AUTHOR_TESTING}) { - push @tests, find_tests($authortestdir); - } + if ($ENV{AUTHOR_TESTING}) { + push @tests, find_tests($authortestdir); + } - return @tests; + return @tests; } -sub run_test -{ - my ($self) = @_; +sub run_test { + my ($self) = @_; - if (!defined $basedir) - { - xlog $self, "JMAP Tests are not enabled. To enabled them, please"; - xlog $self, "install JMAP-TestSuite from https://github.com/fastmail/JMAP-TestSuite"; - xlog $self, "and edit [jmaptestsuite]basedir in cassandane.ini"; - xlog $self, "This is not a failure"; - return; - } + if (!defined $basedir) { + xlog $self, "JMAP Tests are not enabled. To enabled them, please"; + xlog $self, + "install JMAP-TestSuite from https://github.com/fastmail/JMAP-TestSuite"; + xlog $self, "and edit [jmaptestsuite]basedir in cassandane.ini"; + xlog $self, "This is not a failure"; + return; + } - if (!cyrus_version_supports_jmap()) - { - xlog $self, "The version of Cyrus being tested does not support JMAP"; - xlog $self, "JMAP-TestSuite WebSockets tests skipped"; - return; - } + if (!cyrus_version_supports_jmap()) { + xlog $self, "The version of Cyrus being tested does not support JMAP"; + xlog $self, "JMAP-TestSuite WebSockets tests skipped"; + return; + } - if (!have_jmap_tester_websocket()) + if (!have_jmap_tester_websocket()) { + xlog $self, "The JMAP::Tester::WebSockets module is not available"; + xlog $self, "JMAP-TestSuite WebSockets tests skipped"; + return; + } + + my $name = $self->name(); + $name =~ s/^test_//; + + my $configfile = "$self->{instance}->{basedir}/testerconfig.json"; + my $errfile = $self->{instance}->{basedir} . "/$name.errors"; + my $outfile = $self->{instance}->{basedir} . "/$name.stdout"; + + my $service = $self->{instance}->get_service("http"); + my $imap = $self->{instance}->get_service("imap"); + + local $ENV{JMAP_SERVER_ADAPTER_FILE} = $configfile; + + open(FH, ">$configfile"); + + print FH encode_json({ + adapter => 'Cyrus', + base_uri => 'http://' . $service->host() . ':' . $service->port() . '/', + cyrus_host => $imap->host(), + cyrus_port => $imap->port(), + cyrus_admin_user => 'admin', + cyrus_admin_pass => 'testpw', + no_sasl => 1, + credentials => [ + { + username => 'cassandane', + password => 'pass', + }, + ], + cyrus_hierarchy_separator => '.', + cyrus_prefix => $self->{instance}->{cyrus_prefix} + }); + close(FH); + + my $status = 0; + + $name =~ s{:}{/}g; + + local $ENV{JMTS_TEST_OUTPUT_TO_STDERR} = 1 if get_verbose; + local $ENV{JMTS_TELEMETRY} = 1 if get_verbose >= 3; + local $ENV{JMTS_USE_WEBSOCKETS} = 1; + + $self->{instance}->run_command( { - xlog $self, "The JMAP::Tester::WebSockets module is not available"; - xlog $self, "JMAP-TestSuite WebSockets tests skipped"; - return; - } - - my $name = $self->name(); - $name =~ s/^test_//; - - my $configfile = "$self->{instance}->{basedir}/testerconfig.json"; - my $errfile = $self->{instance}->{basedir} . "/$name.errors"; - my $outfile = $self->{instance}->{basedir} . "/$name.stdout"; - - my $service = $self->{instance}->get_service("http"); - my $imap = $self->{instance}->get_service("imap"); - - local $ENV{JMAP_SERVER_ADAPTER_FILE} = $configfile; - - open(FH, ">$configfile"); - - print FH encode_json({ - adapter => 'Cyrus', - base_uri => 'http://' . $service->host() . ':' . $service->port() . '/', - cyrus_host => $imap->host(), - cyrus_port => $imap->port(), - cyrus_admin_user => 'admin', - cyrus_admin_pass => 'testpw', - no_sasl => 1, - credentials => [ - { - username => 'cassandane', - password => 'pass', - }, - ], - cyrus_hierarchy_separator => '.', - cyrus_prefix => $self->{instance}->{cyrus_prefix} - }); - close(FH); - - my $status = 0; - - $name =~ s{:}{/}g; - - local $ENV{JMTS_TEST_OUTPUT_TO_STDERR} = 1 if get_verbose; - local $ENV{JMTS_TELEMETRY} = 1 if get_verbose >= 3; - local $ENV{JMTS_USE_WEBSOCKETS} = 1; - - $self->{instance}->run_command({ - redirects => { stderr => $errfile, stdout => $outfile }, - workingdir => $basedir, - handlers => { - exited_normally => sub { $status = 1; }, - exited_abnormally => sub { $status = 0; }, - }, - }, - "perl", '-I' => "$basedir/lib", - "$basedir/$name.t", - ); - - if ((!$status || get_verbose)) { - if (-f $errfile) { - open FH, '<', $errfile - or die "Cannot open $errfile for reading: $!"; - while (readline FH) { - chomp; - xlog $_; - } - close FH; - } + redirects => { stderr => $errfile, stdout => $outfile }, + workingdir => $basedir, + handlers => { + exited_normally => sub { $status = 1; }, + exited_abnormally => sub { $status = 0; }, + }, + }, + "perl", + '-I' => "$basedir/lib", + "$basedir/$name.t", + ); + + if ((!$status || get_verbose)) { + if (-f $errfile) { + open FH, '<', $errfile + or die "Cannot open $errfile for reading: $!"; + while (readline FH) { + chomp; + xlog $_; + } + close FH; } + } - $self->assert($status); + $self->assert($status); } 1; diff --git a/cassandane/Cassandane/Cyrus/JMAPVacationResponse.pm b/cassandane/Cassandane/Cyrus/JMAPVacationResponse.pm index f734ec5f0e..fa1e3b428b 100644 --- a/cassandane/Cassandane/Cyrus/JMAPVacationResponse.pm +++ b/cassandane/Cassandane/Cyrus/JMAPVacationResponse.pm @@ -55,158 +55,170 @@ use Cassandane::Util::Log; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj == 3 && $min == 0) { - # need to explicitly add 'body' to sieve_extensions for 3.0 - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags mailbox mboxmetadata servermetadata variables " . - "body"); - } - elsif ($maj < 3) { - # also for 2.5 (the earliest Cyrus that Cassandane can test) - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags body"); - } - - $config->set(caldav_realm => 'Cassandane', - conversations => 'yes', - httpmodules => 'jmap', - httpallowcompress => 'no'); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - deliver => 1, - adminstore => 1, - services => [ 'imap', 'sieve', 'http' ] - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj == 3 && $min == 0) { + # need to explicitly add 'body' to sieve_extensions for 3.0 + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags mailbox mboxmetadata servermetadata variables " + . "body"); + } elsif ($maj < 3) { + # also for 2.5 (the earliest Cyrus that Cassandane can test) + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags body"); + } + + $config->set( + caldav_realm => 'Cassandane', + conversations => 'yes', + httpmodules => 'jmap', + httpallowcompress => 'no' + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + deliver => 1, + adminstore => 1, + services => [ 'imap', 'sieve', 'http' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:vacationresponse' - ]); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:vacationresponse' ]); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_vacation_get_none - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "get vacation"; - my $res = $jmap->CallMethods([ - ['VacationResponse/get', { - properties => ['isEnabled'] - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('VacationResponse/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('singleton', $res->[0][1]{list}[0]{id}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isEnabled}); - $self->assert(not exists $res->[0][1]{list}[0]{subject}); + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "get vacation"; + my $res = $jmap->CallMethods([ [ + 'VacationResponse/get', + { + properties => ['isEnabled'] + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('singleton', $res->[0][1]{list}[0]{id}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isEnabled}); + $self->assert(not exists $res->[0][1]{list}[0]{subject}); } sub test_vacation_set - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "attempt to create a new vacation response"; - my $res = $jmap->CallMethods([ - ['VacationResponse/set', { - create => { - "1" => { - textBody => "Gone fishing" - } - } - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('VacationResponse/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_str_equals('singleton', $res->[0][1]{notCreated}{1}{type}); - - xlog "enable the vacation response"; - $res = $jmap->CallMethods([ - ['VacationResponse/set', { - update => { - "singleton" => { - isEnabled=> JSON::true, - textBody => "Gone fishing" - } - } - }, "R1"], - ['VacationResponse/get', { - }, "R2"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('VacationResponse/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{singleton}); - $self->assert_str_equals('VacationResponse/get', $res->[1][0]); - $self->assert_str_equals('R2', $res->[1][2]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_str_equals('singleton', $res->[1][1]{list}[0]{id}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isEnabled}); - $self->assert_str_equals('Gone fishing', $res->[1][1]{list}[0]{textBody}); - - xlog "disable the vacation response"; - $res = $jmap->CallMethods([ - ['VacationResponse/set', { - update => { - "singleton" => { - isEnabled=> JSON::false - } - } - }, "R1"], - ['VacationResponse/get', { - }, "R2"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('VacationResponse/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{singleton}); - $self->assert_str_equals('VacationResponse/get', $res->[1][0]); - $self->assert_str_equals('R2', $res->[1][2]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_str_equals('singleton', $res->[1][1]{list}[0]{id}); - $self->assert_equals(JSON::false, $res->[1][1]{list}[0]{isEnabled}); - $self->assert_str_equals('Gone fishing', $res->[1][1]{list}[0]{textBody}); - - xlog "attempt to destroy the vacation response"; - $res = $jmap->CallMethods([ - ['VacationResponse/set', { - destroy => ["singleton"] - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('VacationResponse/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_str_equals('singleton', - $res->[0][1]{notDestroyed}{singleton}{type}); + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "attempt to create a new vacation response"; + my $res = $jmap->CallMethods([ [ + 'VacationResponse/set', + { + create => { + "1" => { + textBody => "Gone fishing" + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_equals('singleton', $res->[0][1]{notCreated}{1}{type}); + + xlog "enable the vacation response"; + $res = $jmap->CallMethods([ + [ + 'VacationResponse/set', + { + update => { + "singleton" => { + isEnabled => JSON::true, + textBody => "Gone fishing" + } + } + }, + "R1" + ], + [ 'VacationResponse/get', {}, "R2" ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{singleton}); + $self->assert_str_equals('VacationResponse/get', $res->[1][0]); + $self->assert_str_equals('R2', $res->[1][2]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_str_equals('singleton', $res->[1][1]{list}[0]{id}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isEnabled}); + $self->assert_str_equals('Gone fishing', $res->[1][1]{list}[0]{textBody}); + + xlog "disable the vacation response"; + $res = $jmap->CallMethods([ + [ + 'VacationResponse/set', + { + update => { + "singleton" => { + isEnabled => JSON::false + } + } + }, + "R1" + ], + [ 'VacationResponse/get', {}, "R2" ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{singleton}); + $self->assert_str_equals('VacationResponse/get', $res->[1][0]); + $self->assert_str_equals('R2', $res->[1][2]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_str_equals('singleton', $res->[1][1]{list}[0]{id}); + $self->assert_equals(JSON::false, $res->[1][1]{list}[0]{isEnabled}); + $self->assert_str_equals('Gone fishing', $res->[1][1]{list}[0]{textBody}); + + xlog "attempt to destroy the vacation response"; + $res = $jmap->CallMethods([ [ + 'VacationResponse/set', + { + destroy => ["singleton"] + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('VacationResponse/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_equals('singleton', + $res->[0][1]{notDestroyed}{singleton}{type}); } 1; diff --git a/cassandane/Cassandane/Cyrus/LDAP.pm b/cassandane/Cassandane/Cyrus/LDAP.pm index c656fe88a6..577846ef2d 100644 --- a/cassandane/Cassandane/Cyrus/LDAP.pm +++ b/cassandane/Cassandane/Cyrus/LDAP.pm @@ -51,298 +51,276 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set( - ldap_base => "o=cyrus", - ldap_group_base => "ou=groups,o=cyrus", - ldap_domain_base_dn => "ou=domains,o=cyrus", - ldap_user_attribute => "uid", - ldap_member_attribute => "memberof", - ldap_sasl => "no", - auth_mech => 'pts', - pts_module => 'ldap', - ptloader_sock => '@basedir@/conf/ptsock', - ); - - my $self = $class->SUPER::new({ - config => $config, - adminstore => 1, - services => [qw( imap ptloader )], - start_instances => 0, - }, @args); - - return $self; +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set( + ldap_base => "o=cyrus", + ldap_group_base => "ou=groups,o=cyrus", + ldap_domain_base_dn => "ou=domains,o=cyrus", + ldap_user_attribute => "uid", + ldap_member_attribute => "memberof", + ldap_sasl => "no", + auth_mech => 'pts', + pts_module => 'ldap', + ptloader_sock => '@basedir@/conf/ptsock', + ); + + my $self = $class->SUPER::new( + { + config => $config, + adminstore => 1, + services => [qw( imap ptloader )], + start_instances => 0, + }, + @args + ); + + return $self; } -sub set_up -{ - my ($self) = @_; +sub set_up { + my ($self) = @_; - $self->SUPER::set_up(); + $self->SUPER::set_up(); - $self->{ldapport} = Cassandane::PortManager::alloc("localhost"); + $self->{ldapport} = Cassandane::PortManager::alloc("localhost"); - $self->{instance}->{config}->set( - ldap_uri => "ldap://localhost:$self->{ldapport}/", - ); + $self->{instance}->{config} + ->set(ldap_uri => "ldap://localhost:$self->{ldapport}/",); - # arrange for the fakeldapd to be started - my ($maj, $min) = Cassandane::Instance->get_version($self->{installation}); - if ($maj < 3 || ($maj == 3 && $min < 4)) { - $self->{instance}->add_start( - name => 'fakeldapd', - argv => [ - realpath('utils/fakeldapd'), - '-p', $self->{ldapport}, - '-l', realpath('data/directory.ldif'), - ], - ); - } - elsif (not exists $self->{daemons}->{fakeldapd}) { - $self->{instance}->add_daemon( - name => 'fakeldapd', - argv => [ - realpath('utils/fakeldapd'), - '-p', $self->{ldapport}, - '-l', realpath('data/directory.ldif'), - ], - wait => 'y', - ); - } + # arrange for the fakeldapd to be started + my ($maj, $min) = Cassandane::Instance->get_version($self->{installation}); + if ($maj < 3 || ($maj == 3 && $min < 4)) { + $self->{instance}->add_start( + name => 'fakeldapd', + argv => [ + realpath('utils/fakeldapd'), '-p', + $self->{ldapport}, '-l', + realpath('data/directory.ldif'), + ], + ); + } elsif (not exists $self->{daemons}->{fakeldapd}) { + $self->{instance}->add_daemon( + name => 'fakeldapd', + argv => [ + realpath('utils/fakeldapd'), '-p', + $self->{ldapport}, '-l', + realpath('data/directory.ldif'), + ], + wait => 'y', + ); + } - $self->_start_instances(); - $self->{instance}->create_user("otheruser"); + $self->_start_instances(); + $self->{instance}->create_user("otheruser"); } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } sub test_alternate_ptscache_db_path - :needs_dependency_ldap :min_version_3_0_8 :AltPTSDBPath -{ - my ($self) = @_; + : needs_dependency_ldap : min_version_3_0_8 : AltPTSDBPath { + my ($self) = @_; - # just interact with the store, and it should work - my $admintalk = $self->{adminstore}->get_client(); + # just interact with the store, and it should work + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->list('user.cassandane', '*'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->list('user.cassandane', '*'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $confdir = $self->{instance}->{basedir} . "/conf"; - $self->assert_file_test($confdir . "/non-default-ptscache.db"); - $self->assert_not_file_test($confdir . "/ptclient/ptscache.db"); + my $confdir = $self->{instance}->{basedir} . "/conf"; + $self->assert_file_test($confdir . "/non-default-ptscache.db"); + $self->assert_not_file_test($confdir . "/ptclient/ptscache.db"); } sub test_setacl_groupid - :needs_dependency_ldap :min_version_3_0_8 -{ - my ($self) = @_; + : needs_dependency_ldap : min_version_3_0_8 { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.cassandane.groupid"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->create("user.cassandane.groupid"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.groupid", - "group:foo", - "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.groupid", "group:foo", "lrswipkxtecdan"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } sub test_setacl_groupid_spaces - :needs_dependency_ldap :min_version_3_0_8 -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - $admintalk->setacl("user.cassandane.groupid_spaces", - "group:this group name has spaces", - "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - my $data = $admintalk->getacl("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - $self->assert(scalar @{$data} % 2 == 0); - my %acl = @{$data}; - $self->assert_str_equals($acl{"group:this group name has spaces"}, - "lrswipkxtecdan"); - - $admintalk->select("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + : needs_dependency_ldap : min_version_3_0_8 { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->create("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + $admintalk->setacl( + "user.cassandane.groupid_spaces", + "group:this group name has spaces", + "lrswipkxtecdan" + ); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + my $data = $admintalk->getacl("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + $self->assert(scalar @{$data} % 2 == 0); + my %acl = @{$data}; + $self->assert_str_equals($acl{"group:this group name has spaces"}, + "lrswipkxtecdan"); + + $admintalk->select("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } sub test_list_groupaccess_noracl - :needs_dependency_ldap :min_version_3_0_8 :NoAltNamespace -{ - my ($self) = @_; + : needs_dependency_ldap : min_version_3_0_8 : NoAltNamespace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $admintalk->create("user.otheruser.groupaccess"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->create("user.otheruser.groupaccess"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl("user.otheruser.groupaccess", - "group:group co", "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->setacl("user.otheruser.groupaccess", "group:group co", + "lrswipkxtecdan"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $list = $imaptalk->list("", "*"); - my @boxes = sort map { $_->[2] } @{$list}; + my $list = $imaptalk->list("", "*"); + my @boxes = sort map { $_->[2] } @{$list}; - $self->assert_deep_equals(\@boxes, - ['INBOX', 'user.otheruser.groupaccess']); + $self->assert_deep_equals(\@boxes, [ 'INBOX', 'user.otheruser.groupaccess' ]); } sub test_list_groupaccess_racl - :needs_dependency_ldap :ReverseACLs :min_version_3_1 :NoAltNamespace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); - - $admintalk->create("user.otheruser.groupaccess"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - $admintalk->setacl("user.otheruser.groupaccess", - "group:group co", "lrswipkxtecdn"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - if (get_verbose()) { - $self->{instance}->run_command( - { cyrus => 1, }, - 'cyr_dbtool', - "$self->{instance}->{basedir}/conf/mailboxes.db", - 'twoskip', - 'show' - ); - } + : needs_dependency_ldap : ReverseACLs : min_version_3_1 : NoAltNamespace { + my ($self) = @_; - my $list = $imaptalk->list("", "*"); - my @boxes = sort map { $_->[2] } @{$list}; + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->assert_deep_equals(\@boxes, - ['INBOX', 'user.otheruser.groupaccess']); + $admintalk->create("user.otheruser.groupaccess"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + $admintalk->setacl("user.otheruser.groupaccess", "group:group co", + "lrswipkxtecdn"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + if (get_verbose()) { + $self->{instance}->run_command( + { cyrus => 1, }, + 'cyr_dbtool', "$self->{instance}->{basedir}/conf/mailboxes.db", + 'twoskip', 'show' + ); + } + + my $list = $imaptalk->list("", "*"); + my @boxes = sort map { $_->[2] } @{$list}; + + $self->assert_deep_equals(\@boxes, [ 'INBOX', 'user.otheruser.groupaccess' ]); } -sub do_test_list_order -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.zzz"); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); - - $imaptalk->create("INBOX.aaa"); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); - - my %adminfolders = ( - 'user.otheruser.order-user' => 'cassandane', - 'user.otheruser.order-co' => 'group:group co', - 'user.otheruser.order-c' => 'group:group c', - 'user.otheruser.order-o' => 'group:group o', - 'shared.order-co' => 'group:group co', - 'shared.order-c' => 'group:group c', - 'shared.order-o' => 'group:group o', +sub do_test_list_order { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.zzz"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->create("INBOX.aaa"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + my %adminfolders = ( + 'user.otheruser.order-user' => 'cassandane', + 'user.otheruser.order-co' => 'group:group co', + 'user.otheruser.order-c' => 'group:group c', + 'user.otheruser.order-o' => 'group:group o', + 'shared.order-co' => 'group:group co', + 'shared.order-c' => 'group:group c', + 'shared.order-o' => 'group:group o', + ); + + while (my ($folder, $identifier) = each %adminfolders) { + $admintalk->create($folder); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "created folder $folder successfully" ); - while (my ($folder, $identifier) = each %adminfolders) { - $admintalk->create($folder); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "created folder $folder successfully"); - - $admintalk->setacl($folder, $identifier, "lrswipkxtecdn"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "setacl folder $folder for $identifier successfully"); - - if ($folder =~ m/^shared/) { - # subvert default permissions on shared namespace for - # purpose of testing ordering - $admintalk->setacl($folder, "anyone", "p"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "setacl folder $folder for anyone successfully"); - } - } + $admintalk->setacl($folder, $identifier, "lrswipkxtecdn"); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "setacl folder $folder for $identifier successfully" + ); - if (get_verbose()) { - $self->{instance}->run_command( - { cyrus => 1, }, - 'cyr_dbtool', - "$self->{instance}->{basedir}/conf/mailboxes.db", - 'twoskip', - 'show' - ); + if ($folder =~ m/^shared/) { + # subvert default permissions on shared namespace for + # purpose of testing ordering + $admintalk->setacl($folder, "anyone", "p"); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "setacl folder $folder for anyone successfully" + ); } + } - my $list = $imaptalk->list("", "*"); - my @boxes = map { $_->[2] } @{$list}; - - # Note: order is - # * mine, alphabetically, - # * other users', alphabetically, - # * shared, alphabetically - # ... which is not the order we created them ;) - # Also, the "order-o" folders are not returned, because cassandane - # is not a member of that group - my @expect = qw( - INBOX - INBOX.aaa - INBOX.zzz - user.otheruser.order-c - user.otheruser.order-co - user.otheruser.order-user + if (get_verbose()) { + $self->{instance}->run_command( + { cyrus => 1, }, + 'cyr_dbtool', "$self->{instance}->{basedir}/conf/mailboxes.db", + 'twoskip', 'show' ); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min > 4)) { - push @expect, qw(shared); - } - push @expect, qw( shared.order-c shared.order-co ); - $self->assert_deep_equals(\@boxes, \@expect); + } + + my $list = $imaptalk->list("", "*"); + my @boxes = map { $_->[2] } @{$list}; + + # Note: order is + # * mine, alphabetically, + # * other users', alphabetically, + # * shared, alphabetically + # ... which is not the order we created them ;) + # Also, the "order-o" folders are not returned, because cassandane + # is not a member of that group + my @expect = qw( + INBOX + INBOX.aaa + INBOX.zzz + user.otheruser.order-c + user.otheruser.order-co + user.otheruser.order-user + ); + my ($maj, $min) = Cassandane::Instance->get_version(); + + if ($maj > 3 || ($maj == 3 && $min > 4)) { + push @expect, qw(shared); + } + push @expect, qw( shared.order-c shared.order-co ); + $self->assert_deep_equals(\@boxes, \@expect); } sub test_list_order_noracl - :needs_dependency_ldap :min_version_3_0_8 :NoAltNamespace -{ - my $self = shift; - return $self->do_test_list_order(@_); + : needs_dependency_ldap : min_version_3_0_8 : NoAltNamespace { + my $self = shift; + return $self->do_test_list_order(@_); } sub test_list_order_racl - :needs_dependency_ldap :ReverseACLs :min_version_3_1 :NoAltNamespace -{ - my $self = shift; - return $self->do_test_list_order(@_); + : needs_dependency_ldap : ReverseACLs : min_version_3_1 : NoAltNamespace { + my $self = shift; + return $self->do_test_list_order(@_); } 1; diff --git a/cassandane/Cassandane/Cyrus/List.pm b/cassandane/Cassandane/Cyrus/List.pm index 1a02c04de5..adfe6c90df 100644 --- a/cassandane/Cassandane/Cyrus/List.pm +++ b/cassandane/Cassandane/Cyrus/List.pm @@ -52,27 +52,24 @@ use Cassandane::Instance; $Data::Dumper::Sortkeys = 1; -sub new -{ - my ($class, @args) = @_; +sub new { + my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); + my $config = Cassandane::Config->default()->clone(); - return $class->SUPER::new({ config => $config, adminstore => 1 }, @args); + return $class->SUPER::new({ config => $config, adminstore => 1 }, @args); } -sub set_up -{ - my ($self) = @_; +sub set_up { + my ($self) = @_; - $self->SUPER::set_up(); + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } # tests based on rfc 5258 examples: @@ -123,35 +120,40 @@ sub tear_down # # Cyrus does not (at least, not at the moment), so this test is disabled. sub bogus_test_rfc6154_ex01_list_non_extended - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash) ] ], - ]); - - $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - my $alldata = $imaptalk->list("", "%"); - - $self->assert_mailbox_structure($alldata, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'ToDo' => [qw( \\HasNoChildren )], - 'Projects' => [qw( \\HasChildren )], - 'SentMail' => [qw( \\Sent \\HasNoChildren )], - 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], - 'Trash' => [qw( \\Trash \\HasNoChildren )], - }); + : UnixHierarchySep : AltNamespace { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash)] ], + ] + ); + + $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + my $alldata = $imaptalk->list("", "%"); + + $self->assert_mailbox_structure( + $alldata, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'ToDo' => [qw( \\HasNoChildren )], + 'Projects' => [qw( \\HasChildren )], + 'SentMail' => [qw( \\Sent \\HasNoChildren )], + 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], + 'Trash' => [qw( \\Trash \\HasNoChildren )], + } + ); } use Cassandane::Tiny::Loader 'tiny-tests/List'; diff --git a/cassandane/Cassandane/Cyrus/Lsub.pm b/cassandane/Cassandane/Cyrus/Lsub.pm index 4ec07e561e..f3279624af 100644 --- a/cassandane/Cassandane/Cyrus/Lsub.pm +++ b/cassandane/Cassandane/Cyrus/Lsub.pm @@ -46,179 +46,124 @@ use Data::Dumper; use lib '.'; use base qw(Cassandane::Cyrus::TestCase); -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Right - let's create ourselves some users and subscriptions - # sub folders of the main user - $admintalk->create("user.cassandane.asub"); - $admintalk->create("user.cassandane.asub.deeper"); + # Right - let's create ourselves some users and subscriptions + # sub folders of the main user + $admintalk->create("user.cassandane.asub"); + $admintalk->create("user.cassandane.asub.deeper"); - # sub folders of another user - one is subscribable - $self->{instance}->create_user("other", - subdirs => [ 'sub', ['sub', 'folder'] ]); - $admintalk->setacl("user.other.sub.folder", "cassandane", "lrs"); + # sub folders of another user - one is subscribable + $self->{instance} + ->create_user("other", subdirs => [ 'sub', [ 'sub', 'folder' ] ]); + $admintalk->setacl("user.other.sub.folder", "cassandane", "lrs"); - my $usertalk = $self->{store}->get_client(); - $usertalk->subscribe("INBOX"); - $usertalk->subscribe("INBOX.asub"); - $usertalk->subscribe("user.other.sub.folder"); + my $usertalk = $self->{store}->get_client(); + $usertalk->subscribe("INBOX"); + $usertalk->subscribe("INBOX.asub"); + $usertalk->subscribe("user.other.sub.folder"); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test LSUB behaviour # sub test_lsub_toplevel - :NoAltNameSpace -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - my $alldata = $imaptalk->lsub("", "*"); - $self->assert_deep_equals($alldata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX' - ], - [ - [], - '.', - 'INBOX.asub' - ], - [ - [], - '.', - 'user.other.sub.folder' - ] - ], "LSUB all data mismatch: " . Dumper($alldata)); - - my $topdata = $imaptalk->lsub("", "%"); - $self->assert_deep_equals($topdata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX' - ], - [ - [ - '\\Noselect', - '\\HasChildren' - ], - '.', - 'user' - ], - ], "LSUB top data mismatch:" . Dumper($topdata)); + : NoAltNameSpace { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + my $alldata = $imaptalk->lsub("", "*"); + $self->assert_deep_equals( + $alldata, + [ + [ ['\\HasChildren'], '.', 'INBOX' ], + [ [], '.', 'INBOX.asub' ], + [ [], '.', 'user.other.sub.folder' ] + ], + "LSUB all data mismatch: " . Dumper($alldata) + ); + + my $topdata = $imaptalk->lsub("", "%"); + $self->assert_deep_equals( + $topdata, + [ + [ ['\\HasChildren'], '.', 'INBOX' ], + [ [ '\\Noselect', '\\HasChildren' ], '.', 'user' ], + ], + "LSUB top data mismatch:" . Dumper($topdata) + ); } -sub test_lsub_delete -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.deltest") || die; - $imaptalk->create("INBOX.deltest.sub1") || die; - $imaptalk->create("INBOX.deltest.sub2") || die; - $imaptalk->subscribe("INBOX.deltest") || die; - $imaptalk->subscribe("INBOX.deltest.sub2") || die; - my $subdata = $imaptalk->lsub("INBOX.deltest", "*"); - $self->assert_deep_equals($subdata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX.deltest' - ], - [ - [], - '.', - 'INBOX.deltest.sub2' - ], - ], "LSUB deltest setup mismatch: " . Dumper($subdata)); - - $imaptalk->delete("INBOX.deltest.sub2"); - my $onedata = $imaptalk->lsub("INBOX.deltest", "*"); - $self->assert_deep_equals($onedata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX.deltest' - ], - ], "LSUB deltest.sub2 after delete mismatch: " . Dumper($onedata)); +sub test_lsub_delete { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.deltest") || die; + $imaptalk->create("INBOX.deltest.sub1") || die; + $imaptalk->create("INBOX.deltest.sub2") || die; + $imaptalk->subscribe("INBOX.deltest") || die; + $imaptalk->subscribe("INBOX.deltest.sub2") || die; + my $subdata = $imaptalk->lsub("INBOX.deltest", "*"); + $self->assert_deep_equals( + $subdata, + [ + [ ['\\HasChildren'], '.', 'INBOX.deltest' ], + [ [], '.', 'INBOX.deltest.sub2' ], + ], + "LSUB deltest setup mismatch: " . Dumper($subdata) + ); + + $imaptalk->delete("INBOX.deltest.sub2"); + my $onedata = $imaptalk->lsub("INBOX.deltest", "*"); + $self->assert_deep_equals( + $onedata, + [ [ ['\\HasChildren'], '.', 'INBOX.deltest' ], ], + "LSUB deltest.sub2 after delete mismatch: " . Dumper($onedata) + ); } sub test_lsub_extrachild - :NoAltNameSpace -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.Test") || die; - $imaptalk->create("INBOX.Test.Sub") || die; - $imaptalk->create("INBOX.Test Foo") || die; - $imaptalk->create("INBOX.Test Bar") || die; - $imaptalk->subscribe("INBOX.Test") || die; - $imaptalk->subscribe("INBOX.Test.Sub") || die; - $imaptalk->subscribe("INBOX.Test Foo") || die; - $imaptalk->delete("INBOX.Test.Sub") || die; - my $subdata = $imaptalk->lsub("", "*"); - $self->assert_deep_equals($subdata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX' - ], - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX.Test' - ], - [ - [], - '.', - 'INBOX.Test Foo' - ], - [ - [], - '.', - 'INBOX.asub' - ], - [ - [], - '.', - 'user.other.sub.folder' - ], - ], "LSUB extrachild mismatch: " . Dumper($subdata)); + : NoAltNameSpace { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.Test") || die; + $imaptalk->create("INBOX.Test.Sub") || die; + $imaptalk->create("INBOX.Test Foo") || die; + $imaptalk->create("INBOX.Test Bar") || die; + $imaptalk->subscribe("INBOX.Test") || die; + $imaptalk->subscribe("INBOX.Test.Sub") || die; + $imaptalk->subscribe("INBOX.Test Foo") || die; + $imaptalk->delete("INBOX.Test.Sub") || die; + my $subdata = $imaptalk->lsub("", "*"); + $self->assert_deep_equals( + $subdata, + [ + [ ['\\HasChildren'], '.', 'INBOX' ], + [ ['\\HasChildren'], '.', 'INBOX.Test' ], + [ [], '.', 'INBOX.Test Foo' ], + [ [], '.', 'INBOX.asub' ], + [ [], '.', 'user.other.sub.folder' ], + ], + "LSUB extrachild mismatch: " . Dumper($subdata) + ); } 1; diff --git a/cassandane/Cassandane/Cyrus/Master.pm b/cassandane/Cassandane/Cyrus/Master.pm index eca56b1be3..218afaa167 100644 --- a/cassandane/Cassandane/Cyrus/Master.pm +++ b/cassandane/Cassandane/Cyrus/Master.pm @@ -57,1349 +57,1324 @@ use Cassandane::Config; my $lemming_bin = getcwd() . '/utils/lemming'; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new({ instance => 0 }, @_); +sub new { + my $class = shift; + my $self = $class->SUPER::new({ instance => 0 }, @_); - return $self; + return $self; } -sub set_up -{ - my ($self) = @_; - die "No lemming binary $lemming_bin. Did you run \"make\" in the Cassandane directory?" - unless (-f $lemming_bin); - $self->SUPER::set_up(); - $self->{instance} = Cassandane::Instance->new(setup_mailbox => 0, - authdaemon => 0); +sub set_up { + my ($self) = @_; + die + "No lemming binary $lemming_bin. Did you run \"make\" in the Cassandane directory?" + unless (-f $lemming_bin); + $self->SUPER::set_up(); + $self->{instance} = Cassandane::Instance->new( + setup_mailbox => 0, + authdaemon => 0 + ); } -sub tear_down -{ - my ($self) = @_; - $self->lemming_cull(); - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->lemming_cull(); + $self->SUPER::tear_down(); } -sub lemming_connect -{ - my ($srv, $address_family) = @_; - - my $sock = create_client_socket( - defined($address_family) ? $address_family : $srv->address_family(), - $srv->host(), $srv->port()) - or die "Cannot connect to lemming " . $srv->address() . ": $@"; - - # The lemming sends us his PID so we can later wait for him to die - # properly. It's easiest for synchronisation purposes to encode - # this as a fixed sized field. - my $pid; - $sock->sysread($pid, 4) - or die "Cannot read from lemming: " . $srv->address() . " $!"; - $pid = unpack("L", $pid); - die "Cannot read from lemming: $!" - unless defined $pid; - - return { sock => $sock, pid => $pid }; +sub lemming_connect { + my ($srv, $address_family) = @_; + + my $sock + = create_client_socket( + defined($address_family) ? $address_family : $srv->address_family(), + $srv->host(), $srv->port()) + or die "Cannot connect to lemming " . $srv->address() . ": $@"; + + # The lemming sends us his PID so we can later wait for him to die + # properly. It's easiest for synchronisation purposes to encode + # this as a fixed sized field. + my $pid; + $sock->sysread($pid, 4) + or die "Cannot read from lemming: " . $srv->address() . " $!"; + $pid = unpack("L", $pid); + die "Cannot read from lemming: $!" + unless defined $pid; + + return { sock => $sock, pid => $pid }; } -sub lemming_push -{ - my ($lemming, $mode) = @_; +sub lemming_push { + my ($lemming, $mode) = @_; -# xlog $self, "Pushing mode=$mode to pid=$lemming->{pid}"; + # xlog $self, "Pushing mode=$mode to pid=$lemming->{pid}"; - # Push the lemming over the metaphorical cliff. - $lemming->{sock}->syswrite($mode . "\r\n"); - $lemming->{sock}->close(); + # Push the lemming over the metaphorical cliff. + $lemming->{sock}->syswrite($mode . "\r\n"); + $lemming->{sock}->close(); - # Wait for the master process to wake up and reap the lemming. - return timed_wait(sub { kill(0, $lemming->{pid}) == 0 }, - description => "master to reap lemming $lemming->{pid}"); + # Wait for the master process to wake up and reap the lemming. + return timed_wait(sub { kill(0, $lemming->{pid}) == 0 }, + description => "master to reap lemming $lemming->{pid}"); } -sub lemming_census -{ - my ($self) = @_; - my $coresdir = $self->{instance}->{basedir} . '/conf/cores'; - - my %pids; - opendir LEMM,$coresdir - or die "cannot open $coresdir for reading: $!"; - while ($_ = readdir LEMM) - { - my ($tag, $pid) = m/^lemming\.(\w+).(\d+)$/; - next - unless defined $pid; - xlog $self, "found lemming tag=$tag pid=$pid"; - $pids{$tag} = [] - unless defined $pids{$tag}; - push (@{$pids{$tag}}, $pid); - } - closedir LEMM; - - my %actual; - foreach my $tag (keys %pids) - { - my $ntotal = scalar @{$pids{$tag}}; - my $nlive = kill(0, @{$pids{$tag}}); - $actual{$tag} = { - live => $nlive, - dead => $ntotal - $nlive, - }; - } - return \%actual; +sub lemming_census { + my ($self) = @_; + my $coresdir = $self->{instance}->{basedir} . '/conf/cores'; + + my %pids; + opendir LEMM, $coresdir + or die "cannot open $coresdir for reading: $!"; + while ($_ = readdir LEMM) { + my ($tag, $pid) = m/^lemming\.(\w+).(\d+)$/; + next + unless defined $pid; + xlog $self, "found lemming tag=$tag pid=$pid"; + $pids{$tag} = [] + unless defined $pids{$tag}; + push(@{ $pids{$tag} }, $pid); + } + closedir LEMM; + + my %actual; + foreach my $tag (keys %pids) { + my $ntotal = scalar @{ $pids{$tag} }; + my $nlive = kill(0, @{ $pids{$tag} }); + $actual{$tag} = { + live => $nlive, + dead => $ntotal - $nlive, + }; + } + return \%actual; } -sub lemming_cull -{ - my ($self) = @_; - return unless defined $self->{instance}; - my $coresdir = $self->{instance}->{basedir} . '/conf/cores'; - - return unless -d $coresdir; - opendir LEMM,$coresdir - or die "cannot open $coresdir for reading: $!"; - while ($_ = readdir LEMM) - { - my ($tag, $pid) = m/^lemming\.(\w+).(\d+)$/; - next - unless defined $pid; - xlog $self, "culled lemming tag=$tag pid=$pid" - if kill(9, $pid); - } - closedir LEMM; +sub lemming_cull { + my ($self) = @_; + return unless defined $self->{instance}; + my $coresdir = $self->{instance}->{basedir} . '/conf/cores'; + + return unless -d $coresdir; + opendir LEMM, $coresdir + or die "cannot open $coresdir for reading: $!"; + while ($_ = readdir LEMM) { + my ($tag, $pid) = m/^lemming\.(\w+).(\d+)$/; + next + unless defined $pid; + xlog $self, "culled lemming tag=$tag pid=$pid" + if kill(9, $pid); + } + closedir LEMM; } -sub _lemming_args -{ - my (%params) = @_; +sub _lemming_args { + my (%params) = @_; - my $tag = delete $params{tag} || 'A'; - my $mode = delete $params{mode} || 'serve'; - my $delay = delete $params{delay}; + my $tag = delete $params{tag} || 'A'; + my $mode = delete $params{mode} || 'serve'; + my $delay = delete $params{delay}; - my @argv = ( $lemming_bin, '-t', $tag, '-m', $mode ); - push(@argv, '-d', $delay) if defined $delay; + my @argv = ($lemming_bin, '-t', $tag, '-m', $mode); + push(@argv, '-d', $delay) if defined $delay; - return (name => $tag, argv => \@argv, %params); + return (name => $tag, argv => \@argv, %params); } -sub lemming_service -{ - my ($self, %params) = @_; - return $self->{instance}->add_service(_lemming_args(%params)); +sub lemming_service { + my ($self, %params) = @_; + return $self->{instance}->add_service(_lemming_args(%params)); } -sub lemming_start -{ - my ($self, %params) = @_; - return $self->{instance}->add_start(_lemming_args(%params)); +sub lemming_start { + my ($self, %params) = @_; + return $self->{instance}->add_start(_lemming_args(%params)); } -sub lemming_event -{ - my ($self, %params) = @_; - return $self->{instance}->add_event(_lemming_args(%params)); +sub lemming_event { + my ($self, %params) = @_; + return $self->{instance}->add_event(_lemming_args(%params)); } -sub lemming_wait -{ - my ($self, %expected_census) = @_; - - timed_wait( - sub - { - my $census = $self->lemming_census(); - map { - my $service_name = $_; - return 0 if !defined $census->{$service_name}; - my $expected = $expected_census{$service_name}; - map { - return 0 if $census->{$service_name}->{$_} != $expected->{$_}; - } keys %$expected; - } keys %expected_census; - return 1; - }, - description => "lemmings to reach the expected census"); +sub lemming_wait { + my ($self, %expected_census) = @_; + + timed_wait( + sub { + my $census = $self->lemming_census(); + map { + my $service_name = $_; + return 0 if !defined $census->{$service_name}; + my $expected = $expected_census{$service_name}; + map { return 0 if $census->{$service_name}->{$_} != $expected->{$_}; } + keys %$expected; + } keys %expected_census; + return 1; + }, + description => "lemmings to reach the expected census" + ); } -sub start -{ - my ($self) = @_; - $self->{instance}->start(); +sub start { + my ($self) = @_; + $self->{instance}->start(); } # # Test a single running programs in SERVICES # -sub test_service -{ - my ($self) = @_; +sub test_service { + my ($self) = @_; - xlog $self, "single successful service"; - my $srv = $self->lemming_service(); - $self->start(); + xlog $self, "single successful service"; + my $srv = $self->lemming_service(); + $self->start(); - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); - my $lemm = lemming_connect($srv); + my $lemm = lemming_connect($srv); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, + $self->lemming_census()); } # # Test multiple connections to a single running program in SERVICES # -sub test_multi_connections -{ - my ($self) = @_; +sub test_multi_connections { + my ($self) = @_; - xlog $self, "multiple connections to a single successful service"; - my $srv = $self->lemming_service(); - $self->start(); + xlog $self, "multiple connections to a single successful service"; + my $srv = $self->lemming_service(); + $self->start(); - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); - my $lemm1 = lemming_connect($srv); + my $lemm1 = lemming_connect($srv); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - my $lemm2 = lemming_connect($srv); + my $lemm2 = lemming_connect($srv); - xlog $self, "two connected so two lemmings forked"; - $self->assert_deep_equals({ A => { live => 2, dead => 0 } }, - $self->lemming_census()); + xlog $self, "two connected so two lemmings forked"; + $self->assert_deep_equals({ A => { live => 2, dead => 0 } }, + $self->lemming_census()); - my $lemm3 = lemming_connect($srv); + my $lemm3 = lemming_connect($srv); - xlog $self, "three connected so three lemmings forked"; - $self->assert_deep_equals({ A => { live => 3, dead => 0 } }, - $self->lemming_census()); + xlog $self, "three connected so three lemmings forked"; + $self->assert_deep_equals({ A => { live => 3, dead => 0 } }, + $self->lemming_census()); - lemming_push($lemm1, 'success'); - lemming_push($lemm2, 'success'); - lemming_push($lemm3, 'success'); + lemming_push($lemm1, 'success'); + lemming_push($lemm2, 'success'); + lemming_push($lemm3, 'success'); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 3 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ A => { live => 0, dead => 3 } }, + $self->lemming_census()); } # # Test multiple running programs in SERVICES # -sub test_multi_services -{ - my ($self) = @_; - - xlog $self, "multiple successful services"; - my $srvA = $self->lemming_service(tag => 'A'); - my $srvB = $self->lemming_service(tag => 'B'); - my $srvC = $self->lemming_service(tag => 'C'); - $self->start(); - - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); - - my $lemmA = lemming_connect($srvA); - - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); - - my $lemmB = lemming_connect($srvB); - - xlog $self, "two connected so two lemmings forked"; - $self->assert_deep_equals({ - A => { live => 1, dead => 0 }, - B => { live => 1, dead => 0 }, - }, $self->lemming_census()); - - my $lemmC = lemming_connect($srvC); - - xlog $self, "three connected so three lemmings forked"; - $self->assert_deep_equals({ - A => { live => 1, dead => 0 }, - B => { live => 1, dead => 0 }, - C => { live => 1, dead => 0 }, - }, $self->lemming_census()); - - lemming_push($lemmA, 'success'); - lemming_push($lemmB, 'success'); - lemming_push($lemmC, 'success'); - - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ - A => { live => 0, dead => 1 }, - B => { live => 0, dead => 1 }, - C => { live => 0, dead => 1 }, - }, $self->lemming_census()); +sub test_multi_services { + my ($self) = @_; + + xlog $self, "multiple successful services"; + my $srvA = $self->lemming_service(tag => 'A'); + my $srvB = $self->lemming_service(tag => 'B'); + my $srvC = $self->lemming_service(tag => 'C'); + $self->start(); + + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); + + my $lemmA = lemming_connect($srvA); + + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); + + my $lemmB = lemming_connect($srvB); + + xlog $self, "two connected so two lemmings forked"; + $self->assert_deep_equals( + { + A => { live => 1, dead => 0 }, + B => { live => 1, dead => 0 }, + }, + $self->lemming_census() + ); + + my $lemmC = lemming_connect($srvC); + + xlog $self, "three connected so three lemmings forked"; + $self->assert_deep_equals( + { + A => { live => 1, dead => 0 }, + B => { live => 1, dead => 0 }, + C => { live => 1, dead => 0 }, + }, + $self->lemming_census() + ); + + lemming_push($lemmA, 'success'); + lemming_push($lemmB, 'success'); + lemming_push($lemmC, 'success'); + + xlog $self, "no more live lemmings"; + $self->assert_deep_equals( + { + A => { live => 0, dead => 1 }, + B => { live => 0, dead => 1 }, + C => { live => 0, dead => 1 }, + }, + $self->lemming_census() + ); } # # Test a preforked single running program in SERVICES # -sub test_prefork -{ - my ($self) = @_; +sub test_prefork { + my ($self) = @_; - xlog $self, "single successful service"; - my $srv = $self->lemming_service(prefork => 1); - $self->start(); - $self->lemming_wait(A => { live => 1 }); + xlog $self, "single successful service"; + my $srv = $self->lemming_service(prefork => 1); + $self->start(); + $self->lemming_wait(A => { live => 1 }); - xlog $self, "preforked, so one lemming running already"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "preforked, so one lemming running already"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - my $lemm1 = lemming_connect($srv); - $self->lemming_wait(A => { live => 2 }); + my $lemm1 = lemming_connect($srv); + $self->lemming_wait(A => { live => 2 }); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 2, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 2, dead => 0 } }, + $self->lemming_census()); - my $lemm2 = lemming_connect($srv); - $self->lemming_wait(A => { live => 3 }); + my $lemm2 = lemming_connect($srv); + $self->lemming_wait(A => { live => 3 }); - xlog $self, "connected again so two additional lemmings forked"; - $self->assert_deep_equals({ A => { live => 3, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected again so two additional lemmings forked"; + $self->assert_deep_equals({ A => { live => 3, dead => 0 } }, + $self->lemming_census()); - lemming_push($lemm1, 'success'); - lemming_push($lemm2, 'success'); + lemming_push($lemm1, 'success'); + lemming_push($lemm2, 'success'); - xlog $self, "always at least one live lemming"; - $self->assert_deep_equals({ A => { live => 1, dead => 2 } }, - $self->lemming_census()); + xlog $self, "always at least one live lemming"; + $self->assert_deep_equals({ A => { live => 1, dead => 2 } }, + $self->lemming_census()); } # # Test multiple running programs in SERVICES, some preforked. # -sub test_multi_prefork -{ - my ($self) = @_; - - xlog $self, "multiple successful service some preforked"; - my $srvA = $self->lemming_service(tag => 'A', prefork => 2); - my $srvB = $self->lemming_service(tag => 'B'); # no preforking - my $srvC = $self->lemming_service(tag => 'C', prefork => 3); - $self->start(); - - # wait for lemmings to be preforked - $self->lemming_wait(A => { live => 2 }, C => { live => 3 }); - - my @lemmings; - my $lemm; - - xlog $self, "connect to A once"; - $lemm = lemming_connect($srvA); - $self->lemming_wait(A => { live => 3 }); - push(@lemmings, $lemm); - $self->assert_deep_equals({ - A => { live => 3, dead => 0 }, - C => { live => 3, dead => 0 }, - }, $self->lemming_census()); - - xlog $self, "connect to A again"; - $lemm = lemming_connect($srvA); - $self->lemming_wait(A => { live => 4 }); - push(@lemmings, $lemm); - $self->assert_deep_equals({ - A => { live => 4, dead => 0 }, - C => { live => 3, dead => 0 }, - }, $self->lemming_census()); - - xlog $self, "connect to A a third time"; - $lemm = lemming_connect($srvA); - $self->lemming_wait(A => { live => 5 }); - push(@lemmings, $lemm); - $self->assert_deep_equals({ - A => { live => 5, dead => 0 }, - C => { live => 3, dead => 0 }, - }, $self->lemming_census()); - - xlog $self, "connect to B"; - $lemm = lemming_connect($srvB); - push(@lemmings, $lemm); - $self->assert_deep_equals({ - A => { live => 5, dead => 0 }, - B => { live => 1, dead => 0 }, - C => { live => 3, dead => 0 }, - }, $self->lemming_census()); +sub test_multi_prefork { + my ($self) = @_; + + xlog $self, "multiple successful service some preforked"; + my $srvA = $self->lemming_service(tag => 'A', prefork => 2); + my $srvB = $self->lemming_service(tag => 'B'); # no preforking + my $srvC = $self->lemming_service(tag => 'C', prefork => 3); + $self->start(); + + # wait for lemmings to be preforked + $self->lemming_wait(A => { live => 2 }, C => { live => 3 }); + + my @lemmings; + my $lemm; + + xlog $self, "connect to A once"; + $lemm = lemming_connect($srvA); + $self->lemming_wait(A => { live => 3 }); + push(@lemmings, $lemm); + $self->assert_deep_equals( + { + A => { live => 3, dead => 0 }, + C => { live => 3, dead => 0 }, + }, + $self->lemming_census() + ); + + xlog $self, "connect to A again"; + $lemm = lemming_connect($srvA); + $self->lemming_wait(A => { live => 4 }); + push(@lemmings, $lemm); + $self->assert_deep_equals( + { + A => { live => 4, dead => 0 }, + C => { live => 3, dead => 0 }, + }, + $self->lemming_census() + ); + + xlog $self, "connect to A a third time"; + $lemm = lemming_connect($srvA); + $self->lemming_wait(A => { live => 5 }); + push(@lemmings, $lemm); + $self->assert_deep_equals( + { + A => { live => 5, dead => 0 }, + C => { live => 3, dead => 0 }, + }, + $self->lemming_census() + ); + + xlog $self, "connect to B"; + $lemm = lemming_connect($srvB); + push(@lemmings, $lemm); + $self->assert_deep_equals( + { + A => { live => 5, dead => 0 }, + B => { live => 1, dead => 0 }, + C => { live => 3, dead => 0 }, + }, + $self->lemming_census() + ); + + foreach $lemm (@lemmings) { + lemming_push($lemm, 'success'); + } - foreach $lemm (@lemmings) + xlog $self, "our lemmings are gone, others have replaced them"; + $self->assert_deep_equals( { - lemming_push($lemm, 'success'); - } - - xlog $self, "our lemmings are gone, others have replaced them"; - $self->assert_deep_equals({ - A => { live => 2, dead => 3 }, - B => { live => 0, dead => 1 }, - C => { live => 3, dead => 0 }, - }, $self->lemming_census()); + A => { live => 2, dead => 3 }, + B => { live => 0, dead => 1 }, + C => { live => 3, dead => 0 }, + }, + $self->lemming_census() + ); } # # Test a single program in SERVICES which fails after connect # -sub test_exit_after_connect -{ - my ($self) = @_; +sub test_exit_after_connect { + my ($self) = @_; - xlog $self, "single service will exit after connect"; - my $srv = $self->lemming_service(); - $self->start(); + xlog $self, "single service will exit after connect"; + my $srv = $self->lemming_service(); + $self->start(); - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); - my $lemm = lemming_connect($srv); + my $lemm = lemming_connect($srv); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - xlog $self, "push the lemming off the cliff"; - lemming_push($lemm, 'exit'); - $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, - $self->lemming_census()); + xlog $self, "push the lemming off the cliff"; + lemming_push($lemm, 'exit'); + $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, + $self->lemming_census()); - xlog $self, "can connect again"; - $lemm = lemming_connect($srv); - $self->assert_deep_equals({ A => { live => 1, dead => 1 } }, - $self->lemming_census()); + xlog $self, "can connect again"; + $lemm = lemming_connect($srv); + $self->assert_deep_equals({ A => { live => 1, dead => 1 } }, + $self->lemming_census()); - xlog $self, "push the lemming off the cliff"; - lemming_push($lemm, 'exit'); - $self->assert_deep_equals({ A => { live => 0, dead => 2 } }, - $self->lemming_census()); + xlog $self, "push the lemming off the cliff"; + lemming_push($lemm, 'exit'); + $self->assert_deep_equals({ A => { live => 0, dead => 2 } }, + $self->lemming_census()); } # # Test a single program in SERVICES which fails during startup # -sub test_service_exit_during_start -{ - my ($self) = @_; - my $lemm; - - xlog $self, "single service will exit during startup"; - my $srv = $self->lemming_service(mode => 'exit', delay => 100); - $self->start(); - - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); +sub test_service_exit_during_start { + my ($self) = @_; + my $lemm; + + xlog $self, "single service will exit during startup"; + my $srv = $self->lemming_service(mode => 'exit', delay => 100); + $self->start(); + + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); + + xlog $self, "connection fails due to dead lemming"; + eval { $lemm = lemming_connect($srv); }; + $self->assert_null($lemm); + + xlog $self, "expect 5 dead lemmings"; + $self->assert_deep_equals({ A => { live => 0, dead => 5 } }, + $self->lemming_census()); + + xlog $self, "connections should fail because service disabled"; + eval { $lemm = lemming_connect($srv); }; + $self->assert_null($lemm); + $self->assert_deep_equals({ A => { live => 0, dead => 5 } }, + $self->lemming_census()); +} - xlog $self, "connection fails due to dead lemming"; - eval - { - $lemm = lemming_connect($srv); - }; - $self->assert_null($lemm); +sub test_startup { + my ($self) = @_; - xlog $self, "expect 5 dead lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 5 } }, - $self->lemming_census()); + xlog $self, "Test a program in the START section"; + $self->lemming_start(tag => 'A', delay => 100, mode => 'success'); + $self->lemming_start(tag => 'B', delay => 200, mode => 'success'); + # This service won't be used + my $srv = $self->lemming_service(tag => 'C'); + $self->start(); - xlog $self, "connections should fail because service disabled"; - eval + xlog $self, "expect 2 dead lemmings"; + $self->assert_deep_equals( { - $lemm = lemming_connect($srv); - }; - $self->assert_null($lemm); - $self->assert_deep_equals({ A => { live => 0, dead => 5 } }, - $self->lemming_census()); + A => { live => 0, dead => 1 }, + B => { live => 0, dead => 1 }, + }, + $self->lemming_census() + ); } -sub test_startup -{ - my ($self) = @_; - - xlog $self, "Test a program in the START section"; - $self->lemming_start(tag => 'A', delay => 100, mode => 'success'); - $self->lemming_start(tag => 'B', delay => 200, mode => 'success'); - # This service won't be used - my $srv = $self->lemming_service(tag => 'C'); - $self->start(); - - xlog $self, "expect 2 dead lemmings"; - $self->assert_deep_equals({ - A => { live => 0, dead => 1 }, - B => { live => 0, dead => 1 }, - }, $self->lemming_census()); -} - -sub test_startup_exits -{ - my ($self) = @_; +sub test_startup_exits { + my ($self) = @_; - xlog $self, "Test a program in the START section which fails"; - $self->lemming_start(tag => 'A', delay => 100, mode => 'exit'); - $self->lemming_start(tag => 'B', delay => 200, mode => 'exit'); - # This service won't be used - my $srv = $self->lemming_service(tag => 'C'); - eval - { - $self->start(); - }; - xlog $self, "start failed (as expected): $@" if $@; + xlog $self, "Test a program in the START section which fails"; + $self->lemming_start(tag => 'A', delay => 100, mode => 'exit'); + $self->lemming_start(tag => 'B', delay => 200, mode => 'exit'); + # This service won't be used + my $srv = $self->lemming_service(tag => 'C'); + eval { $self->start(); }; + xlog $self, "start failed (as expected): $@" if $@; - xlog $self, "master should have exited when first startup failed"; - $self->assert(!$self->{instance}->is_running()); + xlog $self, "master should have exited when first startup failed"; + $self->assert(!$self->{instance}->is_running()); - xlog $self, "expect 1 dead lemming"; - $self->assert_deep_equals({ - A => { live => 0, dead => 1 }, - }, $self->lemming_census()); + xlog $self, "expect 1 dead lemming"; + $self->assert_deep_equals( + { + A => { live => 0, dead => 1 }, + }, + $self->lemming_census() + ); } # TODO: test exit during startup with prefork= -sub test_service_ipv6 -{ - my ($self) = @_; +sub test_service_ipv6 { + my ($self) = @_; - xlog $self, "single successful service on IPv6"; - my $srv = $self->lemming_service(host => '::1'); - $self->start(); + xlog $self, "single successful service on IPv6"; + my $srv = $self->lemming_service(host => '::1'); + $self->start(); - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); - my $lemm = lemming_connect($srv); + my $lemm = lemming_connect($srv); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, + $self->lemming_census()); } -sub test_service_unix -{ - my ($self) = @_; +sub test_service_unix { + my ($self) = @_; - xlog $self, "single successful service on UNIX domain socket"; - my $srv = $self->lemming_service( - host => undef, - port => '@basedir@/conf/socket/lemming.sock'); - $self->start(); + xlog $self, "single successful service on UNIX domain socket"; + my $srv = $self->lemming_service( + host => undef, + port => '@basedir@/conf/socket/lemming.sock' + ); + $self->start(); - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); - my $lemm = lemming_connect($srv); + my $lemm = lemming_connect($srv); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, + $self->lemming_census()); } -sub test_service_nohost -{ - my ($self) = @_; +sub test_service_nohost { + my ($self) = @_; - xlog $self, "single successful service with a port-only listen="; - my $srv = $self->lemming_service(host => undef); - $self->start(); + xlog $self, "single successful service with a port-only listen="; + my $srv = $self->lemming_service(host => undef); + $self->start(); - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); - my $lemm = lemming_connect($srv); + my $lemm = lemming_connect($srv); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, + $self->lemming_census()); } -sub test_service_dup_port -{ - my ($self) = @_; - - xlog $self, "successful two services with listen= "; - xlog $self, "parameters which reference the same IPv4 port"; - my $srvA = $self->lemming_service(tag => 'A'); - my $srvB = $self->lemming_service(tag => 'B', - port => $srvA->port()); - - # master should emit a syslog message like this - # - # Dec 31 14:40:57 enki 0340541/master[26085]: unable to create B - # listener socket: Address already in use - # - # and struggle on. - $self->start(); - - if ($self->{instance}->{have_syslog_replacement}) { - # check syslog for the expected error - my $pat = qr/unable to create (?:A|B) listener socket:/; - my @lines = $self->{instance}->getsyslog($pat); - $self->assert_num_equals(1, scalar @lines); - $self->assert_matches(qr/Address already in use/, $lines[0]); - } - - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); - - my $lemmA = lemming_connect($srvA); - - my $census = $self->lemming_census(); - my ($winner) = keys %$census; # either could be the one that runs - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ $winner => { live => 1, dead => 0 } }, - $self->lemming_census()); - - my $lemmB = lemming_connect($srvB); - - xlog $self, "the port is owned by service A"; - $self->assert_deep_equals({ $winner => { live => 2, dead => 0 } }, - $self->lemming_census()); - - lemming_push($lemmA, 'success'); - lemming_push($lemmB, 'success'); - - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ $winner => { live => 0, dead => 2 } }, - $self->lemming_census()); +sub test_service_dup_port { + my ($self) = @_; + + xlog $self, "successful two services with listen= "; + xlog $self, "parameters which reference the same IPv4 port"; + my $srvA = $self->lemming_service(tag => 'A'); + my $srvB = $self->lemming_service( + tag => 'B', + port => $srvA->port() + ); + + # master should emit a syslog message like this + # + # Dec 31 14:40:57 enki 0340541/master[26085]: unable to create B + # listener socket: Address already in use + # + # and struggle on. + $self->start(); + + if ($self->{instance}->{have_syslog_replacement}) { + # check syslog for the expected error + my $pat = qr/unable to create (?:A|B) listener socket:/; + my @lines = $self->{instance}->getsyslog($pat); + $self->assert_num_equals(1, scalar @lines); + $self->assert_matches(qr/Address already in use/, $lines[0]); + } + + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); + + my $lemmA = lemming_connect($srvA); + + my $census = $self->lemming_census(); + my ($winner) = keys %$census; # either could be the one that runs + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ $winner => { live => 1, dead => 0 } }, + $self->lemming_census()); + + my $lemmB = lemming_connect($srvB); + + xlog $self, "the port is owned by service A"; + $self->assert_deep_equals({ $winner => { live => 2, dead => 0 } }, + $self->lemming_census()); + + lemming_push($lemmA, 'success'); + lemming_push($lemmB, 'success'); + + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ $winner => { live => 0, dead => 2 } }, + $self->lemming_census()); } -sub test_service_noexe -{ - my ($self) = @_; - - xlog $self, "single service with a non-existant executable"; - my $srvA = $self->lemming_service(tag => 'A'); - my $srvB = $self->{instance}->add_service( - name => 'B', - argv => ['/usr/bin/no-such-exe','--foo','--bar']); - - # master should exit while adding services, with a message - # to syslog like this - # - # Dec 31 15:03:26 enki 0403231/master[26825]: cannot find executable - # for service 'B' - eval - { - $self->start(); - }; - xlog $self, "start failed (as expected): $@" if $@; - - # XXX can't currently check syslog in this case because start() bailed - # XXX out before we would have started reading it... - - xlog $self, "master should have exited when service verification failed"; - $self->assert(!$self->{instance}->is_running()); +sub test_service_noexe { + my ($self) = @_; + + xlog $self, "single service with a non-existant executable"; + my $srvA = $self->lemming_service(tag => 'A'); + my $srvB = $self->{instance}->add_service( + name => 'B', + argv => [ '/usr/bin/no-such-exe', '--foo', '--bar' ] + ); + + # master should exit while adding services, with a message + # to syslog like this + # + # Dec 31 15:03:26 enki 0403231/master[26825]: cannot find executable + # for service 'B' + eval { $self->start(); }; + xlog $self, "start failed (as expected): $@" if $@; + + # XXX can't currently check syslog in this case because start() bailed + # XXX out before we would have started reading it... + + xlog $self, "master should have exited when service verification failed"; + $self->assert(!$self->{instance}->is_running()); } -sub test_reap_rate -{ - my ($self) = @_; +sub test_reap_rate { + my ($self) = @_; - xlog $self, "Testing latency after which cyrus reaps dead children"; + xlog $self, "Testing latency after which cyrus reaps dead children"; - my $max_latency = 1.0; # seconds + my $max_latency = 1.0; # seconds - my $srv = $self->lemming_service(tag => 'A'); - $self->start(); + my $srv = $self->lemming_service(tag => 'A'); + $self->start(); - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); - xlog $self, "Build a vast flock of lemmings"; - my @lemmings; - for (1..100) + xlog $self, "Build a vast flock of lemmings"; + my @lemmings; + for (1 .. 100) { + push(@lemmings, lemming_connect($srv)); + } + $self->assert_deep_equals( { - push(@lemmings, lemming_connect($srv)); - } - $self->assert_deep_equals({ - A => { live => 100, dead => 0 }, - }, $self->lemming_census()); - - # This technique avoids having new connections at the - # same time as we're trying to measure reaping latency, - # which can hide racy bugs in the main select() loop. - xlog $self, "Killing all the lemmings one by one"; - my $ss = new Cassandane::Util::Sample; - while (my $lemm = shift @lemmings) + A => { live => 100, dead => 0 }, + }, + $self->lemming_census() + ); + + # This technique avoids having new connections at the + # same time as we're trying to measure reaping latency, + # which can hide racy bugs in the main select() loop. + xlog $self, "Killing all the lemmings one by one"; + my $ss = new Cassandane::Util::Sample; + while (my $lemm = shift @lemmings) { + my $t = lemming_push($lemm, 'success'); + $self->assert($t < $max_latency, + "Child reap latency is >= $max_latency sec"); + $ss->add($t); + } + xlog $self, "Reap times: $ss"; + + xlog $self, "no more live lemmings"; + $self->assert_deep_equals( { - my $t = lemming_push($lemm, 'success'); - $self->assert($t < $max_latency, - "Child reap latency is >= $max_latency sec"); - $ss->add($t); - } - xlog $self, "Reap times: $ss"; - - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ - A => { live => 0, dead => 100 }, - }, $self->lemming_census()); + A => { live => 0, dead => 100 }, + }, + $self->lemming_census() + ); } -sub measure_fork_rate -{ - my ($self, $srv, $rate) = @_; +sub measure_fork_rate { + my ($self, $srv, $rate) = @_; - my $metronome = Cassandane::Util::Metronome->new(rate => $rate); - my @lemmings; - for (1..100) - { - my $lemm = lemming_connect($srv); - push(@lemmings, $lemm); - $metronome->tick(); - } + my $metronome = Cassandane::Util::Metronome->new(rate => $rate); + my @lemmings; + for (1 .. 100) { + my $lemm = lemming_connect($srv); + push(@lemmings, $lemm); + $metronome->tick(); + } - foreach my $lemm (@lemmings) - { - lemming_push($lemm, 'success'); - } + foreach my $lemm (@lemmings) { + lemming_push($lemm, 'success'); + } - return $metronome->actual_rate(); + return $metronome->actual_rate(); } -sub test_maxforkrate -{ - my ($self) = @_; - - xlog $self, "Testing enforcement of the maxforkrate= parameter"; - - # A very loose error factor. We don't care too much if the - # enforcement is slightly off, it's a rough resource limit and - # fairness measure not a precise QoS issue. Also, even modest fork - # rates may be difficult to achieve when running under Valgrind, and - # we don't want that to cause the test to fail spuriously. - my $epsilon = 0.2; - my $fast = 10.0; # forks/sec - my $slow = 5.0; # forks/sec - - my $srvA = $self->lemming_service(tag => 'A'); - my $srvB = $self->lemming_service(tag => 'B', maxforkrate => int($slow)); - $self->start(); - - xlog $self, "not preforked, so no lemmings running yet"; - $self->assert_deep_equals({}, - $self->lemming_census()); - - xlog $self, "Test that we can achieve the fast forks rate on the unlimited service"; - my $r = $self->measure_fork_rate($srvA, $fast); - xlog $self, "Actual rate: $r"; - $self->assert($r >= (1.0-$epsilon)*$fast, - "Fork rate too slow, for $r wanted $fast"); - $self->assert($r <= (1.0+$epsilon)*$fast, - "Fork rate too fast, for $r wanted $fast"); - - xlog $self, "Test that the fork rate is limited on the limited service"; - $r = $self->measure_fork_rate($srvB, $fast); - xlog $self, "Actual rate: $r"; - $self->assert($r >= (1.0-$epsilon)*$slow, - "Fork rate too slow, got $r wanted $slow"); - $self->assert($r <= (1.0+$epsilon)*$slow, - "Fork rate too fast, got $r wanted $slow"); - - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ - A => { live => 0, dead => 100 }, - B => { live => 0, dead => 100 }, - }, $self->lemming_census()); +sub test_maxforkrate { + my ($self) = @_; + + xlog $self, "Testing enforcement of the maxforkrate= parameter"; + + # A very loose error factor. We don't care too much if the + # enforcement is slightly off, it's a rough resource limit and + # fairness measure not a precise QoS issue. Also, even modest fork + # rates may be difficult to achieve when running under Valgrind, and + # we don't want that to cause the test to fail spuriously. + my $epsilon = 0.2; + my $fast = 10.0; # forks/sec + my $slow = 5.0; # forks/sec + + my $srvA = $self->lemming_service(tag => 'A'); + my $srvB = $self->lemming_service(tag => 'B', maxforkrate => int($slow)); + $self->start(); + + xlog $self, "not preforked, so no lemmings running yet"; + $self->assert_deep_equals({}, $self->lemming_census()); + + xlog $self, + "Test that we can achieve the fast forks rate on the unlimited service"; + my $r = $self->measure_fork_rate($srvA, $fast); + xlog $self, "Actual rate: $r"; + $self->assert($r >= (1.0 - $epsilon) * $fast, + "Fork rate too slow, for $r wanted $fast"); + $self->assert($r <= (1.0 + $epsilon) * $fast, + "Fork rate too fast, for $r wanted $fast"); + + xlog $self, "Test that the fork rate is limited on the limited service"; + $r = $self->measure_fork_rate($srvB, $fast); + xlog $self, "Actual rate: $r"; + $self->assert($r >= (1.0 - $epsilon) * $slow, + "Fork rate too slow, got $r wanted $slow"); + $self->assert($r <= (1.0 + $epsilon) * $slow, + "Fork rate too fast, got $r wanted $slow"); + + xlog $self, "no more live lemmings"; + $self->assert_deep_equals( + { + A => { live => 0, dead => 100 }, + B => { live => 0, dead => 100 }, + }, + $self->lemming_census() + ); } -sub test_periodic_event_slow -{ - my ($self) = @_; - - xlog $self, "Testing regular events"; +sub test_periodic_event_slow { + my ($self) = @_; - my $srv = $self->lemming_service(tag => 'A'); - # This is the fastest we can schedule events - every 1 minute - # so in the absence of a per-process time machine our test will - # need to run for several real minutes. - $self->lemming_event(tag => 'B', mode => 'success', period => 1); - $self->start(); + xlog $self, "Testing regular events"; - xlog $self, "periodic events run immediately"; + my $srv = $self->lemming_service(tag => 'A'); + # This is the fastest we can schedule events - every 1 minute + # so in the absence of a per-process time machine our test will + # need to run for several real minutes. + $self->lemming_event(tag => 'B', mode => 'success', period => 1); + $self->start(); - xlog $self, "waiting 5 mins for events to fire, plus some slop"; - sleep(5*60 + 5); + xlog $self, "periodic events run immediately"; - $self->assert_deep_equals({ - B => { live => 0, dead => 6 }, - }, $self->lemming_census()); -} + xlog $self, "waiting 5 mins for events to fire, plus some slop"; + sleep(5 * 60 + 5); -sub test_service_bad_name -{ - my ($self) = @_; - - xlog $self, "services with bad names (Bug 3654)"; - $self->lemming_service(tag => 'foo'); - $self->lemming_service(tag => 'foo_bar'); - $self->lemming_service(tag => 'foo-baz'); - $self->lemming_service(tag => 'foo&baz'); - - # master should exit while adding services, with a message - # to syslog like this - # - # Mar 21 19:53:21 gnb-desktop 0853201/master[8789]: configuration - # file /var/tmp/cass/0853201/conf/cyrus.conf: bad character '-' in - # name on line 2 - # - eval + $self->assert_deep_equals( { - $self->start(); - }; - xlog $self, "start failed (as expected): $@" if $@; - - # XXX can't currently check syslog in this case because start() bailed - # XXX out before we would have started reading it... + B => { live => 0, dead => 6 }, + }, + $self->lemming_census() + ); +} - xlog $self, "master should have exited when service verification failed"; - $self->assert(!$self->{instance}->is_running()); +sub test_service_bad_name { + my ($self) = @_; + + xlog $self, "services with bad names (Bug 3654)"; + $self->lemming_service(tag => 'foo'); + $self->lemming_service(tag => 'foo_bar'); + $self->lemming_service(tag => 'foo-baz'); + $self->lemming_service(tag => 'foo&baz'); + + # master should exit while adding services, with a message + # to syslog like this + # + # Mar 21 19:53:21 gnb-desktop 0853201/master[8789]: configuration + # file /var/tmp/cass/0853201/conf/cyrus.conf: bad character '-' in + # name on line 2 + # + eval { $self->start(); }; + xlog $self, "start failed (as expected): $@" if $@; + + # XXX can't currently check syslog in this case because start() bailed + # XXX out before we would have started reading it... + + xlog $self, "master should have exited when service verification failed"; + $self->assert(!$self->{instance}->is_running()); } -sub test_service_associate -{ - my ($self) = @_; +sub test_service_associate { + my ($self) = @_; - xlog $self, "sending a SIGHUP to a master process with services"; - xlog $self, "whose listen= parameters give more than one result in"; - xlog $self, "getaddrinfo(), such as an IPv4 and IPv6 (Bug 3771)"; + xlog $self, "sending a SIGHUP to a master process with services"; + xlog $self, "whose listen= parameters give more than one result in"; + xlog $self, "getaddrinfo(), such as an IPv4 and IPv6 (Bug 3771)"; - my $host = 'localhost'; + my $host = 'localhost'; - $self->lemming_service(tag => 'foo', host => undef); + $self->lemming_service(tag => 'foo', host => undef); - $self->{instance}->start(); - $self->{instance}->send_sighup(); - $self->{instance}->stop(); + $self->{instance}->start(); + $self->{instance}->send_sighup(); + $self->{instance}->stop(); } -sub XXX_test_service_primary_fail -{ - my ($self) = @_; +sub XXX_test_service_primary_fail { + my ($self) = @_; - my $host = 'localhost'; + my $host = 'localhost'; - my $srv = $self->lemming_service(tag => 'foo', host => undef, mode => 'exit-ipv4/serve'); + my $srv = $self->lemming_service( + tag => 'foo', + host => undef, + mode => 'exit-ipv4/serve' + ); - $self->start(); + $self->start(); - xlog $self, "connection fails due to dead IPv4 lemming"; - my $lemm; - eval - { - $lemm = lemming_connect($srv, 'inet'); - }; - $self->assert_null($lemm); + xlog $self, "connection fails due to dead IPv4 lemming"; + my $lemm; + eval { $lemm = lemming_connect($srv, 'inet'); }; + $self->assert_null($lemm); - xlog $self, "expect 5 dead lemmings"; - $self->assert_deep_equals({ foo => { live => 0, dead => 5 } }, - $self->lemming_census()); + xlog $self, "expect 5 dead lemmings"; + $self->assert_deep_equals({ foo => { live => 0, dead => 5 } }, + $self->lemming_census()); - xlog $self, "check the IPv4 service is really dead"; - eval - { - $lemm = lemming_connect($srv, 'inet'); - }; - $self->assert_null($lemm); - $self->assert_deep_equals({ foo => { live => 0, dead => 5 } }, - $self->lemming_census()); + xlog $self, "check the IPv4 service is really dead"; + eval { $lemm = lemming_connect($srv, 'inet'); }; + $self->assert_null($lemm); + $self->assert_deep_equals({ foo => { live => 0, dead => 5 } }, + $self->lemming_census()); - xlog $self, "breed one IPv6 lemming"; - $lemm = lemming_connect($srv, 'inet6'); - $self->assert_deep_equals({ foo => { live => 1, dead => 5 } }, - $self->lemming_census()); + xlog $self, "breed one IPv6 lemming"; + $lemm = lemming_connect($srv, 'inet6'); + $self->assert_deep_equals({ foo => { live => 1, dead => 5 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ foo => { live => 0, dead => 6 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ foo => { live => 0, dead => 6 } }, + $self->lemming_census()); - xlog $self, "revive the dead IPv4 service"; - $self->{instance}->send_sighup(); + xlog $self, "revive the dead IPv4 service"; + $self->{instance}->send_sighup(); - xlog $self, "connection fails again due to dead IPv4 lemming"; - $lemm = undef; - eval - { - $lemm = lemming_connect($srv, 'inet'); - }; - $self->assert_null($lemm); + xlog $self, "connection fails again due to dead IPv4 lemming"; + $lemm = undef; + eval { $lemm = lemming_connect($srv, 'inet'); }; + $self->assert_null($lemm); - xlog $self, "expect 5 more dead lemmings"; - $self->assert_deep_equals({ foo => { live => 0, dead => 11 } }, - $self->lemming_census()); + xlog $self, "expect 5 more dead lemmings"; + $self->assert_deep_equals({ foo => { live => 0, dead => 11 } }, + $self->lemming_census()); } -sub XXX_test_service_associate_fail -{ - my ($self) = @_; +sub XXX_test_service_associate_fail { + my ($self) = @_; - my $host = 'localhost'; + my $host = 'localhost'; - my $srv = $self->lemming_service(tag => 'foo', host => undef, mode => 'exit-ipv6/serve'); + my $srv = $self->lemming_service( + tag => 'foo', + host => undef, + mode => 'exit-ipv6/serve' + ); - $self->start(); + $self->start(); - xlog $self, "connection fails due to dead IPv6 lemming"; - my $lemm; - eval - { - $lemm = lemming_connect($srv, 'inet6'); - }; - $self->assert_null($lemm); + xlog $self, "connection fails due to dead IPv6 lemming"; + my $lemm; + eval { $lemm = lemming_connect($srv, 'inet6'); }; + $self->assert_null($lemm); - xlog $self, "expect 5 dead lemmings"; - $self->assert_deep_equals({ foo => { live => 0, dead => 5 } }, - $self->lemming_census()); + xlog $self, "expect 5 dead lemmings"; + $self->assert_deep_equals({ foo => { live => 0, dead => 5 } }, + $self->lemming_census()); - xlog $self, "check the IPv6 service is really dead"; - eval - { - $lemm = lemming_connect($srv, 'inet6'); - }; - $self->assert_null($lemm); - $self->assert_deep_equals({ foo => { live => 0, dead => 5 } }, - $self->lemming_census()); + xlog $self, "check the IPv6 service is really dead"; + eval { $lemm = lemming_connect($srv, 'inet6'); }; + $self->assert_null($lemm); + $self->assert_deep_equals({ foo => { live => 0, dead => 5 } }, + $self->lemming_census()); - xlog $self, "breed one IPv4 lemming"; - $lemm = lemming_connect($srv, 'inet'); - $self->assert_deep_equals({ foo => { live => 1, dead => 5 } }, - $self->lemming_census()); + xlog $self, "breed one IPv4 lemming"; + $lemm = lemming_connect($srv, 'inet'); + $self->assert_deep_equals({ foo => { live => 1, dead => 5 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ foo => { live => 0, dead => 6 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ foo => { live => 0, dead => 6 } }, + $self->lemming_census()); - xlog $self, "revive the dead IPv6 service"; - $self->{instance}->send_sighup(); + xlog $self, "revive the dead IPv6 service"; + $self->{instance}->send_sighup(); - xlog $self, "connection fails again due to dead IPv6 lemming"; - $lemm = undef; - eval - { - $lemm = lemming_connect($srv, 'inet6'); - }; - $self->assert_null($lemm); + xlog $self, "connection fails again due to dead IPv6 lemming"; + $lemm = undef; + eval { $lemm = lemming_connect($srv, 'inet6'); }; + $self->assert_null($lemm); - xlog $self, "expect 5 dead lemmings"; - $self->assert_deep_equals({ foo => { live => 0, dead => 11 } }, - $self->lemming_census()); + xlog $self, "expect 5 dead lemmings"; + $self->assert_deep_equals({ foo => { live => 0, dead => 11 } }, + $self->lemming_census()); } -sub test_sighup_recycling -{ - my ($self) = @_; +sub test_sighup_recycling { + my ($self) = @_; - my $host = 'localhost'; + my $host = 'localhost'; - my $srv = $self->lemming_service(tag => 'foo', prefork => 1); - $self->start(); - $self->lemming_wait(foo => { live => 1 }); + my $srv = $self->lemming_service(tag => 'foo', prefork => 1); + $self->start(); + $self->lemming_wait(foo => { live => 1 }); - xlog $self, "preforked, so one lemming running already"; - $self->assert_deep_equals({ foo => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "preforked, so one lemming running already"; + $self->assert_deep_equals({ foo => { live => 1, dead => 0 } }, + $self->lemming_census()); - my $lemm = lemming_connect($srv); - $self->lemming_wait(foo => { live => 2 }); + my $lemm = lemming_connect($srv); + $self->lemming_wait(foo => { live => 2 }); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ foo => { live => 2, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ foo => { live => 2, dead => 0 } }, + $self->lemming_census()); - $self->{instance}->send_sighup(); - $self->lemming_wait(foo => { live => 2, dead => 1 }); + $self->{instance}->send_sighup(); + $self->lemming_wait(foo => { live => 2, dead => 1 }); - xlog $self, "recycled, so expect one dead lemming"; - $self->assert_deep_equals({ foo => { live => 2, dead => 1 } }, - $self->lemming_census()); + xlog $self, "recycled, so expect one dead lemming"; + $self->assert_deep_equals({ foo => { live => 2, dead => 1 } }, + $self->lemming_census()); - $self->{instance}->send_sighup(); - $self->lemming_wait(foo => { live => 2, dead => 2 }); + $self->{instance}->send_sighup(); + $self->lemming_wait(foo => { live => 2, dead => 2 }); - xlog $self, "recycled, again so expect one more dead lemming"; - $self->assert_deep_equals({ foo => { live => 2, dead => 2 } }, - $self->lemming_census()); + xlog $self, "recycled, again so expect one more dead lemming"; + $self->assert_deep_equals({ foo => { live => 2, dead => 2 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "always at least one live lemming"; - $self->assert_deep_equals({ foo => { live => 1, dead => 3 } }, - $self->lemming_census()); + xlog $self, "always at least one live lemming"; + $self->assert_deep_equals({ foo => { live => 1, dead => 3 } }, + $self->lemming_census()); } -sub test_sighup_reloading -{ - my ($self) = @_; - - my $host = 'localhost'; - - my $srvA = $self->lemming_service(tag => 'A'); - $self->start(); - my $srvB = $self->lemming_service(tag => 'B'); +sub test_sighup_reloading { + my ($self) = @_; + my $host = 'localhost'; - my $lemmA = lemming_connect($srvA); + my $srvA = $self->lemming_service(tag => 'A'); + $self->start(); + my $srvB = $self->lemming_service(tag => 'B'); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + my $lemmA = lemming_connect($srvA); - lemming_push($lemmA, 'success'); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, - $self->lemming_census()); - - xlog $self, "connection fails due to unexisting lemming"; - my $lemmB; - eval - { - $lemmB = lemming_connect($srvB); - }; - $self->assert_null($lemmB); + lemming_push($lemmA, 'success'); - $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, + $self->lemming_census()); + xlog $self, "connection fails due to unexisting lemming"; + my $lemmB; + eval { $lemmB = lemming_connect($srvB); }; + $self->assert_null($lemmB); - xlog $self, "add service in cyrus.conf and reload"; - $self->{instance}->_generate_master_conf(); - $self->{instance}->send_sighup(); + $self->assert_deep_equals({ A => { live => 0, dead => 1 } }, + $self->lemming_census()); - $lemmA = lemming_connect($srvA); + xlog $self, "add service in cyrus.conf and reload"; + $self->{instance}->_generate_master_conf(); + $self->{instance}->send_sighup(); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 1, dead => 1 } }, - $self->lemming_census()); + $lemmA = lemming_connect($srvA); - lemming_push($lemmA, 'success'); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 1, dead => 1 } }, + $self->lemming_census()); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 2 } }, - $self->lemming_census()); + lemming_push($lemmA, 'success'); - $lemmB = lemming_connect($srvB); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals({ A => { live => 0, dead => 2 } }, + $self->lemming_census()); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 0, dead => 2 }, - B => { live => 1, dead => 0 } }, - $self->lemming_census()); + $lemmB = lemming_connect($srvB); - lemming_push($lemmB, 'success'); - - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 2 }, - B => { live => 0, dead => 1 } }, - $self->lemming_census()); - - - xlog $self, "remove service in cyrus.conf and reload"; - $self->{instance}->remove_service('A'); - $self->{instance}->_generate_master_conf(); - $self->{instance}->send_sighup(); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals( + { + A => { live => 0, dead => 2 }, + B => { live => 1, dead => 0 } + }, + $self->lemming_census() + ); - # wait a moment for the sighup to be processed - # XXX next test does something tricky with prefork/wait, - # XXX but i'm not sure if that can be used here. - sleep 1; + lemming_push($lemmB, 'success'); - xlog $self, "connection fails due to unexisting lemming"; - $lemmA = undef; - eval + xlog $self, "no more live lemmings"; + $self->assert_deep_equals( { - $lemmA = lemming_connect($srvA); - }; - $self->assert_null($lemmA); - - $self->assert_deep_equals({ A => { live => 0, dead => 2 }, - B => { live => 0, dead => 1 } }, - $self->lemming_census()); + A => { live => 0, dead => 2 }, + B => { live => 0, dead => 1 } + }, + $self->lemming_census() + ); + + xlog $self, "remove service in cyrus.conf and reload"; + $self->{instance}->remove_service('A'); + $self->{instance}->_generate_master_conf(); + $self->{instance}->send_sighup(); + + # wait a moment for the sighup to be processed + # XXX next test does something tricky with prefork/wait, + # XXX but i'm not sure if that can be used here. + sleep 1; + + xlog $self, "connection fails due to unexisting lemming"; + $lemmA = undef; + eval { $lemmA = lemming_connect($srvA); }; + $self->assert_null($lemmA); + + $self->assert_deep_equals( + { + A => { live => 0, dead => 2 }, + B => { live => 0, dead => 1 } + }, + $self->lemming_census() + ); - $lemmB = lemming_connect($srvB); + $lemmB = lemming_connect($srvB); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 0, dead => 2 }, - B => { live => 1, dead => 1 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals( + { + A => { live => 0, dead => 2 }, + B => { live => 1, dead => 1 } + }, + $self->lemming_census() + ); - lemming_push($lemmB, 'success'); + lemming_push($lemmB, 'success'); - xlog $self, "no more live lemmings"; - $self->assert_deep_equals({ A => { live => 0, dead => 2 }, - B => { live => 0, dead => 2 } }, - $self->lemming_census()); + xlog $self, "no more live lemmings"; + $self->assert_deep_equals( + { + A => { live => 0, dead => 2 }, + B => { live => 0, dead => 2 } + }, + $self->lemming_census() + ); } -sub test_sighup_reloading_listen -{ - my ($self) = @_; +sub test_sighup_reloading_listen { + my ($self) = @_; - my $host = 'localhost'; + my $host = 'localhost'; - # Note: we need to wait for SIGHUP to be processed; prefork can do the trick - # to help us check that - my $srv = $self->lemming_service(tag => 'A', prefork => 1); - $self->start(); - $self->lemming_wait(A => { live => 1 }); + # Note: we need to wait for SIGHUP to be processed; prefork can do the trick + # to help us check that + my $srv = $self->lemming_service(tag => 'A', prefork => 1); + $self->start(); + $self->lemming_wait(A => { live => 1 }); - xlog $self, "preforked, so one lemming running already"; - $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, - $self->lemming_census()); + xlog $self, "preforked, so one lemming running already"; + $self->assert_deep_equals({ A => { live => 1, dead => 0 } }, + $self->lemming_census()); - my $lemm = lemming_connect($srv); - $self->lemming_wait(A => { live => 2 }); + my $lemm = lemming_connect($srv); + $self->lemming_wait(A => { live => 2 }); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 2, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 2, dead => 0 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); - - xlog $self, "always at least one live lemming"; - $self->assert_deep_equals({ A => { live => 1, dead => 1 } }, - $self->lemming_census()); + lemming_push($lemm, 'success'); + xlog $self, "always at least one live lemming"; + $self->assert_deep_equals({ A => { live => 1, dead => 1 } }, + $self->lemming_census()); - xlog $self, "change service listen port in cyrus.conf and reload"; - my $port1 = $srv->port(); - $srv->set_port(); - my $port2 = $srv->port(); - $self->assert_not_equals($port1, $port2); - $self->{instance}->_generate_master_conf(); - $self->{instance}->send_sighup(); - # Here is the trick with prefork: wait for the previously forked A instance - # to die and be replaced by a new one - $self->lemming_wait(A => { live => 1, dead => 2 }); + xlog $self, "change service listen port in cyrus.conf and reload"; + my $port1 = $srv->port(); + $srv->set_port(); + my $port2 = $srv->port(); + $self->assert_not_equals($port1, $port2); + $self->{instance}->_generate_master_conf(); + $self->{instance}->send_sighup(); + # Here is the trick with prefork: wait for the previously forked A instance + # to die and be replaced by a new one + $self->lemming_wait(A => { live => 1, dead => 2 }); - $self->assert_deep_equals({ A => { live => 1, dead => 2 } }, - $self->lemming_census()); + $self->assert_deep_equals({ A => { live => 1, dead => 2 } }, + $self->lemming_census()); - $lemm = lemming_connect($srv); - $self->lemming_wait(A => { live => 2 }); + $lemm = lemming_connect($srv); + $self->lemming_wait(A => { live => 2 }); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 2, dead => 2 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 2, dead => 2 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "always at least one live lemming"; - $self->assert_deep_equals({ A => { live => 1, dead => 3 } }, - $self->lemming_census()); + xlog $self, "always at least one live lemming"; + $self->assert_deep_equals({ A => { live => 1, dead => 3 } }, + $self->lemming_census()); } -sub test_sighup_reloading_proto -{ - my ($self) = @_; +sub test_sighup_reloading_proto { + my ($self) = @_; - my $host = 'localhost'; + my $host = 'localhost'; - # Note: we need to wait for SIGHUP to be processed; prefork can do the trick - # to help us check that - # Note: since we are listening on IPv4 *and* IPv6, there will be 2 preforked - # instances - my $srv = $self->lemming_service(tag => 'A', host => undef, prefork => 1); - $self->start(); - $self->lemming_wait(A => { live => 2 }); + # Note: we need to wait for SIGHUP to be processed; prefork can do the trick + # to help us check that + # Note: since we are listening on IPv4 *and* IPv6, there will be 2 preforked + # instances + my $srv = $self->lemming_service(tag => 'A', host => undef, prefork => 1); + $self->start(); + $self->lemming_wait(A => { live => 2 }); - xlog $self, "preforked, so two lemmings running already"; - $self->assert_deep_equals({ A => { live => 2, dead => 0 } }, - $self->lemming_census()); + xlog $self, "preforked, so two lemmings running already"; + $self->assert_deep_equals({ A => { live => 2, dead => 0 } }, + $self->lemming_census()); - # check IPv4 - my $lemm = lemming_connect($srv, 'inet'); - $self->lemming_wait(A => { live => 3 }); + # check IPv4 + my $lemm = lemming_connect($srv, 'inet'); + $self->lemming_wait(A => { live => 3 }); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 3, dead => 0 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 3, dead => 0 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); - - xlog $self, "always at least two live lemmings"; - $self->assert_deep_equals({ A => { live => 2, dead => 1 } }, - $self->lemming_census()); + lemming_push($lemm, 'success'); - # check IPv6 - $lemm = lemming_connect($srv, 'inet6'); - $self->lemming_wait(A => { live => 3 }); + xlog $self, "always at least two live lemmings"; + $self->assert_deep_equals({ A => { live => 2, dead => 1 } }, + $self->lemming_census()); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 3, dead => 1 } }, - $self->lemming_census()); + # check IPv6 + $lemm = lemming_connect($srv, 'inet6'); + $self->lemming_wait(A => { live => 3 }); - lemming_push($lemm, 'success'); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 3, dead => 1 } }, + $self->lemming_census()); - xlog $self, "always at least two live lemmings"; - $self->assert_deep_equals({ A => { live => 2, dead => 2 } }, - $self->lemming_census()); + lemming_push($lemm, 'success'); + xlog $self, "always at least two live lemmings"; + $self->assert_deep_equals({ A => { live => 2, dead => 2 } }, + $self->lemming_census()); - xlog $self, "change service listen proto in cyrus.conf and reload"; - $srv->set_master_param('proto', 'tcp4'); - $self->{instance}->_generate_master_conf(); - $self->{instance}->send_sighup(); - # Here is the trick with prefork: wait for the previously forked A instances - # to die and be replaced by a new one - $self->lemming_wait(A => { live => 1, dead => 4 }); + xlog $self, "change service listen proto in cyrus.conf and reload"; + $srv->set_master_param('proto', 'tcp4'); + $self->{instance}->_generate_master_conf(); + $self->{instance}->send_sighup(); + # Here is the trick with prefork: wait for the previously forked A instances + # to die and be replaced by a new one + $self->lemming_wait(A => { live => 1, dead => 4 }); - $self->assert_deep_equals({ A => { live => 1, dead => 4 } }, - $self->lemming_census()); + $self->assert_deep_equals({ A => { live => 1, dead => 4 } }, + $self->lemming_census()); - # check IPv4 - $lemm = lemming_connect($srv, 'inet'); - $self->lemming_wait(A => { live => 2 }); + # check IPv4 + $lemm = lemming_connect($srv, 'inet'); + $self->lemming_wait(A => { live => 2 }); - xlog $self, "connected so one lemming forked"; - $self->assert_deep_equals({ A => { live => 2, dead => 4 } }, - $self->lemming_census()); + xlog $self, "connected so one lemming forked"; + $self->assert_deep_equals({ A => { live => 2, dead => 4 } }, + $self->lemming_census()); - lemming_push($lemm, 'success'); + lemming_push($lemm, 'success'); - xlog $self, "always at least one live lemming"; - $self->assert_deep_equals({ A => { live => 1, dead => 5 } }, - $self->lemming_census()); + xlog $self, "always at least one live lemming"; + $self->assert_deep_equals({ A => { live => 1, dead => 5 } }, + $self->lemming_census()); - # check IPv6 - xlog $self, "connection fails due to unexisting IPv6 lemming"; - $lemm = undef; - eval - { - $lemm = lemming_connect($srv, 'inet6'); - }; - $self->assert_null($lemm); + # check IPv6 + xlog $self, "connection fails due to unexisting IPv6 lemming"; + $lemm = undef; + eval { $lemm = lemming_connect($srv, 'inet6'); }; + $self->assert_null($lemm); - xlog $self, "always at least one live lemming"; - $self->assert_deep_equals({ A => { live => 1, dead => 5 } }, - $self->lemming_census()); + xlog $self, "always at least one live lemming"; + $self->assert_deep_equals({ A => { live => 1, dead => 5 } }, + $self->lemming_census()); } -sub test_ready_file_new -{ - my ($self) = @_; +sub test_ready_file_new { + my ($self) = @_; - my $ready_file = $self->{instance}->get_basedir() . '/conf/master.ready'; - my $pid_file = $self->{instance}->_pid_file(); + my $ready_file = $self->{instance}->get_basedir() . '/conf/master.ready'; + my $pid_file = $self->{instance}->_pid_file(); - # pid file should not already exist - my $pid_sb = stat($pid_file); - $self->assert_null($pid_sb); + # pid file should not already exist + my $pid_sb = stat($pid_file); + $self->assert_null($pid_sb); - # ready file should not already exist - my $ready_sb = stat($ready_file); - $self->assert_null($ready_sb); + # ready file should not already exist + my $ready_sb = stat($ready_file); + $self->assert_null($ready_sb); - # start cyrus - $self->start(); + # start cyrus + $self->start(); - # pid file should exist now - $pid_sb = stat($pid_file); - $self->assert_not_null($pid_sb); + # pid file should exist now + $pid_sb = stat($pid_file); + $self->assert_not_null($pid_sb); - # ready file should exist soon... - timed_wait(sub { $ready_sb = stat($ready_file) }, - description => "$ready_file to exist"); - $self->assert_not_null($ready_sb); + # ready file should exist soon... + timed_wait( + sub { $ready_sb = stat($ready_file) }, + description => "$ready_file to exist" + ); + $self->assert_not_null($ready_sb); - # ready file should be newer than pid file - $self->assert_num_gte($pid_sb->mtime, $ready_sb->mtime); + # ready file should be newer than pid file + $self->assert_num_gte($pid_sb->mtime, $ready_sb->mtime); } -sub test_ready_file_exists -{ - my ($self) = @_; +sub test_ready_file_exists { + my ($self) = @_; - # force basedir to be computed - $self->{instance}->get_basedir(); + # force basedir to be computed + $self->{instance}->get_basedir(); - # cannot be under basedir because it'll be blown away at startup - my $ready_file = "/tmp/cassandane-$$-master.ready"; - $self->{instance}->{config}->set('master_ready_file', $ready_file); + # cannot be under basedir because it'll be blown away at startup + my $ready_file = "/tmp/cassandane-$$-master.ready"; + $self->{instance}->{config}->set('master_ready_file', $ready_file); - # must be after the get_basedir() call above - my $pid_file = $self->{instance}->_pid_file(); + # must be after the get_basedir() call above + my $pid_file = $self->{instance}->_pid_file(); - system("touch", $ready_file) == 0 or die "touch $ready_file: $?"; - sleep 3; + system("touch", $ready_file) == 0 or die "touch $ready_file: $?"; + sleep 3; - # pid file should not already exist - my $pid_sb = stat($pid_file); - $self->assert_null($pid_sb); + # pid file should not already exist + my $pid_sb = stat($pid_file); + $self->assert_null($pid_sb); - # ready file should already exist - my $ready_sb = stat($ready_file); - $self->assert_not_null($ready_sb); - my $orig_mtime = $ready_sb->mtime; + # ready file should already exist + my $ready_sb = stat($ready_file); + $self->assert_not_null($ready_sb); + my $orig_mtime = $ready_sb->mtime; - # start cyrus - $self->start(); + # start cyrus + $self->start(); - # pid file should exist now - $pid_sb = stat($pid_file); - $self->assert_not_null($pid_sb); + # pid file should exist now + $pid_sb = stat($pid_file); + $self->assert_not_null($pid_sb); - # ready file should be touched soon - timed_wait(sub { - $ready_sb = stat($ready_file); - return 1 if $ready_sb->mtime >= $pid_sb->mtime; - return undef; - }, - description => "$ready_file to be newer than $pid_file"); - $self->assert_not_null($ready_sb); + # ready file should be touched soon + timed_wait( + sub { + $ready_sb = stat($ready_file); + return 1 if $ready_sb->mtime >= $pid_sb->mtime; + return undef; + }, + description => "$ready_file to be newer than $pid_file" + ); + $self->assert_not_null($ready_sb); - # ready file should be newer than pid file - $self->assert_num_gte($pid_sb->mtime, $ready_sb->mtime); - $self->assert_num_gt($orig_mtime, $ready_sb->mtime); + # ready file should be newer than pid file + $self->assert_num_gte($pid_sb->mtime, $ready_sb->mtime); + $self->assert_num_gt($orig_mtime, $ready_sb->mtime); - # don't pollute /tmp - unlink $ready_file; + # don't pollute /tmp + unlink $ready_file; } 1; diff --git a/cassandane/Cassandane/Cyrus/MaxMessages.pm b/cassandane/Cassandane/Cyrus/MaxMessages.pm index 36080a3ecc..63ba561b6c 100644 --- a/cassandane/Cassandane/Cyrus/MaxMessages.pm +++ b/cassandane/Cassandane/Cyrus/MaxMessages.pm @@ -51,66 +51,64 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Generator; use Cassandane::Util::Log; -my $LOTS = 20; +my $LOTS = 20; my $LIMITED = 5; -sub new -{ - my $class = shift; - - my $config = Cassandane::Config->default()->clone(); - $config->set( - caldav_realm => 'Cassandane', - calendar_user_address_set => 'example.com', - caldav_historical_age => -1, - conversations => 1, - httpmodules => 'caldav carddav jmap', - httpallowcompress => 'no', - icalendar_max_size => 100000, - jmap_nonstandard_extensions => 'yes', - sieve_maxscripts => $LOTS, - vcard_max_size => 100000, - ); - - return $class->SUPER::new({ - config => $config, - jmap => 1, - services => ['imap', 'http', 'sieve'], - }, @_); +sub new { + my $class = shift; + + my $config = Cassandane::Config->default()->clone(); + $config->set( + caldav_realm => 'Cassandane', + calendar_user_address_set => 'example.com', + caldav_historical_age => -1, + conversations => 1, + httpmodules => 'caldav carddav jmap', + httpallowcompress => 'no', + icalendar_max_size => 100000, + jmap_nonstandard_extensions => 'yes', + sieve_maxscripts => $LOTS, + vcard_max_size => 100000, + ); + + return $class->SUPER::new( + { + config => $config, + jmap => 1, + services => [ 'imap', 'http', 'sieve' ], + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - $ENV{DEBUGDAV} = 1; - $ENV{JMAP_ALWAYS_FULL} = 1; +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + $ENV{DEBUGDAV} = 1; + $ENV{JMAP_ALWAYS_FULL} = 1; } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub skip_check -{ - my ($self) = @_; +sub skip_check { + my ($self) = @_; - # XXX skip all tests from this suite in verbose mode for now -- see - # XXX detailed comment on put_submission below - my $reason = "test would hang in verbose mode"; - return get_verbose() ? $reason : undef; + # XXX skip all tests from this suite in verbose mode for now -- see + # XXX detailed comment on put_submission below + my $reason = "test would hang in verbose mode"; + return get_verbose() ? $reason : undef; } -sub _random_vevent -{ - my ($self) = @_; - state $counter = 1; +sub _random_vevent { + my ($self) = @_; + state $counter = 1; - my $uuid = $self->{caldav}->genuuid(); + my $uuid = $self->{caldav}->genuuid(); - my $ics = <<"EOF"; + my $ics = <<"EOF"; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -127,17 +125,16 @@ END:VEVENT END:VCALENDAR EOF - $counter ++; + $counter++; - return $ics, $uuid; + return $ics, $uuid; } -sub _random_vcard -{ - my $fn = Cassandane::Generator::make_random_address()->name(); - my ($first, $middle, $last) = split /[\s\.]+/, $fn; - my $n = "$last;$first;$middle;;"; - my $str = <<"EOF"; +sub _random_vcard { + my $fn = Cassandane::Generator::make_random_address()->name(); + my ($first, $middle, $last) = split /[\s\.]+/, $fn; + my $n = "$last;$first;$middle;;"; + my $str = <<"EOF"; BEGIN:VCARD VERSION:3.0 N:$n @@ -145,105 +142,112 @@ FN:$fn REV:2008-04-24T19:52:43Z END:VCARD EOF - return $str; + return $str; } -sub put_vevent -{ - my ($self, $calendarid) = @_; +sub put_vevent { + my ($self, $calendarid) = @_; - my ($ics, $uuid) = $self->_random_vevent(); - my $href = "$calendarid/$uuid.ics"; + my ($ics, $uuid) = $self->_random_vevent(); + my $href = "$calendarid/$uuid.ics"; - $self->{caldav}->Request('PUT', $href, $ics, - 'Content-Type' => 'text/calendar'); + $self->{caldav} + ->Request('PUT', $href, $ics, 'Content-Type' => 'text/calendar'); } -sub put_vcard -{ - my ($self, $addrbookid) = @_; +sub put_vcard { + my ($self, $addrbookid) = @_; - my $vcard = Net::CardDAVTalk::VCard->new_fromstring(_random_vcard()); + my $vcard = Net::CardDAVTalk::VCard->new_fromstring(_random_vcard()); - $self->{carddav}->NewContact($addrbookid, $vcard); + $self->{carddav}->NewContact($addrbookid, $vcard); } -sub put_script -{ - my ($self) = @_; - state $counter = 1; - - my $name = "script $counter"; - my $script = "# $name\r\nkeep;\r\n"; - $counter ++; - - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/sieve', - 'https://cyrusimap.org/ns/jmap/blob', - ]); - - my $res = $self->{jmap}->CallMethods([ - ['Blob/upload', { - create => { - "A" => { data => [{'data:asText' => $script}] } - } - }, "R0"], - ['SieveScript/set', { - create => { - "1" => { - name => $name, - blobId => "#A" - }, - }, - }, "R1"], - ]); +sub put_script { + my ($self) = @_; + state $counter = 1; + + my $name = "script $counter"; + my $script = "# $name\r\nkeep;\r\n"; + $counter++; + + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/sieve', + 'https://cyrusimap.org/ns/jmap/blob', + ]); + + my $res = $self->{jmap}->CallMethods([ + [ + 'Blob/upload', + { + create => { + "A" => { data => [ { 'data:asText' => $script } ] } + } + }, + "R0" + ], + [ + 'SieveScript/set', + { + create => { + "1" => { + name => $name, + blobId => "#A" + }, + }, + }, + "R1" + ], + ]); - $self->assert_not_null($res); - $self->assert_not_null($res->[1][1]{created}{"1"}{id}); + $self->assert_not_null($res); + $self->assert_not_null($res->[1][1]{created}{"1"}{id}); } # XXX lots of copies of getinbox -- dedup them! -sub getinbox -{ - my ($self, $args) = @_; +sub getinbox { + my ($self, $args) = @_; - $args = {} unless $args; + $args = {} unless $args; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get existing mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', $args, "R1"]]); - $self->assert_not_null($res); + xlog $self, "get existing mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', $args, "R1" ] ]); + $self->assert_not_null($res); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - return $m{"Inbox"}; + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + return $m{"Inbox"}; } -sub _submissions_mailbox -{ - my ($self, $counter) = @_; +sub _submissions_mailbox { + my ($self, $counter) = @_; - my $jmap = $self->{jmap}; - my $folder = "submission $counter"; + my $jmap = $self->{jmap}; + my $folder = "submission $counter"; - my $inboxId = $self->getinbox()->{id}; - $self->assert_not_null($inboxId); + my $inboxId = $self->getinbox()->{id}; + $self->assert_not_null($inboxId); - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - "m$counter" => { - parentId => $inboxId, - name => $folder, - }, + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "m$counter" => { + parentId => $inboxId, + name => $folder, }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - $self->assert_not_null($res); - $self->assert_not_null($res->[0][1]{created}{"m$counter"}{id}); + $self->assert_not_null($res); + $self->assert_not_null($res->[0][1]{created}{"m$counter"}{id}); - return $res->[0][1]{created}{"m$counter"}{id}; + return $res->[0][1]{created}{"m$counter"}{id}; } # XXX When creating JMAP EmailSubmissions objects, the http service @@ -257,309 +261,301 @@ sub _submissions_mailbox # XXX correctly in verbose mode, sorry. This affects tests in the # XXX JMAPEmailSubmission suite too, though it affects this one worse # XXX because it wants to create a lot more EmailSubmissions objects. -sub put_submission -{ - my ($self) = @_; - state $counter = 0; - - $counter ++; - - my $jmap = $self->{jmap}; - $jmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - ]); - - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityId = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityId); - - # upload each draft to its own mailbox so that we don't accidentally - # exceed mailbox_maxmessages_email - my $mailboxId = $self->_submissions_mailbox($counter); - $self->assert_not_null($mailboxId); - - my $rcpt = Cassandane::Generator::make_random_address(); - - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - "m$counter" => { - mailboxIds => { - $mailboxId => JSON::true, - }, - from => [{ - name => 'cassandane', - email => 'cassandane@local', - }], - to => [{ - name => $rcpt->name(), - email => $rcpt->address(), - }], - subject => "message $counter", - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'hello world', - } - }, - }, +sub put_submission { + my ($self) = @_; + state $counter = 0; + + $counter++; + + my $jmap = $self->{jmap}; + $jmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + ]); + + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityId = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityId); + + # upload each draft to its own mailbox so that we don't accidentally + # exceed mailbox_maxmessages_email + my $mailboxId = $self->_submissions_mailbox($counter); + $self->assert_not_null($mailboxId); + + my $rcpt = Cassandane::Generator::make_random_address(); + + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + "m$counter" => { + mailboxIds => { + $mailboxId => JSON::true, + }, + from => [ { + name => 'cassandane', + email => 'cassandane@local', + } ], + to => [ { + name => $rcpt->name(), + email => $rcpt->address(), + } ], + subject => "message $counter", + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'hello world', + } }, - }, 'R1'], - [ 'EmailSubmission/set', { - create => { - "s$counter" => { - identityId => $identityId, - emailId => "#m$counter", - envelope => { - mailFrom => { - email => 'cassandane@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [{ - email => $rcpt->address(), - }], - }, + }, + }, + }, + 'R1' + ], + [ + 'EmailSubmission/set', + { + create => { + "s$counter" => { + identityId => $identityId, + emailId => "#m$counter", + envelope => { + mailFrom => { + email => 'cassandane@localhost', + parameters => { + "holdfor" => "30", } - }, - }, 'R2' ], - ]); - - $self->assert_not_null($res); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar keys %{$res->[0][1]{notCreated}}); - $self->assert_num_equals(1, scalar keys %{$res->[1][1]{created}}); - $self->assert_num_equals(0, scalar keys %{$res->[1][1]{notCreated}}); + }, + rcptTo => [ { + email => $rcpt->address(), + } ], + }, + } + }, + }, + 'R2' + ], + ]); + + $self->assert_not_null($res); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar keys %{ $res->[0][1]{notCreated} }); + $self->assert_num_equals(1, scalar keys %{ $res->[1][1]{created} }); + $self->assert_num_equals(0, scalar keys %{ $res->[1][1]{notCreated} }); } -sub put_email -{ - my ($self) = @_; - state $counter = 0; +sub put_email { + my ($self) = @_; + state $counter = 0; - $counter ++; + $counter++; - $self->make_message(subject => "message $counter"); + $self->make_message(subject => "message $counter"); } sub test_maxmsg_addressbook_limited - :needs_component_sieve :needs_component_jmap - :JMAPExtensions - :NoStartInstances -{ - my ($self) = @_; - - my $mailbox_maxmessages_addressbook = $LIMITED; - $self->{instance}->{config}->set( - mailbox_maxmessages_addressbook => $mailbox_maxmessages_addressbook, - ); - $self->_start_instances(); - $self->_setup_http_service_objects(); - - my $carddav = $self->{carddav}; - my $addrbookid = $carddav->NewAddressBook('foo'); - $self->assert_not_null($addrbookid); - - # should be able to upload 5 - foreach my $i (1..$mailbox_maxmessages_addressbook) { - $self->put_vcard($addrbookid); - } - - # but any more should be rejected - eval { - $self->put_vcard($addrbookid); - }; - my $e = $@; - $self->assert_not_null($e); - $self->assert_matches(qr{quota-not-exceeded}, $e); - - # should have syslogged about it too - $self->assert_syslog_matches($self->{instance}, - qr{client hit per-addressbook exists limit}); - - # should be able to upload lots of calendar events - my $caldav = $self->{caldav}; - my $calendarid = $caldav->NewCalendar({name => 'mycalendar'}); - $self->assert_not_null($calendarid); - foreach my $i (1..$LOTS) { - $self->put_vevent($calendarid); - } - - # should be able to upload lots of sieve scripts - foreach my $i (1..$LOTS) { - $self->put_script(); - } - - # should be able to upload lots of jmap submissions - foreach my $i (1..$LOTS) { - $self->put_submission(); - } - - # should be able to upload lots of regular emails - foreach my $i (1..$LOTS) { - $self->put_email(); - } + : needs_component_sieve : needs_component_jmap + : JMAPExtensions + : NoStartInstances { + my ($self) = @_; + + my $mailbox_maxmessages_addressbook = $LIMITED; + $self->{instance}->{config} + ->set(mailbox_maxmessages_addressbook => $mailbox_maxmessages_addressbook,); + $self->_start_instances(); + $self->_setup_http_service_objects(); + + my $carddav = $self->{carddav}; + my $addrbookid = $carddav->NewAddressBook('foo'); + $self->assert_not_null($addrbookid); + + # should be able to upload 5 + foreach my $i (1 .. $mailbox_maxmessages_addressbook) { + $self->put_vcard($addrbookid); + } + + # but any more should be rejected + eval { $self->put_vcard($addrbookid); }; + my $e = $@; + $self->assert_not_null($e); + $self->assert_matches(qr{quota-not-exceeded}, $e); + + # should have syslogged about it too + $self->assert_syslog_matches($self->{instance}, + qr{client hit per-addressbook exists limit}); + + # should be able to upload lots of calendar events + my $caldav = $self->{caldav}; + my $calendarid = $caldav->NewCalendar({ name => 'mycalendar' }); + $self->assert_not_null($calendarid); + foreach my $i (1 .. $LOTS) { + $self->put_vevent($calendarid); + } + + # should be able to upload lots of sieve scripts + foreach my $i (1 .. $LOTS) { + $self->put_script(); + } + + # should be able to upload lots of jmap submissions + foreach my $i (1 .. $LOTS) { + $self->put_submission(); + } + + # should be able to upload lots of regular emails + foreach my $i (1 .. $LOTS) { + $self->put_email(); + } } sub test_maxmsg_calendar_limited - :needs_component_sieve :needs_component_jmap - :JMAPExtensions - :NoStartInstances -{ - my ($self) = @_; - - my $mailbox_maxmessages_calendar = $LIMITED; - $self->{instance}->{config}->set( - mailbox_maxmessages_calendar => $mailbox_maxmessages_calendar, - ); - $self->_start_instances(); - $self->_setup_http_service_objects(); - - my $caldav = $self->{caldav}; - my $calendarid = $caldav->NewCalendar({name => 'mycalendar'}); - $self->assert_not_null($calendarid); - - # should be able to upload 5 - foreach my $i (1..$mailbox_maxmessages_calendar) { - $self->put_vevent($calendarid); - } - - # but any more should be rejected - eval { - $self->put_vevent($calendarid); - }; - my $e = $@; - $self->assert_not_null($e); - $self->assert_matches(qr{quota-not-exceeded}, $e); - - # should have syslogged about it too - $self->assert_syslog_matches($self->{instance}, - qr{client hit per-calendar exists limit}); - - # should be able to upload lots of contacts - my $carddav = $self->{carddav}; - my $addrbookid = $carddav->NewAddressBook('foo'); - $self->assert_not_null($addrbookid); - foreach my $i (1..$LOTS) { - $self->put_vcard($addrbookid); - } - - # should be able to upload lots of sieve scripts - foreach my $i (1..$LOTS) { - $self->put_script(); - } - - # should be able to upload lots of jmap submissions - foreach my $i (1..$LOTS) { - $self->put_submission(); - } - - # should be able to upload lots of regular emails - foreach my $i (1..$LOTS) { - $self->put_email(); - } + : needs_component_sieve : needs_component_jmap + : JMAPExtensions + : NoStartInstances { + my ($self) = @_; + + my $mailbox_maxmessages_calendar = $LIMITED; + $self->{instance}->{config} + ->set(mailbox_maxmessages_calendar => $mailbox_maxmessages_calendar,); + $self->_start_instances(); + $self->_setup_http_service_objects(); + + my $caldav = $self->{caldav}; + my $calendarid = $caldav->NewCalendar({ name => 'mycalendar' }); + $self->assert_not_null($calendarid); + + # should be able to upload 5 + foreach my $i (1 .. $mailbox_maxmessages_calendar) { + $self->put_vevent($calendarid); + } + + # but any more should be rejected + eval { $self->put_vevent($calendarid); }; + my $e = $@; + $self->assert_not_null($e); + $self->assert_matches(qr{quota-not-exceeded}, $e); + + # should have syslogged about it too + $self->assert_syslog_matches($self->{instance}, + qr{client hit per-calendar exists limit}); + + # should be able to upload lots of contacts + my $carddav = $self->{carddav}; + my $addrbookid = $carddav->NewAddressBook('foo'); + $self->assert_not_null($addrbookid); + foreach my $i (1 .. $LOTS) { + $self->put_vcard($addrbookid); + } + + # should be able to upload lots of sieve scripts + foreach my $i (1 .. $LOTS) { + $self->put_script(); + } + + # should be able to upload lots of jmap submissions + foreach my $i (1 .. $LOTS) { + $self->put_submission(); + } + + # should be able to upload lots of regular emails + foreach my $i (1 .. $LOTS) { + $self->put_email(); + } } sub test_maxmsg_email_limited - :needs_component_sieve :needs_component_jmap - :JMAPExtensions - :NoStartInstances -{ - my ($self) = @_; - - my $mailbox_maxmessages_email = $LIMITED; - $self->{instance}->{config}->set( - mailbox_maxmessages_email => $mailbox_maxmessages_email, - ); - $self->_start_instances(); - $self->_setup_http_service_objects(); - - # should be able to upload 5 - foreach my $i (1..$mailbox_maxmessages_email) { - $self->put_email(); - } - - # but any more should be rejected - eval { - $self->put_email(); - }; - my $e = $@; - $self->assert_not_null($e); - $self->assert_matches(qr{Over quota}, $e); - - # should have syslogged about it too - $self->assert_syslog_matches($self->{instance}, - qr{client hit per-mailbox exists limit}); - - # should be able to upload lots of contacts - my $carddav = $self->{carddav}; - my $addrbookid = $carddav->NewAddressBook('foo'); - $self->assert_not_null($addrbookid); - foreach my $i (1..$LOTS) { - $self->put_vcard($addrbookid); - } - - # should be able to upload lots of calendar events - my $caldav = $self->{caldav}; - my $calendarid = $caldav->NewCalendar({name => 'mycalendar'}); - $self->assert_not_null($calendarid); - foreach my $i (1..$LOTS) { - $self->put_vevent($calendarid); - } - - # should be able to upload lots of sieve scripts - foreach my $i (1..$LOTS) { - $self->put_script(); - } - - # should be able to upload lots of jmap submissions - foreach my $i (1..$LOTS) { - $self->put_submission(); - } + : needs_component_sieve : needs_component_jmap + : JMAPExtensions + : NoStartInstances { + my ($self) = @_; + + my $mailbox_maxmessages_email = $LIMITED; + $self->{instance}->{config} + ->set(mailbox_maxmessages_email => $mailbox_maxmessages_email,); + $self->_start_instances(); + $self->_setup_http_service_objects(); + + # should be able to upload 5 + foreach my $i (1 .. $mailbox_maxmessages_email) { + $self->put_email(); + } + + # but any more should be rejected + eval { $self->put_email(); }; + my $e = $@; + $self->assert_not_null($e); + $self->assert_matches(qr{Over quota}, $e); + + # should have syslogged about it too + $self->assert_syslog_matches($self->{instance}, + qr{client hit per-mailbox exists limit}); + + # should be able to upload lots of contacts + my $carddav = $self->{carddav}; + my $addrbookid = $carddav->NewAddressBook('foo'); + $self->assert_not_null($addrbookid); + foreach my $i (1 .. $LOTS) { + $self->put_vcard($addrbookid); + } + + # should be able to upload lots of calendar events + my $caldav = $self->{caldav}; + my $calendarid = $caldav->NewCalendar({ name => 'mycalendar' }); + $self->assert_not_null($calendarid); + foreach my $i (1 .. $LOTS) { + $self->put_vevent($calendarid); + } + + # should be able to upload lots of sieve scripts + foreach my $i (1 .. $LOTS) { + $self->put_script(); + } + + # should be able to upload lots of jmap submissions + foreach my $i (1 .. $LOTS) { + $self->put_submission(); + } } sub test_maxmsg_unlimited - :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - - # should be able to upload lots of contacts - my $carddav = $self->{carddav}; - my $addrbookid = $carddav->NewAddressBook('foo'); - $self->assert_not_null($addrbookid); - foreach my $i (1..$LOTS) { - $self->put_vcard($addrbookid); - } - - # should be able to upload lots of calendar events - my $caldav = $self->{caldav}; - my $calendarid = $caldav->NewCalendar({name => 'mycalendar'}); - $self->assert_not_null($calendarid); - foreach my $i (1..$LOTS) { - $self->put_vevent($calendarid); - } - - # should be able to upload lots of sieve scripts - foreach my $i (1..$LOTS) { - $self->put_script(); - } - - # should be able to upload lots of jmap submissions - foreach my $i (1..$LOTS) { - $self->put_submission(); - } - - # should be able to upload lots of regular emails - foreach my $i (1..$LOTS) { - $self->put_email(); - } + : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + + # should be able to upload lots of contacts + my $carddav = $self->{carddav}; + my $addrbookid = $carddav->NewAddressBook('foo'); + $self->assert_not_null($addrbookid); + foreach my $i (1 .. $LOTS) { + $self->put_vcard($addrbookid); + } + + # should be able to upload lots of calendar events + my $caldav = $self->{caldav}; + my $calendarid = $caldav->NewCalendar({ name => 'mycalendar' }); + $self->assert_not_null($calendarid); + foreach my $i (1 .. $LOTS) { + $self->put_vevent($calendarid); + } + + # should be able to upload lots of sieve scripts + foreach my $i (1 .. $LOTS) { + $self->put_script(); + } + + # should be able to upload lots of jmap submissions + foreach my $i (1 .. $LOTS) { + $self->put_submission(); + } + + # should be able to upload lots of regular emails + foreach my $i (1 .. $LOTS) { + $self->put_email(); + } } 1; diff --git a/cassandane/Cassandane/Cyrus/MboxEvent.pm b/cassandane/Cassandane/Cyrus/MboxEvent.pm index ad01405d21..1c1463f09e 100644 --- a/cassandane/Cassandane/Cyrus/MboxEvent.pm +++ b/cassandane/Cassandane/Cyrus/MboxEvent.pm @@ -50,87 +50,86 @@ use Cassandane::Generator; use Cassandane::MessageStoreFactory; use Cassandane::Instance; -sub new -{ - my ($class, @args) = @_; - - # all of them! - my @event_groups = qw( - message - quota - flags - access - mailbox - subscription - calendar - applepushservice - ); - - my $config = Cassandane::Config->default()->clone(); - $config->set(event_groups => join(' ', @event_groups)); - - return $class->SUPER::new({ - config => $config, - }, @args); +sub new { + my ($class, @args) = @_; + + # all of them! + my @event_groups = qw( + message + quota + flags + access + mailbox + subscription + calendar + applepushservice + ); + + my $config = Cassandane::Config->default()->clone(); + $config->set(event_groups => join(' ', @event_groups)); + + return $class->SUPER::new( + { + config => $config, + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_tls_login_event - :TLS :min_version_3_0 -{ - my ($self) = @_; + : TLS : min_version_3_0 { + my ($self) = @_; - my $instance = $self->{instance}; + my $instance = $self->{instance}; - my $svc = $instance->get_service('imaps'); - $self->assert_not_null($svc); + my $svc = $instance->get_service('imaps'); + $self->assert_not_null($svc); - my $store = $svc->create_store(); - $self->assert_not_null($store); + my $store = $svc->create_store(); + $self->assert_not_null($store); - # discard unwanted events from setup_mailbox - $self->{instance}->getnotify(); + # discard unwanted events from setup_mailbox + $self->{instance}->getnotify(); - # we're just gonna log in, but not do anything else - my $client = $store->get_client(); + # we're just gonna log in, but not do anything else + my $client = $store->get_client(); - my $events = $self->{instance}->getnotify(); - my %event_counts; + my $events = $self->{instance}->getnotify(); + my %event_counts; - foreach my $e (@{$events}) { - my $message = decode_json($e->{MESSAGE}); - $event_counts{$message->{event}}++; - } + foreach my $e (@{$events}) { + my $message = decode_json($e->{MESSAGE}); + $event_counts{ $message->{event} }++; + } - # client should still be connected - # XXX on an ssl socket (which must be blocking), is_open blocks! - # XXX prefer to do this: - # my $still_connected = $client->is_open(); - # $self->assert_not_null($still_connected, "connection dropped"); - # XXX but instead, check if a select succeeds - $client->select('INBOX'); - $self->assert_str_equals('ok', $client->get_last_completion_response()); + # client should still be connected + # XXX on an ssl socket (which must be blocking), is_open blocks! + # XXX prefer to do this: + # my $still_connected = $client->is_open(); + # $self->assert_not_null($still_connected, "connection dropped"); + # XXX but instead, check if a select succeeds + $client->select('INBOX'); + $self->assert_str_equals('ok', $client->get_last_completion_response()); - # we should have gotten one Login event and no others - $self->assert_equals(1, $event_counts{'Login'}); + # we should have gotten one Login event and no others + $self->assert_equals(1, $event_counts{'Login'}); - # XXX more correct, but may race against setup_mailbox finishing up - #$self->assert_deep_equals({ Login => 1 }, \%event_counts); + # XXX more correct, but may race against setup_mailbox finishing up + #$self->assert_deep_equals({ Login => 1 }, \%event_counts); - # XXX explicitly log out to work around Mail::IMAPTalk destructor - # XXX calling is_open() - $client->logout(); + # XXX explicitly log out to work around Mail::IMAPTalk destructor + # XXX calling is_open() + $client->logout(); } 1; diff --git a/cassandane/Cassandane/Cyrus/Mboxgroups.pm b/cassandane/Cassandane/Cyrus/Mboxgroups.pm index c52fb8f353..e01f84158a 100644 --- a/cassandane/Cassandane/Cyrus/Mboxgroups.pm +++ b/cassandane/Cassandane/Cyrus/Mboxgroups.pm @@ -49,253 +49,233 @@ use base qw(Cassandane::Cyrus::TestCase); use base qw(Cassandane::Unit::TestCase); use Cassandane::Util::Log; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set( - auth_mech => 'mboxgroups', - ); - - my $self = $class->SUPER::new({ - config => $config, - adminstore => 1, - services => [qw( imap )], - start_instances => 0, - }, @args); - - return $self; +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set(auth_mech => 'mboxgroups',); + + my $self = $class->SUPER::new( + { + config => $config, + adminstore => 1, + services => [qw( imap )], + start_instances => 0, + }, + @args + ); + + return $self; } -sub set_up -{ - my ($self) = @_; +sub set_up { + my ($self) = @_; - $self->SUPER::set_up(); + $self->SUPER::set_up(); - $self->_start_instances(); + $self->_start_instances(); - $self->{instance}->create_user("otheruser"); + $self->{instance}->create_user("otheruser"); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'cassandane', 'group:group c'); - $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'cassandane', 'group:group co'); - $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'otheruser', 'group:group co'); - $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'otheruser', 'group:group o'); + $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'cassandane', 'group:group c'); + $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'cassandane', 'group:group co'); + $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'otheruser', 'group:group co'); + $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'otheruser', 'group:group o'); } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - # clean this up as soon as we're done with it, cause it's holding a - # port open! - delete $self->{server}; + # clean this up as soon as we're done with it, cause it's holding a + # port open! + delete $self->{server}; - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } -sub test_setacl_groupid -{ - my ($self) = @_; +sub test_setacl_groupid { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.cassandane.groupid"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->create("user.cassandane.groupid"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.groupid", - "group:foo", - "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->setacl("user.cassandane.groupid", "group:foo", "lrswipkxtecdan"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } -sub test_setacl_groupid_spaces -{ - my ($self) = @_; +sub test_setacl_groupid_spaces { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->create("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl("user.cassandane.groupid_spaces", - "group:this group name has spaces", - "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->setacl( + "user.cassandane.groupid_spaces", + "group:this group name has spaces", + "lrswipkxtecdan" + ); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $data = $admintalk->getacl("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $data = $admintalk->getacl("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->assert(scalar @{$data} % 2 == 0); - my %acl = @{$data}; - $self->assert_str_equals($acl{"group:this group name has spaces"}, - "lrswipkxtecdan"); + $self->assert(scalar @{$data} % 2 == 0); + my %acl = @{$data}; + $self->assert_str_equals($acl{"group:this group name has spaces"}, + "lrswipkxtecdan"); - $admintalk->select("user.cassandane.groupid_spaces"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->select("user.cassandane.groupid_spaces"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } sub test_list_groupaccess_noracl - :NoAltNamespace -{ - my ($self) = @_; + : NoAltNamespace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $admintalk->create("user.otheruser.groupaccess"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->create("user.otheruser.groupaccess"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl("user.otheruser.groupaccess", - "group:group co", "lrswipkxtecdan"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->setacl("user.otheruser.groupaccess", "group:group co", + "lrswipkxtecdan"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $list = $imaptalk->list("", "*"); - my @boxes = sort map { $_->[2] } @{$list}; + my $list = $imaptalk->list("", "*"); + my @boxes = sort map { $_->[2] } @{$list}; - $self->assert_deep_equals(\@boxes, - ['INBOX', 'user.otheruser.groupaccess']); + $self->assert_deep_equals(\@boxes, [ 'INBOX', 'user.otheruser.groupaccess' ]); } sub test_list_groupaccess_racl - :ReverseACLs :NoAltNamespace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); - - $admintalk->create("user.otheruser.groupaccess"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - $admintalk->setacl("user.otheruser.groupaccess", - "group:group co", "lrswipkxtecdn"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - if (get_verbose()) { - $self->{instance}->run_command( - { cyrus => 1, }, - 'cyr_dbtool', - "$self->{instance}->{basedir}/conf/mailboxes.db", - 'twoskip', - 'show' - ); - } + : ReverseACLs : NoAltNamespace { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + + $admintalk->create("user.otheruser.groupaccess"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $list = $imaptalk->list("", "*"); - my @boxes = sort map { $_->[2] } @{$list}; + $admintalk->setacl("user.otheruser.groupaccess", "group:group co", + "lrswipkxtecdn"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals(\@boxes, - ['INBOX', 'user.otheruser.groupaccess']); + if (get_verbose()) { + $self->{instance}->run_command( + { cyrus => 1, }, + 'cyr_dbtool', "$self->{instance}->{basedir}/conf/mailboxes.db", + 'twoskip', 'show' + ); + } + + my $list = $imaptalk->list("", "*"); + my @boxes = sort map { $_->[2] } @{$list}; + + $self->assert_deep_equals(\@boxes, [ 'INBOX', 'user.otheruser.groupaccess' ]); } -sub do_test_list_order -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.zzz"); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); - - $imaptalk->create("INBOX.aaa"); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); - - my %adminfolders = ( - 'user.otheruser.order-user' => 'cassandane', - 'user.otheruser.order-co' => 'group:group co', - 'user.otheruser.order-c' => 'group:group c', - 'user.otheruser.order-o' => 'group:group o', - 'shared.order-co' => 'group:group co', - 'shared.order-c' => 'group:group c', - 'shared.order-o' => 'group:group o', +sub do_test_list_order { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.zzz"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->create("INBOX.aaa"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + my %adminfolders = ( + 'user.otheruser.order-user' => 'cassandane', + 'user.otheruser.order-co' => 'group:group co', + 'user.otheruser.order-c' => 'group:group c', + 'user.otheruser.order-o' => 'group:group o', + 'shared.order-co' => 'group:group co', + 'shared.order-c' => 'group:group c', + 'shared.order-o' => 'group:group o', + ); + + while (my ($folder, $identifier) = each %adminfolders) { + $admintalk->create($folder); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "created folder $folder successfully" ); - while (my ($folder, $identifier) = each %adminfolders) { - $admintalk->create($folder); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "created folder $folder successfully"); - - $admintalk->setacl($folder, $identifier, "lrswipkxtecdn"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "setacl folder $folder for $identifier successfully"); - - if ($folder =~ m/^shared/) { - # subvert default permissions on shared namespace for - # purpose of testing ordering - $admintalk->setacl($folder, "anyone", "p"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response(), - "setacl folder $folder for anyone successfully"); - } - } + $admintalk->setacl($folder, $identifier, "lrswipkxtecdn"); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "setacl folder $folder for $identifier successfully" + ); - if (get_verbose()) { - $self->{instance}->run_command( - { cyrus => 1, }, - 'cyr_dbtool', - "$self->{instance}->{basedir}/conf/mailboxes.db", - 'twoskip', - 'show' - ); + if ($folder =~ m/^shared/) { + # subvert default permissions on shared namespace for + # purpose of testing ordering + $admintalk->setacl($folder, "anyone", "p"); + $self->assert_str_equals( + 'ok', + $admintalk->get_last_completion_response(), + "setacl folder $folder for anyone successfully" + ); } + } - my $list = $imaptalk->list("", "*"); - my @boxes = map { $_->[2] } @{$list}; - - # Note: order is - # * mine, alphabetically, - # * other users', alphabetically, - # * shared, alphabetically - # ... which is not the order we created them ;) - # Also, the "order-o" folders are not returned, because cassandane - # is not a member of that group - my @expect = qw( - INBOX - INBOX.aaa - INBOX.zzz - user.otheruser.order-c - user.otheruser.order-co - user.otheruser.order-user + if (get_verbose()) { + $self->{instance}->run_command( + { cyrus => 1, }, + 'cyr_dbtool', "$self->{instance}->{basedir}/conf/mailboxes.db", + 'twoskip', 'show' ); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min > 4)) { - push @expect, qw(shared); - } - push @expect, qw( shared.order-c shared.order-co ); - $self->assert_deep_equals(\@boxes, \@expect); + } + + my $list = $imaptalk->list("", "*"); + my @boxes = map { $_->[2] } @{$list}; + + # Note: order is + # * mine, alphabetically, + # * other users', alphabetically, + # * shared, alphabetically + # ... which is not the order we created them ;) + # Also, the "order-o" folders are not returned, because cassandane + # is not a member of that group + my @expect = qw( + INBOX + INBOX.aaa + INBOX.zzz + user.otheruser.order-c + user.otheruser.order-co + user.otheruser.order-user + ); + my ($maj, $min) = Cassandane::Instance->get_version(); + + if ($maj > 3 || ($maj == 3 && $min > 4)) { + push @expect, qw(shared); + } + push @expect, qw( shared.order-c shared.order-co ); + $self->assert_deep_equals(\@boxes, \@expect); } sub test_list_order_noracl - :NoAltNamespace -{ - my $self = shift; - return $self->do_test_list_order(@_); + : NoAltNamespace { + my $self = shift; + return $self->do_test_list_order(@_); } sub test_list_order_racl - :ReverseACLs :NoAltNamespace -{ - my $self = shift; - return $self->do_test_list_order(@_); + : ReverseACLs : NoAltNamespace { + my $self = shift; + return $self->do_test_list_order(@_); } 1; diff --git a/cassandane/Cassandane/Cyrus/Mbpath.pm b/cassandane/Cassandane/Cyrus/Mbpath.pm index 70b43b728c..4232ed56c9 100644 --- a/cassandane/Cassandane/Cyrus/Mbpath.pm +++ b/cassandane/Cassandane/Cyrus/Mbpath.pm @@ -46,41 +46,39 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_mbpath_8bit -{ - my ($self) = @_; +sub test_mbpath_8bit { + my ($self) = @_; - xlog $self, "Test mbpath 8 bit name parsing"; + xlog $self, "Test mbpath 8 bit name parsing"; - # create and prepare the user - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create('A & B'); + # create and prepare the user + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create('A & B'); - xlog "check with 8bit name"; - my $mbpath = $self->{instance}->run_mbpath('user.cassandane.A & B'); - $self->assert_str_equals($mbpath->{mbname}{intname}, 'user.cassandane.A &- B'); + xlog "check with 8bit name"; + my $mbpath = $self->{instance}->run_mbpath('user.cassandane.A & B'); + $self->assert_str_equals($mbpath->{mbname}{intname}, + 'user.cassandane.A &- B'); - xlog "check with 7bit name"; - $mbpath = $self->{instance}->run_mbpath("-7", 'user.cassandane.A &- B'); - $self->assert_str_equals($mbpath->{mbname}{intname}, 'user.cassandane.A &- B'); + xlog "check with 7bit name"; + $mbpath = $self->{instance}->run_mbpath("-7", 'user.cassandane.A &- B'); + $self->assert_str_equals($mbpath->{mbname}{intname}, + 'user.cassandane.A &- B'); } 1; diff --git a/cassandane/Cassandane/Cyrus/Metadata.pm b/cassandane/Cassandane/Cyrus/Metadata.pm index e617bb1e25..a61de6cbd3 100644 --- a/cassandane/Cassandane/Cyrus/Metadata.pm +++ b/cassandane/Cassandane/Cyrus/Metadata.pm @@ -51,22 +51,19 @@ use Cassandane::Util::Log; use lib '../perl/imap'; use Cyrus::DList; -sub new -{ - my ($class, @args) = @_; - return $class->SUPER::new({ adminstore => 1 }, @args); +sub new { + my ($class, @args) = @_; + return $class->SUPER::new({ adminstore => 1 }, @args); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # @@ -79,206 +76,201 @@ sub tear_down # # Returns: Message, Message in the order they went to Stores # -sub make_message_pair -{ - my ($self, $store0, $store1) = @_; - - # Generate two messages and detect their resulting GUIDs - my $msg0 = $self->{gen}->generate(subject => 'Message Zero'); - my $msg1 = $self->{gen}->generate(subject => 'Message One'); - my $guid0 = $msg0->get_guid(); - my $guid1 = $msg1->get_guid(); - xlog $self, "Message 'Message Zero' has GUID $guid0"; - xlog $self, "Message 'Message One' has GUID $guid1"; - - # choose ordering of messages - $self->assert_str_not_equals($guid0, $guid1); - if ($guid0 gt $guid1) - { - # swap - my $t = $msg0; - $msg0 = $msg1; - $msg1 = $t; - } - - # Save and return the messages - $self->_save_message($msg0, $store0); - $self->_save_message($msg1, $store1); - return ($msg0, $msg1); +sub make_message_pair { + my ($self, $store0, $store1) = @_; + + # Generate two messages and detect their resulting GUIDs + my $msg0 = $self->{gen}->generate(subject => 'Message Zero'); + my $msg1 = $self->{gen}->generate(subject => 'Message One'); + my $guid0 = $msg0->get_guid(); + my $guid1 = $msg1->get_guid(); + xlog $self, "Message 'Message Zero' has GUID $guid0"; + xlog $self, "Message 'Message One' has GUID $guid1"; + + # choose ordering of messages + $self->assert_str_not_equals($guid0, $guid1); + if ($guid0 gt $guid1) { + # swap + my $t = $msg0; + $msg0 = $msg1; + $msg1 = $t; + } + + # Save and return the messages + $self->_save_message($msg0, $store0); + $self->_save_message($msg1, $store1); + return ($msg0, $msg1); } # List annotations actually stored in the database. -sub list_annotations -{ - my ($self, %params) = @_; - - my $scope = delete $params{scope} || 'global'; - my $mailbox = delete $params{mailbox} || 'user.cassandane'; - my $tombstones = delete $params{tombstones}; - my $withmdata = delete $params{withmdata}; - my $instance = delete $params{instance} || $self->{instance}; - my $uids = delete $params{uids}; - die "Unknown parameters: " . join(' ', map { $_ . '=' . $params{$_}; } keys %params) - if scalar %params; - - my $basedir = $instance->{basedir}; - - my $mailbox_db; - if ($scope eq 'global' || $scope eq 'mailbox') - { - $mailbox_db = "$basedir/conf/annotations.db"; - } - elsif ($scope eq 'message') - { - my $mb = $mailbox; - my $datadir = $self->{instance}->folder_to_directory($mailbox); - $mailbox_db = "$datadir/cyrus.annotations"; - } - else - { - die "Unknown scope: $scope"; - } - - my $format = $instance->{config}->get('annotation_db'); - $format = $format // 'twoskip'; - - my @annots; - - my $res = $instance->run_dbcommand_cb(sub { - my ($key, $value) = @_; - my ($uid, $item, $userid, @rest) = split '\0', $key; - my ($data, $modseq, $flags); - if (substr($value, 0, 1) eq '%') { - my $dlist = Cyrus::DList->parse_string($value, 0); - my $hash = $dlist->as_perl; - $data = $hash->{V}; - $modseq = $hash->{M}; - $flags = $hash->{F} ? 1 : 0; # XXX - parse more options later - } - else { - my $offset = 0; - my $vallen = unpack('N', substr($value, $offset, 4)); - $offset += 8; # 4 more bytes of rubbish - $data = substr($value, $offset, $vallen); - $offset += $vallen + 1; # trailing null - my $strend = index($value, "\0", $offset); - my $type = substr($value, $offset, ($strend - $offset)); - $offset = $strend + 1; - my $modtime = unpack('N', substr($value, $offset, 4)); - $offset += 8; # 4 more bytes of rubbish again - $modseq = unpack('x[N]N', substr($value, $offset, 8)); - $offset += 8; - $flags = unpack('C', substr($value, $offset, 1)); - } - - if ($flags and not $tombstones) { - return; - } - my $annot = { - uid => ($scope eq 'message' ? $uid : 0), - mboxname => ($scope eq 'message' ? $mailbox : $uid), - entry => $item, - userid => $userid, - data => $data, - }; - - if ($withmdata) { - $annot->{modseq} = $modseq; - $annot->{flags} = $flags; - } - - if ($uids) { - my %wantuids = map { $_ => 1 } $uids; - if ($uids and not exists($wantuids{$annot->{uid}})) { - return; - } - } - - if ($annot->{userid} eq '[.OwNeR.]') { - $annot->{userid} = 'cassandane'; # XXX - strip owner from $mailbox? +sub list_annotations { + my ($self, %params) = @_; + + my $scope = delete $params{scope} || 'global'; + my $mailbox = delete $params{mailbox} || 'user.cassandane'; + my $tombstones = delete $params{tombstones}; + my $withmdata = delete $params{withmdata}; + my $instance = delete $params{instance} || $self->{instance}; + my $uids = delete $params{uids}; + die "Unknown parameters: " + . join(' ', map { $_ . '=' . $params{$_}; } keys %params) + if scalar %params; + + my $basedir = $instance->{basedir}; + + my $mailbox_db; + if ($scope eq 'global' || $scope eq 'mailbox') { + $mailbox_db = "$basedir/conf/annotations.db"; + } elsif ($scope eq 'message') { + my $mb = $mailbox; + my $datadir = $self->{instance}->folder_to_directory($mailbox); + $mailbox_db = "$datadir/cyrus.annotations"; + } else { + die "Unknown scope: $scope"; + } + + my $format = $instance->{config}->get('annotation_db'); + $format = $format // 'twoskip'; + + my @annots; + + my $res = $instance->run_dbcommand_cb( + sub { + my ($key, $value) = @_; + my ($uid, $item, $userid, @rest) = split '\0', $key; + my ($data, $modseq, $flags); + if (substr($value, 0, 1) eq '%') { + my $dlist = Cyrus::DList->parse_string($value, 0); + my $hash = $dlist->as_perl; + $data = $hash->{V}; + $modseq = $hash->{M}; + $flags = $hash->{F} ? 1 : 0; # XXX - parse more options later + } else { + my $offset = 0; + my $vallen = unpack('N', substr($value, $offset, 4)); + $offset += 8; # 4 more bytes of rubbish + $data = substr($value, $offset, $vallen); + $offset += $vallen + 1; # trailing null + my $strend = index($value, "\0", $offset); + my $type = substr($value, $offset, ($strend - $offset)); + $offset = $strend + 1; + my $modtime = unpack('N', substr($value, $offset, 4)); + $offset += 8; # 4 more bytes of rubbish again + $modseq = unpack('x[N]N', substr($value, $offset, 8)); + $offset += 8; + $flags = unpack('C', substr($value, $offset, 1)); + } + + if ($flags and not $tombstones) { + return; + } + my $annot = { + uid => ($scope eq 'message' ? $uid : 0), + mboxname => ($scope eq 'message' ? $mailbox : $uid), + entry => $item, + userid => $userid, + data => $data, + }; + + if ($withmdata) { + $annot->{modseq} = $modseq; + $annot->{flags} = $flags; + } + + if ($uids) { + my %wantuids = map { $_ => 1 } $uids; + if ($uids and not exists($wantuids{ $annot->{uid} })) { + return; } - - push(@annots, $annot); - }, $mailbox_db, $format, ['SHOW']); - - # enforce a stable order so we have some chance of - # comparing the results - @annots = sort { - $a->{mboxname} cmp $b->{mboxname} || - $a->{uid} <=> $b->{uid} || - $a->{userid} cmp $b->{userid} || - $a->{entry} cmp $b->{entry}; - } @annots; - - return \@annots; + } + + if ($annot->{userid} eq '[.OwNeR.]') { + $annot->{userid} = 'cassandane'; # XXX - strip owner from $mailbox? + } + + push(@annots, $annot); + }, + $mailbox_db, + $format, + ['SHOW'] + ); + + # enforce a stable order so we have some chance of + # comparing the results + @annots = sort { + $a->{mboxname} cmp $b->{mboxname} + || $a->{uid} <=> $b->{uid} + || $a->{userid} cmp $b->{userid} + || $a->{entry} cmp $b->{entry}; + } @annots; + + return \@annots; } -sub list_uids -{ - my ($self, $store) = @_; - my @uids; +sub list_uids { + my ($self, $store) = @_; + my @uids; - $store->read_begin(); - while (my $msg = $store->read_message()) - { - push(@uids, $msg->uid); - } - $store->read_end(); + $store->read_begin(); + while (my $msg = $store->read_message()) { + push(@uids, $msg->uid); + } + $store->read_end(); - return \@uids; + return \@uids; } -sub check_msg_annotation_replication -{ - my ($self, $master_store, $replica_store, %params) = @_; - - my $master_annots = $self->list_annotations((%params, - scope => 'message', - instance => $self->{instance}, - withmdata => 1, - tombstones => 1, - uids => $self->list_uids($master_store), - )); - my $replica_annots = $self->list_annotations((%params, - scope => 'message', - instance => $self->{replica}, - withmdata => 1, - tombstones => 1, - uids => $self->list_uids($replica_store), - )); - - $self->assert_deep_equals($master_annots, $replica_annots); +sub check_msg_annotation_replication { + my ($self, $master_store, $replica_store, %params) = @_; + + my $master_annots = $self->list_annotations(( + %params, + scope => 'message', + instance => $self->{instance}, + withmdata => 1, + tombstones => 1, + uids => $self->list_uids($master_store), + )); + my $replica_annots = $self->list_annotations(( + %params, + scope => 'message', + instance => $self->{replica}, + withmdata => 1, + tombstones => 1, + uids => $self->list_uids($replica_store), + )); + + $self->assert_deep_equals($master_annots, $replica_annots); } -sub set_msg_annotation -{ - my ($self, $store, $uid, $entry, $attrib, $value) = @_; - - $store ||= $self->{store}; - $store->connect(); - $store->_select(); - my $talk = $store->get_client(); - # Note $value might have no whitespace so we have to - # convince Mail::IMAPTalk to quote it anyway - $talk->store('' . $uid, 'annotation', [$entry, [$attrib, { Quote => $value }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); +sub set_msg_annotation { + my ($self, $store, $uid, $entry, $attrib, $value) = @_; + + $store ||= $self->{store}; + $store->connect(); + $store->_select(); + my $talk = $store->get_client(); + # Note $value might have no whitespace so we have to + # convince Mail::IMAPTalk to quote it anyway + $talk->store('' . $uid, 'annotation', + [ $entry, [ $attrib, { Quote => $value } ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); } # Not sure if this cases can even work... # sub test_msg_replication_mod_bot_mse # Get the highestmodseq of the folder -sub get_highestmodseq -{ - my ($self) = @_; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $stat = $talk->status($store->{folder}, '(highestmodseq)'); - return undef unless defined $stat; - return undef unless ref $stat eq 'HASH'; - return undef unless defined $stat->{highestmodseq}; - return 0 + $stat->{highestmodseq}; +sub get_highestmodseq { + my ($self) = @_; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $stat = $talk->status($store->{folder}, '(highestmodseq)'); + return undef unless defined $stat; + return undef unless ref $stat eq 'HASH'; + return undef unless defined $stat->{highestmodseq}; + return 0 + $stat->{highestmodseq}; } # sub test_mbox_replication_new_rep @@ -290,187 +282,187 @@ sub get_highestmodseq # sub test_mbox_replication_del_rep # sub test_mbox_replication_del_bot -sub folder_delete_mboxa_common -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - # data thanks to hipsteripsum.me - my $folder = 'INBOX.williamsburg'; - my $fentry = '/private/comment'; - my $data = $self->make_random_data(0.3, maxreps => 15); - - xlog $self, "create a mailbox"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; - - xlog $self, "set and then get the same back again"; - $imaptalk->setmetadata($folder, $fentry, $data) - or die "Cannot setmetadata: $@"; - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); +sub folder_delete_mboxa_common { + my ($self) = @_; - my $res = $imaptalk->getmetadata($folder, $fentry) - or die "Cannot getmetadata: $@"; - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => $data } - }, $res); + my $imaptalk = $self->{store}->get_client(); + # data thanks to hipsteripsum.me + my $folder = 'INBOX.williamsburg'; + my $fentry = '/private/comment'; + my $data = $self->make_random_data(0.3, maxreps => 15); - xlog $self, "delete the mailbox"; - $imaptalk->delete($folder) - or die "Cannot delete mailbox $folder: $@"; + xlog $self, "create a mailbox"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; - xlog $self, "create a new mailbox with the same name"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; + xlog $self, "set and then get the same back again"; + $imaptalk->setmetadata($folder, $fentry, $data) + or die "Cannot setmetadata: $@"; + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "new mailbox reports NIL for the per-mailbox metadata"; - $res = $imaptalk->getmetadata($folder, $fentry) - or die "Cannot getmetadata: $@"; - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => undef } - }, $res); + my $res = $imaptalk->getmetadata($folder, $fentry) + or die "Cannot getmetadata: $@"; + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $folder => { $fentry => $data } + }, + $res + ); + + xlog $self, "delete the mailbox"; + $imaptalk->delete($folder) + or die "Cannot delete mailbox $folder: $@"; + + xlog $self, "create a new mailbox with the same name"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; + + xlog $self, "new mailbox reports NIL for the per-mailbox metadata"; + $res = $imaptalk->getmetadata($folder, $fentry) + or die "Cannot getmetadata: $@"; + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $folder => { $fentry => undef } + }, + $res + ); } -sub folder_delete_mboxm_common -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - # data thanks to hipsteripsum.me - my $folder = 'INBOX.williamsburg'; - my $fentry = '/private/comment'; - my $data = $self->make_random_data(0.3, maxreps => 15); +sub folder_delete_mboxm_common { + my ($self) = @_; - xlog $self, "create a mailbox"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; + my $imaptalk = $self->{store}->get_client(); + # data thanks to hipsteripsum.me + my $folder = 'INBOX.williamsburg'; + my $fentry = '/private/comment'; + my $data = $self->make_random_data(0.3, maxreps => 15); - xlog $self, "set and then get the same back again"; - $imaptalk->setmetadata($folder, $fentry, $data) - or die "Cannot setmetadata: $@"; - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "create a mailbox"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; - my $res = $imaptalk->getmetadata($folder, $fentry) - or die "Cannot getmetadata: $@"; - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => $data } - }, $res); - - xlog $self, "delete the mailbox"; - $imaptalk->delete($folder) - or die "Cannot delete mailbox $folder: $@"; - - xlog $self, "cannot get metadata for deleted mailbox"; - $res = $imaptalk->getmetadata($folder, $fentry); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert($imaptalk->get_last_error() =~ m/does not exist/i); - - xlog $self, "create a new mailbox with the same name"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; - - xlog $self, "new mailbox reports NIL for the per-mailbox metadata"; - $res = $imaptalk->getmetadata($folder, $fentry) - or die "Cannot getmetadata: $@"; - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => undef } - }, $res); -} + xlog $self, "set and then get the same back again"; + $imaptalk->setmetadata($folder, $fentry, $data) + or die "Cannot setmetadata: $@"; + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); -sub folder_delete_msg_common -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - # data thanks to hipsteripsum.me - my $folder = 'INBOX.williamsburg'; - my $mentry = '/comment'; - my $mattrib = 'value.priv'; - $self->{store}->set_fetch_attributes('uid', "annotation ($mentry $mattrib)"); - $self->{store}->set_folder($folder); - - xlog $self, "create a mailbox"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; - - xlog $self, "add some messages"; - my $uid = 1; - my %exp; - for (1..10) + my $res = $imaptalk->getmetadata($folder, $fentry) + or die "Cannot getmetadata: $@"; + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals( { - my $msg = $self->make_message("Message $_"); - $exp{$uid} = $msg; - $msg->set_attribute('uid', $uid); - my $data = $self->make_random_data(0.3, maxreps => 15); - $msg->set_annotation($mentry, $mattrib, $data); - $imaptalk->store('' . $uid, 'annotation', - [$mentry, [$mattrib, $data]]); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $uid++; - } - - xlog $self, "Check the messages are all there"; - $self->check_messages(\%exp); - - xlog $self, "delete the mailbox"; - $imaptalk->unselect(); - $imaptalk->delete($folder) - or die "Cannot delete mailbox $folder: $@"; - - xlog $self, "create a new mailbox with the same name"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; - - xlog $self, "create some new messages"; - %exp = (); - $uid = 1; - for (1..10) + $folder => { $fentry => $data } + }, + $res + ); + + xlog $self, "delete the mailbox"; + $imaptalk->delete($folder) + or die "Cannot delete mailbox $folder: $@"; + + xlog $self, "cannot get metadata for deleted mailbox"; + $res = $imaptalk->getmetadata($folder, $fentry); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert($imaptalk->get_last_error() =~ m/does not exist/i); + + xlog $self, "create a new mailbox with the same name"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; + + xlog $self, "new mailbox reports NIL for the per-mailbox metadata"; + $res = $imaptalk->getmetadata($folder, $fentry) + or die "Cannot getmetadata: $@"; + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals( { - my $msg = $self->make_message("Message NEW $_"); - $exp{$uid} = $msg; - $msg->set_attribute('uid', $uid); - # Note: no annotation on the new message - $uid++; - } + $folder => { $fentry => undef } + }, + $res + ); +} - xlog $self, "new mailbox reports NIL for the per-message metadata"; - $self->check_messages(\%exp); +sub folder_delete_msg_common { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + # data thanks to hipsteripsum.me + my $folder = 'INBOX.williamsburg'; + my $mentry = '/comment'; + my $mattrib = 'value.priv'; + $self->{store}->set_fetch_attributes('uid', "annotation ($mentry $mattrib)"); + $self->{store}->set_folder($folder); + + xlog $self, "create a mailbox"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; + + xlog $self, "add some messages"; + my $uid = 1; + my %exp; + for (1 .. 10) { + my $msg = $self->make_message("Message $_"); + $exp{$uid} = $msg; + $msg->set_attribute('uid', $uid); + my $data = $self->make_random_data(0.3, maxreps => 15); + $msg->set_annotation($mentry, $mattrib, $data); + $imaptalk->store('' . $uid, 'annotation', [ $mentry, [ $mattrib, $data ] ]); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $uid++; + } + + xlog $self, "Check the messages are all there"; + $self->check_messages(\%exp); + + xlog $self, "delete the mailbox"; + $imaptalk->unselect(); + $imaptalk->delete($folder) + or die "Cannot delete mailbox $folder: $@"; + + xlog $self, "create a new mailbox with the same name"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; + + xlog $self, "create some new messages"; + %exp = (); + $uid = 1; + for (1 .. 10) { + my $msg = $self->make_message("Message NEW $_"); + $exp{$uid} = $msg; + $msg->set_attribute('uid', $uid); + # Note: no annotation on the new message + $uid++; + } + + xlog $self, "new mailbox reports NIL for the per-message metadata"; + $self->check_messages(\%exp); } # This is like Mail::IMAPTalk::getmetadata, but # a) doesn't assume incorrect placement of the options, and # b) handles the METADATA LONGENTRIES response code -sub getmetadata -{ - my ($talk, @args) = @_; - - my $res = {}; - - my %handlers = - ( - metadata => sub - { - my ($response, $rr, $id) = @_; - if ($rr->[0] =~ m/^longentries/i) - { - $res->{longentries} = 0 + $rr->[1]; - } - else - { - my $f = $talk->_unfix_folder_name($rr->[0]); - my %kv = ( @{$rr->[1]} ); - map { $res->{$f}->{$_} = $kv{$_}; } keys %kv; - } - } - ); +sub getmetadata { + my ($talk, @args) = @_; + + my $res = {}; + + my %handlers = ( + metadata => sub { + my ($response, $rr, $id) = @_; + if ($rr->[0] =~ m/^longentries/i) { + $res->{longentries} = 0 + $rr->[1]; + } else { + my $f = $talk->_unfix_folder_name($rr->[0]); + my %kv = (@{ $rr->[1] }); + map { $res->{$f}->{$_} = $kv{$_}; } keys %kv; + } + } + ); - my $r = $talk->_imap_cmd('getmetadata', 0, \%handlers, @args); - return if !defined $r; - return $res; + my $r = $talk->_imap_cmd('getmetadata', 0, \%handlers, @args); + return if !defined $r; + return $res; } use Cassandane::Tiny::Loader 'tiny-tests/Metadata'; diff --git a/cassandane/Cassandane/Cyrus/Move.pm b/cassandane/Cassandane/Cyrus/Move.pm index 3f015e6b16..9bf750a0f4 100644 --- a/cassandane/Cassandane/Cyrus/Move.pm +++ b/cassandane/Cassandane/Cyrus/Move.pm @@ -47,62 +47,58 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -sub new -{ - my $class = shift; - my $config = Cassandane::Config::default()->clone(); - $config->set("conversations", "yes"); - $config->set("reverseacls", "yes"); - $config->set("annotation_allow_undefined", "yes"); - return $class->SUPER::new({ config => $config, adminstore => 1 }, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config::default()->clone(); + $config->set("conversations", "yes"); + $config->set("reverseacls", "yes"); + $config->set("annotation_allow_undefined", "yes"); + return $class->SUPER::new({ config => $config, adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } sub test_move_new_user - :NoAltNameSpace -{ - # test whether the imap_admins setting works correctly - my ($self) = @_; + : NoAltNameSpace { + # test whether the imap_admins setting works correctly + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $admintalk->create("user.user2"); - $admintalk->create("user.user2.sub"); - $admintalk->setacl("user.user2.sub", "cassandane", "lrswited"); + $admintalk->create("user.user2"); + $admintalk->create("user.user2.sub"); + $admintalk->setacl("user.user2.sub", "cassandane", "lrswited"); - $talk->enable("QRESYNC"); - $talk->select("INBOX"); + $talk->enable("QRESYNC"); + $talk->select("INBOX"); - xlog $self, "create a message and mark it \\Seen"; - $self->make_message("Message foo"); - $talk->store("1", "+flags", "\\Seen"); + xlog $self, "create a message and mark it \\Seen"; + $self->make_message("Message foo"); + $talk->store("1", "+flags", "\\Seen"); - xlog $self, "moving to second user works"; - $talk->move("1", "user.user2.sub"); - $talk->select("user.user2.sub"); - my $res = $talk->fetch("1", "(flags)"); - my $flags = $res->{1}->{flags}; - $self->assert(grep { $_ eq "\\Seen" } @$flags); + xlog $self, "moving to second user works"; + $talk->move("1", "user.user2.sub"); + $talk->select("user.user2.sub"); + my $res = $talk->fetch("1", "(flags)"); + my $flags = $res->{1}->{flags}; + $self->assert(grep { $_ eq "\\Seen" } @$flags); - xlog $self, "moving back works"; - $talk->move("1", "INBOX"); - $talk->select("INBOX"); - $res = $talk->fetch("1", "(flags)"); - $flags = $res->{1}->{flags}; - $self->assert(grep { $_ eq "\\Seen" } @$flags); + xlog $self, "moving back works"; + $talk->move("1", "INBOX"); + $talk->select("INBOX"); + $res = $talk->fetch("1", "(flags)"); + $flags = $res->{1}->{flags}; + $self->assert(grep { $_ eq "\\Seen" } @$flags); } 1; diff --git a/cassandane/Cassandane/Cyrus/MurderDAV.pm b/cassandane/Cassandane/Cyrus/MurderDAV.pm index eede5eeeb8..0a1d5bdf5b 100644 --- a/cassandane/Cassandane/Cyrus/MurderDAV.pm +++ b/cassandane/Cassandane/Cyrus/MurderDAV.pm @@ -50,84 +50,81 @@ use Cassandane::Instance; $Data::Dumper::Sortkeys = 1; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set('conversations' => 'yes'); - $config->set_bits('httpmodules', 'caldav', 'carddav'); - - return $class->SUPER::new({ - config => $config, - httpmurder => 1, - jmap => 1, - adminstore => 1 - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set('conversations' => 'yes'); + $config->set_bits('httpmodules', 'caldav', 'carddav'); + + return $class->SUPER::new( + { + config => $config, + httpmurder => 1, + jmap => 1, + adminstore => 1 + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_aaa_setup - :needs_component_murder -{ - my ($self) = @_; + : needs_component_murder { + my ($self) = @_; - # does everything set up and tear down cleanly? - $self->assert(1); + # does everything set up and tear down cleanly? + $self->assert(1); } # XXX This can't pass because we don't support multiple murder services # XXX at once, but renaming out the "bogus" and running it, and it failing, # XXX proves the infrastructure to prevent requesting both works. sub bogustest_aaa_imapdav_setup - :needs_component_murder - :IMAPMurder -{ - my ($self) = @_; + : needs_component_murder + : IMAPMurder { + my ($self) = @_; - # does everything set up and tear down cleanly? - $self->assert(1); + # does everything set up and tear down cleanly? + $self->assert(1); } sub test_frontend_commands - :needs_component_murder :needs_component_httpd :min_version_3_5 -{ - my ($self) = @_; - my $result; - - my $frontend_svc = $self->{frontend}->get_service("http"); - my $frontend_host = $frontend_svc->host(); - my $frontend_port = $frontend_svc->port(); - my $proxy_re = qr{ + : needs_component_murder : needs_component_httpd : min_version_3_5 { + my ($self) = @_; + my $result; + + my $frontend_svc = $self->{frontend}->get_service("http"); + my $frontend_host = $frontend_svc->host(); + my $frontend_port = $frontend_svc->port(); + my $proxy_re = qr{ \b ( localhost | $frontend_host ) : $frontend_port \b }x; - my $frontend_caldav = Net::CalDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $frontend_host, - port => $frontend_port, - scheme => 'http', - url => "http://$frontend_host:$frontend_port" - ); - - my $CALDAV = "urn:ietf:params:xml:ns:caldav"; - my $CARDDAV = "urn:ietf:params:xml:ns:carddav"; - my $xml = <new( + user => 'cassandane', + password => 'pass', + host => $frontend_host, + port => $frontend_port, + scheme => 'http', + url => "http://$frontend_host:$frontend_port" + ); + + my $CALDAV = "urn:ietf:params:xml:ns:caldav"; + my $CARDDAV = "urn:ietf:params:xml:ns:carddav"; + my $xml = < @@ -137,102 +134,106 @@ sub test_frontend_commands EOF - xlog $self, "Get current-user-principal"; - my $url = $frontend_caldav->GetCurrentUserPrincipal(); - $self->assert_not_null($url); - - # Copied from Net::DAVTalk::SetURL - my (undef, undef, undef, $cur_princ) = - $url =~ m{^http(s)?://([^/:]+)(?::(\d+))?(.*)?}; - - xlog $self, "PROPFIND for home-sets"; - my $res = $frontend_caldav->Request('PROPFIND', $cur_princ, - $xml, 'Content-Type' => 'text/xml'); - - my $propstat = $res->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]; - my $props = $propstat->{'{DAV:}prop'}; - $self->assert_str_equals('HTTP/1.1 200 OK', - $propstat->{'{DAV:}status'}{content}); - my $cal_home = $props->{"{$CALDAV}calendar-home-set"}{'{DAV:}href'}{content}; - my $card_home = - $props->{"{$CARDDAV}addressbook-home-set"}{'{DAV:}href'}{content}; - $self->assert_not_null($cal_home); - $self->assert_not_null($card_home); - - xlog $self, "Create new calendar"; - $frontend_caldav->SetURL($cal_home); - my $calid1 = $frontend_caldav->NewCalendar({name => 'foo'}); - $self->assert_not_null($calid1); - - xlog $self, "Change calendar name"; - my $newid = $frontend_caldav->UpdateCalendar({ id => $calid1, - name => 'bar'}); - $self->assert_str_equals($calid1, $newid); - - xlog $self, "Create new event"; - my $eventid1 = $frontend_caldav->NewEvent($calid1, { - timeZone => 'Etc/UTC', - start => '2015-01-01T12:00:00', - duration => 'PT1H', - title => 'waterfall', - }); - $self->assert_not_null($eventid1); - - xlog $self, "GET event"; - $res = $frontend_caldav->Request('GET', $cal_home . $eventid1); - $self->assert_matches(qr/SUMMARY:waterfall/, $res->{content}); - - xlog $self, "Get calendars"; - $res = $frontend_caldav->GetCalendars(); - $self->assert_num_equals(2, scalar @{$res}); - - my $sync1; - my $sync2;; - my $calid2; - if ($res->[0]{id} eq $calid1) { - $sync1 = $res->[0]{syncToken}; - $sync2 = $res->[1]{syncToken}; - $calid2 = $res->[1]{id}; + xlog $self, "Get current-user-principal"; + my $url = $frontend_caldav->GetCurrentUserPrincipal(); + $self->assert_not_null($url); + + # Copied from Net::DAVTalk::SetURL + my (undef, undef, undef, $cur_princ) + = $url =~ m{^http(s)?://([^/:]+)(?::(\d+))?(.*)?}; + + xlog $self, "PROPFIND for home-sets"; + my $res = $frontend_caldav->Request('PROPFIND', $cur_princ, + $xml, 'Content-Type' => 'text/xml'); + + my $propstat = $res->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]; + my $props = $propstat->{'{DAV:}prop'}; + $self->assert_str_equals('HTTP/1.1 200 OK', + $propstat->{'{DAV:}status'}{content}); + my $cal_home = $props->{"{$CALDAV}calendar-home-set"}{'{DAV:}href'}{content}; + my $card_home + = $props->{"{$CARDDAV}addressbook-home-set"}{'{DAV:}href'}{content}; + $self->assert_not_null($cal_home); + $self->assert_not_null($card_home); + + xlog $self, "Create new calendar"; + $frontend_caldav->SetURL($cal_home); + my $calid1 = $frontend_caldav->NewCalendar({ name => 'foo' }); + $self->assert_not_null($calid1); + + xlog $self, "Change calendar name"; + my $newid = $frontend_caldav->UpdateCalendar({ + id => $calid1, + name => 'bar' + }); + $self->assert_str_equals($calid1, $newid); + + xlog $self, "Create new event"; + my $eventid1 = $frontend_caldav->NewEvent( + $calid1, + { + timeZone => 'Etc/UTC', + start => '2015-01-01T12:00:00', + duration => 'PT1H', + title => 'waterfall', } - else { - $sync1 = $res->[1]{syncToken}; - $sync2 = $res->[0]{syncToken}; - $calid2 = $res->[0]{id}; - } - $self->assert_not_null($calid1); - $self->assert_not_null($sync1); - $self->assert_not_null($sync2); - - xlog $self, "Move event"; - my $eventid2 = $frontend_caldav->MoveEvent($eventid1, $calid2); - $self->assert_not_null($eventid2); - - xlog $self, "Sync Calendars"; - my ($adds, $removes, $errors) = - $frontend_caldav->SyncEvents($calid1, syncToken => $sync1); - $self->assert_num_equals(0, scalar @{$adds}); - $self->assert_num_equals(1, scalar @{$removes}); - $self->assert_str_equals($removes->[0], - $frontend_caldav->fullpath($eventid1)); - - ($adds, $removes, $errors) = - $frontend_caldav->SyncEvents($calid2, syncToken => $sync2); - $self->assert_num_equals(1, scalar @{$adds}); - $self->assert_str_equals($adds->[0]{href}, - $frontend_caldav->fullpath($eventid2)); - $self->assert_str_equals('waterfall', $adds->[0]{title}); - $self->assert_num_equals(0, scalar @{$removes}); - - xlog $self, "Delete event"; - $res = $frontend_caldav->DeleteEvent($eventid2); - $self->assert_num_equals(1, $res); - - xlog $self, "Delete calendar"; - $frontend_caldav->DeleteCalendar($calid1); - $res = $frontend_caldav->GetCalendar($calid1); - $self->assert_null($res); - - # XXX test other commands + ); + $self->assert_not_null($eventid1); + + xlog $self, "GET event"; + $res = $frontend_caldav->Request('GET', $cal_home . $eventid1); + $self->assert_matches(qr/SUMMARY:waterfall/, $res->{content}); + + xlog $self, "Get calendars"; + $res = $frontend_caldav->GetCalendars(); + $self->assert_num_equals(2, scalar @{$res}); + + my $sync1; + my $sync2; + my $calid2; + if ($res->[0]{id} eq $calid1) { + $sync1 = $res->[0]{syncToken}; + $sync2 = $res->[1]{syncToken}; + $calid2 = $res->[1]{id}; + } else { + $sync1 = $res->[1]{syncToken}; + $sync2 = $res->[0]{syncToken}; + $calid2 = $res->[0]{id}; + } + $self->assert_not_null($calid1); + $self->assert_not_null($sync1); + $self->assert_not_null($sync2); + + xlog $self, "Move event"; + my $eventid2 = $frontend_caldav->MoveEvent($eventid1, $calid2); + $self->assert_not_null($eventid2); + + xlog $self, "Sync Calendars"; + my ($adds, $removes, $errors) + = $frontend_caldav->SyncEvents($calid1, syncToken => $sync1); + $self->assert_num_equals(0, scalar @{$adds}); + $self->assert_num_equals(1, scalar @{$removes}); + $self->assert_str_equals($removes->[0], + $frontend_caldav->fullpath($eventid1)); + + ($adds, $removes, $errors) + = $frontend_caldav->SyncEvents($calid2, syncToken => $sync2); + $self->assert_num_equals(1, scalar @{$adds}); + $self->assert_str_equals($adds->[0]{href}, + $frontend_caldav->fullpath($eventid2)); + $self->assert_str_equals('waterfall', $adds->[0]{title}); + $self->assert_num_equals(0, scalar @{$removes}); + + xlog $self, "Delete event"; + $res = $frontend_caldav->DeleteEvent($eventid2); + $self->assert_num_equals(1, $res); + + xlog $self, "Delete calendar"; + $frontend_caldav->DeleteCalendar($calid1); + $res = $frontend_caldav->GetCalendar($calid1); + $self->assert_null($res); + + # XXX test other commands } 1; diff --git a/cassandane/Cassandane/Cyrus/MurderIMAP.pm b/cassandane/Cassandane/Cyrus/MurderIMAP.pm index 3873ad6d5f..e203047f6a 100644 --- a/cassandane/Cassandane/Cyrus/MurderIMAP.pm +++ b/cassandane/Cassandane/Cyrus/MurderIMAP.pm @@ -50,1060 +50,1083 @@ use Cassandane::Instance; $Data::Dumper::Sortkeys = 1; -sub new -{ - my $class = shift; +sub new { + my $class = shift; - my $config = Cassandane::Config->default()->clone(); + my $config = Cassandane::Config->default()->clone(); - $config->set(conversations => 'yes'); + $config->set(conversations => 'yes'); - return $class->SUPER::new({ - imapmurder => 1, adminstore => 1, deliver => 1, - }, @_); + return $class->SUPER::new( + { + imapmurder => 1, + adminstore => 1, + deliver => 1, + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_aaasetup - :needs_component_murder -{ - my ($self) = @_; + : needs_component_murder { + my ($self) = @_; - # does everything set up and tear down cleanly? - $self->assert(1); + # does everything set up and tear down cleanly? + $self->assert(1); } sub test_frontend_commands - :needs_component_murder -{ - my ($self) = @_; - my $result; - - my $frontend = $self->{frontend_store}->get_client(); - - # should be able to list - $result = $frontend->list("", "*"); - $self->assert_not_null($result); - - # select a folder that doesn't exist yet - $result = $frontend->select('INBOX.newfolder'); - $self->assert_null($result); - $self->assert_matches(qr/Mailbox does not exist/i, - $frontend->get_last_error()); - - # create should be proxied through - $result = $frontend->create('INBOX.newfolder'); - $self->assert_not_null($result); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - - # should be able to select it now - $result = $frontend->select('INBOX.newfolder'); - $self->assert_not_null($result); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - - # should be able to getmetadata - $result = $frontend->getmetadata('INBOX', - '/shared/vendor/cmu/cyrus-imapd/size'); - $self->assert_not_null($result); - $self->assert(exists $result->{'INBOX'}{'/shared/vendor/cmu/cyrus-imapd/size'}); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - $result = $frontend->getmetadata('(INBOX INBOX.newfolder)', - '/shared/vendor/cmu/cyrus-imapd/size'); - $self->assert_not_null($result); - $self->assert(exists $result->{'INBOX'}{'/shared/vendor/cmu/cyrus-imapd/size'}); - $self->assert(exists $result->{'INBOX.newfolder'}{'/shared/vendor/cmu/cyrus-imapd/size'}); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - - # check frontend version for which resource type to use - my $res_mailbox = 'MAILBOX'; - my ($maj, $min) = Cassandane::Instance->get_version('murder'); - if ($maj < 3 || ($maj == 3 && $min < 9)) { - $res_mailbox = 'X-NUM-FOLDERS'; - } - - my $frontend_admin = $self->{frontend_adminstore}->get_client(); - $result = $frontend_admin->setquota('user.cassandane', - "(STORAGE 1024 MESSAGE 5000 $res_mailbox 100)"); - $self->assert_not_null($result); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - - # XXX test other commands + : needs_component_murder { + my ($self) = @_; + my $result; + + my $frontend = $self->{frontend_store}->get_client(); + + # should be able to list + $result = $frontend->list("", "*"); + $self->assert_not_null($result); + + # select a folder that doesn't exist yet + $result = $frontend->select('INBOX.newfolder'); + $self->assert_null($result); + $self->assert_matches(qr/Mailbox does not exist/i, + $frontend->get_last_error()); + + # create should be proxied through + $result = $frontend->create('INBOX.newfolder'); + $self->assert_not_null($result); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + + # should be able to select it now + $result = $frontend->select('INBOX.newfolder'); + $self->assert_not_null($result); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + + # should be able to getmetadata + $result + = $frontend->getmetadata('INBOX', '/shared/vendor/cmu/cyrus-imapd/size'); + $self->assert_not_null($result); + $self->assert( + exists $result->{'INBOX'}{'/shared/vendor/cmu/cyrus-imapd/size'}); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + $result = $frontend->getmetadata('(INBOX INBOX.newfolder)', + '/shared/vendor/cmu/cyrus-imapd/size'); + $self->assert_not_null($result); + $self->assert( + exists $result->{'INBOX'}{'/shared/vendor/cmu/cyrus-imapd/size'}); + $self->assert( + exists $result->{'INBOX.newfolder'}{'/shared/vendor/cmu/cyrus-imapd/size'}); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + + # check frontend version for which resource type to use + my $res_mailbox = 'MAILBOX'; + my ($maj, $min) = Cassandane::Instance->get_version('murder'); + if ($maj < 3 || ($maj == 3 && $min < 9)) { + $res_mailbox = 'X-NUM-FOLDERS'; + } + + my $frontend_admin = $self->{frontend_adminstore}->get_client(); + $result = $frontend_admin->setquota('user.cassandane', + "(STORAGE 1024 MESSAGE 5000 $res_mailbox 100)"); + $self->assert_not_null($result); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + + # XXX test other commands } sub test_list_specialuse - :needs_component_murder -{ - my ($self) = @_; - - my $frontend = $self->{frontend_store}->get_client(); - my $backend = $self->{backend1_store}->get_client(); + : needs_component_murder { + my ($self) = @_; - my %specialuse = map { $_ => 1 } qw( Drafts Junk Sent Trash ); - my %other = map { $_ => 1 } qw( lists personal timesheets ); + my $frontend = $self->{frontend_store}->get_client(); + my $backend = $self->{backend1_store}->get_client(); - # create some special-use folders - foreach my $f (keys %specialuse) { - $frontend->create("INBOX.$f"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + my %specialuse = map { $_ => 1 } qw( Drafts Junk Sent Trash ); + my %other = map { $_ => 1 } qw( lists personal timesheets ); - $frontend->subscribe("INBOX.$f"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - - $frontend->setmetadata("INBOX.$f", - '/private/specialuse', "\\$f"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - } - - # create some other non special-use folders (control group) - foreach my $f (keys %other) { - $frontend->create("INBOX.$f"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - - $frontend->subscribe("INBOX.$f"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - } + # create some special-use folders + foreach my $f (keys %specialuse) { + $frontend->create("INBOX.$f"); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - # ask the backend about them - my $bresult = $backend->list([qw(SPECIAL-USE)], "", "*", - 'RETURN', [qw(SUBSCRIBED)]); - $self->assert_str_equals('ok', $backend->get_last_completion_response()); - xlog $self, Dumper $bresult; - - # check the responses - my %found; - foreach my $r (@{$bresult}) { - my ($flags, $sep, $name) = @{$r}; - # carve out the interesting part of the name - $self->assert_matches(qr/^INBOX$sep/, $name); - $name = substr($name, 6); - $found{$name} = 1; - # only want specialuse folders - $self->assert(exists $specialuse{$name}); - # must be flagged with appropriate flag - $self->assert_equals(1, scalar grep { $_ eq "\\$name" } @{$flags}); - # must be flagged with \subscribed - $self->assert_equals(1, scalar grep { $_ eq '\\Subscribed' } @{$flags}); - } + $frontend->subscribe("INBOX.$f"); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - # make sure no expected responses were missing - $self->assert_deep_equals(\%specialuse, \%found); + $frontend->setmetadata("INBOX.$f", '/private/specialuse', "\\$f"); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + } - # ask the frontend about them - my $fresult = $frontend->list([qw(SPECIAL-USE)], "", "*", - 'RETURN', [qw(SUBSCRIBED)]); + # create some other non special-use folders (control group) + foreach my $f (keys %other) { + $frontend->create("INBOX.$f"); $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - xlog $self, Dumper $fresult; - # expect the same results as on backend - $self->assert_deep_equals($bresult, $fresult); + $frontend->subscribe("INBOX.$f"); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + } + + # ask the backend about them + my $bresult + = $backend->list([qw(SPECIAL-USE)], "", "*", 'RETURN', [qw(SUBSCRIBED)]); + $self->assert_str_equals('ok', $backend->get_last_completion_response()); + xlog $self, Dumper $bresult; + + # check the responses + my %found; + foreach my $r (@{$bresult}) { + my ($flags, $sep, $name) = @{$r}; + # carve out the interesting part of the name + $self->assert_matches(qr/^INBOX$sep/, $name); + $name = substr($name, 6); + $found{$name} = 1; + # only want specialuse folders + $self->assert(exists $specialuse{$name}); + # must be flagged with appropriate flag + $self->assert_equals(1, scalar grep { $_ eq "\\$name" } @{$flags}); + # must be flagged with \subscribed + $self->assert_equals(1, scalar grep { $_ eq '\\Subscribed' } @{$flags}); + } + + # make sure no expected responses were missing + $self->assert_deep_equals(\%specialuse, \%found); + + # ask the frontend about them + my $fresult + = $frontend->list([qw(SPECIAL-USE)], "", "*", 'RETURN', [qw(SUBSCRIBED)]); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + xlog $self, Dumper $fresult; + + # expect the same results as on backend + $self->assert_deep_equals($bresult, $fresult); } sub test_xlist - :needs_component_murder -{ - my ($self) = @_; - - my $frontend = $self->{frontend_store}->get_client(); - my $backend = $self->{backend1_store}->get_client(); + : needs_component_murder { + my ($self) = @_; - my %specialuse = map { $_ => 1 } qw( Drafts Junk Sent Trash ); - my %other = map { $_ => 1 } qw( lists personal timesheets ); + my $frontend = $self->{frontend_store}->get_client(); + my $backend = $self->{backend1_store}->get_client(); - # create some special-use folders - foreach my $f (keys %specialuse) { - $frontend->create("INBOX.$f"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + my %specialuse = map { $_ => 1 } qw( Drafts Junk Sent Trash ); + my %other = map { $_ => 1 } qw( lists personal timesheets ); - $frontend->setmetadata("INBOX.$f", - '/private/specialuse', "\\$f"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - } + # create some special-use folders + foreach my $f (keys %specialuse) { + $frontend->create("INBOX.$f"); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - # create some other non special-use folders (control group) - foreach my $f (keys %other) { - $frontend->create("INBOX.$f"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - } + $frontend->setmetadata("INBOX.$f", '/private/specialuse', "\\$f"); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + } - # ask the backend about them - my $bresult = $backend->xlist("", "*"); - $self->assert_str_equals('ok', $backend->get_last_completion_response()); - xlog $self, "backend: " . Dumper $bresult; - - # check the responses - my %found; - foreach my $r (@{$bresult}) { - my ($flags, $sep, $name) = @{$r}; - if ($name eq 'INBOX') { - $found{$name} = 1; - # must be flagged with \Inbox - $self->assert_equals(1, scalar grep { $_ eq '\\Inbox' } @{$flags}); - } - else { - # carve out the interesting part of the name - $self->assert_matches(qr/^INBOX$sep/, $name); - $name = substr($name, 6); - $found{$name} = 1; - $self->assert(exists $specialuse{$name} or exists $other{$name}); - if (exists $specialuse{$name}) { - # must be flagged with appropriate flag - $self->assert_equals(1, scalar grep { $_ eq "\\$name" } @{$flags}); - } - else { - # must not be flagged with name-based flag - $self->assert_equals(0, scalar grep { $_ eq "\\$name" } @{$flags}); - } - } + # create some other non special-use folders (control group) + foreach my $f (keys %other) { + $frontend->create("INBOX.$f"); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + } + + # ask the backend about them + my $bresult = $backend->xlist("", "*"); + $self->assert_str_equals('ok', $backend->get_last_completion_response()); + xlog $self, "backend: " . Dumper $bresult; + + # check the responses + my %found; + foreach my $r (@{$bresult}) { + my ($flags, $sep, $name) = @{$r}; + if ($name eq 'INBOX') { + $found{$name} = 1; + # must be flagged with \Inbox + $self->assert_equals(1, scalar grep { $_ eq '\\Inbox' } @{$flags}); + } else { + # carve out the interesting part of the name + $self->assert_matches(qr/^INBOX$sep/, $name); + $name = substr($name, 6); + $found{$name} = 1; + $self->assert(exists $specialuse{$name} or exists $other{$name}); + if (exists $specialuse{$name}) { + # must be flagged with appropriate flag + $self->assert_equals(1, scalar grep { $_ eq "\\$name" } @{$flags}); + } else { + # must not be flagged with name-based flag + $self->assert_equals(0, scalar grep { $_ eq "\\$name" } @{$flags}); + } } + } - # make sure no expected responses were missing - $self->assert_deep_equals({ 'INBOX' => 1, %specialuse, %other }, \%found); + # make sure no expected responses were missing + $self->assert_deep_equals({ 'INBOX' => 1, %specialuse, %other }, \%found); - # ask the frontend about them - my $fresult = $frontend->xlist("", "*"); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - xlog $self, "frontend: " . Dumper $fresult; + # ask the frontend about them + my $fresult = $frontend->xlist("", "*"); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + xlog $self, "frontend: " . Dumper $fresult; - # expect the same results as on backend - $self->assert_deep_equals($bresult, $fresult); + # expect the same results as on backend + $self->assert_deep_equals($bresult, $fresult); } sub test_move_to_backend_nonexistent - :needs_component_murder -{ - my ($self) = @_; - - my $dest_folder = 'INBOX.dest'; - - # put some messages into the INBOX - my %exp; - $exp{A} = $self->make_message("Message A", store => $self->{frontend_store}); - $exp{B} = $self->make_message("Message B", store => $self->{frontend_store}); - $exp{C} = $self->make_message("Message C", store => $self->{frontend_store}); - - my $frontend = $self->{frontend_store}->get_client(); - my $backend = $self->{backend1_store}->get_client(); - - # create a destination folder (on both frontend and backend) - $frontend->create($dest_folder); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - - # nuke the destination folder (on the backend only) - $backend->localdelete($dest_folder); - $self->assert_str_equals('ok', $backend->get_last_completion_response()); - - my $f_folders = $frontend->list('', '*'); - $self->assert_deep_equals( - [[[ '\\HasChildren' ], '.', 'INBOX' ], - [[ '\\HasNoChildren' ], '.', 'INBOX.dest' ]], - $f_folders); - - my $b_folders = $backend->list('', '*'); - $self->assert_deep_equals( - [[[ '\\HasNoChildren' ], '.', 'INBOX' ]], - $b_folders); - - # try to move a message to dest - $frontend->move($exp{A}->get_attribute('uid'), $dest_folder); - - # it should fail nicely - $self->assert_str_equals('no', $frontend->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/, $frontend->get_last_error()); - - # try to copy a message to dest - $frontend->copy($exp{B}->get_attribute('uid'), $dest_folder); - - # it should fail nicely - $self->assert_str_equals('no', $frontend->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/, $frontend->get_last_error()); + : needs_component_murder { + my ($self) = @_; + + my $dest_folder = 'INBOX.dest'; + + # put some messages into the INBOX + my %exp; + $exp{A} = $self->make_message("Message A", store => $self->{frontend_store}); + $exp{B} = $self->make_message("Message B", store => $self->{frontend_store}); + $exp{C} = $self->make_message("Message C", store => $self->{frontend_store}); + + my $frontend = $self->{frontend_store}->get_client(); + my $backend = $self->{backend1_store}->get_client(); + + # create a destination folder (on both frontend and backend) + $frontend->create($dest_folder); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + + # nuke the destination folder (on the backend only) + $backend->localdelete($dest_folder); + $self->assert_str_equals('ok', $backend->get_last_completion_response()); + + my $f_folders = $frontend->list('', '*'); + $self->assert_deep_equals( + [ + [ ['\\HasChildren'], '.', 'INBOX' ], + [ ['\\HasNoChildren'], '.', 'INBOX.dest' ] + ], + $f_folders + ); + + my $b_folders = $backend->list('', '*'); + $self->assert_deep_equals([ [ ['\\HasNoChildren'], '.', 'INBOX' ] ], + $b_folders); + + # try to move a message to dest + $frontend->move($exp{A}->get_attribute('uid'), $dest_folder); + + # it should fail nicely + $self->assert_str_equals('no', $frontend->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/, + $frontend->get_last_error()); + + # try to copy a message to dest + $frontend->copy($exp{B}->get_attribute('uid'), $dest_folder); + + # it should fail nicely + $self->assert_str_equals('no', $frontend->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/, + $frontend->get_last_error()); } sub test_move_to_nonexistent - :needs_component_murder -{ - my ($self) = @_; + : needs_component_murder { + my ($self) = @_; - my $dest_folder = 'INBOX.nonexistent'; + my $dest_folder = 'INBOX.nonexistent'; - # put some messages into the INBOX - my %exp; - $exp{A} = $self->make_message("Message A", store => $self->{frontend_store}); - $exp{B} = $self->make_message("Message B", store => $self->{frontend_store}); - $exp{C} = $self->make_message("Message C", store => $self->{frontend_store}); + # put some messages into the INBOX + my %exp; + $exp{A} = $self->make_message("Message A", store => $self->{frontend_store}); + $exp{B} = $self->make_message("Message B", store => $self->{frontend_store}); + $exp{C} = $self->make_message("Message C", store => $self->{frontend_store}); - my $frontend = $self->{frontend_store}->get_client(); - my $backend = $self->{backend1_store}->get_client(); + my $frontend = $self->{frontend_store}->get_client(); + my $backend = $self->{backend1_store}->get_client(); - # make sure we don't unexpectedly have the nonexistent folder - my $f_folders = $frontend->list('', '*'); - $self->assert_deep_equals( - [[[ '\\HasNoChildren' ], '.', 'INBOX' ]], - $f_folders); + # make sure we don't unexpectedly have the nonexistent folder + my $f_folders = $frontend->list('', '*'); + $self->assert_deep_equals([ [ ['\\HasNoChildren'], '.', 'INBOX' ] ], + $f_folders); - my $b_folders = $backend->list('', '*'); - $self->assert_deep_equals( - [[[ '\\HasNoChildren' ], '.', 'INBOX' ]], - $b_folders); + my $b_folders = $backend->list('', '*'); + $self->assert_deep_equals([ [ ['\\HasNoChildren'], '.', 'INBOX' ] ], + $b_folders); - # try to move a message to dest - $frontend->move($exp{A}->get_attribute('uid'), $dest_folder); + # try to move a message to dest + $frontend->move($exp{A}->get_attribute('uid'), $dest_folder); - # it should fail nicely - $self->assert_str_equals('no', $frontend->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/, $frontend->get_last_error()); + # it should fail nicely + $self->assert_str_equals('no', $frontend->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/, + $frontend->get_last_error()); - # try to copy a message to dest - $frontend->copy($exp{B}->get_attribute('uid'), $dest_folder); + # try to copy a message to dest + $frontend->copy($exp{B}->get_attribute('uid'), $dest_folder); - # it should fail nicely - $self->assert_str_equals('no', $frontend->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/, $frontend->get_last_error()); + # it should fail nicely + $self->assert_str_equals('no', $frontend->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/, + $frontend->get_last_error()); } sub test_rename_with_location - :needs_component_murder :AllowMoves -{ - my ($self) = @_; - - my $frontend_adminstore = $self->{frontend_adminstore}->get_client(); - - my $backend2_servername = $self->{backend2}->get_servername(); - - xlog $self, "backend2 servername: $backend2_servername"; - - # not allowed to change mailbox name if location also specified - $frontend_adminstore->rename('user.cassandane', 'user.foo', "$backend2_servername!"); - $self->assert_str_equals('no', $frontend_adminstore->get_last_completion_response()); - - # but can change location if mailbox name remains the same - $frontend_adminstore->rename('user.cassandane', 'user.cassandane', "$backend2_servername!"); - # XXX need to check for "* NO USER cassandane (some error)" untagged response - $self->assert_str_equals('ok', $frontend_adminstore->get_last_completion_response()); - - # verify that it moved - my $backend1_store = $self->{backend1_store}->get_client(); - $backend1_store->select('INBOX'); - $self->assert_str_equals('no', $backend1_store->get_last_completion_response()); - - my $backend2_store = $self->{backend2_store}->get_client(); - $backend2_store->select('INBOX'); - $self->assert_str_equals('ok', $backend2_store->get_last_completion_response()); + : needs_component_murder : AllowMoves { + my ($self) = @_; + + my $frontend_adminstore = $self->{frontend_adminstore}->get_client(); + + my $backend2_servername = $self->{backend2}->get_servername(); + + xlog $self, "backend2 servername: $backend2_servername"; + + # not allowed to change mailbox name if location also specified + $frontend_adminstore->rename('user.cassandane', 'user.foo', + "$backend2_servername!"); + $self->assert_str_equals('no', + $frontend_adminstore->get_last_completion_response()); + + # but can change location if mailbox name remains the same + $frontend_adminstore->rename('user.cassandane', 'user.cassandane', + "$backend2_servername!"); + # XXX need to check for "* NO USER cassandane (some error)" untagged response + $self->assert_str_equals('ok', + $frontend_adminstore->get_last_completion_response()); + + # verify that it moved + my $backend1_store = $self->{backend1_store}->get_client(); + $backend1_store->select('INBOX'); + $self->assert_str_equals('no', + $backend1_store->get_last_completion_response()); + + my $backend2_store = $self->{backend2_store}->get_client(); + $backend2_store->select('INBOX'); + $self->assert_str_equals('ok', + $backend2_store->get_last_completion_response()); } sub test_xfer_nonexistent_unixhs - :needs_component_murder :UnixHierarchySep -{ - my ($self) = @_; - - my $admintalk = $self->{backend1_adminstore}->get_client(); - my $backend2_servername = $self->{backend2}->get_servername(); - - # xfer a user that doesn't exist - $admintalk->_imap_cmd('xfer', 0, {}, - 'user/nonexistent', $backend2_servername); - $self->assert_str_equals( - 'no', $admintalk->get_last_completion_response() - ); - - # xfer a mailbox that doesn't exist - $admintalk->_imap_cmd('xfer', 0, {}, - 'user/cassandane/nonexistent', $backend2_servername); - $self->assert_str_equals( - 'no', $admintalk->get_last_completion_response() - ); - - # xfer a pattern that doesn't match anything - $admintalk->_imap_cmd('xfer', 0, {}, - 'user/cassandane/non%', $backend2_servername); - $self->assert_str_equals( - 'no', $admintalk->get_last_completion_response() - ); - - # xfer a partition that doesn't exist - $admintalk->_imap_cmd('xfer', 0, {}, - 'nonexistent', $backend2_servername); - $self->assert_str_equals( - 'no', $admintalk->get_last_completion_response() - ); + : needs_component_murder : UnixHierarchySep { + my ($self) = @_; + + my $admintalk = $self->{backend1_adminstore}->get_client(); + my $backend2_servername = $self->{backend2}->get_servername(); + + # xfer a user that doesn't exist + $admintalk->_imap_cmd('xfer', 0, {}, + 'user/nonexistent', $backend2_servername); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + + # xfer a mailbox that doesn't exist + $admintalk->_imap_cmd('xfer', 0, {}, + 'user/cassandane/nonexistent', $backend2_servername); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + + # xfer a pattern that doesn't match anything + $admintalk->_imap_cmd('xfer', 0, {}, + 'user/cassandane/non%', $backend2_servername); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + + # xfer a partition that doesn't exist + $admintalk->_imap_cmd('xfer', 0, {}, 'nonexistent', $backend2_servername); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); } sub test_xfer_user_altns_unixhs - :AllowMoves :AltNamespace :UnixHierarchySep - :needs_component_murder :min_version_3_2 -{ - my ($self) = @_; - - # set up some data for cassandane on backend1 - my $expected = $self->populate_user($self->{instance}, - $self->{backend1_store}, - [qw(INBOX Drafts)]); - - my $imaptalk = $self->{backend1_store}->get_client(); - my $admintalk = $self->{backend1_adminstore}->get_client(); - my $backend2_servername = $self->{backend2}->get_servername(); - - # what's the frontend mailboxes.db say before we move? - my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; - - # what's imap LIST say before we move? - # original backend: - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # frontend doesn't know about annotations - my $frontendtalk = $self->{frontend_store}->get_client(); - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren )], - }); - - # ... but if we ask for them it'll proxy the request and find them - $data = $frontendtalk->list("", "*", 'RETURN', [ 'SPECIAL-USE' ]); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # now xfer the cassandane user to backend2 - my $ret = $admintalk->_imap_cmd('xfer', 0, {}, - 'user/cassandane', $backend2_servername); - xlog "XXX xfer returned: " . Dumper $ret; - # XXX 3.2+ with 3.0 target fails here: syntax error in parameters - $self->assert_str_equals('ok', $ret); - # XXX 3.2+ with 2.5 target fails here: mailbox has an invalid format - $self->assert_str_equals( - 'ok', $admintalk->get_last_completion_response() - ); - - # account contents should be on the other store now - $self->check_user($self->{backend2}, $self->{backend2_store}, $expected); - - # frontend should now say the user is on the other store - # XXX is there a better way to discover this? - $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; - # XXX 3.0 with 2.5 frontend fails here: server field is blank - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'user.cassandane'}->{server} - ); - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'user.cassandane.Drafts'}->{server} - ); - - # what's imap LIST say after the move? - undef $imaptalk; - $self->{store}->disconnect(); - $imaptalk = $self->{store}->get_client(); - xlog "checking LIST on old backend"; - $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', {}); - - my $backend2talk = $self->{backend2_store}->get_client(); - xlog "checking LIST on new backend"; - $data = $backend2talk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # frontend doesn't know about annotations - $frontendtalk = $self->{frontend_store}->get_client(); - xlog "checking LIST on frontend"; - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren )], - }); - - # ... but if we ask for them it'll proxy the request and find them - $data = $frontendtalk->list("", "*", 'RETURN', [ 'SPECIAL-USE' ]); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); + : AllowMoves : AltNamespace : UnixHierarchySep + : needs_component_murder : min_version_3_2 { + my ($self) = @_; + + # set up some data for cassandane on backend1 + my $expected + = $self->populate_user($self->{instance}, $self->{backend1_store}, + [qw(INBOX Drafts)]); + + my $imaptalk = $self->{backend1_store}->get_client(); + my $admintalk = $self->{backend1_adminstore}->get_client(); + my $backend2_servername = $self->{backend2}->get_servername(); + + # what's the frontend mailboxes.db say before we move? + my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; + + # what's imap LIST say before we move? + # original backend: + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # frontend doesn't know about annotations + my $frontendtalk = $self->{frontend_store}->get_client(); + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren )], + } + ); + + # ... but if we ask for them it'll proxy the request and find them + $data = $frontendtalk->list("", "*", 'RETURN', ['SPECIAL-USE']); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # now xfer the cassandane user to backend2 + my $ret = $admintalk->_imap_cmd('xfer', 0, {}, + 'user/cassandane', $backend2_servername); + xlog "XXX xfer returned: " . Dumper $ret; + # XXX 3.2+ with 3.0 target fails here: syntax error in parameters + $self->assert_str_equals('ok', $ret); + # XXX 3.2+ with 2.5 target fails here: mailbox has an invalid format + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # account contents should be on the other store now + $self->check_user($self->{backend2}, $self->{backend2_store}, $expected); + + # frontend should now say the user is on the other store + # XXX is there a better way to discover this? + $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; + # XXX 3.0 with 2.5 frontend fails here: server field is blank + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'user.cassandane'}->{server}); + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'user.cassandane.Drafts'}->{server}); + + # what's imap LIST say after the move? + undef $imaptalk; + $self->{store}->disconnect(); + $imaptalk = $self->{store}->get_client(); + xlog "checking LIST on old backend"; + $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure($data, '/', {}); + + my $backend2talk = $self->{backend2_store}->get_client(); + xlog "checking LIST on new backend"; + $data = $backend2talk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # frontend doesn't know about annotations + $frontendtalk = $self->{frontend_store}->get_client(); + xlog "checking LIST on frontend"; + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren )], + } + ); + + # ... but if we ask for them it'll proxy the request and find them + $data = $frontendtalk->list("", "*", 'RETURN', ['SPECIAL-USE']); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); } sub test_xfer_user_noaltns_nounixhs - :AllowMoves :NoAltNamespace - :needs_component_murder :min_version_3_2 -{ - my ($self) = @_; - - # set up some data for cassandane on backend1 - my $expected = $self->populate_user($self->{instance}, - $self->{backend1_store}, - [qw(INBOX INBOX.Drafts)]); - - my $imaptalk = $self->{backend1_store}->get_client(); - my $admintalk = $self->{backend1_adminstore}->get_client(); - my $backend2_servername = $self->{backend2}->get_servername(); - - # what's the frontend mailboxes.db say before we move? - my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; - - # what's imap LIST say before we move? - # original backend: - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # frontend doesn't know about annotations - my $frontendtalk = $self->{frontend_store}->get_client(); - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren )], - }); - - # ... but if we ask for them it'll proxy the request and find them - $data = $frontendtalk->list("", "*", 'RETURN', [ 'SPECIAL-USE' ]); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # now xfer the cassandane user to backend2 - my $ret = $admintalk->_imap_cmd('xfer', 0, {}, - 'user.cassandane', $backend2_servername); - xlog "XXX xfer returned: " . Dumper $ret; - # XXX 3.2+ with 3.0 target fails here: syntax error in parameters - $self->assert_str_equals('ok', $ret); - # XXX 3.2+ with 2.5 target fails here: mailbox has an invalid format - $self->assert_str_equals( - 'ok', $admintalk->get_last_completion_response() - ); - - # account contents should be on the other store now - $self->check_user($self->{backend2}, $self->{backend2_store}, $expected); - - # frontend should now say the user is on the other store - # XXX is there a better way to discover this? - $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; - # XXX 3.0 with 2.5 frontend fails here: server field is blank - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'user.cassandane'}->{server} - ); - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'user.cassandane.Drafts'}->{server} - ); - - # what's imap LIST say after the move? - undef $imaptalk; - $self->{store}->disconnect(); - $imaptalk = $self->{store}->get_client(); - xlog "checking LIST on old backend"; - $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', {}); - - my $backend2talk = $self->{backend2_store}->get_client(); - xlog "checking LIST on new backend"; - $data = $backend2talk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # frontend doesn't know about annotations - $frontendtalk = $self->{frontend_store}->get_client(); - xlog "checking LIST on frontend"; - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren )], - }); - - # ... but if we ask for them it'll proxy the request and find them - $data = $frontendtalk->list("", "*", 'RETURN', [ 'SPECIAL-USE' ]); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); + : AllowMoves : NoAltNamespace + : needs_component_murder : min_version_3_2 { + my ($self) = @_; + + # set up some data for cassandane on backend1 + my $expected + = $self->populate_user($self->{instance}, $self->{backend1_store}, + [qw(INBOX INBOX.Drafts)]); + + my $imaptalk = $self->{backend1_store}->get_client(); + my $admintalk = $self->{backend1_adminstore}->get_client(); + my $backend2_servername = $self->{backend2}->get_servername(); + + # what's the frontend mailboxes.db say before we move? + my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; + + # what's imap LIST say before we move? + # original backend: + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # frontend doesn't know about annotations + my $frontendtalk = $self->{frontend_store}->get_client(); + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren )], + } + ); + + # ... but if we ask for them it'll proxy the request and find them + $data = $frontendtalk->list("", "*", 'RETURN', ['SPECIAL-USE']); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # now xfer the cassandane user to backend2 + my $ret = $admintalk->_imap_cmd('xfer', 0, {}, + 'user.cassandane', $backend2_servername); + xlog "XXX xfer returned: " . Dumper $ret; + # XXX 3.2+ with 3.0 target fails here: syntax error in parameters + $self->assert_str_equals('ok', $ret); + # XXX 3.2+ with 2.5 target fails here: mailbox has an invalid format + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # account contents should be on the other store now + $self->check_user($self->{backend2}, $self->{backend2_store}, $expected); + + # frontend should now say the user is on the other store + # XXX is there a better way to discover this? + $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; + # XXX 3.0 with 2.5 frontend fails here: server field is blank + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'user.cassandane'}->{server}); + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'user.cassandane.Drafts'}->{server}); + + # what's imap LIST say after the move? + undef $imaptalk; + $self->{store}->disconnect(); + $imaptalk = $self->{store}->get_client(); + xlog "checking LIST on old backend"; + $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure($data, '.', {}); + + my $backend2talk = $self->{backend2_store}->get_client(); + xlog "checking LIST on new backend"; + $data = $backend2talk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # frontend doesn't know about annotations + $frontendtalk = $self->{frontend_store}->get_client(); + xlog "checking LIST on frontend"; + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren )], + } + ); + + # ... but if we ask for them it'll proxy the request and find them + $data = $frontendtalk->list("", "*", 'RETURN', ['SPECIAL-USE']); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); } sub test_xfer_user_verify_cleanup - :AllowMoves :NoAltNamespace :Conversations - :needs_component_murder :min_version_3_9 -{ - my ($self) = @_; + : AllowMoves : NoAltNamespace : Conversations + : needs_component_murder : min_version_3_9 { + my ($self) = @_; - # set up some data for cassandane on backend1 - my $expected = $self->populate_user($self->{instance}, - $self->{backend1_store}, - [qw(INBOX INBOX.Drafts)]); + # set up some data for cassandane on backend1 + my $expected + = $self->populate_user($self->{instance}, $self->{backend1_store}, + [qw(INBOX INBOX.Drafts)]); - my $imaptalk = $self->{backend1_store}->get_client(); - my $admintalk = $self->{backend1_adminstore}->get_client(); - my $backend2_servername = $self->{backend2}->get_servername(); + my $imaptalk = $self->{backend1_store}->get_client(); + my $admintalk = $self->{backend1_adminstore}->get_client(); + my $backend2_servername = $self->{backend2}->get_servername(); - xlog $self, "Subscribe to INBOX"; - $imaptalk->subscribe("INBOX"); + xlog $self, "Subscribe to INBOX"; + $imaptalk->subscribe("INBOX"); - xlog $self, "Install a sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "Verify user mailbox directories exist"; - my $inbox_dir = $self->{instance}->folder_to_directory('INBOX'); - my $drafts_dir = $self->{instance}->folder_to_directory('INBOX.Drafts'); - $self->assert_file_test($inbox_dir, '-d'); - $self->assert_file_test($drafts_dir, '-d'); - - xlog $self, "Verify user data files/directories exist"; - my $data = $self->{instance}->run_mbpath('-u', 'cassandane'); - $self->assert_file_test($data->{user}{'sub'}, '-f'); - $self->assert_file_test($data->{user}{counters}, '-f'); - $self->assert_file_test($data->{user}{conversations}, '-f'); - $self->assert_file_test($data->{user}{xapianactive}, '-f'); - $self->assert_file_test("$data->{user}{sieve}/defaultbc", '-f'); - $self->assert_file_test($data->{xapian}{t1}, '-d'); - - # now xfer the cassandane user to backend2 - my $ret = $admintalk->_imap_cmd('xfer', 0, {}, - 'user.cassandane', $backend2_servername); - - xlog $self, "Verify user mailbox directories have been deleted"; - $self->assert_not_file_test($inbox_dir, '-e'); - $self->assert_not_file_test($drafts_dir, '-e'); - - xlog $self, "Verify user data files/directories have been deleted"; - $self->assert_not_file_test($data->{user}{'sub'}, '-e'); - $self->assert_not_file_test($data->{user}{counters}, '-e'); - $self->assert_not_file_test($data->{user}{conversations}, '-e'); - $self->assert_not_file_test($data->{user}{xapianactive}, '-e'); - $self->assert_not_file_test($data->{user}{sieve}, '-e'); - $self->assert_not_file_test($data->{xapian}{t1}, '-e'); + ); + + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "Verify user mailbox directories exist"; + my $inbox_dir = $self->{instance}->folder_to_directory('INBOX'); + my $drafts_dir = $self->{instance}->folder_to_directory('INBOX.Drafts'); + $self->assert_file_test($inbox_dir, '-d'); + $self->assert_file_test($drafts_dir, '-d'); + + xlog $self, "Verify user data files/directories exist"; + my $data = $self->{instance}->run_mbpath('-u', 'cassandane'); + $self->assert_file_test($data->{user}{'sub'}, '-f'); + $self->assert_file_test($data->{user}{counters}, '-f'); + $self->assert_file_test($data->{user}{conversations}, '-f'); + $self->assert_file_test($data->{user}{xapianactive}, '-f'); + $self->assert_file_test("$data->{user}{sieve}/defaultbc", '-f'); + $self->assert_file_test($data->{xapian}{t1}, '-d'); + + # now xfer the cassandane user to backend2 + my $ret = $admintalk->_imap_cmd('xfer', 0, {}, + 'user.cassandane', $backend2_servername); + + xlog $self, "Verify user mailbox directories have been deleted"; + $self->assert_not_file_test($inbox_dir, '-e'); + $self->assert_not_file_test($drafts_dir, '-e'); + + xlog $self, "Verify user data files/directories have been deleted"; + $self->assert_not_file_test($data->{user}{'sub'}, '-e'); + $self->assert_not_file_test($data->{user}{counters}, '-e'); + $self->assert_not_file_test($data->{user}{conversations}, '-e'); + $self->assert_not_file_test($data->{user}{xapianactive}, '-e'); + $self->assert_not_file_test($data->{user}{sieve}, '-e'); + $self->assert_not_file_test($data->{xapian}{t1}, '-e'); } sub test_xfer_user_altns_unixhs_virtdom - :AllowMoves :AltNamespace :UnixHierarchySep :VirtDomains - :needs_component_murder :min_version_3_2 -{ - my ($self) = @_; - - # set up a user with a domain - my $admintalk = $self->{backend1_adminstore}->get_client(); - $admintalk->create('user/foo@example.com'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - my $frontend_store = $self->{frontend}->get_service('imap')->create_store( - username => 'foo@example.com'); - my $backend1_store = $self->{instance}->get_service('imap')->create_store( - username => 'foo@example.com'); - my $backend2_store = $self->{backend2}->get_service('imap')->create_store( - username => 'foo@example.com'); - - # set up some data for cassandane on backend1 - my $expected = $self->populate_user($self->{instance}, - $backend1_store, - [qw(INBOX Drafts)]); - - my $imaptalk = $backend1_store->get_client(); - my $backend2_servername = $self->{backend2}->get_servername(); - - # what's the frontend mailboxes.db say before we move? - my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; - - # what's imap LIST say before we move? - # original backend: - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # frontend doesn't know about annotations - my $frontendtalk = $frontend_store->get_client(); - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren )], - }); - - # ... but if we ask for them it'll proxy the request and find them - $data = $frontendtalk->list("", "*", 'RETURN', [ 'SPECIAL-USE' ]); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # now xfer the cassandane user to backend2 - my $ret = $admintalk->_imap_cmd('xfer', 0, {}, - 'user/foo@example.com', - $backend2_servername); - xlog "XXX xfer returned: " . Dumper $ret; - # XXX 3.2+ with 3.0 target fails here: syntax error in parameters - $self->assert_str_equals('ok', $ret); - # XXX 3.2+ with 2.5 target fails here: mailbox has an invalid format - $self->assert_str_equals( - 'ok', $admintalk->get_last_completion_response() - ); - - # account contents should be on the other store now - $self->check_user($self->{backend2}, $backend2_store, $expected); - - # frontend should now say the user is on the other store - # XXX is there a better way to discover this? - $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; - # XXX 3.0 with 2.5 frontend fails here: server field is blank - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'example.com!user.foo'}->{server} - ); - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'example.com!user.foo.Drafts'}->{server} - ); - - # what's imap LIST say after the move? - undef $imaptalk; - $backend1_store->disconnect(); - $imaptalk = $backend1_store->get_client(); - xlog "checking LIST on old backend"; - $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', {}); - - my $backend2talk = $backend2_store->get_client(); - xlog "checking LIST on new backend"; - $data = $backend2talk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # frontend doesn't know about annotations - $frontendtalk = $frontend_store->get_client(); - xlog "checking LIST on frontend"; - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren )], - }); - - # ... but if we ask for them it'll proxy the request and find them - $data = $frontendtalk->list("", "*", 'RETURN', [ 'SPECIAL-USE' ]); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); + : AllowMoves : AltNamespace : UnixHierarchySep : VirtDomains + : needs_component_murder : min_version_3_2 { + my ($self) = @_; + + # set up a user with a domain + my $admintalk = $self->{backend1_adminstore}->get_client(); + $admintalk->create('user/foo@example.com'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + my $frontend_store = $self->{frontend}->get_service('imap') + ->create_store(username => 'foo@example.com'); + my $backend1_store = $self->{instance}->get_service('imap') + ->create_store(username => 'foo@example.com'); + my $backend2_store = $self->{backend2}->get_service('imap') + ->create_store(username => 'foo@example.com'); + + # set up some data for cassandane on backend1 + my $expected = $self->populate_user($self->{instance}, $backend1_store, + [qw(INBOX Drafts)]); + + my $imaptalk = $backend1_store->get_client(); + my $backend2_servername = $self->{backend2}->get_servername(); + + # what's the frontend mailboxes.db say before we move? + my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; + + # what's imap LIST say before we move? + # original backend: + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # frontend doesn't know about annotations + my $frontendtalk = $frontend_store->get_client(); + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren )], + } + ); + + # ... but if we ask for them it'll proxy the request and find them + $data = $frontendtalk->list("", "*", 'RETURN', ['SPECIAL-USE']); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # now xfer the cassandane user to backend2 + my $ret = $admintalk->_imap_cmd('xfer', 0, {}, 'user/foo@example.com', + $backend2_servername); + xlog "XXX xfer returned: " . Dumper $ret; + # XXX 3.2+ with 3.0 target fails here: syntax error in parameters + $self->assert_str_equals('ok', $ret); + # XXX 3.2+ with 2.5 target fails here: mailbox has an invalid format + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # account contents should be on the other store now + $self->check_user($self->{backend2}, $backend2_store, $expected); + + # frontend should now say the user is on the other store + # XXX is there a better way to discover this? + $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; + # XXX 3.0 with 2.5 frontend fails here: server field is blank + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'example.com!user.foo'}->{server}); + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'example.com!user.foo.Drafts'}->{server}); + + # what's imap LIST say after the move? + undef $imaptalk; + $backend1_store->disconnect(); + $imaptalk = $backend1_store->get_client(); + xlog "checking LIST on old backend"; + $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure($data, '/', {}); + + my $backend2talk = $backend2_store->get_client(); + xlog "checking LIST on new backend"; + $data = $backend2talk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # frontend doesn't know about annotations + $frontendtalk = $frontend_store->get_client(); + xlog "checking LIST on frontend"; + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren )], + } + ); + + # ... but if we ask for them it'll proxy the request and find them + $data = $frontendtalk->list("", "*", 'RETURN', ['SPECIAL-USE']); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); } sub test_xfer_user_noaltns_nounixhs_virtdom - :AllowMoves :NoAltNamespace :VirtDomains - :needs_component_murder :min_version_3_2 -{ - my ($self) = @_; - - # set up a user with a domain - my $admintalk = $self->{backend1_adminstore}->get_client(); - $admintalk->create('user.foo@example.com'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - my $frontend_store = $self->{frontend}->get_service('imap')->create_store( - username => 'foo@example.com'); - my $backend1_store = $self->{instance}->get_service('imap')->create_store( - username => 'foo@example.com'); - my $backend2_store = $self->{backend2}->get_service('imap')->create_store( - username => 'foo@example.com'); - - # set up some data for cassandane on backend1 - my $expected = $self->populate_user($self->{instance}, - $backend1_store, - [qw(INBOX INBOX.Drafts)]); - - my $imaptalk = $backend1_store->get_client(); - my $backend2_servername = $self->{backend2}->get_servername(); - - # what's the frontend mailboxes.db say before we move? - my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; - - # what's imap LIST say before we move? - # original backend: - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # frontend doesn't know about annotations - my $frontendtalk = $frontend_store->get_client(); - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren )], - }); - - # ... but if we ask for them it'll proxy the request and find them - $data = $frontendtalk->list("", "*", 'RETURN', [ 'SPECIAL-USE' ]); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # now xfer the cassandane user to backend2 - my $ret = $admintalk->_imap_cmd('xfer', 0, {}, - 'user.foo@example.com', - $backend2_servername); - xlog "XXX xfer returned: " . Dumper $ret; - # XXX 3.2+ with 3.0 target fails here: syntax error in parameters - $self->assert_str_equals('ok', $ret); - # XXX 3.2+ with 2.5 target fails here: mailbox has an invalid format - $self->assert_str_equals( - 'ok', $admintalk->get_last_completion_response() - ); - - # account contents should be on the other store now - $self->check_user($self->{backend2}, $backend2_store, $expected); - - # frontend should now say the user is on the other store - # XXX is there a better way to discover this? - $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; - # XXX 3.0 with 2.5 frontend fails here: server field is blank - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'example.com!user.foo'}->{server} - ); - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'example.com!user.foo.Drafts'}->{server} - ); - - # what's imap LIST say after the move? - undef $imaptalk; - $backend1_store->disconnect(); - $imaptalk = $backend1_store->get_client(); - xlog "checking LIST on old backend"; - $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', {}); - - my $backend2talk = $backend2_store->get_client(); - xlog "checking LIST on new backend"; - $data = $backend2talk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); - - # frontend doesn't know about annotations - $frontendtalk = $frontend_store->get_client(); - xlog "checking LIST on frontend"; - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren )], - }); - - # ... but if we ask for them it'll proxy the request and find them - $data = $frontendtalk->list("", "*", 'RETURN', [ 'SPECIAL-USE' ]); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], - }); + : AllowMoves : NoAltNamespace : VirtDomains + : needs_component_murder : min_version_3_2 { + my ($self) = @_; + + # set up a user with a domain + my $admintalk = $self->{backend1_adminstore}->get_client(); + $admintalk->create('user.foo@example.com'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + my $frontend_store = $self->{frontend}->get_service('imap') + ->create_store(username => 'foo@example.com'); + my $backend1_store = $self->{instance}->get_service('imap') + ->create_store(username => 'foo@example.com'); + my $backend2_store = $self->{backend2}->get_service('imap') + ->create_store(username => 'foo@example.com'); + + # set up some data for cassandane on backend1 + my $expected = $self->populate_user($self->{instance}, $backend1_store, + [qw(INBOX INBOX.Drafts)]); + + my $imaptalk = $backend1_store->get_client(); + my $backend2_servername = $self->{backend2}->get_servername(); + + # what's the frontend mailboxes.db say before we move? + my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; + + # what's imap LIST say before we move? + # original backend: + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # frontend doesn't know about annotations + my $frontendtalk = $frontend_store->get_client(); + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren )], + } + ); + + # ... but if we ask for them it'll proxy the request and find them + $data = $frontendtalk->list("", "*", 'RETURN', ['SPECIAL-USE']); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # now xfer the cassandane user to backend2 + my $ret = $admintalk->_imap_cmd('xfer', 0, {}, 'user.foo@example.com', + $backend2_servername); + xlog "XXX xfer returned: " . Dumper $ret; + # XXX 3.2+ with 3.0 target fails here: syntax error in parameters + $self->assert_str_equals('ok', $ret); + # XXX 3.2+ with 2.5 target fails here: mailbox has an invalid format + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # account contents should be on the other store now + $self->check_user($self->{backend2}, $backend2_store, $expected); + + # frontend should now say the user is on the other store + # XXX is there a better way to discover this? + $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; + # XXX 3.0 with 2.5 frontend fails here: server field is blank + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'example.com!user.foo'}->{server}); + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'example.com!user.foo.Drafts'}->{server}); + + # what's imap LIST say after the move? + undef $imaptalk; + $backend1_store->disconnect(); + $imaptalk = $backend1_store->get_client(); + xlog "checking LIST on old backend"; + $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure($data, '.', {}); + + my $backend2talk = $backend2_store->get_client(); + xlog "checking LIST on new backend"; + $data = $backend2talk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); + + # frontend doesn't know about annotations + $frontendtalk = $frontend_store->get_client(); + xlog "checking LIST on frontend"; + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren )], + } + ); + + # ... but if we ask for them it'll proxy the request and find them + $data = $frontendtalk->list("", "*", 'RETURN', ['SPECIAL-USE']); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.Drafts' => [qw( \\HasNoChildren \\Drafts )], + } + ); } sub test_xfer_mailbox_altns_unixhs - :AllowMoves :AltNamespace :UnixHierarchySep - :needs_component_murder :min_version_3_2 :max_version_3_4 -{ - my ($self) = @_; - - # what we expect from this test will depend on the cyrus version being - # run on backend2 - my $backend2_permits_single_mailbox = 1; - my ($maj, $min) = Cassandane::Instance->get_version('murder'); - if ($maj > 3 || ($maj == 3 && $min >= 5)) { - $backend2_permits_single_mailbox = 0; + : AllowMoves : AltNamespace : UnixHierarchySep + : needs_component_murder : min_version_3_2 : max_version_3_4 { + my ($self) = @_; + + # what we expect from this test will depend on the cyrus version being + # run on backend2 + my $backend2_permits_single_mailbox = 1; + my ($maj, $min) = Cassandane::Instance->get_version('murder'); + if ($maj > 3 || ($maj == 3 && $min >= 5)) { + $backend2_permits_single_mailbox = 0; + } + + # set up some data for cassandane on backend1 + my $expected_stay = $self->populate_user( + $self->{instance}, + $self->{backend1_store}, + [qw(INBOX Big Big/Red Big/Red/Dog)] + ); + + # we're planning to only XFER "Big/Red" (but not the others!) + my $expected_move->{mailboxes}->{'Big/Red'} + = $expected_stay->{mailboxes}->{'Big/Red'}; + delete $expected_stay->{mailboxes}->{'Big/Red'}; + + my $imaptalk = $self->{backend1_store}->get_client(); + my $admintalk = $self->{backend1_adminstore}->get_client(); + my $backend1_servername = $self->{instance}->get_servername(); + my $backend2_servername = $self->{backend2}->get_servername(); + + # what's the frontend mailboxes.db say before we move? + my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; + + # what's imap LIST say before we move? + # original backend: + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Big' => [qw( \\HasChildren )], + 'Big/Red' => [qw( \\HasChildren )], + 'Big/Red/Dog' => [qw( \\HasNoChildren )], } - - # set up some data for cassandane on backend1 - my $expected_stay = $self->populate_user( - $self->{instance}, - $self->{backend1_store}, - [qw(INBOX Big Big/Red Big/Red/Dog)] - ); - - # we're planning to only XFER "Big/Red" (but not the others!) - my $expected_move->{mailboxes}->{'Big/Red'} - = $expected_stay->{mailboxes}->{'Big/Red'}; - delete $expected_stay->{mailboxes}->{'Big/Red'}; - - my $imaptalk = $self->{backend1_store}->get_client(); - my $admintalk = $self->{backend1_adminstore}->get_client(); - my $backend1_servername = $self->{instance}->get_servername(); - my $backend2_servername = $self->{backend2}->get_servername(); - - # what's the frontend mailboxes.db say before we move? - my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; - - # what's imap LIST say before we move? - # original backend: - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Big' => [qw( \\HasChildren )], - 'Big/Red' => [qw( \\HasChildren )], - 'Big/Red/Dog' => [qw( \\HasNoChildren )], - }); - - my $frontendtalk = $self->{frontend_store}->get_client(); - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Big' => [qw( \\HasChildren )], - 'Big/Red' => [qw( \\HasChildren )], - 'Big/Red/Dog' => [qw( \\HasNoChildren )], - }); - - # now xfer the BigRed folder (only) to backend2 - my $ret = $admintalk->_imap_cmd('xfer', 0, {}, - 'user/cassandane/Big/Red', - $backend2_servername); - - # 3.5+ won't permit receiving just one mid-tree mailbox - if (not $backend2_permits_single_mailbox) { - $self->assert_str_equals( - 'no', $admintalk->get_last_completion_response() - ); - return; # nothing more to test here! + ); + + my $frontendtalk = $self->{frontend_store}->get_client(); + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Big' => [qw( \\HasChildren )], + 'Big/Red' => [qw( \\HasChildren )], + 'Big/Red/Dog' => [qw( \\HasNoChildren )], } - - $self->assert_str_equals('ok', $ret); - $self->assert_str_equals( - 'ok', $admintalk->get_last_completion_response() - ); - - # most of the account should have remained on the original backend - $self->check_user($self->{instance}, - $self->{backend1_store}, - $expected_stay); - # but Big/Red should have been moved - $self->check_user($self->{backend2}, - $self->{backend2_store}, - $expected_move); - - # frontend should now say the new mailbox locations - # XXX is there a better way to discover this? - $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; - # XXX 3.0 with 2.5 frontend fails here: server field is blank - $self->assert_str_equals( - $backend1_servername, - $mailboxes_db->{'user.cassandane'}->{server} - ); - $self->assert_str_equals( - $backend1_servername, - $mailboxes_db->{'user.cassandane.Big'}->{server} - ); - $self->assert_str_equals( - $backend2_servername, - $mailboxes_db->{'user.cassandane.Big.Red'}->{server} - ); - $self->assert_str_equals( - $backend1_servername, - $mailboxes_db->{'user.cassandane.Big.Red.Dog'}->{server} - ); - - # what's imap LIST say after the move? - undef $imaptalk; - $self->{store}->disconnect(); - $imaptalk = $self->{store}->get_client(); - xlog "checking LIST on old backend"; - $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Big' => [qw( \\HasChildren )], - 'Big/Red/Dog' => [qw( \\HasNoChildren )], - }); - - my $backend2talk = $self->{backend2_store}->get_client(); - xlog "checking LIST on new backend"; - $data = $backend2talk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'Big/Red' => [qw( \\HasNoChildren )], - }); - - $frontendtalk = $self->{frontend_store}->get_client(); - xlog "checking LIST on frontend"; - $data = $frontendtalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Big' => [qw( \\HasChildren )], - 'Big/Red' => [qw( \\HasChildren )], - 'Big/Red/Dog' => [qw( \\HasNoChildren )], - }); + ); + + # now xfer the BigRed folder (only) to backend2 + my $ret = $admintalk->_imap_cmd('xfer', 0, {}, 'user/cassandane/Big/Red', + $backend2_servername); + + # 3.5+ won't permit receiving just one mid-tree mailbox + if (not $backend2_permits_single_mailbox) { + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + return; # nothing more to test here! + } + + $self->assert_str_equals('ok', $ret); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # most of the account should have remained on the original backend + $self->check_user($self->{instance}, $self->{backend1_store}, $expected_stay); + # but Big/Red should have been moved + $self->check_user($self->{backend2}, $self->{backend2_store}, $expected_move); + + # frontend should now say the new mailbox locations + # XXX is there a better way to discover this? + $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX after move, frontend mailboxes.db: " . Dumper $mailboxes_db; + # XXX 3.0 with 2.5 frontend fails here: server field is blank + $self->assert_str_equals($backend1_servername, + $mailboxes_db->{'user.cassandane'}->{server}); + $self->assert_str_equals($backend1_servername, + $mailboxes_db->{'user.cassandane.Big'}->{server}); + $self->assert_str_equals($backend2_servername, + $mailboxes_db->{'user.cassandane.Big.Red'}->{server}); + $self->assert_str_equals($backend1_servername, + $mailboxes_db->{'user.cassandane.Big.Red.Dog'}->{server}); + + # what's imap LIST say after the move? + undef $imaptalk; + $self->{store}->disconnect(); + $imaptalk = $self->{store}->get_client(); + xlog "checking LIST on old backend"; + $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Big' => [qw( \\HasChildren )], + 'Big/Red/Dog' => [qw( \\HasNoChildren )], + } + ); + + my $backend2talk = $self->{backend2_store}->get_client(); + xlog "checking LIST on new backend"; + $data = $backend2talk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Big/Red' => [qw( \\HasNoChildren )], + } + ); + + $frontendtalk = $self->{frontend_store}->get_client(); + xlog "checking LIST on frontend"; + $data = $frontendtalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Big' => [qw( \\HasChildren )], + 'Big/Red' => [qw( \\HasChildren )], + 'Big/Red/Dog' => [qw( \\HasNoChildren )], + } + ); } sub test_xfer_no_user_intermediates - :AllowMoves :AltNamespace :UnixHierarchySep - :needs_component_murder :min_version_3_5 -{ - my ($self) = @_; - - # set up some data for cassandane on backend1 - my $expected = $self->populate_user( - $self->{instance}, - $self->{backend1_store}, - [qw(INBOX Big Big/Red Big/Red/Dog)] - ); - - my $admintalk = $self->{backend1_adminstore}->get_client(); - my $backend2_servername = $self->{backend2}->get_servername(); - - # what's the frontend mailboxes.db say before we move? - my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); - xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; - - # try to xfer individual non-INBOX mailboxes, all should be refused - foreach my $folder (qw(Big Big/Red Big/Red/Dog)) { - $admintalk->_imap_cmd('xfer', 0, {}, - "user/cassandane/$folder", - $backend2_servername); - $self->assert_str_equals( - 'no', $admintalk->get_last_completion_response() - ); - $self->assert_matches( - qr{Operation is not supported on mailbox}, - $admintalk->get_last_error() - ); - } - - # everything should still be on the original backend - $self->check_user($self->{instance}, $self->{backend1_store}, $expected); + : AllowMoves : AltNamespace : UnixHierarchySep + : needs_component_murder : min_version_3_5 { + my ($self) = @_; + + # set up some data for cassandane on backend1 + my $expected = $self->populate_user( + $self->{instance}, + $self->{backend1_store}, + [qw(INBOX Big Big/Red Big/Red/Dog)] + ); + + my $admintalk = $self->{backend1_adminstore}->get_client(); + my $backend2_servername = $self->{backend2}->get_servername(); + + # what's the frontend mailboxes.db say before we move? + my $mailboxes_db = $self->{frontend}->read_mailboxes_db(); + xlog "XXX before move, frontend mailboxes.db:" . Dumper $mailboxes_db; + + # try to xfer individual non-INBOX mailboxes, all should be refused + foreach my $folder (qw(Big Big/Red Big/Red/Dog)) { + $admintalk->_imap_cmd('xfer', 0, {}, "user/cassandane/$folder", + $backend2_servername); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr{Operation is not supported on mailbox}, + $admintalk->get_last_error()); + } + + # everything should still be on the original backend + $self->check_user($self->{instance}, $self->{backend1_store}, $expected); } # XXX test_xfer_partition @@ -1111,117 +1134,114 @@ sub test_xfer_no_user_intermediates # XXX shared mailboxes! sub test_copy_across_backends - :needs_component_murder :NoAltNamespace -{ - my ($self) = @_; + : needs_component_murder : NoAltNamespace { + my ($self) = @_; - my $shared = 'shared'; + my $shared = 'shared'; - my $admintalk = $self->{backend2_adminstore}->get_client(); + my $admintalk = $self->{backend2_adminstore}->get_client(); - # create a shared folder (on backend2) - $admintalk->create($shared); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl($shared, 'anyone', 'lrswi'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + # create a shared folder (on backend2) + $admintalk->create($shared); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl($shared, 'anyone', 'lrswi'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - # put some messages into the INBOX - my %exp; - $self->make_message("Message A", store => $self->{frontend_store}); - $exp{B} = $self->make_message("Message B", store => $self->{frontend_store}); - $self->make_message("Message C", store => $self->{frontend_store}); - $exp{D} = $self->make_message("Message D", store => $self->{frontend_store}); + # put some messages into the INBOX + my %exp; + $self->make_message("Message A", store => $self->{frontend_store}); + $exp{B} = $self->make_message("Message B", store => $self->{frontend_store}); + $self->make_message("Message C", store => $self->{frontend_store}); + $exp{D} = $self->make_message("Message D", store => $self->{frontend_store}); - my $frontend = $self->{frontend_store}->get_client(); + my $frontend = $self->{frontend_store}->get_client(); - my $res = $frontend->select('INBOX'); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + my $res = $frontend->select('INBOX'); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - # expunge the some messages so that seqno != uid - $frontend->store('1,3', '+flags', '(\\Deleted)'); - $frontend->expunge(); + # expunge the some messages so that seqno != uid + $frontend->store('1,3', '+flags', '(\\Deleted)'); + $frontend->expunge(); - $res = $frontend->copy('1:*', $shared); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + $res = $frontend->copy('1:*', $shared); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - $exp{B}->set_attribute('uid', 1); - $exp{D}->set_attribute('uid', 2); - $self->{frontend_store}->set_folder($shared); - $self->check_messages(\%exp, store => $self->{frontend_store}); + $exp{B}->set_attribute('uid', 1); + $exp{D}->set_attribute('uid', 2); + $self->{frontend_store}->set_folder($shared); + $self->check_messages(\%exp, store => $self->{frontend_store}); } sub test_replace_same_backend - :needs_component_murder :NoAltNamespace :min_version_3_9 -{ - # :min_version_3_9 checks backend1 version. The test below checks frontend - my ($maj, $min) = Cassandane::Instance->get_version('murder'); - if ($maj < 3 || ($maj == 3 && $min < 9)) { - return; - } + : needs_component_murder : NoAltNamespace : min_version_3_9 { + # :min_version_3_9 checks backend1 version. The test below checks frontend + my ($maj, $min) = Cassandane::Instance->get_version('murder'); + if ($maj < 3 || ($maj == 3 && $min < 9)) { + return; + } - my ($self) = @_; + my ($self) = @_; - my $talk = $self->{frontend_store}->get_client(); + my $talk = $self->{frontend_store}->get_client(); - my %exp; - $exp{A} = $self->make_message("Message A", store => $self->{store}); - $self->check_messages(\%exp); + my %exp; + $exp{A} = $self->make_message("Message A", store => $self->{store}); + $self->check_messages(\%exp); - $talk->select('INBOX'); + $talk->select('INBOX'); - %exp = (); - $exp{B} = $self->{gen}->generate(subject => "Message B"); + %exp = (); + $exp{B} = $self->{gen}->generate(subject => "Message B"); - $talk->_imap_cmd('REPLACE', 0, '', "1", "INBOX", - { Literal => $exp{B}->as_string() }); - $self->check_messages(\%exp); + $talk->_imap_cmd('REPLACE', 0, '', "1", "INBOX", + { Literal => $exp{B}->as_string() }); + $self->check_messages(\%exp); } sub test_replace_across_backends - :needs_component_murder :NoAltNamespace :min_version_3_9 -{ - # :min_version_3_9 checks backend1 version. The test below checks frontend - my ($maj, $min) = Cassandane::Instance->get_version('murder'); - if ($maj < 3 || ($maj == 3 && $min < 9)) { - return; - } + : needs_component_murder : NoAltNamespace : min_version_3_9 { + # :min_version_3_9 checks backend1 version. The test below checks frontend + my ($maj, $min) = Cassandane::Instance->get_version('murder'); + if ($maj < 3 || ($maj == 3 && $min < 9)) { + return; + } - my ($self) = @_; + my ($self) = @_; - my $shared = 'shared'; + my $shared = 'shared'; - my $admintalk = $self->{backend2_adminstore}->get_client(); + my $admintalk = $self->{backend2_adminstore}->get_client(); - # create a shared folder (on backend2) - $admintalk->create($shared); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl($shared, 'anyone', 'lrswi'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + # create a shared folder (on backend2) + $admintalk->create($shared); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl($shared, 'anyone', 'lrswi'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - # put some messages into the INBOX - $self->make_message("Message A", store => $self->{frontend_store}); - $self->make_message("Message B", store => $self->{frontend_store}); + # put some messages into the INBOX + $self->make_message("Message A", store => $self->{frontend_store}); + $self->make_message("Message B", store => $self->{frontend_store}); - my $frontend = $self->{frontend_store}->get_client(); + my $frontend = $self->{frontend_store}->get_client(); - my $res = $frontend->select('INBOX'); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + my $res = $frontend->select('INBOX'); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - # expunge the first message so that seqno != uid - $frontend->store('1', '+flags', '(\\Deleted)'); - $frontend->expunge(); + # expunge the first message so that seqno != uid + $frontend->store('1', '+flags', '(\\Deleted)'); + $frontend->expunge(); - my %exp; - $exp{C} = $self->{gen}->generate(subject => "Message C", uid => 1); + my %exp; + $exp{C} = $self->{gen}->generate(subject => "Message C", uid => 1); - $res = $frontend->_imap_cmd('REPLACE', 0, '', "1", "shared", - { Literal => $exp{C}->as_string() }); - $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + $res = $frontend->_imap_cmd('REPLACE', 0, '', "1", "shared", + { Literal => $exp{C}->as_string() }); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); - $self->check_messages({}); + $self->check_messages({}); - $self->{frontend_store}->set_folder($shared); - $self->check_messages(\%exp, store => $self->{frontend_store}); + $self->{frontend_store}->set_folder($shared); + $self->check_messages(\%exp, store => $self->{frontend_store}); } 1; diff --git a/cassandane/Cassandane/Cyrus/MurderJMAP.pm b/cassandane/Cassandane/Cyrus/MurderJMAP.pm index ecc33438af..06498ce267 100644 --- a/cassandane/Cassandane/Cyrus/MurderJMAP.pm +++ b/cassandane/Cassandane/Cyrus/MurderJMAP.pm @@ -49,169 +49,164 @@ use Cassandane::Instance; $Data::Dumper::Sortkeys = 1; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - $config->set('conversations' => 'yes'); - $config->set_bits('httpmodules', 'jmap'); - - return $class->SUPER::new({ - config => $config, - httpmurder => 1, - jmap => 1, - adminstore => 1 - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + $config->set('conversations' => 'yes'); + $config->set_bits('httpmodules', 'jmap'); + + return $class->SUPER::new( + { + config => $config, + httpmurder => 1, + jmap => 1, + adminstore => 1 + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_aaa_setup - :needs_component_murder -{ - my ($self) = @_; + : needs_component_murder { + my ($self) = @_; - # does everything set up and tear down cleanly? - $self->assert(1); + # does everything set up and tear down cleanly? + $self->assert(1); } # XXX This can't pass because we don't support multiple murder services # XXX at once, but renaming out the "bogus" and running it, and it failing, # XXX proves the infrastructure to prevent requesting both works. sub bogustest_aaa_imapjmap_setup - :needs_component_murder - :IMAPMurder -{ - my ($self) = @_; + : needs_component_murder + : IMAPMurder { + my ($self) = @_; - # does everything set up and tear down cleanly? - $self->assert(1); + # does everything set up and tear down cleanly? + $self->assert(1); } sub test_frontend_commands - :needs_component_murder :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $result; - - my $frontend_svc = $self->{frontend}->get_service("http"); - my $frontend_host = $frontend_svc->host(); - my $frontend_port = $frontend_svc->port(); - my $proxy_re = qr{ + : needs_component_murder : needs_component_jmap : min_version_3_5 { + my ($self) = @_; + my $result; + + my $frontend_svc = $self->{frontend}->get_service("http"); + my $frontend_host = $frontend_svc->host(); + my $frontend_port = $frontend_svc->port(); + my $proxy_re = qr{ \b ( localhost | $frontend_host ) : $frontend_port \b }x; - my $frontend_jmap = Mail::JMAPTalk->new( - user => 'cassandane', - password => 'pass', - host => $frontend_host, - port => $frontend_port, - scheme => 'http', - url => '/jmap/', - ); + my $frontend_jmap = Mail::JMAPTalk->new( + user => 'cassandane', + password => 'pass', + host => $frontend_host, + port => $frontend_port, + scheme => 'http', + url => '/jmap/', + ); - # upload a blob - my ($resp, $data) = $frontend_jmap->Upload("some test", "text/plain"); + # upload a blob + my ($resp, $data) = $frontend_jmap->Upload("some test", "text/plain"); - # request should have been proxied - $self->assert_matches($proxy_re, $resp->{headers}{via}); + # request should have been proxied + $self->assert_matches($proxy_re, $resp->{headers}{via}); - # download the same blob - $resp = $frontend_jmap->Download({ accept => 'text/plain' }, - 'cassandane', $data->{blobId}); + # download the same blob + $resp = $frontend_jmap->Download({ accept => 'text/plain' }, + 'cassandane', $data->{blobId}); - # request should have been proxied - $self->assert_matches($proxy_re, $resp->{headers}{via}); + # request should have been proxied + $self->assert_matches($proxy_re, $resp->{headers}{via}); - # content should match - $self->assert_str_equals('text/plain', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); + # content should match + $self->assert_str_equals('text/plain', $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); - # XXX test other commands + # XXX test other commands } sub test_backend1_commands - :needs_component_murder :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $result; - - my $backend1_svc = $self->{instance}->get_service("http"); - my $backend1_host = $backend1_svc->host(); - my $backend1_port = $backend1_svc->port(); - - my $backend1_jmap = Mail::JMAPTalk->new( - user => 'cassandane', - password => 'pass', - host => $backend1_host, - port => $backend1_port, - scheme => 'http', - url => '/jmap/', - ); - - # upload a blob - my ($resp, $data) = $backend1_jmap->Upload("some test", "text/plain"); - - # request should not have been proxied - $self->assert_null($resp->{headers}{via}); - - # download the same blob - $resp = $backend1_jmap->Download({ accept => 'text/plain' }, - 'cassandane', $data->{blobId}); - - # request should not have been proxied - $self->assert_null($resp->{headers}{via}); - - # content should match - $self->assert_str_equals('text/plain', $resp->{headers}{'content-type'}); - $self->assert_str_equals('some test', $resp->{content}); - - # XXX test other commands + : needs_component_murder : needs_component_jmap : min_version_3_5 { + my ($self) = @_; + my $result; + + my $backend1_svc = $self->{instance}->get_service("http"); + my $backend1_host = $backend1_svc->host(); + my $backend1_port = $backend1_svc->port(); + + my $backend1_jmap = Mail::JMAPTalk->new( + user => 'cassandane', + password => 'pass', + host => $backend1_host, + port => $backend1_port, + scheme => 'http', + url => '/jmap/', + ); + + # upload a blob + my ($resp, $data) = $backend1_jmap->Upload("some test", "text/plain"); + + # request should not have been proxied + $self->assert_null($resp->{headers}{via}); + + # download the same blob + $resp = $backend1_jmap->Download({ accept => 'text/plain' }, + 'cassandane', $data->{blobId}); + + # request should not have been proxied + $self->assert_null($resp->{headers}{via}); + + # content should match + $self->assert_str_equals('text/plain', $resp->{headers}{'content-type'}); + $self->assert_str_equals('some test', $resp->{content}); + + # XXX test other commands } sub test_backend2_commands - :needs_component_murder :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $result; - - my $backend2_svc = $self->{backend2}->get_service("http"); - my $backend2_host = $backend2_svc->host(); - my $backend2_port = $backend2_svc->port(); - - my $backend2_jmap = Mail::JMAPTalk->new( - user => 'cassandane', - password => 'pass', - host => $backend2_host, - port => $backend2_port, - scheme => 'http', - url => '/jmap/', - ); - - # try to upload a blob - my ($resp, $data) = $backend2_jmap->Upload("some test", "text/plain"); - - # user doesn't exist on this backend, so upload url should not exist - $self->assert_num_equals(404, $resp->{status}); - $self->assert_str_equals('Not Found', $resp->{reason}); - - $self->assert_null($data); - -# # XXX test other commands + : needs_component_murder : needs_component_jmap : min_version_3_5 { + my ($self) = @_; + my $result; + + my $backend2_svc = $self->{backend2}->get_service("http"); + my $backend2_host = $backend2_svc->host(); + my $backend2_port = $backend2_svc->port(); + + my $backend2_jmap = Mail::JMAPTalk->new( + user => 'cassandane', + password => 'pass', + host => $backend2_host, + port => $backend2_port, + scheme => 'http', + url => '/jmap/', + ); + + # try to upload a blob + my ($resp, $data) = $backend2_jmap->Upload("some test", "text/plain"); + + # user doesn't exist on this backend, so upload url should not exist + $self->assert_num_equals(404, $resp->{status}); + $self->assert_str_equals('Not Found', $resp->{reason}); + + $self->assert_null($data); + + # # XXX test other commands } 1; diff --git a/cassandane/Cassandane/Cyrus/Nntp.pm b/cassandane/Cassandane/Cyrus/Nntp.pm index 10b7c6881c..7fde43ca45 100644 --- a/cassandane/Cassandane/Cyrus/Nntp.pm +++ b/cassandane/Cassandane/Cyrus/Nntp.pm @@ -48,98 +48,87 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Util::Words; -sub new -{ - my ($class, @args) = @_; - return $class->SUPER::new({ gen => 0, services => ['nntp'] }, @args); +sub new { + my ($class, @args) = @_; + return $class->SUPER::new({ gen => 0, services => ['nntp'] }, @args); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - - my $svc = $self->{instance}->get_service('nntp'); - if (defined $svc) - { - my $debug = get_verbose() ? 2 : 0; - $self->{client} = new News::NNTPClient($svc->host(), - $svc->port(), - $debug); - $self->{client}->authinfo('cassandane', 'testpw'); - } +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + + my $svc = $self->{instance}->get_service('nntp'); + if (defined $svc) { + my $debug = get_verbose() ? 2 : 0; + $self->{client} = new News::NNTPClient($svc->host(), $svc->port(), $debug); + $self->{client}->authinfo('cassandane', 'testpw'); + } } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - if (defined $self->{client}) - { - $self->{client}->quit(); - $self->{client} = undef; - } + if (defined $self->{client}) { + $self->{client}->quit(); + $self->{client} = undef; + } - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } my $stack_slosh = 256; sub test_cve_2011_3208_list_newsgroups - :needs_component_nttpd -{ - my ($self) = @_; - - my $client = $self->{client}; - my $wildmat = ''; - while (length $wildmat < 1024+$stack_slosh) - { - $wildmat .= ($wildmat eq '' ? '' : '.'); - $wildmat .= random_word(); - $client->list('newsgroups', $wildmat); - $self->assert_num_equals(215, $client->code()); - $self->assert($client->message() =~ m/List of newsgroups follows/i); - } + : needs_component_nttpd { + my ($self) = @_; + + my $client = $self->{client}; + my $wildmat = ''; + while (length $wildmat < 1024 + $stack_slosh) { + $wildmat .= ($wildmat eq '' ? '' : '.'); + $wildmat .= random_word(); + $client->list('newsgroups', $wildmat); + $self->assert_num_equals(215, $client->code()); + $self->assert($client->message() =~ m/List of newsgroups follows/i); + } } sub test_cve_2011_3208_list_active - :needs_component_nttpd -{ - my ($self) = @_; - - my $client = $self->{client}; - my $wildmat = ''; - while (length $wildmat < 1024+$stack_slosh) - { - $wildmat .= ($wildmat eq '' ? '' : '.'); - $wildmat .= random_word(); - $client->list('active', $wildmat); - $self->assert_num_equals(215, $client->code()); - $self->assert($client->message() =~ m/List of newsgroups follows/i); - } + : needs_component_nttpd { + my ($self) = @_; + + my $client = $self->{client}; + my $wildmat = ''; + while (length $wildmat < 1024 + $stack_slosh) { + $wildmat .= ($wildmat eq '' ? '' : '.'); + $wildmat .= random_word(); + $client->list('active', $wildmat); + $self->assert_num_equals(215, $client->code()); + $self->assert($client->message() =~ m/List of newsgroups follows/i); + } } - # The NEWNEWS command is disabled by default. -Cassandane::Cyrus::TestCase::magic(AllowNewNews => sub { +# The NEWNEWS command is disabled by default. +Cassandane::Cyrus::TestCase::magic( + AllowNewNews => sub { shift->config_set(allownewnews => 1); -}); + } +); sub test_cve_2011_3208_newnews - :AllowNewNews :needs_component_nttpd -{ - my ($self) = @_; - - my $client = $self->{client}; - my $wildmat = ''; - my $since = time() - 3600; - while (length $wildmat < 1024+$stack_slosh) - { - $wildmat .= ($wildmat eq '' ? '' : '.'); - $wildmat .= random_word(); - $client->newnews($wildmat, $since); - $self->assert_num_equals(230, $client->code()); - $self->assert($client->message() =~ m/List of new articles follows/i); - } + : AllowNewNews : needs_component_nttpd { + my ($self) = @_; + + my $client = $self->{client}; + my $wildmat = ''; + my $since = time() - 3600; + while (length $wildmat < 1024 + $stack_slosh) { + $wildmat .= ($wildmat eq '' ? '' : '.'); + $wildmat .= random_word(); + $client->newnews($wildmat, $since); + $self->assert_num_equals(230, $client->code()); + $self->assert($client->message() =~ m/List of new articles follows/i); + } } 1; diff --git a/cassandane/Cassandane/Cyrus/Notify.pm b/cassandane/Cassandane/Cyrus/Notify.pm index 0db087fcbc..a265a2c43e 100644 --- a/cassandane/Cassandane/Cyrus/Notify.pm +++ b/cassandane/Cassandane/Cyrus/Notify.pm @@ -47,649 +47,670 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(imapidlepoll => 2); - return $class->SUPER::new({ - config => $config, - deliver => 1, - start_instances => 0, - }, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config->default()->clone(); + $config->set(imapidlepoll => 2); + return $class->SUPER::new( + { + config => $config, + deliver => 1, + start_instances => 0, + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_bad - :needs_component_idled :min_version_3_9 -{ - my ($self) = @_; - - xlog $self, "Message test of the NOTIFY command (idled required)"; - - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - - my $svc = $self->{instance}->get_service('imap'); - - my $store = $svc->create_store(); - my $talk = $store->get_client(); - - xlog $self, "The server should report the NOTIFY capability"; - $self->assert($talk->capability()->{notify}); - - xlog $self, "Enable Notify with a missing arg"; - my $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS'); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with an invalid arg"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'FOO'); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with a missing filter"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET'); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with an invalid filter"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', - "(FOO (MessageNew MessageExpunge))"); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with a duplicate filter"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', - "(SELECTED (MessageNew MessageExpunge))", - "(SELECTED-DELAYED (MessageNew MessageExpunge))"); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with another duplicate filter"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', - "(INBOXES (MessageNew MessageExpunge))", - "(INBOXES (MessageNew MessageExpunge FlagChange))"); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with an invalid event"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', - "(INBOXES (MessageNew MessageExpunge Foo))"); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with an invalid event group"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', - "(SELECTED-DELAYED (MessageNew))"); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with another invalid event group"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', - "(PERSONAL (MessageExpunge FlagChange))"); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "Enable Notify with an empty mailbox list"; - $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', - "(MAILBOXES () (MessageNew MessageExpunge))"); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); + : needs_component_idled : min_version_3_9 { + my ($self) = @_; + + xlog $self, "Message test of the NOTIFY command (idled required)"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + + my $svc = $self->{instance}->get_service('imap'); + + my $store = $svc->create_store(); + my $talk = $store->get_client(); + + xlog $self, "The server should report the NOTIFY capability"; + $self->assert($talk->capability()->{notify}); + + xlog $self, "Enable Notify with a missing arg"; + my $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS'); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with an invalid arg"; + $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'FOO'); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with a missing filter"; + $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET'); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with an invalid filter"; + $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', + "(FOO (MessageNew MessageExpunge))"); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with a duplicate filter"; + $res = $talk->_imap_cmd( + 'NOTIFY', 0, 'STATUS', 'SET', + "(SELECTED (MessageNew MessageExpunge))", + "(SELECTED-DELAYED (MessageNew MessageExpunge))" + ); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with another duplicate filter"; + $res = $talk->_imap_cmd( + 'NOTIFY', 0, 'STATUS', 'SET', + "(INBOXES (MessageNew MessageExpunge))", + "(INBOXES (MessageNew MessageExpunge FlagChange))" + ); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with an invalid event"; + $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', + "(INBOXES (MessageNew MessageExpunge Foo))"); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with an invalid event group"; + $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', + "(SELECTED-DELAYED (MessageNew))"); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with another invalid event group"; + $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', + "(PERSONAL (MessageExpunge FlagChange))"); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "Enable Notify with an empty mailbox list"; + $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', + "(MAILBOXES () (MessageNew MessageExpunge))"); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); } sub test_message - :needs_component_idled :min_version_3_9 -{ - my ($self) = @_; - - xlog $self, "Message test of the NOTIFY command (idled required)"; - - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - - my $svc = $self->{instance}->get_service('imap'); - - my $store = $svc->create_store(); - my $talk = $store->get_client(); - - my $otherstore = $svc->create_store(); - my $othertalk = $otherstore->get_client(); - - xlog $self, "The server should report the NOTIFY capability"; - $self->assert($talk->capability()->{notify}); - - xlog $self, "Create two mailboxes"; - $talk->create("INBOX.foo"); - $talk->create("INBOX.bar"); - - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); - - xlog $self, "Examine INBOX.foo"; - $talk->examine("INBOX.foo"); - - xlog $self, "Enable Notify"; - my $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', 'STATUS', - "(SELECTED (MessageNew" . - " (UID BODY.PEEK[HEADER.FIELDS (From Subject)])" . - " MessageExpunge FlagChange))", - "(PERSONAL (MessageNew MessageExpunge))"); - - # Should get STATUS responses for unselected mailboxes - my $status = $talk->get_response_code('status'); - $self->assert_num_equals(1, $status->{'INBOX'}{messages}); - $self->assert_num_equals(2, $status->{'INBOX'}{uidnext}); - $self->assert_num_equals(0, $status->{'INBOX.bar'}{messages}); - $self->assert_num_equals(1, $status->{'INBOX.bar'}{uidnext}); - - xlog $self, "Deliver a message"; - $msg = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg); - - # Should get STATUS response for INBOX - $res = $store->idle_response('STATUS', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $status = $talk->get_response_code('status'); - $self->assert_num_equals(2, $status->{'INBOX'}{messages}); - $self->assert_num_equals(3, $status->{'INBOX'}{uidnext}); - - xlog $self, "EXPUNGE message from INBOX in other session"; - $othertalk->select("INBOX"); - $res = $othertalk->store('1', '+flags', '(\\Deleted)'); - $res = $othertalk->expunge(); - - # Should get STATUS response for INBOX - $res = $store->idle_response('STATUS', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $status = $talk->get_response_code('status'); - $self->assert_num_equals(1, $status->{'INBOX'}{messages}); - $self->assert_num_equals(3, $status->{'INBOX'}{uidnext}); - - xlog $self, "Select INBOX"; - $talk->examine("INBOX"); - - xlog $self, "Deliver a message"; - $msg = $self->{gen}->generate(subject => "Message 3"); - $self->{instance}->deliver($msg); - - # Should get EXISTS, RECENT, FETCH response - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response('FETCH', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $self->assert_num_equals(2, $talk->get_response_code('exists')); - $self->assert_num_equals(1, $talk->get_response_code('recent')); - - my $fetch = $talk->get_response_code('fetch'); - $self->assert_num_equals(3, $fetch->{2}{uid}); - $self->assert_str_equals('Message 3', $fetch->{2}{headers}{subject}[0]); - $self->assert_not_null($fetch->{2}{headers}{from}); - - xlog $self, "DELETE message from INBOX in other session"; - $res = $othertalk->store('1', '+flags', '(\\Deleted)'); - - # Should get FETCH response for INBOX - $res = $store->idle_response('FETCH', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $fetch = $talk->get_response_code('fetch'); - $self->assert_num_equals(2, $fetch->{1}{uid}); - $self->assert_str_equals('\\Deleted', $fetch->{1}{flags}[0]); - - xlog $self, "EXPUNGE message from INBOX in other session"; - $res = $othertalk->expunge(); - - # Should get EXPUNGE response for INBOX - $res = $store->idle_response('EXPUNGE', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $self->assert_num_equals(1, $talk->get_response_code('expunge')); - - xlog $self, "Disable Notify"; - $res = $talk->_imap_cmd('NOTIFY', 0, "", "NONE"); - - xlog $self, "Deliver a message"; - $msg = $self->{gen}->generate(subject => "Message 4"); - $self->{instance}->deliver($msg); - - # Should get no unsolicited responses - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no unsolicited responses"); - - # make sure that the connection is ended so that imapd reset happens - $talk->logout(); - undef $talk; - - # we enabled NOTIFY, so we should see it in client behaviors - my $pat = qr/session ended.*notify=<1>/; - $self->assert_syslog_matches($self->{instance}, $pat); + : needs_component_idled : min_version_3_9 { + my ($self) = @_; + + xlog $self, "Message test of the NOTIFY command (idled required)"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + + my $svc = $self->{instance}->get_service('imap'); + + my $store = $svc->create_store(); + my $talk = $store->get_client(); + + my $otherstore = $svc->create_store(); + my $othertalk = $otherstore->get_client(); + + xlog $self, "The server should report the NOTIFY capability"; + $self->assert($talk->capability()->{notify}); + + xlog $self, "Create two mailboxes"; + $talk->create("INBOX.foo"); + $talk->create("INBOX.bar"); + + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); + + xlog $self, "Examine INBOX.foo"; + $talk->examine("INBOX.foo"); + + xlog $self, "Enable Notify"; + my $res = $talk->_imap_cmd( + 'NOTIFY', + 0, + 'STATUS', + 'SET', + 'STATUS', + "(SELECTED (MessageNew" + . " (UID BODY.PEEK[HEADER.FIELDS (From Subject)])" + . " MessageExpunge FlagChange))", + "(PERSONAL (MessageNew MessageExpunge))" + ); + + # Should get STATUS responses for unselected mailboxes + my $status = $talk->get_response_code('status'); + $self->assert_num_equals(1, $status->{'INBOX'}{messages}); + $self->assert_num_equals(2, $status->{'INBOX'}{uidnext}); + $self->assert_num_equals(0, $status->{'INBOX.bar'}{messages}); + $self->assert_num_equals(1, $status->{'INBOX.bar'}{uidnext}); + + xlog $self, "Deliver a message"; + $msg = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg); + + # Should get STATUS response for INBOX + $res = $store->idle_response('STATUS', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $status = $talk->get_response_code('status'); + $self->assert_num_equals(2, $status->{'INBOX'}{messages}); + $self->assert_num_equals(3, $status->{'INBOX'}{uidnext}); + + xlog $self, "EXPUNGE message from INBOX in other session"; + $othertalk->select("INBOX"); + $res = $othertalk->store('1', '+flags', '(\\Deleted)'); + $res = $othertalk->expunge(); + + # Should get STATUS response for INBOX + $res = $store->idle_response('STATUS', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $status = $talk->get_response_code('status'); + $self->assert_num_equals(1, $status->{'INBOX'}{messages}); + $self->assert_num_equals(3, $status->{'INBOX'}{uidnext}); + + xlog $self, "Select INBOX"; + $talk->examine("INBOX"); + + xlog $self, "Deliver a message"; + $msg = $self->{gen}->generate(subject => "Message 3"); + $self->{instance}->deliver($msg); + + # Should get EXISTS, RECENT, FETCH response + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response('FETCH', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $self->assert_num_equals(2, $talk->get_response_code('exists')); + $self->assert_num_equals(1, $talk->get_response_code('recent')); + + my $fetch = $talk->get_response_code('fetch'); + $self->assert_num_equals(3, $fetch->{2}{uid}); + $self->assert_str_equals('Message 3', $fetch->{2}{headers}{subject}[0]); + $self->assert_not_null($fetch->{2}{headers}{from}); + + xlog $self, "DELETE message from INBOX in other session"; + $res = $othertalk->store('1', '+flags', '(\\Deleted)'); + + # Should get FETCH response for INBOX + $res = $store->idle_response('FETCH', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $fetch = $talk->get_response_code('fetch'); + $self->assert_num_equals(2, $fetch->{1}{uid}); + $self->assert_str_equals('\\Deleted', $fetch->{1}{flags}[0]); + + xlog $self, "EXPUNGE message from INBOX in other session"; + $res = $othertalk->expunge(); + + # Should get EXPUNGE response for INBOX + $res = $store->idle_response('EXPUNGE', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $self->assert_num_equals(1, $talk->get_response_code('expunge')); + + xlog $self, "Disable Notify"; + $res = $talk->_imap_cmd('NOTIFY', 0, "", "NONE"); + + xlog $self, "Deliver a message"; + $msg = $self->{gen}->generate(subject => "Message 4"); + $self->{instance}->deliver($msg); + + # Should get no unsolicited responses + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no unsolicited responses"); + + # make sure that the connection is ended so that imapd reset happens + $talk->logout(); + undef $talk; + + # we enabled NOTIFY, so we should see it in client behaviors + my $pat = qr/session ended.*notify=<1>/; + $self->assert_syslog_matches($self->{instance}, $pat); } sub test_mailbox - :needs_component_idled :min_version_3_9 -{ - my ($self) = @_; - - xlog $self, "Mailbox test of the NOTIFY command (idled required)"; - - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - - my $svc = $self->{instance}->get_service('imap'); - - my $store = $svc->create_store(); - my $talk = $store->get_client(); - - my $otherstore = $svc->create_store(); - my $othertalk = $otherstore->get_client(); - - xlog $self, "The server should report the NOTIFY capability"; - $self->assert($talk->capability()->{notify}); - - xlog $self, "Enable Notify"; - my $res = $talk->_imap_cmd('NOTIFY', 0, "", "SET", - "(PERSONAL (MailboxName SubscriptionChange))"); - - xlog $self, "Create mailbox in other session"; - $othertalk->create("INBOX.rename-me"); - - # Should get LIST response - $res = $store->idle_response('LIST', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - my $list = $talk->get_response_code('list'); - $self->assert_str_equals('INBOX.rename-me', $list->[0][2]); - - xlog $self, "Subscribe mailbox in other session"; - $othertalk->subscribe("INBOX.rename-me"); - - # Should get LIST response with \Subscribed - $res = $store->idle_response('LIST', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $list = $talk->get_response_code('list'); - $self->assert_str_equals('\\Subscribed', $list->[0][0][0]); - $self->assert_str_equals('INBOX.rename-me', $list->[0][2]); - - xlog $self, "Rename mailbox in other session"; - $othertalk->rename("INBOX.rename-me", "INBOX.delete-me"); - - # Use our own handler since IMAPTalk will lose OLDNAME - my %handlers = - ( - list => sub - { - my (undef, $data) = @_; - $list = [ $data ]; - }, - ); - - # Should get LIST response with OLDNAME - $res = $store->idle_response(\%handlers, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $self->assert_str_equals('INBOX.delete-me', $list->[0][2]); - $self->assert_str_equals('OLDNAME', $list->[0][3][0]); - $self->assert_str_equals('INBOX.rename-me', $list->[0][3][1][0]); - - xlog $self, "Delete mailbox in other session"; - $othertalk->delete("INBOX.delete-me"); - - # Should get LIST response with \NonExistent - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $list = $talk->get_response_code('list'); - $self->assert_str_equals('\\NonExistent', $list->[0][0][0]); - $self->assert_str_equals('INBOX.delete-me', $list->[0][2]); - - xlog $self, "Disable Notify"; - $res = $talk->_imap_cmd('NOTIFY', 0, "", "NONE"); - - xlog $self, "Create mailbox in other session"; - $othertalk->create("INBOX.foo"); - - # Should get no unsolicited responses - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no unsolicited responses"); + : needs_component_idled : min_version_3_9 { + my ($self) = @_; + + xlog $self, "Mailbox test of the NOTIFY command (idled required)"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + + my $svc = $self->{instance}->get_service('imap'); + + my $store = $svc->create_store(); + my $talk = $store->get_client(); + + my $otherstore = $svc->create_store(); + my $othertalk = $otherstore->get_client(); + + xlog $self, "The server should report the NOTIFY capability"; + $self->assert($talk->capability()->{notify}); + + xlog $self, "Enable Notify"; + my $res = $talk->_imap_cmd('NOTIFY', 0, "", "SET", + "(PERSONAL (MailboxName SubscriptionChange))"); + + xlog $self, "Create mailbox in other session"; + $othertalk->create("INBOX.rename-me"); + + # Should get LIST response + $res = $store->idle_response('LIST', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + my $list = $talk->get_response_code('list'); + $self->assert_str_equals('INBOX.rename-me', $list->[0][2]); + + xlog $self, "Subscribe mailbox in other session"; + $othertalk->subscribe("INBOX.rename-me"); + + # Should get LIST response with \Subscribed + $res = $store->idle_response('LIST', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $list = $talk->get_response_code('list'); + $self->assert_str_equals('\\Subscribed', $list->[0][0][0]); + $self->assert_str_equals('INBOX.rename-me', $list->[0][2]); + + xlog $self, "Rename mailbox in other session"; + $othertalk->rename("INBOX.rename-me", "INBOX.delete-me"); + + # Use our own handler since IMAPTalk will lose OLDNAME + my %handlers = ( + list => sub { + my (undef, $data) = @_; + $list = [$data]; + }, + ); + + # Should get LIST response with OLDNAME + $res = $store->idle_response(\%handlers, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $self->assert_str_equals('INBOX.delete-me', $list->[0][2]); + $self->assert_str_equals('OLDNAME', $list->[0][3][0]); + $self->assert_str_equals('INBOX.rename-me', $list->[0][3][1][0]); + + xlog $self, "Delete mailbox in other session"; + $othertalk->delete("INBOX.delete-me"); + + # Should get LIST response with \NonExistent + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $list = $talk->get_response_code('list'); + $self->assert_str_equals('\\NonExistent', $list->[0][0][0]); + $self->assert_str_equals('INBOX.delete-me', $list->[0][2]); + + xlog $self, "Disable Notify"; + $res = $talk->_imap_cmd('NOTIFY', 0, "", "NONE"); + + xlog $self, "Create mailbox in other session"; + $othertalk->create("INBOX.foo"); + + # Should get no unsolicited responses + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no unsolicited responses"); } sub test_idle - :needs_component_idled :min_version_3_9 -{ - my ($self) = @_; - - xlog $self, "Test of the NOTIFY + IDLE commands (idled required)"; - - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - - my $svc = $self->{instance}->get_service('imap'); - - my $store = $svc->create_store(); - my $talk = $store->get_client(); - - my $otherstore = $svc->create_store(); - my $othertalk = $otherstore->get_client(); - - xlog $self, "The server should report the NOTIFY capability"; - $self->assert($talk->capability()->{notify}); - - xlog $self, "Enable Notify"; - my $res = $talk->_imap_cmd('NOTIFY', 0, "", 'SET', - "(SELECTED (MessageNew" . - " (UID BODY.PEEK[HEADER.FIELDS (From Subject)])" . - " MessageExpunge))", - "(PERSONAL (MessageNew MessageExpunge MailboxName))"); - - # Should NOT get STATUS response for INBOX - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - xlog $self, "Examine INBOX"; - $talk->examine("INBOX"); - $self->assert_num_equals(0, $talk->get_response_code('exists')); - $self->assert_num_equals(0, $talk->get_response_code('recent')); - $self->assert_num_equals(1, $talk->get_response_code('uidnext')); - - xlog $self, "Sending the IDLE command"; - $store->idle_begin() - or die "IDLE failed: $@"; - - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); - - # Should get EXISTS, RECENT, FETCH response - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response('FETCH', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $self->assert_num_equals(1, $talk->get_response_code('exists')); - $self->assert_num_equals(1, $talk->get_response_code('recent')); - - my $fetch = $talk->get_response_code('fetch'); - $self->assert_num_equals(1, $fetch->{1}{uid}); - $self->assert_str_equals('Message 1', $fetch->{1}{headers}{subject}[0]); - - xlog $self, "Create mailbox in other session"; - $othertalk->create("INBOX.foo"); - - # Should get LIST response - $res = $store->idle_response('LIST', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - my $list = $talk->get_response_code('list'); - $self->assert_str_equals('INBOX.foo', $list->[0][2]); - - $othertalk->select("INBOX"); - - xlog $self, "Add \Flagged to message in INBOX in other session"; - $res = $othertalk->store('1', '+flags', '(\\Flagged)'); - - # Should NOT get FETCH response for INBOX - $res = $store->idle_response('FETCH', 1); - $self->assert(!$res, "no more unsolicited responses"); - - xlog $self, "MOVE message from INBOX to INBOX.foo in other session"; - $res = $othertalk->move('1', "INBOX.foo"); - - # Should get STATUS response for INBOX.foo and EXPUNGE response for INBOX - $res = $store->idle_response('STATUS', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - my $status = $talk->get_response_code('status'); - $self->assert_num_equals(1, $status->{'INBOX.foo'}{messages}); - $self->assert_num_equals(2, $status->{'INBOX.foo'}{uidnext}); - $self->assert_num_equals(1, $talk->get_response_code('expunge')); - - xlog $self, "Sending DONE continuation"; - $store->idle_end({}); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Deliver a message"; - $msg = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg); - - # Should get EXISTS, RECENT, FETCH response - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response('FETCH', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $self->assert_num_equals(1, $talk->get_response_code('exists')); - $self->assert_num_equals(1, $talk->get_response_code('recent')); - - $fetch = $talk->get_response_code('fetch'); - $self->assert_num_equals(2, $fetch->{1}{uid}); - $self->assert_str_equals('Message 2', $fetch->{1}{headers}{subject}[0]); - - xlog $self, "Unselect INBOX"; - $talk->unselect(); - - xlog $self, "Deliver a message"; - $msg = $self->{gen}->generate(subject => "Message 3"); - $self->{instance}->deliver($msg); - - # Should get STATUS response for INBOX - $res = $store->idle_response('STATUS', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $status = $talk->get_response_code('status'); - $self->assert_num_equals(2, $status->{'INBOX'}{messages}); - $self->assert_num_equals(4, $status->{'INBOX'}{uidnext}); - - xlog $self, "Delete mailbox in other session"; - $othertalk->delete("INBOX.foo"); - - # Should get LIST response with \NonExistent - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $list = $talk->get_response_code('list'); - $self->assert_str_equals('\\NonExistent', $list->[0][0][0]); - $self->assert_str_equals('INBOX.foo', $list->[0][2]); + : needs_component_idled : min_version_3_9 { + my ($self) = @_; + + xlog $self, "Test of the NOTIFY + IDLE commands (idled required)"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + + my $svc = $self->{instance}->get_service('imap'); + + my $store = $svc->create_store(); + my $talk = $store->get_client(); + + my $otherstore = $svc->create_store(); + my $othertalk = $otherstore->get_client(); + + xlog $self, "The server should report the NOTIFY capability"; + $self->assert($talk->capability()->{notify}); + + xlog $self, "Enable Notify"; + my $res = $talk->_imap_cmd( + 'NOTIFY', + 0, + "", + 'SET', + "(SELECTED (MessageNew" + . " (UID BODY.PEEK[HEADER.FIELDS (From Subject)])" + . " MessageExpunge))", + "(PERSONAL (MessageNew MessageExpunge MailboxName))" + ); + + # Should NOT get STATUS response for INBOX + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + xlog $self, "Examine INBOX"; + $talk->examine("INBOX"); + $self->assert_num_equals(0, $talk->get_response_code('exists')); + $self->assert_num_equals(0, $talk->get_response_code('recent')); + $self->assert_num_equals(1, $talk->get_response_code('uidnext')); + + xlog $self, "Sending the IDLE command"; + $store->idle_begin() + or die "IDLE failed: $@"; + + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); + + # Should get EXISTS, RECENT, FETCH response + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response('FETCH', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $self->assert_num_equals(1, $talk->get_response_code('exists')); + $self->assert_num_equals(1, $talk->get_response_code('recent')); + + my $fetch = $talk->get_response_code('fetch'); + $self->assert_num_equals(1, $fetch->{1}{uid}); + $self->assert_str_equals('Message 1', $fetch->{1}{headers}{subject}[0]); + + xlog $self, "Create mailbox in other session"; + $othertalk->create("INBOX.foo"); + + # Should get LIST response + $res = $store->idle_response('LIST', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + my $list = $talk->get_response_code('list'); + $self->assert_str_equals('INBOX.foo', $list->[0][2]); + + $othertalk->select("INBOX"); + + xlog $self, "Add \Flagged to message in INBOX in other session"; + $res = $othertalk->store('1', '+flags', '(\\Flagged)'); + + # Should NOT get FETCH response for INBOX + $res = $store->idle_response('FETCH', 1); + $self->assert(!$res, "no more unsolicited responses"); + + xlog $self, "MOVE message from INBOX to INBOX.foo in other session"; + $res = $othertalk->move('1', "INBOX.foo"); + + # Should get STATUS response for INBOX.foo and EXPUNGE response for INBOX + $res = $store->idle_response('STATUS', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + my $status = $talk->get_response_code('status'); + $self->assert_num_equals(1, $status->{'INBOX.foo'}{messages}); + $self->assert_num_equals(2, $status->{'INBOX.foo'}{uidnext}); + $self->assert_num_equals(1, $talk->get_response_code('expunge')); + + xlog $self, "Sending DONE continuation"; + $store->idle_end({}); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Deliver a message"; + $msg = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg); + + # Should get EXISTS, RECENT, FETCH response + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response('FETCH', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $self->assert_num_equals(1, $talk->get_response_code('exists')); + $self->assert_num_equals(1, $talk->get_response_code('recent')); + + $fetch = $talk->get_response_code('fetch'); + $self->assert_num_equals(2, $fetch->{1}{uid}); + $self->assert_str_equals('Message 2', $fetch->{1}{headers}{subject}[0]); + + xlog $self, "Unselect INBOX"; + $talk->unselect(); + + xlog $self, "Deliver a message"; + $msg = $self->{gen}->generate(subject => "Message 3"); + $self->{instance}->deliver($msg); + + # Should get STATUS response for INBOX + $res = $store->idle_response('STATUS', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $status = $talk->get_response_code('status'); + $self->assert_num_equals(2, $status->{'INBOX'}{messages}); + $self->assert_num_equals(4, $status->{'INBOX'}{uidnext}); + + xlog $self, "Delete mailbox in other session"; + $othertalk->delete("INBOX.foo"); + + # Should get LIST response with \NonExistent + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $list = $talk->get_response_code('list'); + $self->assert_str_equals('\\NonExistent', $list->[0][0][0]); + $self->assert_str_equals('INBOX.foo', $list->[0][2]); } sub test_selected_delayed - :needs_component_idled :min_version_3_9 -{ - my ($self) = @_; + : needs_component_idled : min_version_3_9 { + my ($self) = @_; - xlog $self, "Selected-delayed test of the NOTIFY command (idled required)"; + xlog $self, "Selected-delayed test of the NOTIFY command (idled required)"; - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); - my $svc = $self->{instance}->get_service('imap'); + my $svc = $self->{instance}->get_service('imap'); - my $store = $svc->create_store(); - my $talk = $store->get_client(); + my $store = $svc->create_store(); + my $talk = $store->get_client(); - my $otherstore = $svc->create_store(); - my $othertalk = $otherstore->get_client(); + my $otherstore = $svc->create_store(); + my $othertalk = $otherstore->get_client(); - xlog $self, "The server should report the NOTIFY capability"; - $self->assert($talk->capability()->{notify}); + xlog $self, "The server should report the NOTIFY capability"; + $self->assert($talk->capability()->{notify}); - xlog $self, "Enable Notify"; - my $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', - "(SELECTED-DELAYED (MessageNew MessageExpunge FlagChange))"); + xlog $self, "Enable Notify"; + my $res = $talk->_imap_cmd('NOTIFY', 0, 'STATUS', 'SET', + "(SELECTED-DELAYED (MessageNew MessageExpunge FlagChange))"); - xlog $self, "Examine INBOX"; - $talk->examine("INBOX"); + xlog $self, "Examine INBOX"; + $talk->examine("INBOX"); - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); - # Should get EXISTS, RECENT response - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); + # Should get EXISTS, RECENT response + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); - $self->assert_num_equals(1, $talk->get_response_code('exists')); - $self->assert_num_equals(1, $talk->get_response_code('recent')); + $self->assert_num_equals(1, $talk->get_response_code('exists')); + $self->assert_num_equals(1, $talk->get_response_code('recent')); - xlog $self, "EXPUNGE message from INBOX in other session"; - $othertalk->select("INBOX"); - $res = $othertalk->store('1', '+flags', '(\\Deleted)'); - $res = $othertalk->expunge(); + xlog $self, "EXPUNGE message from INBOX in other session"; + $othertalk->select("INBOX"); + $res = $othertalk->store('1', '+flags', '(\\Deleted)'); + $res = $othertalk->expunge(); - # Should get FETCH response, but NO EXPUNGE response - $res = $store->idle_response('FETCH', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); + # Should get FETCH response, but NO EXPUNGE response + $res = $store->idle_response('FETCH', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); - my $fetch = $talk->get_response_code('fetch'); - $self->assert_num_equals(1, $fetch->{1}{uid}); - $self->assert_str_equals('\\Recent', $fetch->{1}{flags}[0]); - $self->assert_str_equals('\\Deleted', $fetch->{1}{flags}[1]); + my $fetch = $talk->get_response_code('fetch'); + $self->assert_num_equals(1, $fetch->{1}{uid}); + $self->assert_str_equals('\\Recent', $fetch->{1}{flags}[0]); + $self->assert_str_equals('\\Deleted', $fetch->{1}{flags}[1]); - xlog $self, "Poll for changes"; - $talk->noop(); + xlog $self, "Poll for changes"; + $talk->noop(); - # Should get EXPUNGE response - $self->assert_num_equals(1, $talk->get_response_code('expunge')); + # Should get EXPUNGE response + $self->assert_num_equals(1, $talk->get_response_code('expunge')); } sub test_change_selected - :needs_component_idled :min_version_3_9 -{ - my ($self) = @_; - - xlog $self, "Test of NOTIFY events following SELECTED mailbox"; - - $self->{instance}->{config}->set(imapidlepoll => '2'); - $self->{instance}->add_start(name => 'idled', - argv => [ 'idled' ]); - $self->{instance}->start(); - - my $svc = $self->{instance}->get_service('imap'); - - my $store = $svc->create_store(); - my $talk = $store->get_client(); - - my $otherstore = $svc->create_store(); - my $othertalk = $otherstore->get_client(); - - xlog $self, "The server should report the NOTIFY capability"; - $self->assert($talk->capability()->{notify}); - - xlog $self, "Create another mailbox"; - $talk->create("INBOX.foo"); - - xlog $self, "Enable Notify"; - my $res = $talk->_imap_cmd('NOTIFY', 0, "", 'SET', - "(SELECTED (MessageNew MessageExpunge))", - "(PERSONAL (MessageNew MessageExpunge))"); - - xlog $self, "Examine INBOX"; - $talk->examine("INBOX"); - $self->assert_num_equals(0, $talk->get_response_code('exists')); - $self->assert_num_equals(0, $talk->get_response_code('recent')); - $self->assert_num_equals(1, $talk->get_response_code('uidnext')); - - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); - - # Should get EXISTS, RECENT response - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $self->assert_num_equals(1, $talk->get_response_code('exists')); - $self->assert_num_equals(1, $talk->get_response_code('recent')); - - xlog $self, "Examine INBOX.foo"; - $talk->examine("INBOX.foo"); - $self->assert_num_equals(0, $talk->get_response_code('exists')); - $self->assert_num_equals(0, $talk->get_response_code('recent')); - $self->assert_num_equals(1, $talk->get_response_code('uidnext')); - - xlog $self, "MOVE message from INBOX to INBOX.foo in other session"; - $othertalk->select("INBOX"); - $res = $othertalk->move('1', "INBOX.foo"); - - # Should get EXISTS, RECENT response for INBOX.foo and STATUS response for INBOX - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response('STATUS', 3); - $self->assert($res, "received an unsolicited response"); - $res = $store->idle_response({}, 1); - $self->assert(!$res, "no more unsolicited responses"); - - $self->assert_num_equals(1, $talk->get_response_code('exists')); - $self->assert_num_equals(1, $talk->get_response_code('recent')); - - my $status = $talk->get_response_code('status'); - $self->assert_num_equals(0, $status->{'INBOX'}{messages}); - $self->assert_num_equals(2, $status->{'INBOX'}{uidnext}); + : needs_component_idled : min_version_3_9 { + my ($self) = @_; + + xlog $self, "Test of NOTIFY events following SELECTED mailbox"; + + $self->{instance}->{config}->set(imapidlepoll => '2'); + $self->{instance}->add_start( + name => 'idled', + argv => ['idled'] + ); + $self->{instance}->start(); + + my $svc = $self->{instance}->get_service('imap'); + + my $store = $svc->create_store(); + my $talk = $store->get_client(); + + my $otherstore = $svc->create_store(); + my $othertalk = $otherstore->get_client(); + + xlog $self, "The server should report the NOTIFY capability"; + $self->assert($talk->capability()->{notify}); + + xlog $self, "Create another mailbox"; + $talk->create("INBOX.foo"); + + xlog $self, "Enable Notify"; + my $res = $talk->_imap_cmd( + 'NOTIFY', 0, "", 'SET', + "(SELECTED (MessageNew MessageExpunge))", + "(PERSONAL (MessageNew MessageExpunge))" + ); + + xlog $self, "Examine INBOX"; + $talk->examine("INBOX"); + $self->assert_num_equals(0, $talk->get_response_code('exists')); + $self->assert_num_equals(0, $talk->get_response_code('recent')); + $self->assert_num_equals(1, $talk->get_response_code('uidnext')); + + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); + + # Should get EXISTS, RECENT response + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $self->assert_num_equals(1, $talk->get_response_code('exists')); + $self->assert_num_equals(1, $talk->get_response_code('recent')); + + xlog $self, "Examine INBOX.foo"; + $talk->examine("INBOX.foo"); + $self->assert_num_equals(0, $talk->get_response_code('exists')); + $self->assert_num_equals(0, $talk->get_response_code('recent')); + $self->assert_num_equals(1, $talk->get_response_code('uidnext')); + + xlog $self, "MOVE message from INBOX to INBOX.foo in other session"; + $othertalk->select("INBOX"); + $res = $othertalk->move('1', "INBOX.foo"); + +# Should get EXISTS, RECENT response for INBOX.foo and STATUS response for INBOX + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response('STATUS', 3); + $self->assert($res, "received an unsolicited response"); + $res = $store->idle_response({}, 1); + $self->assert(!$res, "no more unsolicited responses"); + + $self->assert_num_equals(1, $talk->get_response_code('exists')); + $self->assert_num_equals(1, $talk->get_response_code('recent')); + + my $status = $talk->get_response_code('status'); + $self->assert_num_equals(0, $status->{'INBOX'}{messages}); + $self->assert_num_equals(2, $status->{'INBOX'}{uidnext}); } 1; diff --git a/cassandane/Cassandane/Cyrus/Objectid.pm b/cassandane/Cassandane/Cyrus/Objectid.pm index 8eb9cced72..464895ac29 100644 --- a/cassandane/Cassandane/Cyrus/Objectid.pm +++ b/cassandane/Cassandane/Cyrus/Objectid.pm @@ -50,102 +50,97 @@ use Cassandane::Generator; use Cassandane::MessageStoreFactory; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({adminstore => 1}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test uniqueid and rename # sub test_uniqueid - :AltNamespace :min_version_3_1 -{ - my ($self) = @_; + : AltNamespace : min_version_3_1 { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create('foo'); - $talk->create('bar'); - $talk->create('foo'); - my $status1 = $talk->status('foo', "(mailboxid)"); - my $status2 = $talk->status('bar', "(mailboxid)"); + $talk->create('foo'); + $talk->create('bar'); + $talk->create('foo'); + my $status1 = $talk->status('foo', "(mailboxid)"); + my $status2 = $talk->status('bar', "(mailboxid)"); - $talk->rename('foo', 'renamed'); - my $status3 = $talk->status('renamed', "(mailboxid)"); - my $status4 = $talk->status('bar', "(mailboxid)"); + $talk->rename('foo', 'renamed'); + my $status3 = $talk->status('renamed', "(mailboxid)"); + my $status4 = $talk->status('bar', "(mailboxid)"); - $self->assert_str_equals($status1->{mailboxid}[0], $status3->{mailboxid}[0]); - $self->assert_str_equals($status2->{mailboxid}[0], $status4->{mailboxid}[0]); + $self->assert_str_equals($status1->{mailboxid}[0], $status3->{mailboxid}[0]); + $self->assert_str_equals($status2->{mailboxid}[0], $status4->{mailboxid}[0]); - $talk->list('', '*', 'return', [ "status", [ "mailboxid" ] ]); + $talk->list('', '*', 'return', [ "status", ["mailboxid"] ]); } # # Test uniqueid and rename # sub test_emailid_threadid - :AltNamespace :Conversations :min_version_3_1 -{ - my ($self) = @_; + : AltNamespace : Conversations : min_version_3_1 { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create('foo'); + $talk->create('foo'); - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); - my %exp; + my %exp; - $self->{store}->set_fetch_attributes('uid', 'cid'); + $self->{store}->set_fetch_attributes('uid', 'cid'); - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); - xlog $self, "generating message B"; - $exp{B} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{B}->set_attributes(uid => 2, cid => $exp{A}->make_cid()); - $self->check_messages(\%exp); + xlog $self, "generating message B"; + $exp{B} = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{B}->set_attributes(uid => 2, cid => $exp{A}->make_cid()); + $self->check_messages(\%exp); - xlog $self, "generating message C"; - $exp{C} = $self->make_message("Message C"); - $exp{C}->set_attributes(uid => 3, cid => $exp{C}->make_cid()); - $self->check_messages(\%exp); + xlog $self, "generating message C"; + $exp{C} = $self->make_message("Message C"); + $exp{C}->set_attributes(uid => 3, cid => $exp{C}->make_cid()); + $self->check_messages(\%exp); - $talk->select('INBOX'); - my $data = $talk->fetch('1:*', "(emailid threadid)"); + $talk->select('INBOX'); + my $data = $talk->fetch('1:*', "(emailid threadid)"); - $talk->search('emailid', $data->{1}{emailid}); - $talk->search('threadid', $data->{1}{threadid}); + $talk->search('emailid', $data->{1}{emailid}); + $talk->search('threadid', $data->{1}{threadid}); - $talk->move("2", "foo"); + $talk->move("2", "foo"); - $talk->fetch('1:*', "(emailid threadid)"); + $talk->fetch('1:*', "(emailid threadid)"); - $talk->select('foo'); - $talk->fetch('1:*', "(emailid threadid)"); + $talk->select('foo'); + $talk->fetch('1:*', "(emailid threadid)"); - $talk->select('INBOX'); + $talk->select('INBOX'); - my $email = < @@ -153,7 +148,7 @@ From: Body EOF - my $email2 = < @@ -161,13 +156,19 @@ From: Body2 EOF - $email =~ s/\r?\n/\r\n/gs; - $email2 =~ s/\r?\n/\r\n/gs; + $email =~ s/\r?\n/\r\n/gs; + $email2 =~ s/\r?\n/\r\n/gs; - $talk->append("INBOX", "()", " 7-Feb-1994 22:43:04 -0800", { Literal => "$email" }, - "()", " 7-Feb-1994 22:43:04 -0800", { Literal => "$email2" }); + $talk->append( + "INBOX", "()", + " 7-Feb-1994 22:43:04 -0800", + { Literal => "$email" }, + "()", + " 7-Feb-1994 22:43:04 -0800", + { Literal => "$email2" } + ); - # XXX and then what??? is this test incomplete? + # XXX and then what??? is this test incomplete? } 1; diff --git a/cassandane/Cassandane/Cyrus/Pop3.pm b/cassandane/Cassandane/Cyrus/Pop3.pm index f8fa3b9550..10d08ad539 100644 --- a/cassandane/Cassandane/Cyrus/Pop3.pm +++ b/cassandane/Cassandane/Cyrus/Pop3.pm @@ -47,146 +47,151 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -Cassandane::Cyrus::TestCase::magic(PopSubFolders => sub { +Cassandane::Cyrus::TestCase::magic( + PopSubFolders => sub { shift->config_set(popsubfolders => 1); -}); - -sub new -{ - my ($class, @args) = @_; - return $class->SUPER::new({ - # We need IMAP to be able to create the mailbox for POP - services => ['imap', 'pop3'], - }, @args); + } +); + +sub new { + my ($class, @args) = @_; + return $class->SUPER::new( + { + # We need IMAP to be able to create the mailbox for POP + services => [ 'imap', 'pop3' ], + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - my $svc = $self->{instance}->get_service('pop3'); - if (defined $svc) - { - $self->{pop_store} = $svc->create_store(); - } + my $svc = $self->{instance}->get_service('pop3'); + if (defined $svc) { + $self->{pop_store} = $svc->create_store(); + } } -sub tear_down -{ - my ($self) = @_; +sub tear_down { + my ($self) = @_; - if (defined $self->{pop_store}) - { - $self->{pop_store}->disconnect(); - $self->{pop_store} = undef; - } + if (defined $self->{pop_store}) { + $self->{pop_store}->disconnect(); + $self->{pop_store} = undef; + } - $self->SUPER::tear_down(); + $self->SUPER::tear_down(); } -sub test_top_args -{ - my ($self) = @_; - - xlog $self, "Testing whether the TOP command checks its arguments [Bug 3641]"; - # Note, the POP client checks its arguments before sending - # them so we have to reach around it to do bad things. - - xlog $self, "Ensure a message exists, before logging in to POP"; - my %exp; - $exp{A} = $self->make_message('Message A'); - - my $client = $self->{pop_store}->get_client(); - - xlog $self, "TOP with no arguments should return an error"; - my $r = $client->command('TOP')->response(); - $self->assert_equals($r, Net::Cmd::CMD_ERROR); - $self->assert_equals($client->code(), 500); - $self->assert_matches(qr/Missing argument/, $client->message()); - - xlog $self, "TOP with 1 argument should return an error"; - $r = $client->command('TOP', 1)->response(); - $self->assert_equals($r, Net::Cmd::CMD_ERROR); - $self->assert_equals($client->code(), 500); - $self->assert_matches(qr/Missing argument/, $client->message()); - - xlog $self, "TOP with 2 correct arguments should actually work"; - $r = $client->command('TOP', 1, 2)->response(); - $self->assert_equals($r, Net::Cmd::CMD_OK); - $self->assert_equals($client->code(), 200); - my $lines = $client->read_until_dot(); - my %actual; - $actual{'Message A'} = Cassandane::Message->new(lines => $lines, - attrs => { uid => 1 }); - $self->check_messages(\%exp, actual => \%actual); - - xlog $self, "TOP with 2 arguments, first one not a number, should return an error"; - $r = $client->command('TOP', '1xyz', 2)->response(); - $self->assert_equals($r, Net::Cmd::CMD_ERROR); - $self->assert_equals($client->code(), 500); - - xlog $self, "TOP with 2 arguments, second one not a number, should return an error"; - $r = $client->command('TOP', 1, '2xyz')->response(); - $self->assert_equals($r, Net::Cmd::CMD_ERROR); - $self->assert_equals($client->code(), 500); - - xlog $self, "TOP with 3 arguments should return an error"; - $r = $client->command('TOP', 1, 2, 3)->response(); - $self->assert_equals($r, Net::Cmd::CMD_ERROR); - $self->assert_equals($client->code(), 500); - $self->assert_matches(qr/Unexpected extra argument/, $client->message()); +sub test_top_args { + my ($self) = @_; + + xlog $self, "Testing whether the TOP command checks its arguments [Bug 3641]"; + # Note, the POP client checks its arguments before sending + # them so we have to reach around it to do bad things. + + xlog $self, "Ensure a message exists, before logging in to POP"; + my %exp; + $exp{A} = $self->make_message('Message A'); + + my $client = $self->{pop_store}->get_client(); + + xlog $self, "TOP with no arguments should return an error"; + my $r = $client->command('TOP')->response(); + $self->assert_equals($r, Net::Cmd::CMD_ERROR); + $self->assert_equals($client->code(), 500); + $self->assert_matches(qr/Missing argument/, $client->message()); + + xlog $self, "TOP with 1 argument should return an error"; + $r = $client->command('TOP', 1)->response(); + $self->assert_equals($r, Net::Cmd::CMD_ERROR); + $self->assert_equals($client->code(), 500); + $self->assert_matches(qr/Missing argument/, $client->message()); + + xlog $self, "TOP with 2 correct arguments should actually work"; + $r = $client->command('TOP', 1, 2)->response(); + $self->assert_equals($r, Net::Cmd::CMD_OK); + $self->assert_equals($client->code(), 200); + my $lines = $client->read_until_dot(); + my %actual; + $actual{'Message A'} = Cassandane::Message->new( + lines => $lines, + attrs => { uid => 1 } + ); + $self->check_messages(\%exp, actual => \%actual); + + xlog $self, + "TOP with 2 arguments, first one not a number, should return an error"; + $r = $client->command('TOP', '1xyz', 2)->response(); + $self->assert_equals($r, Net::Cmd::CMD_ERROR); + $self->assert_equals($client->code(), 500); + + xlog $self, + "TOP with 2 arguments, second one not a number, should return an error"; + $r = $client->command('TOP', 1, '2xyz')->response(); + $self->assert_equals($r, Net::Cmd::CMD_ERROR); + $self->assert_equals($client->code(), 500); + + xlog $self, "TOP with 3 arguments should return an error"; + $r = $client->command('TOP', 1, 2, 3)->response(); + $self->assert_equals($r, Net::Cmd::CMD_ERROR); + $self->assert_equals($client->code(), 500); + $self->assert_matches(qr/Unexpected extra argument/, $client->message()); } sub test_subfolder_login - :PopSubFolders :NoAltNameSpace -{ - my ($self) = @_; - - xlog $self, "Testing whether + address login gets subfolder"; - - my $imapclient = $self->{store}->get_client(); - - xlog $self, "Ensure a messages exist"; - my %exp; - $exp{A} = $self->make_message('Message A'); - - $imapclient->create('INBOX.sub'); - $self->{store}->set_folder('INBOX.sub'); - # different mailbox, so reset generator's expected uid sequence - $self->{gen}->set_next_uid(1); - - my %subexp; - $subexp{B} = $self->make_message('Message B'); - - my $popclient = $self->{pop_store}->get_client(); - - xlog $self, "Test regular TOP gets the right message"; - my $r = $popclient->command('TOP', 1, 2)->response(); - $self->assert_equals($r, Net::Cmd::CMD_OK); - $self->assert_equals($popclient->code(), 200); - my $lines = $popclient->read_until_dot(); - my %actual; - $actual{'Message A'} = Cassandane::Message->new(lines => $lines, - attrs => { uid => 1 }); - $self->check_messages(\%exp, actual => \%actual); - - my $svc = $self->{instance}->get_service('pop3'); - my $substore = $svc->create_store(folder => 'INBOX.sub'); - - # create a new client - my $subclient = $substore->get_client(); - - - xlog $self, "Test subfolder TOP gets the right message"; - my $subr = $subclient->command('TOP', 1, 2)->response(); - $self->assert_equals($subr, Net::Cmd::CMD_OK); - $self->assert_equals($subclient->code(), 200); - my $sublines = $subclient->read_until_dot(); - my %subactual; - $subactual{'Message B'} = Cassandane::Message->new(lines => $sublines, - attrs => { uid => 1 }); - $self->check_messages(\%subexp, actual => \%subactual); + : PopSubFolders : NoAltNameSpace { + my ($self) = @_; + + xlog $self, "Testing whether + address login gets subfolder"; + + my $imapclient = $self->{store}->get_client(); + + xlog $self, "Ensure a messages exist"; + my %exp; + $exp{A} = $self->make_message('Message A'); + + $imapclient->create('INBOX.sub'); + $self->{store}->set_folder('INBOX.sub'); + # different mailbox, so reset generator's expected uid sequence + $self->{gen}->set_next_uid(1); + + my %subexp; + $subexp{B} = $self->make_message('Message B'); + + my $popclient = $self->{pop_store}->get_client(); + + xlog $self, "Test regular TOP gets the right message"; + my $r = $popclient->command('TOP', 1, 2)->response(); + $self->assert_equals($r, Net::Cmd::CMD_OK); + $self->assert_equals($popclient->code(), 200); + my $lines = $popclient->read_until_dot(); + my %actual; + $actual{'Message A'} = Cassandane::Message->new( + lines => $lines, + attrs => { uid => 1 } + ); + $self->check_messages(\%exp, actual => \%actual); + + my $svc = $self->{instance}->get_service('pop3'); + my $substore = $svc->create_store(folder => 'INBOX.sub'); + + # create a new client + my $subclient = $substore->get_client(); + + xlog $self, "Test subfolder TOP gets the right message"; + my $subr = $subclient->command('TOP', 1, 2)->response(); + $self->assert_equals($subr, Net::Cmd::CMD_OK); + $self->assert_equals($subclient->code(), 200); + my $sublines = $subclient->read_until_dot(); + my %subactual; + $subactual{'Message B'} = Cassandane::Message->new( + lines => $sublines, + attrs => { uid => 1 } + ); + $self->check_messages(\%subexp, actual => \%subactual); } 1; diff --git a/cassandane/Cassandane/Cyrus/Prometheus.pm b/cassandane/Cassandane/Cyrus/Prometheus.pm index 4cb3456f33..aa16a766db 100644 --- a/cassandane/Cassandane/Cyrus/Prometheus.pm +++ b/cassandane/Cassandane/Cyrus/Prometheus.pm @@ -51,434 +51,433 @@ use Cassandane::Instance; $Data::Dumper::Sortkeys = 1; -sub new -{ - my $class = shift; - - my $config = Cassandane::Config->default()->clone(); - $config->set(prometheus_enabled => "yes"); - $config->set(httpmodules => "prometheus"); - $config->set(prometheus_need_auth => "none"); - $config->set(prometheus_update_freq => 2); - - return $class->SUPER::new( - { adminstore => 1, - config => $config, - services => ['imap', 'http'] }, - @_); +sub new { + my $class = shift; + + my $config = Cassandane::Config->default()->clone(); + $config->set(prometheus_enabled => "yes"); + $config->set(httpmodules => "prometheus"); + $config->set(prometheus_need_auth => "none"); + $config->set(prometheus_update_freq => 2); + + return $class->SUPER::new( + { + adminstore => 1, + config => $config, + services => [ 'imap', 'http' ] + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub _create_instances -{ - my ($self) = @_; +sub _create_instances { + my ($self) = @_; - $self->SUPER::_create_instances(); - # XXX This should really run from the DAEMON section, - # XXX but Cassandane doesn't know about that. - $self->{instance}->add_start(name => 'promstatsd', - argv => [ 'promstatsd' ]); + $self->SUPER::_create_instances(); + # XXX This should really run from the DAEMON section, + # XXX but Cassandane doesn't know about that. + $self->{instance}->add_start( + name => 'promstatsd', + argv => ['promstatsd'] + ); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub http_report -{ - my ($self) = @_; +sub http_report { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $url = join(q{}, - q{http://}, $service->host(), - q{:}, $service->port(), - q{/metrics}); + my $service = $self->{instance}->get_service("http"); + my $url = join(q{}, q{http://}, $service->host(), q{:}, $service->port(), + q{/metrics}); - return HTTP::Tiny->new()->get($url); + return HTTP::Tiny->new()->get($url); } -sub parse_report -{ - my ($content) = @_; - - my $report = {}; - - foreach my $line (split /\n/, $content) { - next if $line =~ /^\#/; - my ($key, $val, $ts) = split /\s+/, $line; - if ($key =~ m/^([^\{]+)\{([^\}]+)}$/) { - $report->{$1}->{$2} = { value => $val, timestamp => $ts }; - } - else { - $report->{$key} = { value => $val, timestamp => $ts }; - } +sub parse_report { + my ($content) = @_; + + my $report = {}; + + foreach my $line (split /\n/, $content) { + next if $line =~ /^\#/; + my ($key, $val, $ts) = split /\s+/, $line; + if ($key =~ m/^([^\{]+)\{([^\}]+)}$/) { + $report->{$1}->{$2} = { value => $val, timestamp => $ts }; + } else { + $report->{$key} = { value => $val, timestamp => $ts }; } + } - return $report; + return $report; } sub test_aaasetup - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_1 : needs_component_httpd { + my ($self) = @_; - # does everything set up and tear down cleanly? - $self->assert(1); + # does everything set up and tear down cleanly? + $self->assert(1); } sub test_reportfile_exists - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_1 : needs_component_httpd { + my ($self) = @_; - # do something that'll get counted - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - # and wait for a fresh report - sleep 3; + # do something that'll get counted + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + # and wait for a fresh report + sleep 3; - my $reportfile_name = "$self->{instance}->{basedir}/conf/stats/report.txt"; + my $reportfile_name = "$self->{instance}->{basedir}/conf/stats/report.txt"; - $self->assert_file_test($reportfile_name, '-f'); + $self->assert_file_test($reportfile_name, '-f'); - my $report = parse_report(scalar read_file $reportfile_name); + my $report = parse_report(scalar read_file $reportfile_name); - $self->assert(scalar keys %{$report}); - $self->assert(exists $report->{cyrus_imap_connections_total}); + $self->assert(scalar keys %{$report}); + $self->assert(exists $report->{cyrus_imap_connections_total}); } sub test_httpreport - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_1 : needs_component_httpd { + my ($self) = @_; - # do something that'll get counted - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - # and wait for a fresh report - sleep 3; + # do something that'll get counted + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + # and wait for a fresh report + sleep 3; - my $response = $self->http_report(); + my $response = $self->http_report(); - $self->assert($response->{success}); - $self->assert(length $response->{content}); + $self->assert($response->{success}); + $self->assert(length $response->{content}); - my $report = parse_report($response->{content}); + my $report = parse_report($response->{content}); - $self->assert(scalar keys %{$report}); - $self->assert(exists $report->{cyrus_imap_connections_total}); + $self->assert(scalar keys %{$report}); + $self->assert(exists $report->{cyrus_imap_connections_total}); } sub test_disabled - :min_version_3_1 :needs_component_httpd :NoStartInstances -{ - my ($self) = @_; + : min_version_3_1 : needs_component_httpd : NoStartInstances { + my ($self) = @_; - my $instance = $self->{instance}; - $instance->{starts} = [ grep { $_->{name} ne 'promstatsd' } @{$instance->{starts}} ]; - $instance->{config}->set(prometheus_enabled => 'no'); + my $instance = $self->{instance}; + $instance->{starts} + = [ grep { $_->{name} ne 'promstatsd' } @{ $instance->{starts} } ]; + $instance->{config}->set(prometheus_enabled => 'no'); - $self->_start_instances(); + $self->_start_instances(); - # no stats directory - my $stats_dir = "$self->{instance}->{basedir}/conf/stats"; - $self->assert_not_file_test($stats_dir, '-d'); + # no stats directory + my $stats_dir = "$self->{instance}->{basedir}/conf/stats"; + $self->assert_not_file_test($stats_dir, '-d'); - # no http report - my $response = $self->http_report(); - $self->assert_equals(404, $response->{status}); + # no http report + my $response = $self->http_report(); + $self->assert_equals(404, $response->{status}); } # tests for pathological quotaroot/partition subdivisions sub test_quota_commitments - :min_version_3_1 :needs_component_httpd :Partition2 -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my $inbox = 'user.cassandane'; # allocate top level quota here - my $child = "$inbox.child"; - my $gchild1 = "$child.cat"; # we'll stick this one on a sep part - my $gchild2 = "$child.dog"; # give this one its own quota - my $gchild3 = "$child.sheep"; # normal, but sorts after weird ones - my $ggchild1 = "$gchild1.manx"; # and give this one its own quota - my $ggchild2 = "$gchild1.siamese"; # and this one back on def part - my $interm = "$inbox.foo.bar.baz"; # contains intermediate folders - my $inbox2 = 'user.cassandane-child'; # hyphen! own quota - - # make some folders - foreach my $f ($child, $gchild1, $gchild2, $gchild3, $ggchild1, - $ggchild2, $interm, $inbox2) { - $admintalk->create($f); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - } - - # stick one of them on a different partition - $admintalk->rename($gchild1, $gchild1, 'p2'); + : min_version_3_1 : needs_component_httpd : Partition2 { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my $inbox = 'user.cassandane'; # allocate top level quota here + my $child = "$inbox.child"; + my $gchild1 = "$child.cat"; # we'll stick this one on a sep part + my $gchild2 = "$child.dog"; # give this one its own quota + my $gchild3 = "$child.sheep"; # normal, but sorts after weird ones + my $ggchild1 = "$gchild1.manx"; # and give this one its own quota + my $ggchild2 = "$gchild1.siamese"; # and this one back on def part + my $interm = "$inbox.foo.bar.baz"; # contains intermediate folders + my $inbox2 = 'user.cassandane-child'; # hyphen! own quota + + # make some folders + foreach my $f ( + $child, $gchild1, $gchild2, $gchild3, + $ggchild1, $ggchild2, $interm, $inbox2 + ) + { + $admintalk->create($f); $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } - # but not one of its children - $admintalk->rename($ggchild2, $ggchild2, 'default'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + # stick one of them on a different partition + $admintalk->rename($gchild1, $gchild1, 'p2'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - # create a mess of quotas - $admintalk->setquota($inbox, '(STORAGE 8000)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setquota($gchild2, '(STORAGE 4000)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setquota($ggchild1, '(STORAGE 2000)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setquota($inbox2, '(STORAGE 1000)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + # but not one of its children + $admintalk->rename($ggchild2, $ggchild2, 'default'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->logout(); + # create a mess of quotas + $admintalk->setquota($inbox, '(STORAGE 8000)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setquota($gchild2, '(STORAGE 4000)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setquota($ggchild1, '(STORAGE 2000)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setquota($inbox2, '(STORAGE 1000)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - sleep 3; + $admintalk->logout(); - my $response = $self->http_report(); - $self->assert($response->{success}); + sleep 3; - my $report = parse_report($response->{content}); - $self->assert(scalar keys %{$report}); + my $response = $self->http_report(); + $self->assert($response->{success}); - # now we expect default partition to have 8000 + 4000 + 1000 committed - $self->assert_equals(13000, $report->{'cyrus_usage_quota_commitment'}->{'partition="default",resource="STORAGE"'}->{value}); + my $report = parse_report($response->{content}); + $self->assert(scalar keys %{$report}); - # and p2 partition to have 8000 + 2000 committed - $self->assert_equals(10000, $report->{'cyrus_usage_quota_commitment'}->{'partition="p2",resource="STORAGE"'}->{value}); + # now we expect default partition to have 8000 + 4000 + 1000 committed + $self->assert_equals(13000, + $report->{'cyrus_usage_quota_commitment'} + ->{'partition="default",resource="STORAGE"'}->{value}); + + # and p2 partition to have 8000 + 2000 committed + $self->assert_equals(10000, + $report->{'cyrus_usage_quota_commitment'} + ->{'partition="p2",resource="STORAGE"'}->{value}); } # tests for pathological quotaroot/partition subdivisions sub test_quota_commitments_no_improved_mboxlist_sort - :min_version_3_1 :needs_component_httpd :Partition2 :NoStartInstances -{ - my ($self) = @_; - - $self->{instance}->{config}->set('improved_mboxlist_sort', 'no'); - $self->_start_instances(); - - my $admintalk = $self->{adminstore}->get_client(); - - my $inbox = 'user.cassandane'; # allocate top level quota here - my $child = "$inbox.child"; - my $gchild1 = "$child.cat"; # we'll stick this one on a sep part - my $gchild2 = "$child.dog"; # give this one its own quota - my $gchild3 = "$child.sheep"; # normal, but sorts after weird ones - my $ggchild1 = "$gchild1.manx"; # and give this one its own quota - my $ggchild2 = "$gchild1.siamese"; # and this one back on def part - my $interm = "$inbox.foo.bar.baz"; # contains intermediate folders - my $inbox2 = 'user.cassandane-child'; # hyphen! own quota - - # make some folders - foreach my $f ($child, $gchild1, $gchild2, $gchild3, $ggchild1, - $ggchild2, $interm, $inbox2) { - $admintalk->create($f); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - } - - # stick one of them on a different partition - $admintalk->rename($gchild1, $gchild1, 'p2'); + : min_version_3_1 : needs_component_httpd : Partition2 : NoStartInstances { + my ($self) = @_; + + $self->{instance}->{config}->set('improved_mboxlist_sort', 'no'); + $self->_start_instances(); + + my $admintalk = $self->{adminstore}->get_client(); + + my $inbox = 'user.cassandane'; # allocate top level quota here + my $child = "$inbox.child"; + my $gchild1 = "$child.cat"; # we'll stick this one on a sep part + my $gchild2 = "$child.dog"; # give this one its own quota + my $gchild3 = "$child.sheep"; # normal, but sorts after weird ones + my $ggchild1 = "$gchild1.manx"; # and give this one its own quota + my $ggchild2 = "$gchild1.siamese"; # and this one back on def part + my $interm = "$inbox.foo.bar.baz"; # contains intermediate folders + my $inbox2 = 'user.cassandane-child'; # hyphen! own quota + + # make some folders + foreach my $f ( + $child, $gchild1, $gchild2, $gchild3, + $ggchild1, $ggchild2, $interm, $inbox2 + ) + { + $admintalk->create($f); $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } - # but not one of its children - $admintalk->rename($ggchild2, $ggchild2, 'default'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + # stick one of them on a different partition + $admintalk->rename($gchild1, $gchild1, 'p2'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - # create a mess of quotas - $admintalk->setquota($inbox, '(STORAGE 8000)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setquota($gchild2, '(STORAGE 4000)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setquota($ggchild1, '(STORAGE 2000)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setquota($inbox2, '(STORAGE 1000)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + # but not one of its children + $admintalk->rename($ggchild2, $ggchild2, 'default'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # create a mess of quotas + $admintalk->setquota($inbox, '(STORAGE 8000)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setquota($gchild2, '(STORAGE 4000)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setquota($ggchild1, '(STORAGE 2000)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setquota($inbox2, '(STORAGE 1000)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->logout(); + $admintalk->logout(); - sleep 3; + sleep 3; - my $response = $self->http_report(); - $self->assert($response->{success}); + my $response = $self->http_report(); + $self->assert($response->{success}); - my $report = parse_report($response->{content}); - $self->assert(scalar keys %{$report}); + my $report = parse_report($response->{content}); + $self->assert(scalar keys %{$report}); - # now we expect default partition to have 8000 + 4000 +1000 committed - $self->assert_equals(13000, $report->{'cyrus_usage_quota_commitment'}->{'partition="default",resource="STORAGE"'}->{value}); + # now we expect default partition to have 8000 + 4000 +1000 committed + $self->assert_equals(13000, + $report->{'cyrus_usage_quota_commitment'} + ->{'partition="default",resource="STORAGE"'}->{value}); - # and p2 partition to have 8000 + 2000 committed - $self->assert_equals(10000, $report->{'cyrus_usage_quota_commitment'}->{'partition="p2",resource="STORAGE"'}->{value}); + # and p2 partition to have 8000 + 2000 committed + $self->assert_equals(10000, + $report->{'cyrus_usage_quota_commitment'} + ->{'partition="p2",resource="STORAGE"'}->{value}); } sub test_shared_mailbox_namespaces - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_1 : needs_component_httpd { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - my $ns1 = 'foo'; - my $ns2 = 'bar'; - my @folders = map { ("$ns1.$_", "$ns2.$_" ) } - qw(cat sheep dog interm.interm.rabbit); + my $ns1 = 'foo'; + my $ns2 = 'bar'; + my @folders + = map { ("$ns1.$_", "$ns2.$_") } qw(cat sheep dog interm.interm.rabbit); - foreach my $f (@folders) { - $admintalk->create($f); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - } + foreach my $f (@folders) { + $admintalk->create($f); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + } - sleep 3; + sleep 3; - my $response = $self->http_report(); - $self->assert($response->{success}); + my $response = $self->http_report(); + $self->assert($response->{success}); - my $report = parse_report($response->{content}); - $self->assert(scalar keys %{$report}); + my $report = parse_report($response->{content}); + $self->assert(scalar keys %{$report}); - my $num_folders = 4; - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min > 4)) { - $num_folders = 7; - } + my $num_folders = 4; + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min > 4)) { + $num_folders = 7; + } - # expect to find $num_folders folders on each of 'foo' and 'bar' namespaces - $self->assert_equals($num_folders, $report->{'cyrus_usage_shared_mailboxes'}->{'partition="default",namespace="bar"'}->{value}); + # expect to find $num_folders folders on each of 'foo' and 'bar' namespaces + $self->assert_equals($num_folders, + $report->{'cyrus_usage_shared_mailboxes'} + ->{'partition="default",namespace="bar"'}->{value}); - $self->assert_equals($num_folders, $report->{'cyrus_usage_shared_mailboxes'}->{'partition="default",namespace="foo"'}->{value}); + $self->assert_equals($num_folders, + $report->{'cyrus_usage_shared_mailboxes'} + ->{'partition="default",namespace="foo"'}->{value}); } sub slowtest_50000_users - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; - - my $nusers = 50000; - my @subfolders = qw(Drafts Sent Spam Trash); - my $storage = 8000; - - my $admintalk = $self->{adminstore}->get_client(); - - foreach my $n (1..$nusers) { - # reconnect every so often so stuff can flush - if ($n % 5000 == 0) { - $admintalk->logout(); - $self->{adminstore}->disconnect(); - $admintalk = $self->{adminstore}->get_client(); - } - - my $folder = sprintf("user.a%08d", $n); - $admintalk->create($folder); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - $admintalk->setquota($folder, "(STORAGE $storage)"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - foreach my $subfolder (@subfolders) { - $admintalk->create("$folder.$subfolder"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - } + : min_version_3_1 : needs_component_httpd { + my ($self) = @_; + + my $nusers = 50000; + my @subfolders = qw(Drafts Sent Spam Trash); + my $storage = 8000; + + my $admintalk = $self->{adminstore}->get_client(); + + foreach my $n (1 .. $nusers) { + # reconnect every so often so stuff can flush + if ($n % 5000 == 0) { + $admintalk->logout(); + $self->{adminstore}->disconnect(); + $admintalk = $self->{adminstore}->get_client(); } - # XXX may not be long enough! - sleep 3; + my $folder = sprintf("user.a%08d", $n); + $admintalk->create($folder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $response = $self->http_report(); - $self->assert($response->{success}); + $admintalk->setquota($folder, "(STORAGE $storage)"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $report = parse_report($response->{content}); - $self->assert(scalar keys %{$report}); + foreach my $subfolder (@subfolders) { + $admintalk->create("$folder.$subfolder"); + $self->assert_str_equals('ok', + $admintalk->get_last_completion_response()); + } + } - # n.b. user/mailbox counts are +1 cause of user.cassandane! - $self->assert_num_equals(1 + $nusers, - $report->{'cyrus_usage_users'}->{'partition="default"'}->{value}); - $self->assert_num_equals(1 + $nusers + ($nusers * scalar @subfolders), - $report->{'cyrus_usage_mailboxes'}->{'partition="default"'}->{value}); - $self->assert_num_equals($nusers * $storage, - $report->{'cyrus_usage_quota_commitment'}->{'partition="default",resource="STORAGE"'}->{value}); + # XXX may not be long enough! + sleep 3; + + my $response = $self->http_report(); + $self->assert($response->{success}); + + my $report = parse_report($response->{content}); + $self->assert(scalar keys %{$report}); + + # n.b. user/mailbox counts are +1 cause of user.cassandane! + $self->assert_num_equals(1 + $nusers, + $report->{'cyrus_usage_users'}->{'partition="default"'}->{value}); + $self->assert_num_equals(1 + $nusers + ($nusers * scalar @subfolders), + $report->{'cyrus_usage_mailboxes'}->{'partition="default"'}->{value}); + $self->assert_num_equals($nusers * $storage, + $report->{'cyrus_usage_quota_commitment'} + ->{'partition="default",resource="STORAGE"'}->{value}); } sub test_connection_setup_failure_imapd - :min_version_3_2 :needs_component_httpd :TLS -{ - my ($self) = @_; - - my $instance = $self->{instance}; - - my $svc = $instance->get_service('imaps'); - $self->assert_not_null($svc); - - # we're gonna try to connect to it unencrypted, so it fails - my $store = $svc->create_store('type' => 'imap'); - $self->assert_not_null($store); - - my $badconns = 2 + int(rand(4)); # between 2-5 tries - for (1 .. $badconns) { - # try to connect to it using a plain text client - # and expect the server to drop the connection - eval { - $store->get_client(); - }; - my $error = $@; - $self->assert_matches(qr{Connection closed by other end}, $error); - } + : min_version_3_2 : needs_component_httpd : TLS { + my ($self) = @_; + + my $instance = $self->{instance}; + + my $svc = $instance->get_service('imaps'); + $self->assert_not_null($svc); + + # we're gonna try to connect to it unencrypted, so it fails + my $store = $svc->create_store('type' => 'imap'); + $self->assert_not_null($store); + + my $badconns = 2 + int(rand(4)); # between 2-5 tries + for (1 .. $badconns) { + # try to connect to it using a plain text client + # and expect the server to drop the connection + eval { $store->get_client(); }; + my $error = $@; + $self->assert_matches(qr{Connection closed by other end}, $error); + } - # wait a bit for the prometheus report to refresh - sleep 3; + # wait a bit for the prometheus report to refresh + sleep 3; - # check the prom report - my $response = $self->http_report(); - $self->assert($response->{success}); + # check the prom report + my $response = $self->http_report(); + $self->assert($response->{success}); - my $report = parse_report($response->{content}); - $self->assert(scalar keys %{$report}); + my $report = parse_report($response->{content}); + $self->assert(scalar keys %{$report}); - my $active = $report->{'cyrus_imap_active_connections'}; - $self->assert_not_null($active); - my $ready = $report->{'cyrus_imap_ready_listeners'}; - $self->assert_not_null($ready); - my $total = $report->{'cyrus_imap_connections_total'}; - $self->assert_not_null($total); - my $shutdown = $report->{'cyrus_imap_shutdown_total'}; - $self->assert_not_null($shutdown); + my $active = $report->{'cyrus_imap_active_connections'}; + $self->assert_not_null($active); + my $ready = $report->{'cyrus_imap_ready_listeners'}; + $self->assert_not_null($ready); + my $total = $report->{'cyrus_imap_connections_total'}; + $self->assert_not_null($total); + my $shutdown = $report->{'cyrus_imap_shutdown_total'}; + $self->assert_not_null($shutdown); - my $service_label = 'service="imaps"'; + my $service_label = 'service="imaps"'; - # number of active connections should definitely not be negative - $self->assert_num_gte(0, $active->{$service_label}->{value}); + # number of active connections should definitely not be negative + $self->assert_num_gte(0, $active->{$service_label}->{value}); - # number of active connections should in fact be zero - $self->assert_num_equals(0, $active->{$service_label}->{value}); + # number of active connections should in fact be zero + $self->assert_num_equals(0, $active->{$service_label}->{value}); - # number of ready listeners should be zero or one, depending on - # whether it felt like preforking - $self->assert_num_gte(0, $ready->{$service_label}->{value}); - $self->assert_num_lte(1, $ready->{$service_label}->{value}); + # number of ready listeners should be zero or one, depending on + # whether it felt like preforking + $self->assert_num_gte(0, $ready->{$service_label}->{value}); + $self->assert_num_lte(1, $ready->{$service_label}->{value}); - # should not have had any successful connections to imaps - $self->assert(not exists $total->{$service_label}); + # should not have had any successful connections to imaps + $self->assert(not exists $total->{$service_label}); - # should be $badconn shutdowns counted (imapd treats this condition - # as an ok shutdown, not an error) - $self->assert_num_equals( - $badconns, - $shutdown->{"$service_label,status=\"ok\""}->{value} - ); + # should be $badconn shutdowns counted (imapd treats this condition + # as an ok shutdown, not an error) + $self->assert_num_equals($badconns, + $shutdown->{"$service_label,status=\"ok\""}->{value}); - # XXX someday: expect to find $badconns setup failures counted + # XXX someday: expect to find $badconns setup failures counted } 1; diff --git a/cassandane/Cassandane/Cyrus/QResync.pm b/cassandane/Cassandane/Cyrus/QResync.pm index b87465dfb1..c17c6f8fab 100644 --- a/cassandane/Cassandane/Cyrus/QResync.pm +++ b/cassandane/Cassandane/Cyrus/QResync.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::QResync; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use Data::Dumper; @@ -50,58 +50,53 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Util::NetString; - -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1, services => ['smmap', 'imap'] }, @_); +sub new { + my $class = shift; + return $class->SUPER::new( + { adminstore => 1, services => [ 'smmap', 'imap' ] }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_qresync_simple -{ - my ($self) = @_; +sub test_qresync_simple { + my ($self) = @_; - xlog $self, "Make some messages"; - my $uid = 1; - my %msgs; - for (1..50) - { - $msgs{$uid} = $self->make_message("Message $uid"); - $msgs{$uid}->set_attribute('uid', $uid); - $uid++; - } + xlog $self, "Make some messages"; + my $uid = 1; + my %msgs; + for (1 .. 50) { + $msgs{$uid} = $self->make_message("Message $uid"); + $msgs{$uid}->set_attribute('uid', $uid); + $uid++; + } - my $talk = $self->{store}->get_client(); - $talk->select("INBOX"); - my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $talk = $self->{store}->get_client(); + $talk->select("INBOX"); + my $uidvalidity = $talk->get_response_code('uidvalidity'); - xlog $self, "Mark some messages \\Deleted"; - $talk->enable("qresync"); - $talk->store('5:10,25:45', '+flags', '(\\Deleted)'); + xlog $self, "Mark some messages \\Deleted"; + $talk->enable("qresync"); + $talk->store('5:10,25:45', '+flags', '(\\Deleted)'); - xlog $self, "Expunge messages"; - $talk->expunge(); - my @vanished = $talk->get_response_code('vanished'); - $self->assert_equals("5:10,25:45", $vanished[0][0]); + xlog $self, "Expunge messages"; + $talk->expunge(); + my @vanished = $talk->get_response_code('vanished'); + $self->assert_equals("5:10,25:45", $vanished[0][0]); - xlog "QResync mailbox"; - $talk->unselect(); - $talk->select("INBOX", "(QRESYNC ($uidvalidity 0))" => 1); - @vanished = $talk->get_response_code('vanished'); - $self->assert_num_equals(23, $talk->get_response_code('exists')); - $self->assert_equals("5:10,25:45", $vanished[0][1]); + xlog "QResync mailbox"; + $talk->unselect(); + $talk->select("INBOX", "(QRESYNC ($uidvalidity 0))" => 1); + @vanished = $talk->get_response_code('vanished'); + $self->assert_num_equals(23, $talk->get_response_code('exists')); + $self->assert_equals("5:10,25:45", $vanished[0][1]); } 1; diff --git a/cassandane/Cassandane/Cyrus/Quota.pm b/cassandane/Cassandane/Cyrus/Quota.pm index ece52ea6de..87ee52eb6d 100644 --- a/cassandane/Cassandane/Cyrus/Quota.pm +++ b/cassandane/Cassandane/Cyrus/Quota.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::Quota; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use Data::Dumper; @@ -51,383 +51,386 @@ use Cassandane::Util::Log; use Cassandane::Util::NetString; use Cassandane::Util::Slurp; -sub res_mailbox { 'MAILBOX' } +sub res_mailbox { 'MAILBOX' } sub res_annot_storage { 'ANNOTATION-STORAGE' } -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1, services => ['smmap', 'imap'] }, @_); +sub new { + my $class = shift; + return $class->SUPER::new( + { adminstore => 1, services => [ 'smmap', 'imap' ] }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj < 3 || ($maj == 3 && $min < 9)) { - $self->res_mailbox = 'X-NUM-FOLDERS'; - $self->res_annot_storage = 'X-ANNOTATION-STORAGE'; - } + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj < 3 || ($maj == 3 && $min < 9)) { + $self->res_mailbox = 'X-NUM-FOLDERS'; + $self->res_annot_storage = 'X-ANNOTATION-STORAGE'; + } } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub _set_quotaroot -{ - my ($self, $quotaroot) = @_; - $self->{quotaroot} = $quotaroot; +sub _set_quotaroot { + my ($self, $quotaroot) = @_; + $self->{quotaroot} = $quotaroot; } # Utility function to set quota limits and check that it stuck -sub _set_limits -{ - my ($self, %resources) = @_; - my $admintalk = $self->{adminstore}->get_client(); - - my $quotaroot = delete $resources{quotaroot} || $self->{quotaroot}; - my @quotalist; - foreach my $resource (keys %resources) - { - my $limit = $resources{$resource} - or die "No limit specified for $resource"; - push(@quotalist, uc($resource), $limit); - } - $self->{limits}->{$quotaroot} = { @quotalist }; - $admintalk->setquota($quotaroot, \@quotalist); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); +sub _set_limits { + my ($self, %resources) = @_; + my $admintalk = $self->{adminstore}->get_client(); + + my $quotaroot = delete $resources{quotaroot} || $self->{quotaroot}; + my @quotalist; + foreach my $resource (keys %resources) { + my $limit = $resources{$resource} + or die "No limit specified for $resource"; + push(@quotalist, uc($resource), $limit); + } + $self->{limits}->{$quotaroot} = {@quotalist}; + $admintalk->setquota($quotaroot, \@quotalist); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); } # Utility function to check that quota's usages # and limits are where we expect it to be -sub _check_usages -{ - my ($self, %expecteds) = @_; - my $admintalk = $self->{adminstore}->get_client(); - - my $quotaroot = delete $expecteds{quotaroot} || $self->{quotaroot}; - my $limits = $self->{limits}->{$quotaroot}; - - my @result = $admintalk->getquota($quotaroot); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - # check actual and expected number of resources do match - $self->assert_num_equals(scalar(keys %$limits) * 3, scalar(@result)); - - # Convert the IMAP result to a conveniently checkable hash. - # By checkable, we mean that a failure in assert_deep_equals() - # will give a human some idea of what went wrong. - my %act; - while (scalar(@result)) { - my ($res, $used, $limit) = splice(@result, 0, 3); - $res = uc($res); - die "Resource $res appears twice in result" - if defined $act{$res}; - $act{$res} = { - used => $used, - limit => $limit, - }; - } - - # Build a conveniently checkable hash from %expecteds - # and limits previously by _set_limits(). - my %exp; - foreach my $res (keys %expecteds) - { - $exp{uc($res)} = { - used => $expecteds{$res}, - limit => $limits->{uc($res)}, - }; - } - - # Now actually compare - $self->assert_deep_equals(\%exp, \%act); +sub _check_usages { + my ($self, %expecteds) = @_; + my $admintalk = $self->{adminstore}->get_client(); + + my $quotaroot = delete $expecteds{quotaroot} || $self->{quotaroot}; + my $limits = $self->{limits}->{$quotaroot}; + + my @result = $admintalk->getquota($quotaroot); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # check actual and expected number of resources do match + $self->assert_num_equals(scalar(keys %$limits) * 3, scalar(@result)); + + # Convert the IMAP result to a conveniently checkable hash. + # By checkable, we mean that a failure in assert_deep_equals() + # will give a human some idea of what went wrong. + my %act; + while (scalar(@result)) { + my ($res, $used, $limit) = splice(@result, 0, 3); + $res = uc($res); + die "Resource $res appears twice in result" + if defined $act{$res}; + $act{$res} = { + used => $used, + limit => $limit, + }; + } + + # Build a conveniently checkable hash from %expecteds + # and limits previously by _set_limits(). + my %exp; + foreach my $res (keys %expecteds) { + $exp{ uc($res) } = { + used => $expecteds{$res}, + limit => $limits->{ uc($res) }, + }; + } + + # Now actually compare + $self->assert_deep_equals(\%exp, \%act); } # Reset the recorded usage in the database. Used for testing # quota -f. Rather hacky. Both _set_quotaroot() and _set_limits() # can be used to set default values. -sub _zap_quota -{ - my ($self, %params) = @_; - - my $quotaroot = $params{quotaroot} || $self->{quotaroot}; - my $limits = $params{limits} || $self->{limits}->{$quotaroot}; - my $useds = $params{useds} || {}; - $useds = { map { uc($_) => $useds->{$_} } keys %$useds }; - - # double check that some other part of Cassandane didn't - # accidentally futz with the expected quota db backend - my $backend = $self->{instance}->{config}->get('quota_db'); - $self->assert_str_equals('quotalegacy', $backend) - if defined $backend; # the default value is also ok - - my ($uc) = ($quotaroot =~ m/^user[\.\/](.)/); - my ($domain, $dirname); - ($quotaroot, $domain) = split '@', $quotaroot; - if ($domain) { - my ($dc) = ($domain =~ m/^(.)/); - $dirname = $self->{instance}->{basedir} . "/conf/domain/$dc/$domain"; - } - else { - $dirname = $self->{instance}->{basedir} . "/conf"; - } - $dirname .= "/quota/$uc"; - my $qfn = $quotaroot; - $qfn =~ s/\//\./g; - my $filename = "$dirname/$qfn"; - mkpath $dirname; - - open QUOTA,'>',$filename - or die "Failed to open $filename for writing: $!"; - - # STORAGE is special and always present, but -1 if unlimited - my $limit = $limits->{STORAGE} || -1; - my $used = $useds->{STORAGE} || 0; - print QUOTA "$used\n$limit"; - - # other resources have a leading keyword if present - my %keywords = ( MESSAGE => 'M', $self->res_annot_storage => 'AS' ); - foreach my $resource (keys %$limits) - { - my $kw = $keywords{$resource} or next; - $limit = $limits->{$resource}; - $used = $useds->{$resource} || 0; - print QUOTA " $kw $used $limit"; - } - - print QUOTA "\n"; - close QUOTA; - - $self->{instance}->_fix_ownership($self->{instance}{basedir} . "/conf/quota"); +sub _zap_quota { + my ($self, %params) = @_; + + my $quotaroot = $params{quotaroot} || $self->{quotaroot}; + my $limits = $params{limits} || $self->{limits}->{$quotaroot}; + my $useds = $params{useds} || {}; + $useds = { map { uc($_) => $useds->{$_} } keys %$useds }; + + # double check that some other part of Cassandane didn't + # accidentally futz with the expected quota db backend + my $backend = $self->{instance}->{config}->get('quota_db'); + $self->assert_str_equals('quotalegacy', $backend) + if defined $backend; # the default value is also ok + + my ($uc) = ($quotaroot =~ m/^user[\.\/](.)/); + my ($domain, $dirname); + ($quotaroot, $domain) = split '@', $quotaroot; + if ($domain) { + my ($dc) = ($domain =~ m/^(.)/); + $dirname = $self->{instance}->{basedir} . "/conf/domain/$dc/$domain"; + } else { + $dirname = $self->{instance}->{basedir} . "/conf"; + } + $dirname .= "/quota/$uc"; + my $qfn = $quotaroot; + $qfn =~ s/\//\./g; + my $filename = "$dirname/$qfn"; + mkpath $dirname; + + open QUOTA, '>', $filename + or die "Failed to open $filename for writing: $!"; + + # STORAGE is special and always present, but -1 if unlimited + my $limit = $limits->{STORAGE} || -1; + my $used = $useds->{STORAGE} || 0; + print QUOTA "$used\n$limit"; + + # other resources have a leading keyword if present + my %keywords = (MESSAGE => 'M', $self->res_annot_storage => 'AS'); + foreach my $resource (keys %$limits) { + my $kw = $keywords{$resource} or next; + $limit = $limits->{$resource}; + $used = $useds->{$resource} || 0; + print QUOTA " $kw $used $limit"; + } + + print QUOTA "\n"; + close QUOTA; + + $self->{instance}->_fix_ownership($self->{instance}{basedir} . "/conf/quota"); } # Utility function to check that there is no quota -sub _check_no_quota -{ - my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); +sub _check_no_quota { + my ($self) = @_; + my $admintalk = $self->{adminstore}->get_client(); - my @res = $admintalk->getquota($self->{quotaroot}); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + my @res = $admintalk->getquota($self->{quotaroot}); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); } -sub _check_smmap -{ - my ($self, $name, $expected) = @_; - my $service = $self->{instance}->get_service('smmap'); - my $sock = $service->get_socket(); +sub _check_smmap { + my ($self, $name, $expected) = @_; + my $service = $self->{instance}->get_service('smmap'); + my $sock = $service->get_socket(); - print_netstring($sock, "0 $name"); - my $res = get_netstring($sock); + print_netstring($sock, "0 $name"); + my $res = get_netstring($sock); - $self->assert($res =~ m/$expected/); + $self->assert($res =~ m/$expected/); } -sub bogus_test_upgrade_v2_4 -{ - my ($self) = @_; - - xlog $self, "test resources usage computing upon upgrading a cyrus v2.4 mailbox"; - - $self->_set_quotaroot('user.cassandane'); - my $talk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - xlog $self, "set ourselves a basic limit"; - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => 0); - - xlog $self, "store annotations"; - my $data = $self->make_random_data(10); - my $expected_annotation_storage = length($data); - $talk->setmetadata($self->{store}->{folder}, '/private/comment', { Quote => $data }); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->_check_usages($self->res_annot_storage => int($expected_annotation_storage/1024)); - - xlog $self, "restore cyrus v2.4 mailbox content and quota file"; - $self->{instance}->unpackfile(abs_path('data/cyrus/quota_upgrade_v2_4.user.tar.gz'), 'data/user'); - $self->{instance}->unpackfile(abs_path('data/cyrus/quota_upgrade_v2_4.quota.tar.gz'), 'conf/quota/c'); - - xlog $self, "upgrade to version 13 format (v2.5.0)"; - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', '-V' => 13); - - # count messages and size from restored mailbox - my $expected_storage = 0; - my $expected_message = 0; - $talk->select($self->{store}->{folder}); - my $responses = $talk->fetch('1:*', 'RFC822.SIZE'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($responses); - foreach my $response (values(%$responses)) { - $expected_message++; - $expected_storage += $response->{'rfc822.size'}; - } - $talk->close(); - - # check we did restore something - $self->assert_num_not_equals($expected_storage, 0); - $self->assert_num_not_equals($expected_message, 0); - - # set quota limits on resources which did not exist in previous cyrus versions; - # when the mailbox was upgraded, new resources quota usage shall have been - # computed automatically - $self->_set_limits( - storage => 100000, - message => 50000, - $self->res_annot_storage => 10000, - ); - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), - ); +sub bogus_test_upgrade_v2_4 { + my ($self) = @_; + + xlog $self, + "test resources usage computing upon upgrading a cyrus v2.4 mailbox"; + + $self->_set_quotaroot('user.cassandane'); + my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + xlog $self, "set ourselves a basic limit"; + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => 0); + + xlog $self, "store annotations"; + my $data = $self->make_random_data(10); + my $expected_annotation_storage = length($data); + $talk->setmetadata($self->{store}->{folder}, + '/private/comment', { Quote => $data }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->_check_usages( + $self->res_annot_storage => int($expected_annotation_storage / 1024)); + + xlog $self, "restore cyrus v2.4 mailbox content and quota file"; + $self->{instance} + ->unpackfile(abs_path('data/cyrus/quota_upgrade_v2_4.user.tar.gz'), + 'data/user'); + $self->{instance} + ->unpackfile(abs_path('data/cyrus/quota_upgrade_v2_4.quota.tar.gz'), + 'conf/quota/c'); + + xlog $self, "upgrade to version 13 format (v2.5.0)"; + $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', '-V' => 13); + + # count messages and size from restored mailbox + my $expected_storage = 0; + my $expected_message = 0; + $talk->select($self->{store}->{folder}); + my $responses = $talk->fetch('1:*', 'RFC822.SIZE'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($responses); + foreach my $response (values(%$responses)) { + $expected_message++; + $expected_storage += $response->{'rfc822.size'}; + } + $talk->close(); + + # check we did restore something + $self->assert_num_not_equals($expected_storage, 0); + $self->assert_num_not_equals($expected_message, 0); + + # set quota limits on resources which did not exist in previous cyrus versions; + # when the mailbox was upgraded, new resources quota usage shall have been + # computed automatically + $self->_set_limits( + storage => 100000, + message => 50000, + $self->res_annot_storage => 10000, + ); + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); } -sub XXtest_getset_multiple -{ - my ($self) = @_; - - xlog $self, "testing getting and setting multiple quota resources"; - - my $admintalk = $self->{adminstore}->get_client(); - my $folder = "user.cassandane"; - my @res; - - xlog $self, "checking there are no initial quotas"; - @res = $admintalk->getquota($folder); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert($admintalk->get_last_error() =~ m/Quota root does not exist/i); - - xlog $self, "set both X-ANNOT-COUNT and X-ANNOT-SIZE quotas"; - $admintalk->setquota($folder, "(x-annot-count 20 x-annot-size 16384)"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - xlog $self, "get both resources back, and not STORAGE"; - @res = $admintalk->getquota($folder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals(['X-ANNOT-COUNT', 0, 20, 'X-ANNOT-SIZE', 0, 16384], \@res); - - xlog $self, "set the X-ANNOT-SIZE resource only"; - $admintalk->setquota($folder, "(x-annot-size 32768)"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - xlog $self, "get new -SIZE only and neither STORAGE nor -COUNT"; - @res = $admintalk->getquota($folder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals(['X-ANNOT-SIZE', 0, 32768], \@res); - - xlog $self, "set all of -COUNT -SIZE and STORAGE"; - $admintalk->setquota($folder, "(x-annot-count 123 storage 123456 x-annot-size 65536)"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - xlog $self, "get back all three new values"; - @res = $admintalk->getquota($folder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals(['STORAGE', 0, 123456, 'X-ANNOT-COUNT', 0, 123, 'X-ANNOT-SIZE', 0, 65536], \@res); - - xlog $self, "clear all quotas"; - $admintalk->setquota($folder, "()"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - # Note: the RFC does not define what happens if you remove all the - # quotas from a quotaroot. Cyrus leaves the quotaroot around until - # quota -f is run to clean it up. - xlog $self, "get back an empty set of quotas, but the quota root still exists"; - @res = $admintalk->getquota($folder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); +sub XXtest_getset_multiple { + my ($self) = @_; + + xlog $self, "testing getting and setting multiple quota resources"; + + my $admintalk = $self->{adminstore}->get_client(); + my $folder = "user.cassandane"; + my @res; + + xlog $self, "checking there are no initial quotas"; + @res = $admintalk->getquota($folder); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert($admintalk->get_last_error() =~ m/Quota root does not exist/i); + + xlog $self, "set both X-ANNOT-COUNT and X-ANNOT-SIZE quotas"; + $admintalk->setquota($folder, "(x-annot-count 20 x-annot-size 16384)"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + xlog $self, "get both resources back, and not STORAGE"; + @res = $admintalk->getquota($folder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals( + [ 'X-ANNOT-COUNT', 0, 20, 'X-ANNOT-SIZE', 0, 16384 ], \@res); + + xlog $self, "set the X-ANNOT-SIZE resource only"; + $admintalk->setquota($folder, "(x-annot-size 32768)"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + xlog $self, "get new -SIZE only and neither STORAGE nor -COUNT"; + @res = $admintalk->getquota($folder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals([ 'X-ANNOT-SIZE', 0, 32768 ], \@res); + + xlog $self, "set all of -COUNT -SIZE and STORAGE"; + $admintalk->setquota($folder, + "(x-annot-count 123 storage 123456 x-annot-size 65536)"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + xlog $self, "get back all three new values"; + @res = $admintalk->getquota($folder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals( + [ 'STORAGE', 0, 123456, 'X-ANNOT-COUNT', 0, 123, 'X-ANNOT-SIZE', 0, 65536 ], + \@res + ); + + xlog $self, "clear all quotas"; + $admintalk->setquota($folder, "()"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # Note: the RFC does not define what happens if you remove all the + # quotas from a quotaroot. Cyrus leaves the quotaroot around until + # quota -f is run to clean it up. + xlog $self, + "get back an empty set of quotas, but the quota root still exists"; + @res = $admintalk->getquota($folder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); } # Magic: the word 'replication' in the name enables a replica sub XXtest_replication_multiple - :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing replication of multiple quotas"; - - my $mastertalk = $self->{master_adminstore}->get_client(); - my $replicatalk = $self->{replica_adminstore}->get_client(); - - my $folder = "user.cassandane"; - my @res; - - xlog $self, "checking there are no initial quotas"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('no', $mastertalk->get_last_completion_response()); - $self->assert($mastertalk->get_last_error() =~ m/Quota root does not exist/i); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('no', $replicatalk->get_last_completion_response()); - $self->assert($replicatalk->get_last_error() =~ m/Quota root does not exist/i); - - xlog $self, "set a X-ANNOT-COUNT and X-ANNOT-SIZE quotas on the master"; - $mastertalk->setquota($folder, "(x-annot-count 20 x-annot-size 16384)"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); - - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals(['X-ANNOT-COUNT', 0, 20, 'X-ANNOT-SIZE', 0, 16384], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals(['X-ANNOT-COUNT', 0, 20, 'X-ANNOT-SIZE', 0, 16384], \@res); - - xlog $self, "set the X-ANNOT-SIZE quota on the master"; - $mastertalk->setquota($folder, "(x-annot-size 32768)"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); - - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals(['X-ANNOT-SIZE', 0, 32768], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals(['X-ANNOT-SIZE', 0, 32768], \@res); - - xlog $self, "clear all the quotas"; - $mastertalk->setquota($folder, "()"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); - - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); + : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing replication of multiple quotas"; + + my $mastertalk = $self->{master_adminstore}->get_client(); + my $replicatalk = $self->{replica_adminstore}->get_client(); + + my $folder = "user.cassandane"; + my @res; + + xlog $self, "checking there are no initial quotas"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('no', $mastertalk->get_last_completion_response()); + $self->assert($mastertalk->get_last_error() =~ m/Quota root does not exist/i); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('no', $replicatalk->get_last_completion_response()); + $self->assert( + $replicatalk->get_last_error() =~ m/Quota root does not exist/i); + + xlog $self, "set a X-ANNOT-COUNT and X-ANNOT-SIZE quotas on the master"; + $mastertalk->setquota($folder, "(x-annot-count 20 x-annot-size 16384)"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); + + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals( + [ 'X-ANNOT-COUNT', 0, 20, 'X-ANNOT-SIZE', 0, 16384 ], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals( + [ 'X-ANNOT-COUNT', 0, 20, 'X-ANNOT-SIZE', 0, 16384 ], \@res); + + xlog $self, "set the X-ANNOT-SIZE quota on the master"; + $mastertalk->setquota($folder, "(x-annot-size 32768)"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); + + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([ 'X-ANNOT-SIZE', 0, 32768 ], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([ 'X-ANNOT-SIZE', 0, 32768 ], \@res); + + xlog $self, "clear all the quotas"; + $mastertalk->setquota($folder, "()"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); + + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); } -Cassandane::Cyrus::TestCase::magic(Bug3735 => sub { +Cassandane::Cyrus::TestCase::magic( + Bug3735 => sub { my ($testcase) = @_; - $testcase->config_set(quota_db => 'quotalegacy'); + $testcase->config_set(quota_db => 'quotalegacy'); $testcase->config_set(hashimapspool => 1); - $testcase->config_set(fulldirhash => 1); - $testcase->config_set(virtdomains => 0); -}); + $testcase->config_set(fulldirhash => 1); + $testcase->config_set(virtdomains => 0); + } +); use Cassandane::Tiny::Loader 'tiny-tests/Quota'; diff --git a/cassandane/Cassandane/Cyrus/Reconstruct.pm b/cassandane/Cassandane/Cyrus/Reconstruct.pm index d4232f7e4b..7edac3b551 100644 --- a/cassandane/Cassandane/Cyrus/Reconstruct.pm +++ b/cassandane/Cassandane/Cyrus/Reconstruct.pm @@ -55,165 +55,164 @@ use Cyrus::DList; use Cyrus::HeaderFile; use Cyrus::IndexFile; -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test zeroed out data across the UID # -sub test_reconstruct_zerouid -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - for (1..10) { - my $msg = $self->{gen}->generate(subject => "subject $_"); - $self->{store}->write_message($msg, flags => ["\\Seen", "\$NotJunk"]); - } - $self->{store}->write_end(); - $imaptalk->select("INBOX") || die; - - my @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); - - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); - - @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); - - # this needs a bit of magic to know where to write... so - # we do some hard-coded cyrus.index handling - my $dir = $self->{instance}->folder_to_directory('user.cassandane'); - my $file = "$dir/cyrus.index"; - my $fh = IO::File->new($file, "+<"); - die "NO SUCH FILE $file" unless $fh; - my $index = Cyrus::IndexFile->new($fh); - - my $offset = $index->header('StartOffset') + (5 * $index->header('RecordSize')); - warn "seeking to offset $offset"; - $fh->seek($offset, 0); - $fh->syswrite("\0\0\0\0\0\0\0\0", 8); - $fh->close(); - - # this time, the reconstruct will fix up the broken record and re-insert later - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); - - @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(not grep { $_ == 6 } @records); - $self->assert(grep { $_ == 11 } @records); +sub test_reconstruct_zerouid { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + for (1 .. 10) { + my $msg = $self->{gen}->generate(subject => "subject $_"); + $self->{store}->write_message($msg, flags => [ "\\Seen", "\$NotJunk" ]); + } + $self->{store}->write_end(); + $imaptalk->select("INBOX") || die; + + my @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); + + $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); + + @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); + + # this needs a bit of magic to know where to write... so + # we do some hard-coded cyrus.index handling + my $dir = $self->{instance}->folder_to_directory('user.cassandane'); + my $file = "$dir/cyrus.index"; + my $fh = IO::File->new($file, "+<"); + die "NO SUCH FILE $file" unless $fh; + my $index = Cyrus::IndexFile->new($fh); + + my $offset + = $index->header('StartOffset') + (5 * $index->header('RecordSize')); + warn "seeking to offset $offset"; + $fh->seek($offset, 0); + $fh->syswrite("\0\0\0\0\0\0\0\0", 8); + $fh->close(); + + # this time, the reconstruct will fix up the broken record and re-insert later + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); + + @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(not grep { $_ == 6 } @records); + $self->assert(grep { $_ == 11 } @records); } # # Test truncated file # -sub test_reconstruct_truncated -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - for (1..10) { - my $msg = $self->{gen}->generate(subject => "subject $_"); - $self->{store}->write_message($msg, flags => ["\\Seen", "\$NotJunk"]); - } - $self->{store}->write_end(); - $imaptalk->select("INBOX") || die; - - my @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); - - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); - - @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); - - # this needs a bit of magic to know where to write... so - # we do some hard-coded cyrus.index handling - my $dir = $self->{instance}->folder_to_directory('user.cassandane'); - my $file = "$dir/cyrus.index"; - my $fh = IO::File->new($file, "+<"); - die "NO SUCH FILE $file" unless $fh; - my $index = Cyrus::IndexFile->new($fh); - - my $offset = $index->header('StartOffset') + (5 * $index->header('RecordSize')); - $fh->truncate($offset); - $fh->close(); - - # this time, the reconstruct will create the records again - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); - - # XXX - this actually deletes everything, so we unselect and reselect. A - # too-short cyrus.index is a fatal error, so we don't even try to read it. - $imaptalk->unselect(); - $imaptalk->select("INBOX") || die; - - @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); - $self->assert(not grep { $_ == 11 } @records); - - # We should have generated a SYNCERROR or two - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: refreshing index/); +sub test_reconstruct_truncated { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + for (1 .. 10) { + my $msg = $self->{gen}->generate(subject => "subject $_"); + $self->{store}->write_message($msg, flags => [ "\\Seen", "\$NotJunk" ]); + } + $self->{store}->write_end(); + $imaptalk->select("INBOX") || die; + + my @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); + + $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); + + @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); + + # this needs a bit of magic to know where to write... so + # we do some hard-coded cyrus.index handling + my $dir = $self->{instance}->folder_to_directory('user.cassandane'); + my $file = "$dir/cyrus.index"; + my $fh = IO::File->new($file, "+<"); + die "NO SUCH FILE $file" unless $fh; + my $index = Cyrus::IndexFile->new($fh); + + my $offset + = $index->header('StartOffset') + (5 * $index->header('RecordSize')); + $fh->truncate($offset); + $fh->close(); + + # this time, the reconstruct will create the records again + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); + + # XXX - this actually deletes everything, so we unselect and reselect. A + # too-short cyrus.index is a fatal error, so we don't even try to read it. + $imaptalk->unselect(); + $imaptalk->select("INBOX") || die; + + @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); + $self->assert(not grep { $_ == 11 } @records); + + # We should have generated a SYNCERROR or two + $self->assert_syslog_matches($self->{instance}, + qr/IOERROR: refreshing index/); } # # Test removed file # -sub test_reconstruct_removedfile -{ - my ($self) = @_; +sub test_reconstruct_removedfile { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - for (1..10) { - my $msg = $self->{gen}->generate(subject => "subject $_"); - $self->{store}->write_message($msg, flags => ["\\Seen", "\$NotJunk"]); - } - $self->{store}->write_end(); - $imaptalk->select("INBOX") || die; + for (1 .. 10) { + my $msg = $self->{gen}->generate(subject => "subject $_"); + $self->{store}->write_message($msg, flags => [ "\\Seen", "\$NotJunk" ]); + } + $self->{store}->write_end(); + $imaptalk->select("INBOX") || die; - my @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); + my @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); + $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); - @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); + @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); - # this needs a bit of magic to know where to write... so - # we do some hard-coded cyrus.index handling - my $dir = $self->{instance}->folder_to_directory('user.cassandane'); - unlink("$dir/6."); + # this needs a bit of magic to know where to write... so + # we do some hard-coded cyrus.index handling + my $dir = $self->{instance}->folder_to_directory('user.cassandane'); + unlink("$dir/6."); - # this time, the reconstruct will fix up the broken record and re-insert later - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); + # this time, the reconstruct will fix up the broken record and re-insert later + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); - @records = $imaptalk->search("all"); - $self->assert_num_equals(9, scalar @records); - $self->assert(not grep { $_ == 6 } @records); + @records = $imaptalk->search("all"); + $self->assert_num_equals(9, scalar @records); + $self->assert(not grep { $_ == 6 } @records); } # @@ -221,406 +220,404 @@ sub test_reconstruct_removedfile # # XXX need to downgrade min version if this is backported to 3.2 sub test_reconstruct_snoozed - :min_version_3_3 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - for (1..10) { - my $msg = $self->{gen}->generate(subject => "subject $_"); - $self->{store}->write_message($msg, flags => ["\\Seen", "\$NotJunk"]); - } - $self->{store}->write_end(); - $imaptalk->select("INBOX") || die; - - my @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); - - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); - - @records = $imaptalk->search("all"); - $self->assert_num_equals(10, scalar @records); - $self->assert(grep { $_ == 6 } @records); - - $imaptalk->store('5', 'annotation', ["/vendor/cmu/cyrus-imapd/snoozed", - ['value.shared', { Quote => encode_json({until => '2020-01-01T00:00:00'}) }], - ]); - - # this needs a bit of magic to know where to write... so - # we do some hard-coded cyrus.index handling - my $dir = $self->{instance}->folder_to_directory('user.cassandane'); - my $file = "$dir/cyrus.index"; - my $fh = IO::File->new($file, "+<"); - die "NO SUCH FILE $file" unless $fh; - my $index = Cyrus::IndexFile->new($fh); - - while (my $record = $index->next_record_hash()) { - if ($record->{Uid} == 5) { - $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '1'); - } - else { - $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '0'); - } + : min_version_3_3 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + for (1 .. 10) { + my $msg = $self->{gen}->generate(subject => "subject $_"); + $self->{store}->write_message($msg, flags => [ "\\Seen", "\$NotJunk" ]); + } + $self->{store}->write_end(); + $imaptalk->select("INBOX") || die; + + my @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); + + $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct'); + + @records = $imaptalk->search("all"); + $self->assert_num_equals(10, scalar @records); + $self->assert(grep { $_ == 6 } @records); + + $imaptalk->store( + '5', + 'annotation', + [ + "/vendor/cmu/cyrus-imapd/snoozed", + [ + 'value.shared', + { Quote => encode_json({ until => '2020-01-01T00:00:00' }) } + ], + ] + ); + + # this needs a bit of magic to know where to write... so + # we do some hard-coded cyrus.index handling + my $dir = $self->{instance}->folder_to_directory('user.cassandane'); + my $file = "$dir/cyrus.index"; + my $fh = IO::File->new($file, "+<"); + die "NO SUCH FILE $file" unless $fh; + my $index = Cyrus::IndexFile->new($fh); + + while (my $record = $index->next_record_hash()) { + if ($record->{Uid} == 5) { + $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '1'); + } else { + $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '0'); } - close($fh); - - # the reconstruct shouldn't change anything - $self->{instance}->getsyslog(); - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); - $self->assert_syslog_does_not_match($self->{instance}, qr/mismatch/); - - xlog $self, "update some \\Snoozed flags"; - $fh = IO::File->new($file, "+<"); - die "NO SUCH FILE $file" unless $fh; - $index = Cyrus::IndexFile->new($fh); - - while (my $record = $index->next_record_hash()) { - if ($record->{Uid} == 5) { - # nuke the Snoozed flag - $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '1'); - substr($record->{SystemFlags}, 5, 1) = '0'; - $index->rewrite_record($record); - } - elsif ($record->{Uid} == 6) { - # add the Snoozed flag - $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '0'); - substr($record->{SystemFlags}, 5, 1) = '1'; - $index->rewrite_record($record); - } - else { - $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '0'); - } + } + close($fh); + + # the reconstruct shouldn't change anything + $self->{instance}->getsyslog(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); + $self->assert_syslog_does_not_match($self->{instance}, qr/mismatch/); + + xlog $self, "update some \\Snoozed flags"; + $fh = IO::File->new($file, "+<"); + die "NO SUCH FILE $file" unless $fh; + $index = Cyrus::IndexFile->new($fh); + + while (my $record = $index->next_record_hash()) { + if ($record->{Uid} == 5) { + # nuke the Snoozed flag + $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '1'); + substr($record->{SystemFlags}, 5, 1) = '0'; + $index->rewrite_record($record); + } elsif ($record->{Uid} == 6) { + # add the Snoozed flag + $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '0'); + substr($record->{SystemFlags}, 5, 1) = '1'; + $index->rewrite_record($record); + } else { + $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '0'); } - close($fh); - - # this reconstruct should change things back! - $self->{instance}->getsyslog(); - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); - my @lines = $self->{instance}->getsyslog(); - $self->assert_matches(qr/uid 5 snoozed mismatch/, "@lines"); - $self->assert_matches(qr/uid 6 snoozed mismatch/, "@lines"); - - xlog $self, "check that the values are changed back"; - $fh = IO::File->new($file, "+<"); - die "NO SUCH FILE $file" unless $fh; - $index = Cyrus::IndexFile->new($fh); - - while (my $record = $index->next_record_hash()) { - if ($record->{Uid} == 5) { - $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '1'); - } - else { - $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '0'); - } + } + close($fh); + + # this reconstruct should change things back! + $self->{instance}->getsyslog(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); + my @lines = $self->{instance}->getsyslog(); + $self->assert_matches(qr/uid 5 snoozed mismatch/, "@lines"); + $self->assert_matches(qr/uid 6 snoozed mismatch/, "@lines"); + + xlog $self, "check that the values are changed back"; + $fh = IO::File->new($file, "+<"); + die "NO SUCH FILE $file" unless $fh; + $index = Cyrus::IndexFile->new($fh); + + while (my $record = $index->next_record_hash()) { + if ($record->{Uid} == 5) { + $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '1'); + } else { + $self->assert_str_equals(substr($record->{SystemFlags}, 5, 1), '0'); } - close($fh); + } + close($fh); } sub test_reconstruct_uniqueid_from_header_path_legacymb - :min_version_3_7 :MailboxLegacyDirs :NoStartInstances -{ - my ($self) = @_; - my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - - # first start will set up cassandane user - $self->_start_instances(); - my $basedir = $self->{instance}->get_basedir(); - my $mailboxes_db = "$basedir/conf/mailboxes.db"; - $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); - - # find out the uniqueid of the inbox - my $imaptalk = $self->{store}->get_client(); - my $res = $imaptalk->getmetadata("INBOX", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $uniqueid = $res->{INBOX}{$entry}; - $self->assert_not_null($uniqueid); - $imaptalk->logout(); - undef $imaptalk; - - # stop service while tinkering - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; - - # get header path - my $cyrus_header = $self->{instance}->folder_to_directory('INBOX') - . '/cyrus.header'; - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - - # lose uniqueid from cyrus.header - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - copy($cyrus_header, "$cyrus_header.OLD"); - my $hf = Cyrus::HeaderFile->new_file("$cyrus_header.OLD"); - my $dlist = Cyrus::DList->parse_string($hf->{dlistheader}); - my $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - $hash->{I} = undef; - $dlist = Cyrus::DList->new_perl('', $hash); - my $out = IO::File->new($cyrus_header, 'w'); - $hf->write_newheader($out, $dlist->as_string()); - - # reconstruct -P should find and fix the missing uniqueid - $self->{instance}->getsyslog(); - $self->{instance}->run_command({ cyrus => 1 }, - 'reconstruct', '-P', $cyrus_header); - - # should not have existed in cyrus.header, get from mbentry - $self->assert_syslog_matches( - $self->{instance}, - qr{mailbox header had no uniqueid, setting from mbentry} - ); - - # bring service back up - $self->{instance}->start(); - - # header should have the same uniqueid as before - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - $hf = Cyrus::HeaderFile->new_file($cyrus_header); - $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); + : min_version_3_7 : MailboxLegacyDirs : NoStartInstances { + my ($self) = @_; + my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + + # first start will set up cassandane user + $self->_start_instances(); + my $basedir = $self->{instance}->get_basedir(); + my $mailboxes_db = "$basedir/conf/mailboxes.db"; + $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); + + # find out the uniqueid of the inbox + my $imaptalk = $self->{store}->get_client(); + my $res = $imaptalk->getmetadata("INBOX", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $uniqueid = $res->{INBOX}{$entry}; + $self->assert_not_null($uniqueid); + $imaptalk->logout(); + undef $imaptalk; + + # stop service while tinkering + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; + + # get header path + my $cyrus_header + = $self->{instance}->folder_to_directory('INBOX') . '/cyrus.header'; + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + + # lose uniqueid from cyrus.header + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + copy($cyrus_header, "$cyrus_header.OLD"); + my $hf = Cyrus::HeaderFile->new_file("$cyrus_header.OLD"); + my $dlist = Cyrus::DList->parse_string($hf->{dlistheader}); + my $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + $hash->{I} = undef; + $dlist = Cyrus::DList->new_perl('', $hash); + my $out = IO::File->new($cyrus_header, 'w'); + $hf->write_newheader($out, $dlist->as_string()); + + # reconstruct -P should find and fix the missing uniqueid + $self->{instance}->getsyslog(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', '-P', $cyrus_header); + + # should not have existed in cyrus.header, get from mbentry + $self->assert_syslog_matches($self->{instance}, + qr{mailbox header had no uniqueid, setting from mbentry}); + + # bring service back up + $self->{instance}->start(); + + # header should have the same uniqueid as before + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + $hf = Cyrus::HeaderFile->new_file($cyrus_header); + $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); } sub test_reconstruct_uniqueid_from_header_path_uuidmb - :min_version_3_7 :NoStartInstances -{ - my ($self) = @_; - my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - - # first start will set up cassandane user - $self->_start_instances(); - my $basedir = $self->{instance}->get_basedir(); - my $mailboxes_db = "$basedir/conf/mailboxes.db"; - $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); - - # find out the uniqueid of the inbox - my $imaptalk = $self->{store}->get_client(); - my $res = $imaptalk->getmetadata("INBOX", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $uniqueid = $res->{INBOX}{$entry}; - $self->assert_not_null($uniqueid); - $imaptalk->logout(); - undef $imaptalk; - - # stop service while tinkering - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; - - # get header path - my $cyrus_header = $self->{instance}->folder_to_directory('INBOX') - . '/cyrus.header'; - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - - # lose that uniqueid from mailboxes.db - my $I = "I$uniqueid"; - my $N = "Nuser\x1fcassandane"; - $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", - [ 'DELETE', $I ]); - my (undef, $mbentry) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $N]); - my $dlist = Cyrus::DList->parse_string($mbentry); - my $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - $hash->{I} = undef; - $dlist = Cyrus::DList->new_perl('', $hash); - $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - [ 'SET', $N, $dlist->as_string() ]); - - my %updated = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", ['SHOW']); - xlog "updated mailboxes.db: " . Dumper \%updated; - - # lose it from cyrus.header too - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - copy($cyrus_header, "$cyrus_header.OLD"); - my $hf = Cyrus::HeaderFile->new_file("$cyrus_header.OLD"); - $dlist = Cyrus::DList->parse_string($hf->{dlistheader}); - $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - $hash->{I} = undef; - $dlist = Cyrus::DList->new_perl('', $hash); - my $out = IO::File->new($cyrus_header, 'w'); - $hf->write_newheader($out, $dlist->as_string()); - - # reconstruct -P should find and fix the missing uniqueid - $self->{instance}->getsyslog(); - $self->{instance}->run_command({ cyrus => 1 }, - 'reconstruct', '-P', $cyrus_header); - - # should not have existed in cyrus.header, get from path - $self->assert_syslog_matches( - $self->{instance}, - qr{mailbox header had no uniqueid, setting from path} - ); - - # bring service back up - $self->{instance}->start(); - - # header should have the same uniqueid as before - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - $hf = Cyrus::HeaderFile->new_file($cyrus_header); - $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); - - # mbentry should have the same uniqueid as before - (undef, $mbentry) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $N]); - $dlist = Cyrus::DList->parse_string($mbentry); - $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - - # $I entry should be back - my ($key, $value) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $I]); - $self->assert_str_equals($I, $key); - $dlist = Cyrus::DList->parse_string($value); - $hash = $dlist->as_perl(); - $self->assert_str_equals("user\x1fcassandane", $hash->{N}); + : min_version_3_7 : NoStartInstances { + my ($self) = @_; + my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + + # first start will set up cassandane user + $self->_start_instances(); + my $basedir = $self->{instance}->get_basedir(); + my $mailboxes_db = "$basedir/conf/mailboxes.db"; + $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); + + # find out the uniqueid of the inbox + my $imaptalk = $self->{store}->get_client(); + my $res = $imaptalk->getmetadata("INBOX", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $uniqueid = $res->{INBOX}{$entry}; + $self->assert_not_null($uniqueid); + $imaptalk->logout(); + undef $imaptalk; + + # stop service while tinkering + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; + + # get header path + my $cyrus_header + = $self->{instance}->folder_to_directory('INBOX') . '/cyrus.header'; + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + + # lose that uniqueid from mailboxes.db + my $I = "I$uniqueid"; + my $N = "Nuser\x1fcassandane"; + $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", [ 'DELETE', $I ]); + my (undef, $mbentry) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $N ]); + my $dlist = Cyrus::DList->parse_string($mbentry); + my $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + $hash->{I} = undef; + $dlist = Cyrus::DList->new_perl('', $hash); + $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", + [ 'SET', $N, $dlist->as_string() ]); + + my %updated + = $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", ['SHOW']); + xlog "updated mailboxes.db: " . Dumper \%updated; + + # lose it from cyrus.header too + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + copy($cyrus_header, "$cyrus_header.OLD"); + my $hf = Cyrus::HeaderFile->new_file("$cyrus_header.OLD"); + $dlist = Cyrus::DList->parse_string($hf->{dlistheader}); + $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + $hash->{I} = undef; + $dlist = Cyrus::DList->new_perl('', $hash); + my $out = IO::File->new($cyrus_header, 'w'); + $hf->write_newheader($out, $dlist->as_string()); + + # reconstruct -P should find and fix the missing uniqueid + $self->{instance}->getsyslog(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', '-P', $cyrus_header); + + # should not have existed in cyrus.header, get from path + $self->assert_syslog_matches($self->{instance}, + qr{mailbox header had no uniqueid, setting from path}); + + # bring service back up + $self->{instance}->start(); + + # header should have the same uniqueid as before + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + $hf = Cyrus::HeaderFile->new_file($cyrus_header); + $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); + + # mbentry should have the same uniqueid as before + (undef, $mbentry) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $N ]); + $dlist = Cyrus::DList->parse_string($mbentry); + $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + + # $I entry should be back + my ($key, $value) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $I ]); + $self->assert_str_equals($I, $key); + $dlist = Cyrus::DList->parse_string($value); + $hash = $dlist->as_perl(); + $self->assert_str_equals("user\x1fcassandane", $hash->{N}); } sub test_reconstruct_uniqueid_from_header_uuidmb - :min_version_3_7 :NoStartInstances -{ - my ($self) = @_; - my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - - # first start will set up cassandane user - $self->_start_instances(); - my $basedir = $self->{instance}->get_basedir(); - my $mailboxes_db = "$basedir/conf/mailboxes.db"; - $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); - - # find out the uniqueid of the inbox - my $imaptalk = $self->{store}->get_client(); - my $res = $imaptalk->getmetadata("INBOX", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $uniqueid = $res->{INBOX}{$entry}; - $self->assert_not_null($uniqueid); - $imaptalk->logout(); - undef $imaptalk; - - # stop service while tinkering - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; - - # get header path - my $cyrus_header = $self->{instance}->folder_to_directory('INBOX') - . '/cyrus.header'; - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - - # lose that uniqueid from mailboxes.db - my $I = "I$uniqueid"; - my $N = "Nuser\x1fcassandane"; - $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", - [ 'DELETE', $I ]); - my (undef, $mbentry) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $N]); - my $dlist = Cyrus::DList->parse_string($mbentry); - my $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - $hash->{I} = undef; - $dlist = Cyrus::DList->new_perl('', $hash); - $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - [ 'SET', $N, $dlist->as_string() ]); - - my %updated = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", ['SHOW']); - xlog "updated mailboxes.db: " . Dumper \%updated; - - # reconstruct -P should find and fix the missing uniqueid - $self->{instance}->getsyslog(); - $self->{instance}->run_command({ cyrus => 1 }, - 'reconstruct', '-P', $cyrus_header); - my $syslog = join(q{}, $self->{instance}->getsyslog()); - - # should have still existed in cyrus.header - $self->assert_does_not_match(qr{mailbox header had no uniqueid}, $syslog); - - # expect to find the log line - $self->assert_matches(qr{setting mbentry uniqueid from header}, $syslog); - - # bring service back up - $self->{instance}->start(); - - # header should have the same uniqueid as before - $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); - my $hf = Cyrus::HeaderFile->new_file($cyrus_header); - $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); - - # mbentry should have the same uniqueid as before - (undef, $mbentry) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $N]); - $dlist = Cyrus::DList->parse_string($mbentry); - $hash = $dlist->as_perl(); - $self->assert_str_equals($uniqueid, $hash->{I}); - - # $I entry should be back - my ($key, $value) = $self->{instance}->run_dbcommand( - $mailboxes_db, "twoskip", - ['SHOW', $I]); - $self->assert_str_equals($I, $key); - $dlist = Cyrus::DList->parse_string($value); - $hash = $dlist->as_perl(); - $self->assert_str_equals("user\x1fcassandane", $hash->{N}); + : min_version_3_7 : NoStartInstances { + my ($self) = @_; + my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + + # first start will set up cassandane user + $self->_start_instances(); + my $basedir = $self->{instance}->get_basedir(); + my $mailboxes_db = "$basedir/conf/mailboxes.db"; + $self->assert(-f $mailboxes_db, "$mailboxes_db not present"); + + # find out the uniqueid of the inbox + my $imaptalk = $self->{store}->get_client(); + my $res = $imaptalk->getmetadata("INBOX", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $uniqueid = $res->{INBOX}{$entry}; + $self->assert_not_null($uniqueid); + $imaptalk->logout(); + undef $imaptalk; + + # stop service while tinkering + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; + + # get header path + my $cyrus_header + = $self->{instance}->folder_to_directory('INBOX') . '/cyrus.header'; + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + + # lose that uniqueid from mailboxes.db + my $I = "I$uniqueid"; + my $N = "Nuser\x1fcassandane"; + $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", [ 'DELETE', $I ]); + my (undef, $mbentry) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $N ]); + my $dlist = Cyrus::DList->parse_string($mbentry); + my $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + $hash->{I} = undef; + $dlist = Cyrus::DList->new_perl('', $hash); + $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", + [ 'SET', $N, $dlist->as_string() ]); + + my %updated + = $self->{instance}->run_dbcommand($mailboxes_db, "twoskip", ['SHOW']); + xlog "updated mailboxes.db: " . Dumper \%updated; + + # reconstruct -P should find and fix the missing uniqueid + $self->{instance}->getsyslog(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', '-P', $cyrus_header); + my $syslog = join(q{}, $self->{instance}->getsyslog()); + + # should have still existed in cyrus.header + $self->assert_does_not_match(qr{mailbox header had no uniqueid}, $syslog); + + # expect to find the log line + $self->assert_matches(qr{setting mbentry uniqueid from header}, $syslog); + + # bring service back up + $self->{instance}->start(); + + # header should have the same uniqueid as before + $self->assert(-f $cyrus_header, "couldn't find cyrus.header file"); + my $hf = Cyrus::HeaderFile->new_file($cyrus_header); + $self->assert_str_equals($uniqueid, $hf->{header}->{UniqueId}); + + # mbentry should have the same uniqueid as before + (undef, $mbentry) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $N ]); + $dlist = Cyrus::DList->parse_string($mbentry); + $hash = $dlist->as_perl(); + $self->assert_str_equals($uniqueid, $hash->{I}); + + # $I entry should be back + my ($key, $value) + = $self->{instance} + ->run_dbcommand($mailboxes_db, "twoskip", [ 'SHOW', $I ]); + $self->assert_str_equals($I, $key); + $dlist = Cyrus::DList->parse_string($value); + $hash = $dlist->as_perl(); + $self->assert_str_equals("user\x1fcassandane", $hash->{N}); } -sub test_downgrade_upgrade -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); +sub test_downgrade_upgrade { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Add two messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{A}->set_attributes( + id => 1, + uid => 1, + flags => [] + ); + $msg{B} = $self->make_message('Message B'); + $msg{B}->set_attributes( + id => 2, + uid => 2, + flags => [] + ); + $self->check_messages(\%msg); + + xlog $self, "Set \\Seen on message A"; + my $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + xlog $self, "Clear \\Seen on message A"; + $res = $talk->store('1', '-flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => [] } }, $res); + $msg{A}->set_attribute(flags => []); + $self->check_messages(\%msg); + + xlog $self, "Set \\Seen on message A again"; + $res = $talk->store('1', '+flags', '(\\Seen)'); + $self->assert_deep_equals({ '1' => { 'flags' => ['\\Seen'] } }, $res); + $msg{A}->set_attribute(flags => ['\\Seen']); + $self->check_messages(\%msg); + + for my $version (12, 14, 16, 'max') { + xlog $self, "Set to version $version"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', '-V', $version); + + xlog $self, "Reconnect, \\Seen should still be on message A"; + $self->{store}->disconnect(); + $self->{store}->connect(); $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Add two messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{A}->set_attributes(id => 1, - uid => 1, - flags => []); - $msg{B} = $self->make_message('Message B'); - $msg{B}->set_attributes(id => 2, - uid => 2, - flags => []); - $self->check_messages(\%msg); - - xlog $self, "Set \\Seen on message A"; - my $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - xlog $self, "Clear \\Seen on message A"; - $res = $talk->store('1', '-flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [] }}, $res); - $msg{A}->set_attribute(flags => []); $self->check_messages(\%msg); - - xlog $self, "Set \\Seen on message A again"; - $res = $talk->store('1', '+flags', '(\\Seen)'); - $self->assert_deep_equals({ '1' => { 'flags' => [ '\\Seen' ] }}, $res); - $msg{A}->set_attribute(flags => ['\\Seen']); - $self->check_messages(\%msg); - - for my $version (12, 14, 16, 'max') { - xlog $self, "Set to version $version"; - $self->{instance}->run_command({ cyrus => 1 }, 'reconstruct', '-V', $version); - - xlog $self, "Reconnect, \\Seen should still be on message A"; - $self->{store}->disconnect(); - $self->{store}->connect(); - $self->{store}->_select(); - $self->check_messages(\%msg); - } + } } 1; diff --git a/cassandane/Cassandane/Cyrus/RelocateById.pm b/cassandane/Cassandane/Cyrus/RelocateById.pm index 711462d1c5..09136582a9 100644 --- a/cassandane/Cassandane/Cyrus/RelocateById.pm +++ b/cassandane/Cassandane/Cyrus/RelocateById.pm @@ -47,244 +47,244 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Util::Slurp; -sub new -{ - my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); - $config->set( - conversations => 'yes', - mailbox_legacy_dirs => 'yes', - delete_mode => 'delayed', - unixhierarchysep => 'no', # XXX need a :NoUnixHierarchySep - ); - return $class->SUPER::new({ - config => $config, - adminstore => 1, - }, @args); +sub new { + my ($class, @args) = @_; + my $config = Cassandane::Config->default()->clone(); + $config->set( + conversations => 'yes', + mailbox_legacy_dirs => 'yes', + delete_mode => 'delayed', + unixhierarchysep => 'no', # XXX need a :NoUnixHierarchySep + ); + return $class->SUPER::new( + { + config => $config, + adminstore => 1, + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_user_nomatch - :min_version_3_6 -{ - my ($self) = @_; + : min_version_3_6 { + my ($self) = @_; - my $errfile = $self->{instance}->get_basedir() . '/relocate.err'; + my $errfile = $self->{instance}->get_basedir() . '/relocate.err'; - my $user = 'nonexistent'; + my $user = 'nonexistent'; - $self->{instance}->run_command({ - cyrus => 1, - redirects => { - stderr => $errfile, - }, - }, 'relocate_by_id', '-u', $user); + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { + stderr => $errfile, + }, + }, + 'relocate_by_id', + '-u', $user + ); - my $output = slurp_file($errfile); + my $output = slurp_file($errfile); - # better complain if the user requested doesn't exist! - $self->assert_matches(qr{$user: user not found}, $output); + # better complain if the user requested doesn't exist! + $self->assert_matches(qr{$user: user not found}, $output); } sub test_mailbox_nomatch - :min_version_3_6 -{ - my ($self) = @_; + : min_version_3_6 { + my ($self) = @_; - my $errfile = $self->{instance}->get_basedir() . '/relocate.err'; + my $errfile = $self->{instance}->get_basedir() . '/relocate.err'; - my $mailbox = 'user.nonexistent'; + my $mailbox = 'user.nonexistent'; - $self->{instance}->run_command({ - cyrus => 1, - redirects => { - stderr => $errfile, - }, - }, 'relocate_by_id', $mailbox); + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { + stderr => $errfile, + }, + }, + 'relocate_by_id', + $mailbox + ); - my $output = slurp_file($errfile); + my $output = slurp_file($errfile); - # better complain if the mailbox requested doesn't exist! - $self->assert_matches(qr{$mailbox: mailbox not found}, $output); + # better complain if the mailbox requested doesn't exist! + $self->assert_matches(qr{$mailbox: mailbox not found}, $output); } sub test_mailbox_inbox_domain - :min_version_3_6 :NoAltNamespace :VirtDomains -{ - my ($self) = @_; + : min_version_3_6 : NoAltNamespace : VirtDomains { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser\@example.com"; - my $subfolder = "user.magicuser.foo\@example.com"; + my $inbox = "user.magicuser\@example.com"; + my $subfolder = "user.magicuser.foo\@example.com"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, - 'relocate_by_id', "user.magicuser\@example.com" ); + $self->{instance}->run_command({ cyrus => 1 }, + 'relocate_by_id', "user.magicuser\@example.com"); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); } sub test_mailbox_inbox_nodomain - :min_version_3_6 -{ - my ($self) = @_; + : min_version_3_6 { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser"; - my $subfolder = "user.magicuser.foo"; + my $inbox = "user.magicuser"; + my $subfolder = "user.magicuser.foo"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, - 'relocate_by_id', "user.magicuser" ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'relocate_by_id', "user.magicuser"); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); } sub test_mailbox_shared_domain - :min_version_3_6 :NoAltNamespace :VirtDomains -{ - my ($self) = @_; + : min_version_3_6 : NoAltNamespace : VirtDomains { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $mbox = "shared.magic\@example.com"; - my $subfolder = "shared.magic.foo\@example.com"; + my $mbox = "shared.magic\@example.com"; + my $subfolder = "shared.magic.foo\@example.com"; - $admintalk->create($mbox); - $admintalk->setacl($mbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($mbox); + $admintalk->setacl($mbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magic/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magic/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, - 'relocate_by_id', $mbox ); + $self->{instance}->run_command({ cyrus => 1 }, 'relocate_by_id', $mbox); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magic/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magic/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this hierarchy"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this hierarchy"; + $self->assert_equals(0, scalar @files); } sub test_mailbox_shared_nodomain - :min_version_3_6 -{ - my ($self) = @_; + : min_version_3_6 { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $mbox = "shared.magic"; - my $subfolder = "shared.magic.foo"; + my $mbox = "shared.magic"; + my $subfolder = "shared.magic.foo"; - $admintalk->create($mbox); - $admintalk->setacl($mbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($mbox); + $admintalk->setacl($mbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magic/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magic/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, - 'relocate_by_id', $mbox ); + $self->{instance}->run_command({ cyrus => 1 }, 'relocate_by_id', $mbox); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magic/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magic/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); } 1; diff --git a/cassandane/Cassandane/Cyrus/Rename.pm b/cassandane/Cassandane/Cyrus/Rename.pm index 0b9132b884..8aa228a626 100644 --- a/cassandane/Cassandane/Cyrus/Rename.pm +++ b/cassandane/Cassandane/Cyrus/Rename.pm @@ -47,955 +47,935 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -Cassandane::Cyrus::TestCase::magic(MetaPartition => sub { +Cassandane::Cyrus::TestCase::magic( + MetaPartition => sub { shift->config_set( - 'metapartition-default' => '@basedir@/meta', - 'metapartition_files' => 'header index' + 'metapartition-default' => '@basedir@/meta', + 'metapartition_files' => 'header index' ); -}); + } +); - -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test LSUB behaviour # -sub test_rename_asuser -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.user-src") || die; - $self->{store}->set_folder("INBOX.user-src"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); - $imaptalk->select("INBOX.user-src") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); - - $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; - $imaptalk->select("INBOX.user-dst") || die; - my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(1, scalar @postdata); +sub test_rename_asuser { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.user-src") || die; + $self->{store}->set_folder("INBOX.user-src"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); + $imaptalk->select("INBOX.user-src") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); + + $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; + $imaptalk->select("INBOX.user-dst") || die; + my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(1, scalar @postdata); } sub test_xrename - :min_version_3_8_2 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.src") || die; - $self->{store}->set_folder("INBOX.src"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); - $imaptalk->select("INBOX.src") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); - - $imaptalk->_imap_cmd('XRENAME', 0, '', "INBOX.src", "INBOX.dst"); - $imaptalk->select("INBOX.dst") || die; - my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(1, scalar @postdata); + : min_version_3_8_2 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.src") || die; + $self->{store}->set_folder("INBOX.src"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); + $imaptalk->select("INBOX.src") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); + + $imaptalk->_imap_cmd('XRENAME', 0, '', "INBOX.src", "INBOX.dst"); + $imaptalk->select("INBOX.dst") || die; + my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(1, scalar @postdata); } # # Test Bug #3586 - rename subfolders # -sub test_rename_subfolder -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.user-src.subdir") || die; - $self->{store}->set_folder("INBOX.user-src.subdir"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); - $imaptalk->select("INBOX.user-src.subdir") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); - - $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; - $imaptalk->select("INBOX.user-dst.subdir") || die; - my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(1, scalar @postdata); +sub test_rename_subfolder { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.user-src.subdir") || die; + $self->{store}->set_folder("INBOX.user-src.subdir"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); + $imaptalk->select("INBOX.user-src.subdir") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); + + $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; + $imaptalk->select("INBOX.user-dst.subdir") || die; + my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(1, scalar @postdata); } # # Test Deep rename (intermediates) # -sub test_rename_deep_subfolder -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.user-src.a.b.c.subdir") || die; - $self->{store}->set_folder("INBOX.user-src.a.b.c.subdir"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); - $imaptalk->select("INBOX.user-src.a.b.c.subdir") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); - - $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; - $imaptalk->select("INBOX.user-dst.a.b.c.subdir") || die; - my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(1, scalar @postdata); +sub test_rename_deep_subfolder { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.user-src.a.b.c.subdir") || die; + $self->{store}->set_folder("INBOX.user-src.a.b.c.subdir"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); + $imaptalk->select("INBOX.user-src.a.b.c.subdir") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); + + $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; + $imaptalk->select("INBOX.user-dst.a.b.c.subdir") || die; + my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(1, scalar @postdata); } # # Test Deep rename inside a user (intermediates) # -sub test_rename_user_deep_subfolder -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.user-src.a.b.c.subdir") || die; - $self->{store}->set_folder("INBOX.user-src.a.b.c.subdir"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); - $imaptalk->select("INBOX.user-src.a.b.c.subdir") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); - - $imaptalk->rename("INBOX.user-src.a", "INBOX.user-src.z") || die; - $imaptalk->select("INBOX.user-src.z.b.c.subdir") || die; - my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(1, scalar @postdata); +sub test_rename_user_deep_subfolder { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.user-src.a.b.c.subdir") || die; + $self->{store}->set_folder("INBOX.user-src.a.b.c.subdir"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); + $imaptalk->select("INBOX.user-src.a.b.c.subdir") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); + + $imaptalk->rename("INBOX.user-src.a", "INBOX.user-src.z") || die; + $imaptalk->select("INBOX.user-src.z.b.c.subdir") || die; + my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(1, scalar @postdata); } # # Test big conversation rename # sub test_rename_user_bigconversation - :AllowMoves :Conversations :min_version_3_0 -{ - my ($self) = @_; + : AllowMoves : Conversations : min_version_3_0 { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Test user rename with a big conversation"; + xlog $self, "Test user rename with a big conversation"; - my %exp; + my %exp; - $admintalk->create("user.cassandane.foo") || die; - $admintalk->create("user.cassandane.bar") || die; - $admintalk->create("user.cassandane.foo.sub") || die; + $admintalk->create("user.cassandane.foo") || die; + $admintalk->create("user.cassandane.bar") || die; + $admintalk->create("user.cassandane.foo.sub") || die; - $self->{store}->set_folder("INBOX.foo"); - $self->{store}->set_fetch_attributes('uid'); + $self->{store}->set_folder("INBOX.foo"); + $self->{store}->set_fetch_attributes('uid'); - $exp{A} = $self->make_message("Message A"); + $exp{A} = $self->make_message("Message A"); - for (1..200) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - } + for (1 .. 200) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + } - $self->check_conversations(); + $self->check_conversations(); - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert(not $admintalk->get_last_error()); + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert(not $admintalk->get_last_error()); - $res = $admintalk->select("user.newuser.foo.sub"); - $self->assert(not $admintalk->get_last_error()); - $self->check_conversations(); + $res = $admintalk->select("user.newuser.foo.sub"); + $self->assert(not $admintalk->get_last_error()); + $self->check_conversations(); } # # Test big conversation rename # sub test_rename_user_midsizeconversation - :AllowMoves :Conversations :min_version_3_0 :NoAltNameSpace -{ - my ($self) = @_; + : AllowMoves : Conversations : min_version_3_0 : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Test user rename with a big conversation"; + xlog $self, "Test user rename with a big conversation"; - my %exp; + my %exp; - $admintalk->create("user.cassandane.foo") || die; - $admintalk->create("user.cassandane.bar") || die; - $admintalk->create("user.cassandane.foo.sub") || die; + $admintalk->create("user.cassandane.foo") || die; + $admintalk->create("user.cassandane.bar") || die; + $admintalk->create("user.cassandane.foo.sub") || die; - $self->{store}->set_folder("INBOX.foo"); - $self->{store}->set_fetch_attributes('uid', 'cid'); + $self->{store}->set_folder("INBOX.foo"); + $self->{store}->set_fetch_attributes('uid', 'cid'); - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - for (1..80) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } + for (1 .. 80) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } - $self->check_conversations(); + $self->check_conversations(); - $self->check_messages(\%exp, keyed_on => 'uid'); + $self->check_messages(\%exp, keyed_on => 'uid'); - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert(not $admintalk->get_last_error()); + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert(not $admintalk->get_last_error()); - $res = $admintalk->select("user.newuser.foo.sub"); - $self->assert(not $admintalk->get_last_error()); + $res = $admintalk->select("user.newuser.foo.sub"); + $self->assert(not $admintalk->get_last_error()); - $self->{adminstore}->set_folder("user.newuser.foo"); - $self->{adminstore}->set_fetch_attributes('uid', 'cid'); - $self->check_messages(\%exp, keyed_on => 'uid', store => $self->{adminstore}); + $self->{adminstore}->set_folder("user.newuser.foo"); + $self->{adminstore}->set_fetch_attributes('uid', 'cid'); + $self->check_messages(\%exp, keyed_on => 'uid', store => $self->{adminstore}); - $self->check_conversations(); + $self->check_conversations(); } # # Test big conversation rename # sub test_rename_bigconversation - :Conversations :min_version_3_0 -{ - my ($self) = @_; + : Conversations : min_version_3_0 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my %exp; + my %exp; - $imaptalk->create("INBOX.user-src.subdir") || die; - $self->{store}->set_folder("INBOX.user-src.subdir"); - $self->{store}->set_fetch_attributes('uid'); + $imaptalk->create("INBOX.user-src.subdir") || die; + $self->{store}->set_folder("INBOX.user-src.subdir"); + $self->{store}->set_fetch_attributes('uid'); - $exp{A} = $self->make_message("Message A"); + $exp{A} = $self->make_message("Message A"); - for (1..200) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - } + for (1 .. 200) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + } - $imaptalk->select("INBOX.user-src.subdir") || die; + $imaptalk->select("INBOX.user-src.subdir") || die; - $self->check_conversations(); + $self->check_conversations(); - $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; - $imaptalk->select("INBOX.user-dst.subdir") || die; + $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; + $imaptalk->select("INBOX.user-dst.subdir") || die; - $self->check_conversations(); + $self->check_conversations(); } # # Test mid-sized conversation rename # sub test_rename_midsizeconversation - :Conversations :min_version_3_0 -{ - my ($self) = @_; + : Conversations : min_version_3_0 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my %exp; + my %exp; - $imaptalk->create("INBOX.user-src.subdir") || die; - $self->{store}->set_folder("INBOX.user-src.subdir"); - $self->{store}->set_fetch_attributes('uid', 'cid'); + $imaptalk->create("INBOX.user-src.subdir") || die; + $self->{store}->set_folder("INBOX.user-src.subdir"); + $self->{store}->set_fetch_attributes('uid', 'cid'); - $exp{A} = $self->make_message("Message A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + $exp{A} = $self->make_message("Message A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - for (1..80) { - $exp{"A$_"} = $self->make_message("Re: Message A", references => [ $exp{A} ]); - $exp{"A$_"}->set_attributes(uid => 1+$_, cid => $exp{A}->make_cid()); - } - $self->check_messages(\%exp, keyed_on => 'uid'); + for (1 .. 80) { + $exp{"A$_"} + = $self->make_message("Re: Message A", references => [ $exp{A} ]); + $exp{"A$_"}->set_attributes(uid => 1 + $_, cid => $exp{A}->make_cid()); + } + $self->check_messages(\%exp, keyed_on => 'uid'); - $self->check_conversations(); + $self->check_conversations(); - $imaptalk->select("INBOX.user-src.subdir") || die; + $imaptalk->select("INBOX.user-src.subdir") || die; - $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; - $imaptalk->select("INBOX.user-dst.subdir") || die; + $imaptalk->rename("INBOX.user-src", "INBOX.user-dst") || die; + $imaptalk->select("INBOX.user-dst.subdir") || die; - $self->{store}->set_folder("INBOX.user-dst.subdir"); - $self->check_messages(\%exp, keyed_on => 'uid'); + $self->{store}->set_folder("INBOX.user-dst.subdir"); + $self->check_messages(\%exp, keyed_on => 'uid'); - $self->check_conversations(); + $self->check_conversations(); } # # Test Bug #3634 - rename inbox -> inbox.sub # sub test_rename_inbox - :Conversations :min_version_3_0 -{ - my ($self) = @_; + : Conversations : min_version_3_0 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->{store}->set_folder("INBOX"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); + $self->{store}->set_folder("INBOX"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); - $imaptalk->select("INBOX") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); + $imaptalk->select("INBOX") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); - $self->check_conversations(); + $self->check_conversations(); - my $res = $imaptalk->status("INBOX", ['mailboxid']); - my $oldInboxId = $res->{mailboxid}[0]; + my $res = $imaptalk->status("INBOX", ['mailboxid']); + my $oldInboxId = $res->{mailboxid}[0]; - $imaptalk->rename("INBOX", "INBOX.dst") || die; + $imaptalk->rename("INBOX", "INBOX.dst") || die; - $imaptalk->select("INBOX") || die; - my @postinboxdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(0, scalar @postinboxdata); + $imaptalk->select("INBOX") || die; + my @postinboxdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(0, scalar @postinboxdata); - $imaptalk->select("INBOX.dst") || die; - my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(1, scalar @postdata); + $imaptalk->select("INBOX.dst") || die; + my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(1, scalar @postdata); - $self->check_conversations(); + $self->check_conversations(); - # older cyrus may not support mailboxid, we don't need to test - # for ID change in that case - if ($oldInboxId) { - $res = $imaptalk->status("INBOX", ['mailboxid']); - my $newInboxId = $res->{mailboxid}[0]; + # older cyrus may not support mailboxid, we don't need to test + # for ID change in that case + if ($oldInboxId) { + $res = $imaptalk->status("INBOX", ['mailboxid']); + my $newInboxId = $res->{mailboxid}[0]; - $res = $imaptalk->status("INBOX.dst", ['mailboxid']); - my $dstId = $res->{mailboxid}[0]; + $res = $imaptalk->status("INBOX.dst", ['mailboxid']); + my $dstId = $res->{mailboxid}[0]; - $self->assert_str_equals($oldInboxId, $newInboxId); - $self->assert_str_not_equals($oldInboxId, $dstId); - } + $self->assert_str_equals($oldInboxId, $newInboxId); + $self->assert_str_not_equals($oldInboxId, $dstId); + } } sub test_rename_inbox_unselected - :Conversations :min_version_3_7 -{ - my ($self) = @_; + : Conversations : min_version_3_7 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->{store}->set_folder("INBOX"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); + $self->{store}->set_folder("INBOX"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); - $imaptalk->select("INBOX") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); + $imaptalk->select("INBOX") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); - $self->check_conversations(); + $self->check_conversations(); - my $res = $imaptalk->status("INBOX", ['mailboxid']); - my $oldInboxId = $res->{mailboxid}[0]; + my $res = $imaptalk->status("INBOX", ['mailboxid']); + my $oldInboxId = $res->{mailboxid}[0]; - # Unselect the mailbox to release Cyrus index state and test different code path - $imaptalk->unselect() || die; +# Unselect the mailbox to release Cyrus index state and test different code path + $imaptalk->unselect() || die; - $imaptalk->rename("INBOX", "INBOX.dst") || die; + $imaptalk->rename("INBOX", "INBOX.dst") || die; - $imaptalk->select("INBOX") || die; - my @postinboxdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(0, scalar @postinboxdata); + $imaptalk->select("INBOX") || die; + my @postinboxdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(0, scalar @postinboxdata); - $imaptalk->select("INBOX.dst") || die; - my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(1, scalar @postdata); + $imaptalk->select("INBOX.dst") || die; + my @postdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(1, scalar @postdata); - $self->check_conversations(); + $self->check_conversations(); - # older cyrus may not support mailboxid, we don't need to test - # for ID change in that case - if ($oldInboxId) { - $res = $imaptalk->status("INBOX", ['mailboxid']); - my $newInboxId = $res->{mailboxid}[0]; + # older cyrus may not support mailboxid, we don't need to test + # for ID change in that case + if ($oldInboxId) { + $res = $imaptalk->status("INBOX", ['mailboxid']); + my $newInboxId = $res->{mailboxid}[0]; - $res = $imaptalk->status("INBOX.dst", ['mailboxid']); - my $dstId = $res->{mailboxid}[0]; + $res = $imaptalk->status("INBOX.dst", ['mailboxid']); + my $dstId = $res->{mailboxid}[0]; - $self->assert_str_equals($oldInboxId, $newInboxId); - $self->assert_str_not_equals($oldInboxId, $dstId); - } + $self->assert_str_equals($oldInboxId, $newInboxId); + $self->assert_str_not_equals($oldInboxId, $dstId); + } } # # Test evil INBOX rename possibilities # sub test_rename_inbox_intermediate - :Conversations :min_version_3_1 -{ - my ($self) = @_; + : Conversations : min_version_3_1 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->{store}->set_folder("INBOX"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); + $self->{store}->set_folder("INBOX"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); - $imaptalk->select("INBOX") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); + $imaptalk->select("INBOX") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); - $imaptalk->create("INBOX.foo.bar"); - $imaptalk->rename("INBOX.foo", "INBOX") && die "rename should fail"; + $imaptalk->create("INBOX.foo.bar"); + $imaptalk->rename("INBOX.foo", "INBOX") && die "rename should fail"; - $imaptalk->select("INBOX") || die; - my @postinboxdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); - $self->assert_num_equals(1, scalar @postinboxdata); + $imaptalk->select("INBOX") || die; + my @postinboxdata = $imaptalk->search("KEYWORD" => "\$NotJunk"); + $self->assert_num_equals(1, scalar @postinboxdata); } # # Test rename a folder with subfolders # -sub test_rename_withsub_dom -{ - my ($self) = @_; +sub test_rename_withsub_dom { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.a"); - $imaptalk->create("INBOX.b"); - $imaptalk->create("INBOX.c"); + $imaptalk->create("INBOX.a"); + $imaptalk->create("INBOX.b"); + $imaptalk->create("INBOX.c"); - $self->{store}->set_folder("INBOX.c"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); + $self->{store}->set_folder("INBOX.c"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); - $imaptalk->select("INBOX.c") || die; - my @predata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); + $imaptalk->select("INBOX.c") || die; + my @predata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); - $imaptalk->rename("INBOX.c", "INBOX.b.c") || die; - $imaptalk->rename("INBOX.b", "INBOX.a.b") || die; + $imaptalk->rename("INBOX.c", "INBOX.b.c") || die; + $imaptalk->rename("INBOX.b", "INBOX.a.b") || die; - $imaptalk->select("INBOX.a.b.c") || die; - my @postdata = $imaptalk->search("SEEN"); - $self->assert_num_equals(1, scalar @postdata); + $imaptalk->select("INBOX.a.b.c") || die; + my @postdata = $imaptalk->search("SEEN"); + $self->assert_num_equals(1, scalar @postdata); } # # Test rename a folder with subfolders, domain user # sub test_rename_withsub - :VirtDomains -{ - my ($self) = @_; + : VirtDomains { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user("renameuser\@example.com"); - my $domstore = $self->{instance}->get_service('imap')->create_store(username => "renameuser\@example.com"); - my $domtalk = $domstore->get_client(); + $self->{instance}->create_user("renameuser\@example.com"); + my $domstore = $self->{instance}->get_service('imap') + ->create_store(username => "renameuser\@example.com"); + my $domtalk = $domstore->get_client(); - $domtalk->create("INBOX.a"); - $domtalk->create("INBOX.b"); - $domtalk->create("INBOX.c"); + $domtalk->create("INBOX.a"); + $domtalk->create("INBOX.b"); + $domtalk->create("INBOX.c"); - $domstore->set_folder("INBOX.c"); - $domstore->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $domstore->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $domstore->write_end(); + $domstore->set_folder("INBOX.c"); + $domstore->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $domstore->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $domstore->write_end(); - $domtalk->select("INBOX.c") || die; - my @predata = $domtalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); + $domtalk->select("INBOX.c") || die; + my @predata = $domtalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); - $domtalk->rename("INBOX.c", "INBOX.b.c") || die; - $domtalk->rename("INBOX.b", "INBOX.a.b") || die; + $domtalk->rename("INBOX.c", "INBOX.b.c") || die; + $domtalk->rename("INBOX.b", "INBOX.a.b") || die; - $domtalk->select("INBOX.a.b.c") || die; - my @postdata = $domtalk->search("SEEN"); - $self->assert_num_equals(1, scalar @postdata); + $domtalk->select("INBOX.a.b.c") || die; + my @postdata = $domtalk->search("SEEN"); + $self->assert_num_equals(1, scalar @postdata); } sub test_rename_conversations - :Conversations :VirtDomains :min_version_3_0 -{ - my ($self) = @_; + : Conversations : VirtDomains : min_version_3_0 { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user("renameuser\@example.com"); - my $domstore = $self->{instance}->get_service('imap')->create_store(username => "renameuser\@example.com"); - my $domtalk = $domstore->get_client(); + $self->{instance}->create_user("renameuser\@example.com"); + my $domstore = $self->{instance}->get_service('imap') + ->create_store(username => "renameuser\@example.com"); + my $domtalk = $domstore->get_client(); - $domtalk->create("INBOX.a"); - $domtalk->create("INBOX.b"); - $domtalk->create("INBOX.c"); + $domtalk->create("INBOX.a"); + $domtalk->create("INBOX.b"); + $domtalk->create("INBOX.c"); - $domstore->set_folder("INBOX.c"); - $domstore->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $domstore->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $domstore->write_end(); + $domstore->set_folder("INBOX.c"); + $domstore->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $domstore->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $domstore->write_end(); - $domtalk->select("INBOX.c") || die; - my @predata = $domtalk->search("SEEN"); - $self->assert_num_equals(1, scalar @predata); + $domtalk->select("INBOX.c") || die; + my @predata = $domtalk->search("SEEN"); + $self->assert_num_equals(1, scalar @predata); - $domtalk->rename("INBOX.c", "INBOX.b.c") || die; - $domtalk->rename("INBOX.b", "INBOX.a.b") || die; + $domtalk->rename("INBOX.c", "INBOX.b.c") || die; + $domtalk->rename("INBOX.b", "INBOX.a.b") || die; - $domtalk->select("INBOX.a.b.c") || die; - my @postdata = $domtalk->search("SEEN"); - $self->assert_num_equals(1, scalar @postdata); + $domtalk->select("INBOX.a.b.c") || die; + my @postdata = $domtalk->search("SEEN"); + $self->assert_num_equals(1, scalar @postdata); } -sub get_partition -{ - my ($talk, $folder) = @_; +sub get_partition { + my ($talk, $folder) = @_; - my $key = '/shared/vendor/cmu/cyrus-imapd/partition'; - my $md = $talk->getmetadata($folder, $key); + my $key = '/shared/vendor/cmu/cyrus-imapd/partition'; + my $md = $talk->getmetadata($folder, $key); - return undef if $talk->get_last_completion_response() ne 'ok'; - return $md->{$folder}->{$key}; + return undef if $talk->get_last_completion_response() ne 'ok'; + return $md->{$folder}->{$key}; } sub test_rename_user - :Partition2 :AllowMoves -{ - my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - - xlog $self, "Test Cyrus extension which renames a user to a different partition"; - - # set up a sub mailbox - $admintalk->create('user.cassandane.submailbox'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - # rename to same name (only) should fail - $admintalk->rename('user.cassandane', 'user.cassandane'); - $self->assert_str_equals('no', - $admintalk->get_last_completion_response()); - $self->assert_matches(qr{Mailbox already exists}, - $admintalk->get_last_error()); - - # rename to same name with new partition should succeed - $admintalk->rename('user.cassandane', 'user.cassandane', 'p2'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - # rename to same name with same partition should fail - $admintalk->rename('user.cassandane', 'user.cassandane', 'p2'); - $self->assert_str_equals('no', - $admintalk->get_last_completion_response()); - $self->assert_matches(qr{Mailbox already exists}, - $admintalk->get_last_error()); - - # rename to new name with new partition should fail - $admintalk->rename('user.cassandane', 'user.bob', 'default'); - $self->assert_str_equals('no', - $admintalk->get_last_completion_response()); - $self->assert_matches(qr{Cross-server or cross-partition move w/rename not supported}, - $admintalk->get_last_error()); - - # rename to new name without partition should not change partition - my $before_partition = get_partition($admintalk, 'user.cassandane'); - $self->assert_not_null($before_partition); - $admintalk->rename('user.cassandane', 'user.bob'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - my $after_partition = get_partition($admintalk, 'user.bob'); - $self->assert_equals($before_partition, $after_partition); - my $sub_partition = get_partition($admintalk, 'user.bob.submailbox'); - $self->assert_equals($after_partition, $sub_partition); - - # XXX rename to new name with explicit current partition should succeed - # XXX not implemented, but would be nice :) -# $before_partition = get_partition($admintalk, 'user.bob'); -# $self->assert_not_null($before_partition); -# $admintalk->rename('user.bob', 'user.cassandane', $before_partition); -# $self->assert_str_equals('ok', -# $admintalk->get_last_completion_response()); -# $after_partition = get_partition($admintalk, 'user.cassandane'); -# $self->assert_str_equals($before_partition, $after_partition); + : Partition2 : AllowMoves { + my ($self) = @_; + my $admintalk = $self->{adminstore}->get_client(); + + xlog $self, + "Test Cyrus extension which renames a user to a different partition"; + + # set up a sub mailbox + $admintalk->create('user.cassandane.submailbox'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # rename to same name (only) should fail + $admintalk->rename('user.cassandane', 'user.cassandane'); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr{Mailbox already exists}, + $admintalk->get_last_error()); + + # rename to same name with new partition should succeed + $admintalk->rename('user.cassandane', 'user.cassandane', 'p2'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + # rename to same name with same partition should fail + $admintalk->rename('user.cassandane', 'user.cassandane', 'p2'); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr{Mailbox already exists}, + $admintalk->get_last_error()); + + # rename to new name with new partition should fail + $admintalk->rename('user.cassandane', 'user.bob', 'default'); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches( + qr{Cross-server or cross-partition move w/rename not supported}, + $admintalk->get_last_error()); + + # rename to new name without partition should not change partition + my $before_partition = get_partition($admintalk, 'user.cassandane'); + $self->assert_not_null($before_partition); + $admintalk->rename('user.cassandane', 'user.bob'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + my $after_partition = get_partition($admintalk, 'user.bob'); + $self->assert_equals($before_partition, $after_partition); + my $sub_partition = get_partition($admintalk, 'user.bob.submailbox'); + $self->assert_equals($after_partition, $sub_partition); + + # XXX rename to new name with explicit current partition should succeed + # XXX not implemented, but would be nice :) + # $before_partition = get_partition($admintalk, 'user.bob'); + # $self->assert_not_null($before_partition); + # $admintalk->rename('user.bob', 'user.cassandane', $before_partition); + # $self->assert_str_equals('ok', + # $admintalk->get_last_completion_response()); + # $after_partition = get_partition($admintalk, 'user.cassandane'); + # $self->assert_str_equals($before_partition, $after_partition); } sub test_rename_deepuser - :AllowMoves :Replication :SyncLog :needs_component_replication -{ - my ($self) = @_; + : AllowMoves : Replication : SyncLog : needs_component_replication { + my ($self) = @_; - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Test user rename"; + xlog $self, "Test user rename"; - $admintalk->create("user.cassandane.foo") || die; - $admintalk->create("user.cassandane.bar") || die; - $admintalk->create("user.cassandane.bar.sub folder") || die; + $admintalk->create("user.cassandane.foo") || die; + $admintalk->create("user.cassandane.bar") || die; + $admintalk->create("user.cassandane.bar.sub folder") || die; - # replicate and check initial state - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication('cassandane'); - unlink($synclogfname); + # replicate and check initial state + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication('cassandane'); + unlink($synclogfname); - # n.b. run_replication dropped all our store connections... - $admintalk = $self->{adminstore}->get_client(); - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert(not $admintalk->get_last_error()); + # n.b. run_replication dropped all our store connections... + $admintalk = $self->{adminstore}->get_client(); + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert(not $admintalk->get_last_error()); - $res = $admintalk->select("user.newuser.bar.sub folder"); - $self->assert(not $admintalk->get_last_error()); + $res = $admintalk->select("user.newuser.bar.sub folder"); + $self->assert(not $admintalk->get_last_error()); - # replicate and check the renames - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication('newuser'); + # replicate and check the renames + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication('newuser'); } sub test_rename_user_sieve - :AllowMoves :Replication :SyncLog :needs_component_sieve - :needs_component_replication -{ - my ($self) = @_; + : AllowMoves : Replication : SyncLog : needs_component_sieve + : needs_component_replication { + my ($self) = @_; - xlog $self, "Test user rename with Sieve script"; + xlog $self, "Test user rename with Sieve script"; - my $user = 'cassandane'; - my $newuser = 'newuser'; - my $scriptname = 'test1'; - my $scriptcontent = <<'EOF'; + my $user = 'cassandane'; + my $newuser = 'newuser'; + my $scriptname = 'test1'; + my $scriptcontent = <<'EOF'; keep; EOF - # install sieve script on master - $self->{instance}->install_sieve_script($scriptcontent, name=>$scriptname); - - # verify that sieve script exists on master - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); - - # check if we have the new-style sieve - my $mailboxesdb = $self->{instance}->read_mailboxes_db(); - my $have_new_sieve = $mailboxesdb->{'user.cassandane.#sieve'} ? 1 : 0; - xlog "Checking for new sieve resulted in $have_new_sieve"; - - # replicate and check initial state - $self->run_replication(); - $self->check_replication($user); - - # verify that sieve script exists on both master and replica - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); - - if ($have_new_sieve) { - xlog "Checking that sieve mailbox is created on replica"; - my $mailboxesdb = $self->{replica}->read_mailboxes_db(); - $self->assert_not_null($mailboxesdb->{'user.cassandane.#sieve'}); - } - - # rename user - my $admintalk = $self->{adminstore}->get_client(); - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert(not $admintalk->get_last_error()); - - # verify that sieve script exists on master - $self->assert_sieve_exists($self->{instance}, $newuser, $scriptname, 1); - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 1); - - # replicate and check the renames - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication($newuser); - - # verify that sieve script exists replica - $self->assert_sieve_exists($self->{replica}, $newuser, $scriptname, 1); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 1); - - if ($have_new_sieve) { - xlog "Checking that sieve mailboxes are renamed at both ends"; - my $mdb = $self->{instance}->read_mailboxes_db(); - my $rdb = $self->{replica}->read_mailboxes_db(); - $self->assert_null($mdb->{'user.cassandane.#sieve'}); - $self->assert_null($rdb->{'user.cassandane.#sieve'}); - $self->assert_not_null($mdb->{'user.newuser.#sieve'}); - $self->assert_not_null($rdb->{'user.newuser.#sieve'}); - } + # install sieve script on master + $self->{instance}->install_sieve_script($scriptcontent, name => $scriptname); + + # verify that sieve script exists on master + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); + + # check if we have the new-style sieve + my $mailboxesdb = $self->{instance}->read_mailboxes_db(); + my $have_new_sieve = $mailboxesdb->{'user.cassandane.#sieve'} ? 1 : 0; + xlog "Checking for new sieve resulted in $have_new_sieve"; + + # replicate and check initial state + $self->run_replication(); + $self->check_replication($user); + + # verify that sieve script exists on both master and replica + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); + + if ($have_new_sieve) { + xlog "Checking that sieve mailbox is created on replica"; + my $mailboxesdb = $self->{replica}->read_mailboxes_db(); + $self->assert_not_null($mailboxesdb->{'user.cassandane.#sieve'}); + } + + # rename user + my $admintalk = $self->{adminstore}->get_client(); + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert(not $admintalk->get_last_error()); + + # verify that sieve script exists on master + $self->assert_sieve_exists($self->{instance}, $newuser, $scriptname, 1); + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 1); + + # replicate and check the renames + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication($newuser); + + # verify that sieve script exists replica + $self->assert_sieve_exists($self->{replica}, $newuser, $scriptname, 1); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 1); + + if ($have_new_sieve) { + xlog "Checking that sieve mailboxes are renamed at both ends"; + my $mdb = $self->{instance}->read_mailboxes_db(); + my $rdb = $self->{replica}->read_mailboxes_db(); + $self->assert_null($mdb->{'user.cassandane.#sieve'}); + $self->assert_null($rdb->{'user.cassandane.#sieve'}); + $self->assert_not_null($mdb->{'user.newuser.#sieve'}); + $self->assert_not_null($rdb->{'user.newuser.#sieve'}); + } } sub test_rename_paths - :MetaPartition :NoAltNameSpace -{ - my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.rename-src.sub") || die; - - $self->{store}->set_folder("INBOX.rename-src.sub"); - $self->{store}->write_begin(); - my $msg1 = $self->{gen}->generate(subject => "subject 1"); - $self->{store}->write_message($msg1, flags => ["\\Seen", "\$NotJunk"]); - $self->{store}->write_end(); - - # check source files exist - my $srcdata = $self->{instance}->run_mbpath('user.cassandane.rename-src.sub'); - -d "$srcdata->{data}" || die; - -d "$srcdata->{meta}" || die; - -f "$srcdata->{meta}/cyrus.header" || die; - -f "$srcdata->{meta}/cyrus.index" || die; - -f "$srcdata->{data}/cyrus.cache" || die; - -f "$srcdata->{data}/1." || die; - - # and target don't - my $dstdata = eval { $self->{instance}->run_mbpath('user.cassandane.rename-dst.sub') }; - $self->assert(not $dstdata or (not -d $dstdata->{data} and not -d $dstdata->{meta})); - - $imaptalk->rename("INBOX.rename-src.sub", "INBOX.rename-dst.sub"); - - # check dest files exist - $dstdata = $self->{instance}->run_mbpath('user.cassandane.rename-dst.sub'); - -d "$dstdata->{data}" || die; - -d "$dstdata->{meta}" || die; - -f "$dstdata->{meta}/cyrus.header" || die; - -f "$dstdata->{meta}/cyrus.index" || die; - -f "$dstdata->{data}/cyrus.cache" || die; - -f "$dstdata->{data}/1." || die; - - # and src don't any more (unless UUID when the paths are the same!) - $srcdata->{data} ne $dstdata->{data} && -d "$srcdata->{data}" && die; - $srcdata->{meta} ne $dstdata->{meta} && -d "$srcdata->{meta}" && die; + : MetaPartition : NoAltNameSpace { + my ($self) = @_; + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.rename-src.sub") || die; + + $self->{store}->set_folder("INBOX.rename-src.sub"); + $self->{store}->write_begin(); + my $msg1 = $self->{gen}->generate(subject => "subject 1"); + $self->{store}->write_message($msg1, flags => [ "\\Seen", "\$NotJunk" ]); + $self->{store}->write_end(); + + # check source files exist + my $srcdata = $self->{instance}->run_mbpath('user.cassandane.rename-src.sub'); + -d "$srcdata->{data}" || die; + -d "$srcdata->{meta}" || die; + -f "$srcdata->{meta}/cyrus.header" || die; + -f "$srcdata->{meta}/cyrus.index" || die; + -f "$srcdata->{data}/cyrus.cache" || die; + -f "$srcdata->{data}/1." || die; + + # and target don't + my $dstdata + = eval { $self->{instance}->run_mbpath('user.cassandane.rename-dst.sub') }; + $self->assert( + not $dstdata + or (not -d $dstdata->{data} and not -d $dstdata->{meta}) + ); + + $imaptalk->rename("INBOX.rename-src.sub", "INBOX.rename-dst.sub"); + + # check dest files exist + $dstdata = $self->{instance}->run_mbpath('user.cassandane.rename-dst.sub'); + -d "$dstdata->{data}" || die; + -d "$dstdata->{meta}" || die; + -f "$dstdata->{meta}/cyrus.header" || die; + -f "$dstdata->{meta}/cyrus.index" || die; + -f "$dstdata->{data}/cyrus.cache" || die; + -f "$dstdata->{data}/1." || die; + + # and src don't any more (unless UUID when the paths are the same!) + $srcdata->{data} ne $dstdata->{data} && -d "$srcdata->{data}" && die; + $srcdata->{meta} ne $dstdata->{meta} && -d "$srcdata->{meta}" && die; } sub test_rename_deepuser_unixhs - :AllowMoves :Replication :SyncLog :UnixHierarchySep - :needs_component_replication -{ - my ($self) = @_; + : AllowMoves : Replication : SyncLog : UnixHierarchySep + : needs_component_replication { + my ($self) = @_; - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Test user rename"; + xlog $self, "Test user rename"; - $admintalk->create("user/cassandane/foo") || die; - $admintalk->create("user/cassandane/bar") || die; - $admintalk->create("user/cassandane/bar/sub") || die; + $admintalk->create("user/cassandane/foo") || die; + $admintalk->create("user/cassandane/bar") || die; + $admintalk->create("user/cassandane/bar/sub") || die; - # replicate and check initial state - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication('cassandane'); - unlink($synclogfname); + # replicate and check initial state + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication('cassandane'); + unlink($synclogfname); - # n.b. run_replication dropped all our store connections... - $admintalk = $self->{adminstore}->get_client(); - my $res = $admintalk->rename('user/cassandane', 'user/new.user'); - $self->assert(not $admintalk->get_last_error()); + # n.b. run_replication dropped all our store connections... + $admintalk = $self->{adminstore}->get_client(); + my $res = $admintalk->rename('user/cassandane', 'user/new.user'); + $self->assert(not $admintalk->get_last_error()); - $res = $admintalk->select("user/new.user/bar/sub"); - $self->assert(not $admintalk->get_last_error()); + $res = $admintalk->select("user/new.user/bar/sub"); + $self->assert(not $admintalk->get_last_error()); - # replicate and check the renames - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication('new.user'); + # replicate and check the renames + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication('new.user'); } -sub _match_intermediates -{ - my ($self, %expect) = @_; - my @lines = $self->{instance}->getsyslog(); - #'Aug 23 12:34:20 bat 0234200101/ctl_cyrusdb[14527]: mboxlist: creating intermediate with children: user.cassandane.a (ec10f137-1bee-443e-8cb2-c6c893463b0a)', - #'Aug 23 12:34:20 bat 0234200101/ctl_cyrusdb[14527]: mboxlist: deleting intermediate with no children: user.cassandane.hanging (b13ba9d4-9d40-4474-911f-77346a73d747)', - for (@lines) { - if (m/mboxlist: creating intermediate with children: (.*?)($| \()/) { - my $mbox = $1; - $self->assert(exists $expect{$mbox}, "didn't expect touch of $mbox"); - my $val = delete $expect{$mbox}; - $self->assert(!$val, "create when expected delete of $mbox"); - } - if (m/mboxlist: deleting intermediate with no children: (.*?)($| \()/) { - my $mbox = $1; - $self->assert(exists $expect{$mbox}, "didn't expect touch of $mbox"); - my $val = delete $expect{$mbox}; - $self->assert(!!$val, "delete when expected create of $mbox"); - } +sub _match_intermediates { + my ($self, %expect) = @_; + my @lines = $self->{instance}->getsyslog(); +#'Aug 23 12:34:20 bat 0234200101/ctl_cyrusdb[14527]: mboxlist: creating intermediate with children: user.cassandane.a (ec10f137-1bee-443e-8cb2-c6c893463b0a)', +#'Aug 23 12:34:20 bat 0234200101/ctl_cyrusdb[14527]: mboxlist: deleting intermediate with no children: user.cassandane.hanging (b13ba9d4-9d40-4474-911f-77346a73d747)', + for (@lines) { + if (m/mboxlist: creating intermediate with children: (.*?)($| \()/) { + my $mbox = $1; + $self->assert(exists $expect{$mbox}, "didn't expect touch of $mbox"); + my $val = delete $expect{$mbox}; + $self->assert(!$val, "create when expected delete of $mbox"); } - use Data::Dumper; - $self->assert_num_equals(0, scalar keys %expect, "EXPECTED TO SEE " . Dumper(\%expect, \@lines)); + if (m/mboxlist: deleting intermediate with no children: (.*?)($| \()/) { + my $mbox = $1; + $self->assert(exists $expect{$mbox}, "didn't expect touch of $mbox"); + my $val = delete $expect{$mbox}; + $self->assert(!!$val, "delete when expected create of $mbox"); + } + } + use Data::Dumper; + $self->assert_num_equals( + 0, + scalar keys %expect, + "EXPECTED TO SEE " . Dumper(\%expect, \@lines) + ); } -sub _dbset -{ - my ($self, $key, $value) = @_; - $self->assert_str_equals('ok', $self->{instance}->run_dbcommand_cb( - sub { die "got a response!" }, - "$self->{instance}->{basedir}/conf/mailboxes.db", - 'twoskip', - defined($value) - ? ['SET', $key => $value] - : ['DELETE', $key], - )); +sub _dbset { + my ($self, $key, $value) = @_; + $self->assert_str_equals( + 'ok', + $self->{instance}->run_dbcommand_cb( + sub { die "got a response!" }, + "$self->{instance}->{basedir}/conf/mailboxes.db", + 'twoskip', + defined($value) + ? [ 'SET', $key => $value ] + : [ 'DELETE', $key ], + ) + ); } sub test_intermediate_cleanup - :min_version_3_1 :max_version_3_4 :NoAltNameSpace :NoAltNameSpace -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.a.b.c.subdir") || die; - $imaptalk->create("INBOX.x.y.z.subdir") || die; - $imaptalk->create("INBOX.INBOX.subinbox") || die; - $imaptalk->create("INBOX.INBOX.a.b") || die; - - _match_intermediates($self, - 'user.cassandane.a' => undef, - 'user.cassandane.a.b' => undef, - 'user.cassandane.a.b.c' => undef, - 'user.cassandane.x' => undef, - 'user.cassandane.x.y' => undef, - 'user.cassandane.x.y.z' => undef, - 'user.cassandane.INBOX.a' => undef, - ); + : min_version_3_1 : max_version_3_4 : NoAltNameSpace : NoAltNameSpace { + my ($self) = @_; - $imaptalk->create("INBOX.x.y"); + my $imaptalk = $self->{store}->get_client(); - _match_intermediates($self); + $imaptalk->create("INBOX.a.b.c.subdir") || die; + $imaptalk->create("INBOX.x.y.z.subdir") || die; + $imaptalk->create("INBOX.INBOX.subinbox") || die; + $imaptalk->create("INBOX.INBOX.a.b") || die; - $imaptalk->delete("INBOX.x.y.z.subdir"); + _match_intermediates( + $self, + 'user.cassandane.a' => undef, + 'user.cassandane.a.b' => undef, + 'user.cassandane.a.b.c' => undef, + 'user.cassandane.x' => undef, + 'user.cassandane.x.y' => undef, + 'user.cassandane.x.y.z' => undef, + 'user.cassandane.INBOX.a' => undef, + ); - _match_intermediates($self, - 'user.cassandane.x.y.z' => 1, - ); + $imaptalk->create("INBOX.x.y"); - $imaptalk->delete("INBOX.x.y"); + _match_intermediates($self); - _match_intermediates($self, - 'user.cassandane.x' => 1, - ); + $imaptalk->delete("INBOX.x.y.z.subdir"); - $imaptalk->delete("INBOX.INBOX.a.b"); + _match_intermediates($self, 'user.cassandane.x.y.z' => 1,); - _match_intermediates($self, - 'user.cassandane.INBOX.a' => 1, - ); + $imaptalk->delete("INBOX.x.y"); - _dbset($self, 'user.cassandane.old', '%(I 66eb299a-35a8-423d-a0a6-90cbacfd153a T di C 1 F 1 M 1538674002)'); + _match_intermediates($self, 'user.cassandane.x' => 1,); - $imaptalk->create("INBOX.old.foo"); + $imaptalk->delete("INBOX.INBOX.a.b"); - _match_intermediates($self, - 'user.cassandane.old' => undef, - ); + _match_intermediates($self, 'user.cassandane.INBOX.a' => 1,); - $imaptalk->delete("INBOX.old.foo"); + _dbset($self, 'user.cassandane.old', + '%(I 66eb299a-35a8-423d-a0a6-90cbacfd153a T di C 1 F 1 M 1538674002)'); - _match_intermediates($self, - 'user.cassandane.old' => 1, - ); + $imaptalk->create("INBOX.old.foo"); - my %set = ( - 'user.cassandane.hanging' => '%(I b13ba9d4-9d40-4474-911f-77346a73d747 T i C 1 F 1 M 1538674002)', - 'user.cassandane.a' => undef, - 'user.cassandane.a.b' => undef, - 'user.cassandane.x' => '%(I 7c89e632-04a0-4560-9a59-18b07c13ddff T i C 1 F 1 M 1538674002)', - 'user.cassandane.x.y' => '%(I 385d7a66-6173-4b5e-9340-0301ac55b373 T i C 1 F 1 M 1538674002)', - ); + _match_intermediates($self, 'user.cassandane.old' => undef,); - # NOTE: This is all very specific! - foreach my $key (keys %set) { - _dbset($self, $key, $set{$key}); - } + $imaptalk->delete("INBOX.old.foo"); - $self->{instance}->getsyslog(); + _match_intermediates($self, 'user.cassandane.old' => 1,); - # perform startup magic - $self->{instance}->run_command( - { cyrus => 1 }, - 'ctl_cyrusdb', '-r', - ); + my %set = ( + 'user.cassandane.hanging' => + '%(I b13ba9d4-9d40-4474-911f-77346a73d747 T i C 1 F 1 M 1538674002)', + 'user.cassandane.a' => undef, + 'user.cassandane.a.b' => undef, + 'user.cassandane.x' => + '%(I 7c89e632-04a0-4560-9a59-18b07c13ddff T i C 1 F 1 M 1538674002)', + 'user.cassandane.x.y' => + '%(I 385d7a66-6173-4b5e-9340-0301ac55b373 T i C 1 F 1 M 1538674002)', + ); + + # NOTE: This is all very specific! + foreach my $key (keys %set) { + _dbset($self, $key, $set{$key}); + } - _match_intermediates($self, %set); + $self->{instance}->getsyslog(); + + # perform startup magic + $self->{instance}->run_command({ cyrus => 1 }, 'ctl_cyrusdb', '-r',); + + _match_intermediates($self, %set); } sub test_rename_user_sharee - :AllowMoves :NoAltNameSpace :ReverseACLs :min_version_3_6 -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); + : AllowMoves : NoAltNameSpace : ReverseACLs : min_version_3_6 { + my ($self) = @_; - xlog $self, "Test user rename with shares"; + my $admintalk = $self->{adminstore}->get_client(); - my %exp; + xlog $self, "Test user rename with shares"; - $admintalk->create("user.foo") || die; - $admintalk->create("user.foo.shared") || die; - $admintalk->setacl("user.foo.shared", 'cassandane' => 'lrs') || die; + my %exp; - $admintalk->create("user.foo.#calendars") || die; - $admintalk->create("user.foo.#calendars.events") || die; - $admintalk->setacl("user.foo.#calendars.events", 'cassandane' => 'lrs') || die; + $admintalk->create("user.foo") || die; + $admintalk->create("user.foo.shared") || die; + $admintalk->setacl("user.foo.shared", 'cassandane' => 'lrs') || die; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.sub"); - $imaptalk->subscribe("INBOX.sub"); - $imaptalk->subscribe("user.foo.shared"); - $imaptalk->subscribe("user.foo.#calendars.events"); + $admintalk->create("user.foo.#calendars") || die; + $admintalk->create("user.foo.#calendars.events") || die; + $admintalk->setacl("user.foo.#calendars.events", 'cassandane' => 'lrs') + || die; - my $structure = { - 'INBOX' => [ '\\HasChildren' ], - 'INBOX.sub' => [ '\\Subscribed', '\\HasNoChildren' ], - 'user.foo.#calendars.events' => [ '\\Subscribed', '\\HasNoChildren' ], - 'user.foo.shared' => [ '\\Subscribed', '\\HasNoChildren' ], - }; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create("INBOX.sub"); + $imaptalk->subscribe("INBOX.sub"); + $imaptalk->subscribe("user.foo.shared"); + $imaptalk->subscribe("user.foo.#calendars.events"); + my $structure = { + 'INBOX' => ['\\HasChildren'], + 'INBOX.sub' => [ '\\Subscribed', '\\HasNoChildren' ], + 'user.foo.#calendars.events' => [ '\\Subscribed', '\\HasNoChildren' ], + 'user.foo.shared' => [ '\\Subscribed', '\\HasNoChildren' ], + }; - my $list = $imaptalk->list([qw( vendor.cmu-dav )], '', '*', 'return', ['subscribed']); + my $list = $imaptalk->list([qw( vendor.cmu-dav )], '', '*', 'return', + ['subscribed']); - $self->assert_mailbox_structure($list, '.', $structure); + $self->assert_mailbox_structure($list, '.', $structure); - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $newstore = $self->{instance}->get_service('imap')->create_store( - username => "newuser"); - my $newtalk = $newstore->get_client(); + my $newstore = $self->{instance}->get_service('imap') + ->create_store(username => "newuser"); + my $newtalk = $newstore->get_client(); - $list = $newtalk->list([qw( vendor.cmu-dav )], '', '*', 'return', ['subscribed']); + $list + = $newtalk->list([qw( vendor.cmu-dav )], '', '*', 'return', ['subscribed']); - $self->assert_mailbox_structure($list, '.', $structure); + $self->assert_mailbox_structure($list, '.', $structure); } 1; diff --git a/cassandane/Cassandane/Cyrus/Replace.pm b/cassandane/Cassandane/Cyrus/Replace.pm index 8cb0cb7f36..653ce363c2 100644 --- a/cassandane/Cassandane/Cyrus/Replace.pm +++ b/cassandane/Cassandane/Cyrus/Replace.pm @@ -50,78 +50,75 @@ use Cassandane::Generator; use Cassandane::MessageStoreFactory; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({adminstore => 1}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_replace_same_mailbox - :min_version_3_9 -{ - my ($self) = @_; + : min_version_3_9 { + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my %exp; - $exp{A} = $self->make_message("Message A", store => $self->{store}); - $self->check_messages(\%exp); + my %exp; + $exp{A} = $self->make_message("Message A", store => $self->{store}); + $self->check_messages(\%exp); - $talk->select('INBOX'); + $talk->select('INBOX'); - %exp = (); - $exp{B} = $self->{gen}->generate(subject => "Message B"); + %exp = (); + $exp{B} = $self->{gen}->generate(subject => "Message B"); - # REPLACE - $talk->_imap_cmd('REPLACE', 0, '', "1", "INBOX", - { Literal => $exp{B}->as_string() }); - $self->check_messages(\%exp); + # REPLACE + $talk->_imap_cmd('REPLACE', 0, '', "1", "INBOX", + { Literal => $exp{B}->as_string() }); + $self->check_messages(\%exp); - %exp = (); - $exp{C} = $self->{gen}->generate(subject => "Message C"); + %exp = (); + $exp{C} = $self->{gen}->generate(subject => "Message C"); - # UID REPLACE - $talk->_imap_cmd('UID', 0, '', 'REPLACE', "2", "INBOX", - "(\\flagged)", " 7-Feb-1994 22:43:04 -0800", - { Literal => $exp{C}->as_string() }); - $self->check_messages(\%exp); + # UID REPLACE + $talk->_imap_cmd( + 'UID', 0, '', 'REPLACE', "2", "INBOX", "(\\flagged)", + " 7-Feb-1994 22:43:04 -0800", + { Literal => $exp{C}->as_string() } + ); + $self->check_messages(\%exp); } sub test_replace_different_mailbox - :min_version_3_9 -{ - my ($self) = @_; + : min_version_3_9 { + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my %exp; - $exp{A} = $self->make_message("Message A", store => $self->{store}); - $self->check_messages(\%exp); + my %exp; + $exp{A} = $self->make_message("Message A", store => $self->{store}); + $self->check_messages(\%exp); - $talk->create("INBOX.foo"); - $talk->select('INBOX'); + $talk->create("INBOX.foo"); + $talk->select('INBOX'); - %exp = (); - $exp{B} = $self->{gen}->generate(subject => "Message B", uid => 1); + %exp = (); + $exp{B} = $self->{gen}->generate(subject => "Message B", uid => 1); - $talk->_imap_cmd('REPLACE', 0, '', "1", "INBOX.foo", - { Literal => $exp{B}->as_string() }); - $self->check_messages({}); + $talk->_imap_cmd('REPLACE', 0, '', "1", "INBOX.foo", + { Literal => $exp{B}->as_string() }); + $self->check_messages({}); - $self->{store}->set_folder("INBOX.foo"); - $self->check_messages(\%exp); + $self->{store}->set_folder("INBOX.foo"); + $self->check_messages(\%exp); } 1; diff --git a/cassandane/Cassandane/Cyrus/Replication.pm b/cassandane/Cassandane/Cyrus/Replication.pm index fde337ae0f..9fe2064014 100644 --- a/cassandane/Cassandane/Cyrus/Replication.pm +++ b/cassandane/Cassandane/Cyrus/Replication.pm @@ -50,22 +50,19 @@ use Cassandane::Util::Slurp; use Cassandane::Service; use Cassandane::Config; -sub new -{ - my $class = shift; - return $class->SUPER::new({ replica => 1, adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ replica => 1, adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # XXX need a test for version 10 mailbox without guids in it! @@ -76,28 +73,26 @@ sub tear_down #* delete the message from the replica (with expunge_mode default or expunge_mode immediate... try both) #* run sync_client on the master again and make sure it successfully syncs up -sub assert_user_sub_exists -{ - my ($self, $instance, $user) = @_; +sub assert_user_sub_exists { + my ($self, $instance, $user) = @_; - my $subs = $instance->get_conf_user_file($user, 'sub'); - $self->assert_not_null($subs); + my $subs = $instance->get_conf_user_file($user, 'sub'); + $self->assert_not_null($subs); - xlog $self, "Looking for subscriptions file $subs"; + xlog $self, "Looking for subscriptions file $subs"; - $self->assert_file_test($subs, '-f'); + $self->assert_file_test($subs, '-f'); } -sub assert_user_sub_not_exists -{ - my ($self, $instance, $user) = @_; +sub assert_user_sub_not_exists { + my ($self, $instance, $user) = @_; - my $subs = $instance->get_conf_user_file($user, 'sub'); - return unless $subs; # user might not exist + my $subs = $instance->get_conf_user_file($user, 'sub'); + return unless $subs; # user might not exist - xlog $self, "Looking for subscriptions file $subs"; + xlog $self, "Looking for subscriptions file $subs"; - $self->assert_not_file_test($subs, '-f'); + $self->assert_not_file_test($subs, '-f'); } use Cassandane::Tiny::Loader 'tiny-tests/Replication'; diff --git a/cassandane/Cassandane/Cyrus/Search.pm b/cassandane/Cassandane/Cyrus/Search.pm index dd60517f0d..62d35a82c8 100644 --- a/cassandane/Cassandane/Cyrus/Search.pm +++ b/cassandane/Cassandane/Cyrus/Search.pm @@ -48,171 +48,164 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - my $config = Cassandane::Config->default()->clone(); - $config->set(conversations => 'on'); - return $class->SUPER::new({adminstore => 1, config => $config}, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config->default()->clone(); + $config->set(conversations => 'on'); + return $class->SUPER::new({ adminstore => 1, config => $config }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub _fgrep_msgs -{ - my ($msgs, $attr, $s) = @_; - my @res; +sub _fgrep_msgs { + my ($msgs, $attr, $s) = @_; + my @res; - foreach my $msg (values %$msgs) - { - push(@res, $msg->uid()) - if (index($msg->$attr(), $s) >= 0); - } - @res = sort { $a <=> $b } @res; - return \@res; + foreach my $msg (values %$msgs) { + push(@res, $msg->uid()) + if (index($msg->$attr(), $s) >= 0); + } + @res = sort { $a <=> $b } @res; + return \@res; } -sub test_from -{ - my ($self) = @_; - - xlog $self, "test SEARCH with the FROM predicate"; - my $talk = $self->{store}->get_client(); - - xlog $self, "append some messages"; - my %exp; - my %from_domains; - my $N = 20; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - my ($dom) = ($msg->from() =~ m/(@[^>]*)>/); - $from_domains{$dom} = 1; - xlog $self, "Message uid " . $msg->uid() . " from domain " . $dom; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); - - my @found; - foreach my $dom (keys %from_domains) - { - xlog $self, "searching for: FROM $dom"; - my $uids = $talk->search('from', { Quote => $dom }) - or die "Cannot search: $@"; - my $expected_uids = _fgrep_msgs(\%exp, 'from', $dom); - $self->assert_deep_equals($expected_uids, $uids); - map { $found[$_] = 1 } @$uids; - } - - xlog $self, "checking all the message were found"; - for (1..$N) - { - $self->assert($found[$_], - "UID $_ was not returned from a SEARCH"); - } - - xlog $self, "Double-check the messages are still there"; - $self->check_messages(\%exp); +sub test_from { + my ($self) = @_; + + xlog $self, "test SEARCH with the FROM predicate"; + my $talk = $self->{store}->get_client(); + + xlog $self, "append some messages"; + my %exp; + my %from_domains; + my $N = 20; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + my ($dom) = ($msg->from() =~ m/(@[^>]*)>/); + $from_domains{$dom} = 1; + xlog $self, "Message uid " . $msg->uid() . " from domain " . $dom; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); + + my @found; + foreach my $dom (keys %from_domains) { + xlog $self, "searching for: FROM $dom"; + my $uids = $talk->search('from', { Quote => $dom }) + or die "Cannot search: $@"; + my $expected_uids = _fgrep_msgs(\%exp, 'from', $dom); + $self->assert_deep_equals($expected_uids, $uids); + map { $found[$_] = 1 } @$uids; + } + + xlog $self, "checking all the message were found"; + for (1 .. $N) { + $self->assert($found[$_], "UID $_ was not returned from a SEARCH"); + } + + xlog $self, "Double-check the messages are still there"; + $self->check_messages(\%exp); } -sub test_header_multiple -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - - my $extra_headers = [ - ['x-nice-day-for', 'start again (come on)' ], - ['x-nice-day-for', 'white wedding' ], - ['x-nice-day-for', 'start agaaain' ], - ]; - - my %exp; - $exp{1} = $self->make_message('message 1', - 'extra_headers' => $extra_headers); - $exp{2} = $self->make_message('nice day'); - $self->check_messages(\%exp); - - # make sure a search that doesn't match anything doesn't find anything! - my $uids = $talk->search('header', 'x-nice-day-for', 'cease and desist'); - $self->assert_num_equals(0, scalar @{$uids}); - - # we must be able to find a message by the first header value - $uids = $talk->search('header', 'x-nice-day-for', 'come on'); - $self->assert_num_equals(1, scalar @{$uids}); - $self->assert_deep_equals( [ 1 ], $uids); - - # we must be able to find a message by the last header value - $uids = $talk->search('header', 'x-nice-day-for', 'start agaaain'); - $self->assert_num_equals(1, scalar @{$uids}); - $self->assert_deep_equals( [ 1 ], $uids); - - # we must be able to find a message by some other header value - $uids = $talk->search('header', 'x-nice-day-for', 'white wedding'); - $self->assert_num_equals(1, scalar @{$uids}); - $self->assert_deep_equals( [ 1 ], $uids); - - # we must be able to ever find some other message! - $uids = $talk->search('header', 'subject', 'nice day'); - $self->assert_num_equals(1, scalar @{$uids}); - $self->assert_deep_equals( [ 2 ], $uids); +sub test_header_multiple { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + + my $extra_headers = [ + [ 'x-nice-day-for', 'start again (come on)' ], + [ 'x-nice-day-for', 'white wedding' ], + [ 'x-nice-day-for', 'start agaaain' ], + ]; + + my %exp; + $exp{1} = $self->make_message('message 1', 'extra_headers' => $extra_headers); + $exp{2} = $self->make_message('nice day'); + $self->check_messages(\%exp); + + # make sure a search that doesn't match anything doesn't find anything! + my $uids = $talk->search('header', 'x-nice-day-for', 'cease and desist'); + $self->assert_num_equals(0, scalar @{$uids}); + + # we must be able to find a message by the first header value + $uids = $talk->search('header', 'x-nice-day-for', 'come on'); + $self->assert_num_equals(1, scalar @{$uids}); + $self->assert_deep_equals([1], $uids); + + # we must be able to find a message by the last header value + $uids = $talk->search('header', 'x-nice-day-for', 'start agaaain'); + $self->assert_num_equals(1, scalar @{$uids}); + $self->assert_deep_equals([1], $uids); + + # we must be able to find a message by some other header value + $uids = $talk->search('header', 'x-nice-day-for', 'white wedding'); + $self->assert_num_equals(1, scalar @{$uids}); + $self->assert_deep_equals([1], $uids); + + # we must be able to ever find some other message! + $uids = $talk->search('header', 'subject', 'nice day'); + $self->assert_num_equals(1, scalar @{$uids}); + $self->assert_deep_equals([2], $uids); } sub test_esearch - :NoAltNameSpace :needs_search_xapian :Conversations :min_version_3_7 -{ - my ($self) = @_; - - xlog $self, "Create shared folder, writeable by cassandane user"; - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("shared"); - $admintalk->setacl("shared", "cassandane", "lrsip"); - - xlog $self, "Create some personal folders"; - my $imaptalk = $self->{store}->get_client(); - - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( INBOX.a INBOX.a.b.c INBOX.d INBOX.d.e INBOX.f )] ], - [ 'subscribe' => [qw( INBOX.a.b INBOX.d )] ], - [ 'subscribe' => [qw( shared )] ], - ]); - - xlog $self, "Remove 'p' right from most personal folders"; - $imaptalk->setacl("INBOX.a", "anyone", "-p"); - $imaptalk->setacl("INBOX.a.b", "anyone", "-p"); - $imaptalk->setacl("INBOX.a.b.c", "anyone", "-p"); - $imaptalk->setacl("INBOX.d", "anyone", "-p"); - $imaptalk->setacl("INBOX.d.e", "anyone", "-p"); - - my $alldata = $imaptalk->list("", "*"); - - $self->assert_mailbox_structure($alldata, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.a' => [qw( \\HasChildren )], - 'INBOX.a.b' => [qw( \\HasChildren )], - 'INBOX.a.b.c' => [qw( \\HasNoChildren )], - 'INBOX.d' => [qw( \\HasChildren )], - 'INBOX.d.e' => [qw( \\HasNoChildren )], - 'INBOX.f' => [qw( \\HasNoChildren )], - 'shared' => [qw( \\HasNoChildren )], - }); - - xlog $self, "Append some emails into the folders"; - my %raw = ( - A => <<"EOF", + : NoAltNameSpace : needs_search_xapian : Conversations : min_version_3_7 { + my ($self) = @_; + + xlog $self, "Create shared folder, writeable by cassandane user"; + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->create("shared"); + $admintalk->setacl("shared", "cassandane", "lrsip"); + + xlog $self, "Create some personal folders"; + my $imaptalk = $self->{store}->get_client(); + + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( INBOX.a INBOX.a.b.c INBOX.d INBOX.d.e INBOX.f )] ], + [ 'subscribe' => [qw( INBOX.a.b INBOX.d )] ], + [ 'subscribe' => [qw( shared )] ], + ] + ); + + xlog $self, "Remove 'p' right from most personal folders"; + $imaptalk->setacl("INBOX.a", "anyone", "-p"); + $imaptalk->setacl("INBOX.a.b", "anyone", "-p"); + $imaptalk->setacl("INBOX.a.b.c", "anyone", "-p"); + $imaptalk->setacl("INBOX.d", "anyone", "-p"); + $imaptalk->setacl("INBOX.d.e", "anyone", "-p"); + + my $alldata = $imaptalk->list("", "*"); + + $self->assert_mailbox_structure( + $alldata, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.a' => [qw( \\HasChildren )], + 'INBOX.a.b' => [qw( \\HasChildren )], + 'INBOX.a.b.c' => [qw( \\HasNoChildren )], + 'INBOX.d' => [qw( \\HasChildren )], + 'INBOX.d.e' => [qw( \\HasNoChildren )], + 'INBOX.f' => [qw( \\HasNoChildren )], + 'shared' => [qw( \\HasNoChildren )], + } + ); + + xlog $self, "Append some emails into the folders"; + my %raw = ( + A => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -223,7 +216,7 @@ Content-Type: text/plain\r \r test A\r EOF - B => <<"EOF", + B => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -236,7 +229,7 @@ In-Reply-To: \r \r test B\r EOF - C => <<"EOF", + C => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -249,7 +242,7 @@ In-Reply-To: \r \r test C\r EOF - D => <<"EOF", + D => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -260,7 +253,7 @@ Content-Type: text/plain\r \r test D\r EOF - E => <<"EOF", + E => <<"EOF", From: \r To: to\@local\r Subject: test3\r @@ -272,7 +265,7 @@ Content-Type: text/plain\r \r test E\r EOF - F => <<"EOF", + F => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -283,7 +276,7 @@ Content-Type: text/plain\r \r test F\r EOF - G => <<"EOF", + G => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -295,180 +288,172 @@ Content-Type: text/plain\r \r test D\r EOF - ); - - $imaptalk->append('INBOX', "()", $raw{A}) || die $@; - $imaptalk->append('INBOX', "()", $raw{B}) || die $@; - $imaptalk->append('INBOX', "()", $raw{C}) || die $@; - $imaptalk->append('INBOX.a', "()", $raw{B}) || die $@; - $imaptalk->append('INBOX.a.b', "()", $raw{C}) || die $@; - $imaptalk->append('INBOX.a.b.c', "()", $raw{D}) || die $@; - $imaptalk->append('INBOX.d', "()", $raw{E}) || die $@; - $imaptalk->append('INBOX.f', "()", $raw{F}) || die $@; - $imaptalk->append('shared', "()", $raw{G}) || die $@; - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my @results; - my %handlers = - ( - esearch => sub - { - my (undef, $esearch) = @_; - push(@results, $esearch); - }, - ); - - xlog $self, "Search the (un)selected mailbox (should fail)"; - my $res = $imaptalk->_imap_cmd('ESEARCH', 0, 'esearch', - 'IN', '(SELECTED)', - 'subject', 'test'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - - xlog $self, "Now select a mailbox"; - $res = $imaptalk->select("INBOX"); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Search the newly selected mailbox"; - @results = (); - $res = $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(SELECTED)', 'RETURN', '(MIN MAX ALL)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(1, scalar @results); - $self->assert_str_equals('INBOX', $results[0][0][3]); - $self->assert_num_equals(1, $results[0][3]); - $self->assert_num_equals(3, $results[0][5]); - $self->assert_str_equals('1:3', $results[0][7]); - - xlog $self, "Search the personal namespace, returning just counts"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(PERSONAL)', 'RETURN', '(COUNT)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(6, scalar @results); - $self->assert_str_equals('INBOX', $results[0][0][3]); - $self->assert_num_equals(3, $results[0][3]); - $self->assert_str_equals('INBOX.a', $results[1][0][3]); - $self->assert_num_equals(1, $results[1][3]); - $self->assert_str_equals('INBOX.a.b', $results[2][0][3]); - $self->assert_num_equals(1, $results[2][3]); - $self->assert_str_equals('INBOX.a.b.c', $results[3][0][3]); - $self->assert_num_equals(1, $results[3][3]); - $self->assert_str_equals('INBOX.d', $results[4][0][3]); - $self->assert_num_equals(1, $results[4][3]); - $self->assert_str_equals('INBOX.f', $results[5][0][3]); - $self->assert_num_equals(1, $results[5][3]); - - xlog $self, "Search the subscribed folders"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(SUBSCRIBED)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(4, scalar @results); - $self->assert_str_equals('INBOX', $results[0][0][3]); - $self->assert_str_equals('INBOX.a.b', $results[1][0][3]); - $self->assert_str_equals('INBOX.d', $results[2][0][3]); - $self->assert_str_equals('shared', $results[3][0][3]); - - xlog $self, "Search the Inboxes (deliverable)"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(INBOXES)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(2, scalar @results); - $self->assert_str_equals('INBOX', $results[0][0][3]); - $self->assert_str_equals('INBOX.f', $results[1][0][3]); - - xlog $self, "Search a subtree"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(SUBTREE INBOX.a)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(3, scalar @results); - $self->assert_str_equals('INBOX.a', $results[0][0][3]); - $self->assert_str_equals('INBOX.a.b', $results[1][0][3]); - $self->assert_str_equals('INBOX.a.b.c', $results[2][0][3]); - - xlog $self, "Search a limited subtree"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(SUBTREE-ONE INBOX.a)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(2, scalar @results); - $self->assert_str_equals('INBOX.a', $results[0][0][3]); - $self->assert_str_equals('INBOX.a.b', $results[1][0][3]); - - xlog $self, "Search a single folder without a match"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(MAILBOXES INBOX.e)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(0, scalar @results); - - xlog $self, "Search a single folder with a match"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(MAILBOXES INBOX.f)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(1, scalar @results); - $self->assert_str_equals('INBOX.f', $results[0][0][3]); - - xlog $self, "Search a multiple folders with only one match)"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(MAILBOXES (INBOX.e INBOX.f))', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(1, scalar @results); - $self->assert_str_equals('INBOX.f', $results[0][0][3]); - - xlog $self, "Search multiple sourcesand make sure there are no duplicates"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(SUBSCRIBED SELECTED SUBTREE-ONE INBOX.a MAILBOXES (INBOX.e shared))', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(5, scalar @results); - $self->assert_str_equals('INBOX', $results[0][0][3]); - $self->assert_str_equals('INBOX.a.b', $results[1][0][3]); - $self->assert_str_equals('INBOX.d', $results[2][0][3]); - $self->assert_str_equals('shared', $results[3][0][3]); - $self->assert_str_equals('INBOX.a', $results[4][0][3]); - - xlog $self, "Fuzzy search the personal namespace"; - @results = (); - $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(PERSONAL)', 'FUZZY', 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(3, scalar @results); - $self->assert_str_equals('INBOX', $results[0][0][3]); - $self->assert_str_equals('INBOX.a', $results[1][0][3]); - $self->assert_str_equals('INBOX.a.b', $results[2][0][3]); + ); + + $imaptalk->append('INBOX', "()", $raw{A}) || die $@; + $imaptalk->append('INBOX', "()", $raw{B}) || die $@; + $imaptalk->append('INBOX', "()", $raw{C}) || die $@; + $imaptalk->append('INBOX.a', "()", $raw{B}) || die $@; + $imaptalk->append('INBOX.a.b', "()", $raw{C}) || die $@; + $imaptalk->append('INBOX.a.b.c', "()", $raw{D}) || die $@; + $imaptalk->append('INBOX.d', "()", $raw{E}) || die $@; + $imaptalk->append('INBOX.f', "()", $raw{F}) || die $@; + $imaptalk->append('shared', "()", $raw{G}) || die $@; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my @results; + my %handlers = ( + esearch => sub { + my (undef, $esearch) = @_; + push(@results, $esearch); + }, + ); + + xlog $self, "Search the (un)selected mailbox (should fail)"; + my $res = $imaptalk->_imap_cmd('ESEARCH', 0, 'esearch', 'IN', '(SELECTED)', + 'subject', 'test'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + + xlog $self, "Now select a mailbox"; + $res = $imaptalk->select("INBOX"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Search the newly selected mailbox"; + @results = (); + $res = $imaptalk->_imap_cmd( + 'ESEARCH', 0, \%handlers, 'IN', + '(SELECTED)', 'RETURN', '(MIN MAX ALL)', 'subject', + 'test' + ); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(1, scalar @results); + $self->assert_str_equals('INBOX', $results[0][0][3]); + $self->assert_num_equals(1, $results[0][3]); + $self->assert_num_equals(3, $results[0][5]); + $self->assert_str_equals('1:3', $results[0][7]); + + xlog $self, "Search the personal namespace, returning just counts"; + @results = (); + $imaptalk->_imap_cmd( + 'ESEARCH', 0, \%handlers, 'IN', '(PERSONAL)', 'RETURN', + '(COUNT)', 'subject', 'test' + ); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(6, scalar @results); + $self->assert_str_equals('INBOX', $results[0][0][3]); + $self->assert_num_equals(3, $results[0][3]); + $self->assert_str_equals('INBOX.a', $results[1][0][3]); + $self->assert_num_equals(1, $results[1][3]); + $self->assert_str_equals('INBOX.a.b', $results[2][0][3]); + $self->assert_num_equals(1, $results[2][3]); + $self->assert_str_equals('INBOX.a.b.c', $results[3][0][3]); + $self->assert_num_equals(1, $results[3][3]); + $self->assert_str_equals('INBOX.d', $results[4][0][3]); + $self->assert_num_equals(1, $results[4][3]); + $self->assert_str_equals('INBOX.f', $results[5][0][3]); + $self->assert_num_equals(1, $results[5][3]); + + xlog $self, "Search the subscribed folders"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, 'IN', '(SUBSCRIBED)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(4, scalar @results); + $self->assert_str_equals('INBOX', $results[0][0][3]); + $self->assert_str_equals('INBOX.a.b', $results[1][0][3]); + $self->assert_str_equals('INBOX.d', $results[2][0][3]); + $self->assert_str_equals('shared', $results[3][0][3]); + + xlog $self, "Search the Inboxes (deliverable)"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, 'IN', '(INBOXES)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(2, scalar @results); + $self->assert_str_equals('INBOX', $results[0][0][3]); + $self->assert_str_equals('INBOX.f', $results[1][0][3]); + + xlog $self, "Search a subtree"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, 'IN', '(SUBTREE INBOX.a)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(3, scalar @results); + $self->assert_str_equals('INBOX.a', $results[0][0][3]); + $self->assert_str_equals('INBOX.a.b', $results[1][0][3]); + $self->assert_str_equals('INBOX.a.b.c', $results[2][0][3]); + + xlog $self, "Search a limited subtree"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, 'IN', '(SUBTREE-ONE INBOX.a)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(2, scalar @results); + $self->assert_str_equals('INBOX.a', $results[0][0][3]); + $self->assert_str_equals('INBOX.a.b', $results[1][0][3]); + + xlog $self, "Search a single folder without a match"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, 'IN', '(MAILBOXES INBOX.e)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(0, scalar @results); + + xlog $self, "Search a single folder with a match"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, 'IN', '(MAILBOXES INBOX.f)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(1, scalar @results); + $self->assert_str_equals('INBOX.f', $results[0][0][3]); + + xlog $self, "Search a multiple folders with only one match)"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, + 'IN', '(MAILBOXES (INBOX.e INBOX.f))', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(1, scalar @results); + $self->assert_str_equals('INBOX.f', $results[0][0][3]); + + xlog $self, "Search multiple sourcesand make sure there are no duplicates"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, + 'IN', + '(SUBSCRIBED SELECTED SUBTREE-ONE INBOX.a MAILBOXES (INBOX.e shared))', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(5, scalar @results); + $self->assert_str_equals('INBOX', $results[0][0][3]); + $self->assert_str_equals('INBOX.a.b', $results[1][0][3]); + $self->assert_str_equals('INBOX.d', $results[2][0][3]); + $self->assert_str_equals('shared', $results[3][0][3]); + $self->assert_str_equals('INBOX.a', $results[4][0][3]); + + xlog $self, "Fuzzy search the personal namespace"; + @results = (); + $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, + 'IN', '(PERSONAL)', 'FUZZY', 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(3, scalar @results); + $self->assert_str_equals('INBOX', $results[0][0][3]); + $self->assert_str_equals('INBOX.a', $results[1][0][3]); + $self->assert_str_equals('INBOX.a.b', $results[2][0][3]); } sub test_searchres - :NoAltNameSpace :min_version_3_7 -{ - my ($self) = @_; + : NoAltNameSpace : min_version_3_7 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); + $self->setup_mailbox_structure($imaptalk, + [ [ 'create' => [qw( INBOX.target )] ], ]); - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( INBOX.target )] ], - ]); - - xlog $self, "Append some emails into the folders"; - my %raw = ( - A => <<"EOF", + xlog $self, "Append some emails into the folders"; + my %raw = ( + A => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -479,7 +464,7 @@ Content-Type: text/plain\r \r test A\r EOF - B => <<"EOF", + B => <<"EOF", From: \r To: to\@local\r Subject: foo\r @@ -492,7 +477,7 @@ In-Reply-To: \r \r test B\r EOF - C => <<"EOF", + C => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -505,7 +490,7 @@ In-Reply-To: \r \r test C\r EOF - D => <<"EOF", + D => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -516,7 +501,7 @@ Content-Type: text/plain\r \r test D\r EOF - E => <<"EOF", + E => <<"EOF", From: \r To: to\@local\r Subject: test3\r @@ -528,7 +513,7 @@ Content-Type: text/plain\r \r test E\r EOF - F => <<"EOF", + F => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -539,7 +524,7 @@ Content-Type: text/plain\r \r test F\r EOF - G => <<"EOF", + G => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -551,348 +536,337 @@ Content-Type: text/plain\r \r test D\r EOF - ); - - $imaptalk->append('INBOX', "()", $raw{A}) || die $@; - $imaptalk->append('INBOX', "()", $raw{B}) || die $@; - $imaptalk->append('INBOX', "()", $raw{C}) || die $@; - $imaptalk->append('INBOX', "()", $raw{D}) || die $@; - $imaptalk->append('INBOX', "()", $raw{E}) || die $@; - $imaptalk->append('INBOX', "()", $raw{F}) || die $@; - $imaptalk->append('INBOX', "()", $raw{G}) || die $@; - - my @results; - my %handlers = - ( - esearch => sub - { - my (undef, $esearch) = @_; - push(@results, $esearch); - }, - ); - - xlog $self, "Search the (un)selected mailbox (should fail)"; - my $res = $imaptalk->_imap_cmd('SEARCH', 0, 'esearch', - 'RETURN', '(SAVE)', - 'subject', 'test'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - - xlog $self, "Now select a mailbox"; - $res = $imaptalk->select("INBOX"); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Search results should be empty"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(0, scalar keys %{$res}); - - xlog $self, "Attempt to Search the newly selected mailbox and others"; - @results = (); - $res = $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'IN', '(SELECTED PERSONAL)', 'RETURN', '(SAVE)', - 'subject', 'test'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - - xlog $self, "Search the selected mailbox for minimum and save"; - @results = (); - $res = $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, - 'RETURN', '(SAVE MIN)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Fetch using the search results"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(1, scalar keys %{$res}); - $self->assert_str_equals('1', $res->{'1'}->{uid}); - - xlog $self, "Search the mailbox for maximum and save"; - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(MAX SAVE)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Fetch using the search results"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(1, scalar keys %{$res}); - $self->assert_str_equals('7', $res->{'7'}->{uid}); - - xlog $self, "Search the mailbox for minimum & maximum and save"; - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(MAX SAVE MIN)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Fetch using the search results"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(2, scalar keys %{$res}); - $self->assert_str_equals('1', $res->{'1'}->{uid}); - $self->assert_str_equals('7', $res->{'7'}->{uid}); - - xlog $self, "Search the mailbox for all and save"; - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(SAVE)', - 'subject', 'test'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Fetch using the search results"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(6, scalar keys %{$res}); - $self->assert_str_equals('1', $res->{'1'}->{uid}); - $self->assert_str_equals('3', $res->{'3'}->{uid}); - $self->assert_str_equals('4', $res->{'4'}->{uid}); - $self->assert_str_equals('5', $res->{'5'}->{uid}); - $self->assert_str_equals('6', $res->{'6'}->{uid}); - $self->assert_str_equals('7', $res->{'7'}->{uid}); - - xlog $self, "Store using the search results"; - $res = $imaptalk->store('$', '+flags', '(\\Flagged)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(6, scalar keys %{$res}); - $self->assert_str_equals('1', $res->{'1'}->{uid}); - $self->assert_str_equals('3', $res->{'3'}->{uid}); - $self->assert_str_equals('4', $res->{'4'}->{uid}); - $self->assert_str_equals('5', $res->{'5'}->{uid}); - $self->assert_str_equals('6', $res->{'6'}->{uid}); - $self->assert_str_equals('7', $res->{'7'}->{uid}); - - xlog $self, "Copy using the search results"; - $res = $imaptalk->copy('$', 'INBOX.target'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->get_response_code('copyuid'); - $self->assert_str_equals('1,3:7', $res->[1]); - $self->assert_str_equals('1:6', $res->[2]); - - xlog $self, "Expunge the first message"; - $res = $imaptalk->store('1', '+flags', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->expunge(); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Fetch using the search results"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(5, scalar keys %{$res}); - $self->assert_str_equals('3', $res->{'2'}->{uid}); - $self->assert_str_equals('4', $res->{'3'}->{uid}); - $self->assert_str_equals('5', $res->{'4'}->{uid}); - $self->assert_str_equals('6', $res->{'5'}->{uid}); - $self->assert_str_equals('7', $res->{'6'}->{uid}); - - xlog $self, "Expunge the middle message in the search results range"; - $res = $imaptalk->store('4', '+flags', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->expunge(); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Fetch using the search results"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(4, scalar keys %{$res}); - $self->assert_str_equals('3', $res->{'2'}->{uid}); - $self->assert_str_equals('4', $res->{'3'}->{uid}); - $self->assert_str_equals('6', $res->{'4'}->{uid}); - $self->assert_str_equals('7', $res->{'5'}->{uid}); - - xlog $self, "Expunge the 1st message in the 1st range and the last in the 2nd"; - $res = $imaptalk->store('2,5', '+flags', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->expunge(); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Fetch using the search results"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(2, scalar keys %{$res}); - $self->assert_str_equals('4', $res->{'2'}->{uid}); - $self->assert_str_equals('6', $res->{'3'}->{uid}); - - xlog $self, "Search the mailbox for a from address in the saved results"; - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(SAVE ALL)', - 'uid', '$', 'from', 'foo'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Fetch using the search results"; - $res = $imaptalk->fetch('$', 'UID'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(1, scalar keys %{$res}); - $self->assert_str_equals('6', $res->{'3'}->{uid}); + ); + + $imaptalk->append('INBOX', "()", $raw{A}) || die $@; + $imaptalk->append('INBOX', "()", $raw{B}) || die $@; + $imaptalk->append('INBOX', "()", $raw{C}) || die $@; + $imaptalk->append('INBOX', "()", $raw{D}) || die $@; + $imaptalk->append('INBOX', "()", $raw{E}) || die $@; + $imaptalk->append('INBOX', "()", $raw{F}) || die $@; + $imaptalk->append('INBOX', "()", $raw{G}) || die $@; + + my @results; + my %handlers = ( + esearch => sub { + my (undef, $esearch) = @_; + push(@results, $esearch); + }, + ); + + xlog $self, "Search the (un)selected mailbox (should fail)"; + my $res = $imaptalk->_imap_cmd('SEARCH', 0, 'esearch', 'RETURN', '(SAVE)', + 'subject', 'test'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + + xlog $self, "Now select a mailbox"; + $res = $imaptalk->select("INBOX"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Search results should be empty"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(0, scalar keys %{$res}); + + xlog $self, "Attempt to Search the newly selected mailbox and others"; + @results = (); + $res + = $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, + 'IN', '(SELECTED PERSONAL)', + 'RETURN', '(SAVE)', 'subject', 'test'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + + xlog $self, "Search the selected mailbox for minimum and save"; + @results = (); + $res = $imaptalk->_imap_cmd('ESEARCH', 0, \%handlers, 'RETURN', '(SAVE MIN)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Fetch using the search results"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(1, scalar keys %{$res}); + $self->assert_str_equals('1', $res->{'1'}->{uid}); + + xlog $self, "Search the mailbox for maximum and save"; + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, 'RETURN', '(MAX SAVE)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Fetch using the search results"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(1, scalar keys %{$res}); + $self->assert_str_equals('7', $res->{'7'}->{uid}); + + xlog $self, "Search the mailbox for minimum & maximum and save"; + @results = (); + $res + = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, 'RETURN', '(MAX SAVE MIN)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Fetch using the search results"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(2, scalar keys %{$res}); + $self->assert_str_equals('1', $res->{'1'}->{uid}); + $self->assert_str_equals('7', $res->{'7'}->{uid}); + + xlog $self, "Search the mailbox for all and save"; + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, 'RETURN', '(SAVE)', + 'subject', 'test'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Fetch using the search results"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(6, scalar keys %{$res}); + $self->assert_str_equals('1', $res->{'1'}->{uid}); + $self->assert_str_equals('3', $res->{'3'}->{uid}); + $self->assert_str_equals('4', $res->{'4'}->{uid}); + $self->assert_str_equals('5', $res->{'5'}->{uid}); + $self->assert_str_equals('6', $res->{'6'}->{uid}); + $self->assert_str_equals('7', $res->{'7'}->{uid}); + + xlog $self, "Store using the search results"; + $res = $imaptalk->store('$', '+flags', '(\\Flagged)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(6, scalar keys %{$res}); + $self->assert_str_equals('1', $res->{'1'}->{uid}); + $self->assert_str_equals('3', $res->{'3'}->{uid}); + $self->assert_str_equals('4', $res->{'4'}->{uid}); + $self->assert_str_equals('5', $res->{'5'}->{uid}); + $self->assert_str_equals('6', $res->{'6'}->{uid}); + $self->assert_str_equals('7', $res->{'7'}->{uid}); + + xlog $self, "Copy using the search results"; + $res = $imaptalk->copy('$', 'INBOX.target'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $res = $imaptalk->get_response_code('copyuid'); + $self->assert_str_equals('1,3:7', $res->[1]); + $self->assert_str_equals('1:6', $res->[2]); + + xlog $self, "Expunge the first message"; + $res = $imaptalk->store('1', '+flags', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $res = $imaptalk->expunge(); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Fetch using the search results"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(5, scalar keys %{$res}); + $self->assert_str_equals('3', $res->{'2'}->{uid}); + $self->assert_str_equals('4', $res->{'3'}->{uid}); + $self->assert_str_equals('5', $res->{'4'}->{uid}); + $self->assert_str_equals('6', $res->{'5'}->{uid}); + $self->assert_str_equals('7', $res->{'6'}->{uid}); + + xlog $self, "Expunge the middle message in the search results range"; + $res = $imaptalk->store('4', '+flags', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $res = $imaptalk->expunge(); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Fetch using the search results"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(4, scalar keys %{$res}); + $self->assert_str_equals('3', $res->{'2'}->{uid}); + $self->assert_str_equals('4', $res->{'3'}->{uid}); + $self->assert_str_equals('6', $res->{'4'}->{uid}); + $self->assert_str_equals('7', $res->{'5'}->{uid}); + + xlog $self, + "Expunge the 1st message in the 1st range and the last in the 2nd"; + $res = $imaptalk->store('2,5', '+flags', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $res = $imaptalk->expunge(); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Fetch using the search results"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(2, scalar keys %{$res}); + $self->assert_str_equals('4', $res->{'2'}->{uid}); + $self->assert_str_equals('6', $res->{'3'}->{uid}); + + xlog $self, "Search the mailbox for a from address in the saved results"; + @results = (); + $res = $imaptalk->_imap_cmd( + 'SEARCH', 0, \%handlers, 'RETURN', '(SAVE ALL)', 'uid', + '$', 'from', 'foo' + ); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Fetch using the search results"; + $res = $imaptalk->fetch('$', 'UID'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(1, scalar keys %{$res}); + $self->assert_str_equals('6', $res->{'3'}->{uid}); } sub test_uidsearch_empty - :min_version_3_9 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); - - $imap->create('INBOX.test'); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my @results; - my %handlers = - ( - esearch => sub - { - my (undef, $esearch) = @_; - push(@results, $esearch); - }, - ); - - $imap->select('INBOX.test'); - $imap->_imap_cmd('UID', 0, \%handlers, - 'SEARCH', 'RETURN', '(ALL SAVE COUNT) UID 1:*'); - $self->assert_num_equals(1, scalar @results); - $self->assert_str_equals('UID', $results[0][1]); - $self->assert_str_equals('COUNT', $results[0][2]); - $self->assert_str_equals('0', $results[0][3]); + : min_version_3_9 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); + + $imap->create('INBOX.test'); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my @results; + my %handlers = ( + esearch => sub { + my (undef, $esearch) = @_; + push(@results, $esearch); + }, + ); + + $imap->select('INBOX.test'); + $imap->_imap_cmd('UID', 0, \%handlers, + 'SEARCH', 'RETURN', '(ALL SAVE COUNT) UID 1:*'); + $self->assert_num_equals(1, scalar @results); + $self->assert_str_equals('UID', $results[0][1]); + $self->assert_str_equals('COUNT', $results[0][2]); + $self->assert_str_equals('0', $results[0][3]); } -sub test_partial -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - xlog $self, "append some messages"; - my %exp; - my $N = 10; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); - - # delete the 1st and 6th - $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - my @results; - my %handlers = - ( - esearch => sub - { - my (undef, $esearch) = @_; - push(@results, $esearch); - }, - ); - - # search and return non-existent messages - @results = (); - my $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(PARTIAL -100:-1)', '100:300'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('PARTIAL', $results[0][1]); - $self->assert_str_equals('-1:-100', $results[0][2][0]); - $self->assert_null($results[0][2][1]); - - # search and return all messages - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '()', 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('2:5,7:10', $results[0][2]); - - # attempt search with all and partial - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(ALL PARTIAL 1:2)', 'UNDELETED'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - - # search and return first 2 messages - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(PARTIAL 1:2)', 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('PARTIAL', $results[0][1]); - $self->assert_str_equals('1:2', $results[0][2][0]); - $self->assert_str_equals('2:3', $results[0][2][1]); - - # search and return next 2 messages - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(PARTIAL 3:4)', 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('PARTIAL', $results[0][1]); - $self->assert_str_equals('3:4', $results[0][2][0]); - $self->assert_str_equals('4:5', $results[0][2][1]); - - # flag the last message - $imaptalk->store('10', '+FLAGS', '(\\flagged)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - # search and return next 2 messages - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(PARTIAL 5:6)', 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('PARTIAL', $results[0][1]); - $self->assert_str_equals('5:6', $results[0][2][0]); - $self->assert_str_equals('7:8', $results[0][2][1]); - - # search and return last 2 messages - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(PARTIAL -1:-2)', 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('PARTIAL', $results[0][1]); - $self->assert_str_equals('-1:-2', $results[0][2][0]); - $self->assert_str_equals('9:10', $results[0][2][1]); - - # search and return the previous 2 messages - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(PARTIAL -3:-4)', 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('PARTIAL', $results[0][1]); - $self->assert_str_equals('-3:-4', $results[0][2][0]); - $self->assert_str_equals('7:8', $results[0][2][1]); - - # search and return middle 2 messages by UID - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(PARTIAL 2:3)', - 'UID', '4:8', 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('PARTIAL', $results[0][1]); - $self->assert_str_equals('2:3', $results[0][2][0]); - $self->assert_str_equals('5,7', $results[0][2][1]); - - # search and return non-existent messages - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(PARTIAL 9:10)', 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('PARTIAL', $results[0][1]); - $self->assert_str_equals('9:10', $results[0][2][0]); - $self->assert_null($results[0][2][1]); - - # search and return count, min, max, and partial - @results = (); - $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, - 'RETURN', '(MIN MAX COUNT PARTIAL 3:4)', - 'UNDELETED'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('COUNT', $results[0][1]); - $self->assert_str_equals('8', $results[0][2]); - $self->assert_str_equals('MIN', $results[0][3]); - $self->assert_str_equals('2', $results[0][4]); - $self->assert_str_equals('MAX', $results[0][5]); - $self->assert_str_equals('10', $results[0][6]); - $self->assert_str_equals('PARTIAL', $results[0][7]); - $self->assert_str_equals('3:4', $results[0][8][0]); - $self->assert_str_equals('4:5', $results[0][8][1]); +sub test_partial { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + xlog $self, "append some messages"; + my %exp; + my $N = 10; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); + + # delete the 1st and 6th + $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + my @results; + my %handlers = ( + esearch => sub { + my (undef, $esearch) = @_; + push(@results, $esearch); + }, + ); + + # search and return non-existent messages + @results = (); + my $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(PARTIAL -100:-1)', '100:300'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('PARTIAL', $results[0][1]); + $self->assert_str_equals('-1:-100', $results[0][2][0]); + $self->assert_null($results[0][2][1]); + + # search and return all messages + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '()', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('2:5,7:10', $results[0][2]); + + # attempt search with all and partial + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(ALL PARTIAL 1:2)', 'UNDELETED'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + + # search and return first 2 messages + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(PARTIAL 1:2)', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('PARTIAL', $results[0][1]); + $self->assert_str_equals('1:2', $results[0][2][0]); + $self->assert_str_equals('2:3', $results[0][2][1]); + + # search and return next 2 messages + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(PARTIAL 3:4)', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('PARTIAL', $results[0][1]); + $self->assert_str_equals('3:4', $results[0][2][0]); + $self->assert_str_equals('4:5', $results[0][2][1]); + + # flag the last message + $imaptalk->store('10', '+FLAGS', '(\\flagged)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + # search and return next 2 messages + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(PARTIAL 5:6)', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('PARTIAL', $results[0][1]); + $self->assert_str_equals('5:6', $results[0][2][0]); + $self->assert_str_equals('7:8', $results[0][2][1]); + + # search and return last 2 messages + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(PARTIAL -1:-2)', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('PARTIAL', $results[0][1]); + $self->assert_str_equals('-1:-2', $results[0][2][0]); + $self->assert_str_equals('9:10', $results[0][2][1]); + + # search and return the previous 2 messages + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(PARTIAL -3:-4)', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('PARTIAL', $results[0][1]); + $self->assert_str_equals('-3:-4', $results[0][2][0]); + $self->assert_str_equals('7:8', $results[0][2][1]); + + # search and return middle 2 messages by UID + @results = (); + $res + = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, 'RETURN', '(PARTIAL 2:3)', + 'UID', '4:8', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('PARTIAL', $results[0][1]); + $self->assert_str_equals('2:3', $results[0][2][0]); + $self->assert_str_equals('5,7', $results[0][2][1]); + + # search and return non-existent messages + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(PARTIAL 9:10)', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('PARTIAL', $results[0][1]); + $self->assert_str_equals('9:10', $results[0][2][0]); + $self->assert_null($results[0][2][1]); + + # search and return count, min, max, and partial + @results = (); + $res = $imaptalk->_imap_cmd('SEARCH', 0, \%handlers, + 'RETURN', '(MIN MAX COUNT PARTIAL 3:4)', 'UNDELETED'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('COUNT', $results[0][1]); + $self->assert_str_equals('8', $results[0][2]); + $self->assert_str_equals('MIN', $results[0][3]); + $self->assert_str_equals('2', $results[0][4]); + $self->assert_str_equals('MAX', $results[0][5]); + $self->assert_str_equals('10', $results[0][6]); + $self->assert_str_equals('PARTIAL', $results[0][7]); + $self->assert_str_equals('3:4', $results[0][8][0]); + $self->assert_str_equals('4:5', $results[0][8][1]); } 1; diff --git a/cassandane/Cassandane/Cyrus/SearchFuzzy.pm b/cassandane/Cassandane/Cyrus/SearchFuzzy.pm index c7691319f9..ad3997eb17 100644 --- a/cassandane/Cassandane/Cyrus/SearchFuzzy.pm +++ b/cassandane/Cassandane/Cyrus/SearchFuzzy.pm @@ -46,272 +46,264 @@ use Data::Dumper; use File::Temp qw(tempdir); use File::stat; use MIME::Base64 qw(encode_base64); -use Encode qw(decode encode); +use Encode qw(decode encode); use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - - my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); - $config->set( - conversations => 'on', - httpallowcompress => 'no', - httpmodules => 'jmap', - ); - return $class->SUPER::new({ - config => $config, - jmap => 1, - services => [ 'imap', 'http' ] - }, @args); +sub new { + + my ($class, @args) = @_; + my $config = Cassandane::Config->default()->clone(); + $config->set( + conversations => 'on', + httpallowcompress => 'no', + httpmodules => 'jmap', + ); + return $class->SUPER::new( + { + config => $config, + jmap => 1, + services => [ 'imap', 'http' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + + # This will be "words" if Xapian has a CJK word-tokeniser, "ngrams" + # if it doesn't, or "none" if it cannot tokenise CJK at all. + $self->{xapian_cjk_tokens} + = $self->{instance}->{buildinfo}->get('search', 'xapian_cjk_tokens') + || "none"; + + xlog $self, "Xapian CJK tokeniser '$self->{xapian_cjk_tokens}' detected.\n"; + + use experimental 'smartmatch'; + my $skipdiacrit = $self->{instance}->{config}->get('search_skipdiacrit'); + if (not defined $skipdiacrit) { + $skipdiacrit = 1; + } + if ($skipdiacrit ~~ [ 'no', 'off', 'f', 'false', '0' ]) { + $skipdiacrit = 0; + } + $self->{skipdiacrit} = $skipdiacrit; + + my $fuzzyalways = $self->{instance}->{config}->get('search_fuzzy_always'); + if ($fuzzyalways ~~ [ 'yes', 'on', 't', 'true', '1' ]) { + $self->{fuzzyalways} = 1; + } else { + $self->{fuzzyalways} = 0; + } +} - # This will be "words" if Xapian has a CJK word-tokeniser, "ngrams" - # if it doesn't, or "none" if it cannot tokenise CJK at all. - $self->{xapian_cjk_tokens} = - $self->{instance}->{buildinfo}->get('search', 'xapian_cjk_tokens') - || "none"; +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); +} - xlog $self, "Xapian CJK tokeniser '$self->{xapian_cjk_tokens}' detected.\n"; +sub create_testmessages { + my ($self) = @_; + + xlog $self, "Generate test messages."; + # Some subjects with the same verb word stem + $self->make_message("I am running") || die; + $self->make_message("I run") || die; + $self->make_message("He runs") || die; + + # Some bodies with the same word stems but different senders. We use + # the "connect" word stem since it it the first example on Xapian's + # Stemming documentation (https://xapian.org/docs/stemming.html). + # Mails from foo@example.com... + my %params; + %params = ( + from => Cassandane::Address->new( + localpart => "foo", + domain => "example.com" + ), + ); + $params{'body'} = "He has connections.", + $self->make_message("1", %params) || die; + $params{'body'} = "Gonna get myself connected."; + $self->make_message("2", %params) || die; + # ...as well as from bar@example.com. + %params = ( + from => Cassandane::Address->new( + localpart => "bar", + domain => "example.com" + ), + body => + "Einstein's gravitational theory resulted in beautiful relations connecting gravitational phenomena with the geometry of space; this was an exciting idea." + ); + $self->make_message("3", %params) || die; + + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); +} - use experimental 'smartmatch'; - my $skipdiacrit = $self->{instance}->{config}->get('search_skipdiacrit'); - if (not defined $skipdiacrit) { - $skipdiacrit = 1; +sub get_snippets { + # Previous versions of this test module used XSNIPPETS to + # assert snippets but this command got removed from Cyrus. + # Use JMAP instead. + + my ($self, $folder, $uids, $filter) = @_; + + my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + + $self->assert_not_null($jmap); + + $imap->select($folder); + my $res = $imap->fetch($uids, ['emailid']); + my %emailIdToImapUid = map { $res->{$_}{emailid}[0] => $_ } keys %$res; + + $res = $jmap->CallMethods([ + [ + 'SearchSnippet/get', + { + filter => $filter, + emailIds => [ keys %emailIdToImapUid ], + }, + 'R1' + ], + ]); + + my @snippets; + foreach (@{ $res->[0][1]{list} }) { + if ($_->{subject}) { + push(@snippets, + [ 0, $emailIdToImapUid{ $_->{emailId} }, 'SUBJECT', $_->{subject}, ]); } - if ($skipdiacrit ~~ ['no', 'off', 'f', 'false', '0']) { - $skipdiacrit = 0; + if ($_->{preview}) { + push(@snippets, + [ 0, $emailIdToImapUid{ $_->{emailId} }, 'BODY', $_->{preview}, ]); } - $self->{skipdiacrit} = $skipdiacrit; + } - my $fuzzyalways = $self->{instance}->{config}->get('search_fuzzy_always'); - if ($fuzzyalways ~~ ['yes', 'on', 't', 'true', '1']) { - $self->{fuzzyalways} = 1; - } else { - $self->{fuzzyalways} = 0 ; - } + return { snippets => [ sort { $a->[1] <=> $b->[1] } @snippets ], }; } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub run_delve { + my ($self, $dir, @args) = @_; + my $basedir = $self->{instance}->{basedir}; + my @myargs = ('delve'); + push(@myargs, @args); + push(@myargs, $dir); + $self->{instance} + ->run_command({ redirects => { stdout => "$basedir/delve.out" } }, @myargs); + open(FH, "<$basedir/delve.out") || die "can't find delve.out"; + my $data = ; + return $data; } -sub create_testmessages -{ - my ($self) = @_; - - xlog $self, "Generate test messages."; - # Some subjects with the same verb word stem - $self->make_message("I am running") || die; - $self->make_message("I run") || die; - $self->make_message("He runs") || die; - - # Some bodies with the same word stems but different senders. We use - # the "connect" word stem since it it the first example on Xapian's - # Stemming documentation (https://xapian.org/docs/stemming.html). - # Mails from foo@example.com... - my %params; - %params = ( - from => Cassandane::Address->new( - localpart => "foo", - domain => "example.com" - ), - ); - $params{'body'} ="He has connections.", - $self->make_message("1", %params) || die; - $params{'body'} = "Gonna get myself connected."; - $self->make_message("2", %params) || die; - # ...as well as from bar@example.com. - %params = ( - from => Cassandane::Address->new( - localpart => "bar", - domain => "example.com" - ), - body => "Einstein's gravitational theory resulted in beautiful relations connecting gravitational phenomena with the geometry of space; this was an exciting idea." - ); - $self->make_message("3", %params) || die; - - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); +sub delve_docs { + my ($self, $dir) = @_; + my $delveout = $self->run_delve($dir, '-V0'); + $delveout =~ s/^Value 0 for each document: //; + my @docs = split ' ', $delveout; + my @parts = map { $_ =~ /^\d+:\*P\*/ ? substr($_, 5) : () } @docs; + my @gdocs = map { $_ =~ /^\d+:\*G\*/ ? substr($_, 5) : () } @docs; + return \@gdocs, \@parts; } -sub get_snippets -{ - # Previous versions of this test module used XSNIPPETS to - # assert snippets but this command got removed from Cyrus. - # Use JMAP instead. - - my ($self, $folder, $uids, $filter) = @_; - - my $imap = $self->{store}->get_client(); - my $jmap = $self->{jmap}; - - $self->assert_not_null($jmap); - - $imap->select($folder); - my $res = $imap->fetch($uids, ['emailid']); - my %emailIdToImapUid = map { $res->{$_}{emailid}[0] => $_ } keys %$res; - - $res = $jmap->CallMethods([ - ['SearchSnippet/get', { - filter => $filter, - emailIds => [ keys %emailIdToImapUid ], - }, 'R1'], - ]); - - my @snippets; - foreach (@{$res->[0][1]{list}}) { - if ($_->{subject}) { - push(@snippets, [ - 0, - $emailIdToImapUid{$_->{emailId}}, - 'SUBJECT', - $_->{subject}, - ]); - } - if ($_->{preview}) { - push(@snippets, [ - 0, - $emailIdToImapUid{$_->{emailId}}, - 'BODY', - $_->{preview}, - ]); - } +sub start_echo_extractor { + my ($self, %params) = @_; + my $instance = $self->{instance}; + + xlog "Start extractor server with tracedir $params{tracedir}"; + my $nrequests = 0; + my $handler = sub { + my ($conn, $req) = @_; + + $nrequests++; + + if ($params{trace_delay_seconds}) { + sleep $params{trace_delay_seconds}; } - return { - snippets => [ sort { $a->[1] <=> $b->[1] } @snippets ], - }; -} + if ($params{tracedir}) { + # touch trace file in tracedir + my @paths = split(q{/}, URI->new($req->uri)->path); + my $guid = pop(@paths); + my $fname = join(q{}, + $params{tracedir}, "/req", $nrequests, "_", $req->method, "_$guid"); + open(my $fh, ">", $fname) or die "Can't open > $fname: $!"; + close $fh; + } -sub run_delve { - my ($self, $dir, @args) = @_; - my $basedir = $self->{instance}->{basedir}; - my @myargs = ('delve'); - push(@myargs, @args); - push(@myargs, $dir); - $self->{instance}->run_command({redirects => {stdout => "$basedir/delve.out"}}, @myargs); - open(FH, "<$basedir/delve.out") || die "can't find delve.out"; - my $data = ; - return $data; -} + my $res; -sub delve_docs -{ - my ($self, $dir) = @_; - my $delveout = $self->run_delve($dir, '-V0'); - $delveout =~ s/^Value 0 for each document: //; - my @docs = split ' ', $delveout; - my @parts = map { $_ =~ /^\d+:\*P\*/ ? substr($_, 5) : () } @docs; - my @gdocs = map { $_ =~ /^\d+:\*G\*/ ? substr($_, 5) : () } @docs; - return \@gdocs, \@parts; -} + if ($req->method eq 'HEAD') { + $res = HTTP::Response->new(204); + $res->content(""); + } elsif ($req->method eq 'GET') { + $res = HTTP::Response->new(404); + $res->content("nope"); + } else { + $res = HTTP::Response->new(200); + $res->content($req->content); + } + + if ($params{response_delay_seconds}) { + my $secs = $params{response_delay_seconds}; + if (ref($secs) eq 'ARRAY') { + $secs = ($nrequests <= scalar @$secs) ? $secs->[ $nrequests - 1 ] : 0; + } + sleep $secs; + } + + $conn->send_response($res); + }; -sub start_echo_extractor -{ - my ($self, %params) = @_; - my $instance = $self->{instance}; - - xlog "Start extractor server with tracedir $params{tracedir}"; - my $nrequests = 0; - my $handler = sub { - my ($conn, $req) = @_; - - $nrequests++; - - if ($params{trace_delay_seconds}) { - sleep $params{trace_delay_seconds}; - } - - if ($params{tracedir}) { - # touch trace file in tracedir - my @paths = split(q{/}, URI->new($req->uri)->path); - my $guid = pop(@paths); - my $fname = join(q{}, - $params{tracedir}, "/req", $nrequests, "_", $req->method, "_$guid"); - open(my $fh, ">", $fname) or die "Can't open > $fname: $!"; - close $fh; - } - - my $res; - - if ($req->method eq 'HEAD') { - $res = HTTP::Response->new(204); - $res->content(""); - } elsif ($req->method eq 'GET') { - $res = HTTP::Response->new(404); - $res->content("nope"); - } else { - $res = HTTP::Response->new(200); - $res->content($req->content); - } - - if ($params{response_delay_seconds}) { - my $secs = $params{response_delay_seconds}; - if (ref($secs) eq 'ARRAY') { - $secs = ($nrequests <= scalar @$secs) ? - $secs->[$nrequests-1] : 0; - } - sleep $secs; - } - - $conn->send_response($res); - }; - - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); - $instance->start_httpd($handler, $uri->port()); + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); + $instance->start_httpd($handler, $uri->port()); } -sub squatter_attachextract_cache_run -{ - my ($self, $cachedir, @squatterArgs) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); - - xlog "Append emails with identical attachments"; - $self->make_message("msg1", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/pdf\r\n" - ."\r\n" - ."attachterm" - ."\r\n--123456789abcdef--\r\n" - ) || die; - $self->make_message("msg2", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/pdf\r\n" - ."\r\n" - ."attachterm" - ."\r\n--123456789abcdef--\r\n" - ) || die; - - xlog "Run squatter with cachedir $cachedir"; - $self->{instance}->run_command({cyrus => 1}, - 'squatter', "--attachextract-cache-dir=$cachedir", @squatterArgs); +sub squatter_attachextract_cache_run { + my ($self, $cachedir, @squatterArgs) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); + + xlog "Append emails with identical attachments"; + $self->make_message( + "msg1", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/pdf\r\n" . "\r\n" + . "attachterm" + . "\r\n--123456789abcdef--\r\n" + ) || die; + $self->make_message( + "msg2", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/pdf\r\n" . "\r\n" + . "attachterm" + . "\r\n--123456789abcdef--\r\n" + ) || die; + + xlog "Run squatter with cachedir $cachedir"; + $self->{instance}->run_command( + { cyrus => 1 }, + 'squatter', "--attachextract-cache-dir=$cachedir", + @squatterArgs + ); } use Cassandane::Tiny::Loader 'tiny-tests/SearchFuzzy'; diff --git a/cassandane/Cassandane/Cyrus/SearchSquat.pm b/cassandane/Cassandane/Cyrus/SearchSquat.pm index 75e57462ea..cae95f70e3 100644 --- a/cassandane/Cassandane/Cyrus/SearchSquat.pm +++ b/cassandane/Cassandane/Cyrus/SearchSquat.pm @@ -49,230 +49,234 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Util::Slurp; -sub new -{ - my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); - $config->set(conversations => 'on'); - return $class->SUPER::new({ config => $config }, @args); +sub new { + my ($class, @args) = @_; + my $config = Cassandane::Config->default()->clone(); + $config->set(conversations => 'on'); + return $class->SUPER::new({ config => $config }, @args); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub run_squatter -{ - my ($self, @args) = @_; - - my $outfname = $self->{instance}->{basedir} . "/squatter.out"; - my $errfname = $self->{instance}->{basedir} . "/squatter.err"; - - $self->{instance}->run_command({ - cyrus => 1, - redirects => { - stdout => $outfname, - stderr => $errfname, - }, - }, - 'squatter', - @args - ); - - return (slurp_file($outfname), slurp_file($errfname)); +sub run_squatter { + my ($self, @args) = @_; + + my $outfname = $self->{instance}->{basedir} . "/squatter.out"; + my $errfname = $self->{instance}->{basedir} . "/squatter.err"; + + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { + stdout => $outfname, + stderr => $errfname, + }, + }, + 'squatter', + @args + ); + + return (slurp_file($outfname), slurp_file($errfname)); } # XXX version gated to 3.4+ for now to keep travis happy, but if we # XXX backport the fix we should change or remove the gate... sub test_simple - :SearchEngineSquat :min_version_3_4 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); - - $self->make_message("term2", body => "term1") || die; - $self->make_message("term2", body => "term1") || die; - $self->make_message("term1", body => "term2") || die; - $self->make_message("term3", body => "term4") || die; - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my @tests = ({ - search => ['body', 'term1'], - wantUids => [1,2], - }, { - search => ['text', 'term1'], - wantUids => [1,2,3], - }, { - search => ['subject', 'term2'], - wantUids => [1,2], - }, { - search => ['subject', 'term3'], - wantUids => [4], - }, { - search => ['body', 'term4'], - wantUids => [4], - }, { - search => ['fuzzy', 'body', 'term4'], - wantUids => [4], - }, { - # we don't index content-type, make sure we actually didn't - search => ['from', 'text/plain'], - wantUids => [], - }, { - # we don't index content-type, make sure we actually didn't - search => ['to', 'text/plain'], - wantUids => [], - }, { - # we don't index content-type, make sure we actually didn't - search => ['subject', 'text/plain'], - wantUids => [], - }); - - foreach (@tests) { - $self->{instance}->getsyslog(); - - my $uids = $imap->search(@{$_->{search}}) || die; - $self->assert_deep_equals($_->{wantUids}, $uids); - - $self->assert_syslog_matches($self->{instance}, qr{Squat run}); + : SearchEngineSquat : min_version_3_4 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); + + $self->make_message("term2", body => "term1") || die; + $self->make_message("term2", body => "term1") || die; + $self->make_message("term1", body => "term2") || die; + $self->make_message("term3", body => "term4") || die; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my @tests = ( + { + search => [ 'body', 'term1' ], + wantUids => [ 1, 2 ], + }, + { + search => [ 'text', 'term1' ], + wantUids => [ 1, 2, 3 ], + }, + { + search => [ 'subject', 'term2' ], + wantUids => [ 1, 2 ], + }, + { + search => [ 'subject', 'term3' ], + wantUids => [4], + }, + { + search => [ 'body', 'term4' ], + wantUids => [4], + }, + { + search => [ 'fuzzy', 'body', 'term4' ], + wantUids => [4], + }, + { + # we don't index content-type, make sure we actually didn't + search => [ 'from', 'text/plain' ], + wantUids => [], + }, + { + # we don't index content-type, make sure we actually didn't + search => [ 'to', 'text/plain' ], + wantUids => [], + }, + { + # we don't index content-type, make sure we actually didn't + search => [ 'subject', 'text/plain' ], + wantUids => [], } -} + ); -sub test_one_doc_per_message - :SearchEngineSquat :min_version_3_4 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); - - # make some messages where the only indexed field is the body - foreach my $body (qw(term1 term1 term2 term4)) { - $self->make_message(undef, - from => undef, - to => undef, - body => $body) || die; - } + foreach (@tests) { + $self->{instance}->getsyslog(); - # make enough other messages such that an incremental reindex - # will need to realloc doc_ID_map - for (1..50) { - $self->make_message() || die; - } + my $uids = $imap->search(@{ $_->{search} }) || die; + $self->assert_deep_equals($_->{wantUids}, $uids); - $self->run_squatter(); - - my @tests = ({ - search => ['body', 'term1'], - wantUids => [1,2], - }, { - search => ['body', 'term2'], - wantUids => [3], - }, { - search => ['body', 'term3'], - wantUids => [], - }, { - search => ['body', 'term4'], - wantUids => [4], - }); - - foreach (@tests) { - $self->{instance}->getsyslog(); - - my $uids = $imap->search(@{$_->{search}}) || die; - $self->assert_deep_equals($_->{wantUids}, $uids); - - $self->assert_syslog_matches($self->{instance}, qr/Squat run/); - } + $self->assert_syslog_matches($self->{instance}, qr{Squat run}); + } +} - # make some more messages - foreach my $body (qw(term5 term6 term6 term8)) { - $self->make_message(undef, - from => undef, - to => undef, - body => $body) || die; +sub test_one_doc_per_message + : SearchEngineSquat : min_version_3_4 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); + + # make some messages where the only indexed field is the body + foreach my $body (qw(term1 term1 term2 term4)) { + $self->make_message( + undef, + from => undef, + to => undef, + body => $body + ) || die; + } + + # make enough other messages such that an incremental reindex + # will need to realloc doc_ID_map + for (1 .. 50) { + $self->make_message() || die; + } + + $self->run_squatter(); + + my @tests = ( + { + search => [ 'body', 'term1' ], + wantUids => [ 1, 2 ], + }, + { + search => [ 'body', 'term2' ], + wantUids => [3], + }, + { + search => [ 'body', 'term3' ], + wantUids => [], + }, + { + search => [ 'body', 'term4' ], + wantUids => [4], } + ); + + foreach (@tests) { + $self->{instance}->getsyslog(); - # incremental reindex - my (undef, $err) = $self->run_squatter('-i', '-v'); - $self->assert_matches(qr{indexed 4 messages}, $err); - - push @tests, { - search => ['body', 'term5'], - wantUids => [55], - }, { - search => ['body', 'term6'], - wantUids => [56, 57], - }, { - search => ['body', 'term7'], - wantUids => [], - }, { - search => ['body', 'term8'], - wantUids => [58], + my $uids = $imap->search(@{ $_->{search} }) || die; + $self->assert_deep_equals($_->{wantUids}, $uids); + + $self->assert_syslog_matches($self->{instance}, qr/Squat run/); + } + + # make some more messages + foreach my $body (qw(term5 term6 term6 term8)) { + $self->make_message( + undef, + from => undef, + to => undef, + body => $body + ) || die; + } + + # incremental reindex + my (undef, $err) = $self->run_squatter('-i', '-v'); + $self->assert_matches(qr{indexed 4 messages}, $err); + + push @tests, + { + search => [ 'body', 'term5' ], + wantUids => [55], + }, + { + search => [ 'body', 'term6' ], + wantUids => [ 56, 57 ], + }, + { + search => [ 'body', 'term7' ], + wantUids => [], + }, + { + search => [ 'body', 'term8' ], + wantUids => [58], }; - # better not be any off-by-one errors in search results! - foreach (@tests) { - $self->{instance}->getsyslog(); + # better not be any off-by-one errors in search results! + foreach (@tests) { + $self->{instance}->getsyslog(); - my $uids = $imap->search(@{$_->{search}}) || die; - $self->assert_deep_equals($_->{wantUids}, $uids); + my $uids = $imap->search(@{ $_->{search} }) || die; + $self->assert_deep_equals($_->{wantUids}, $uids); - $self->assert_syslog_matches($self->{instance}, qr/Squat run/); - } + $self->assert_syslog_matches($self->{instance}, qr/Squat run/); + } } # XXX version gated to 3.4+ for now to keep travis happy, but if we # XXX backport the fix we should change or remove the gate... sub test_skip_unmodified - :SearchEngineSquat :min_version_3_4 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); + : SearchEngineSquat : min_version_3_4 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); - $self->make_message() || die; + $self->make_message() || die; - sleep(1); + sleep(1); - $self->{instance}->getsyslog(); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - $self->assert_syslog_does_not_match($self->{instance}, - qr{Squat skipping mailbox}); + $self->{instance}->getsyslog(); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + $self->assert_syslog_does_not_match($self->{instance}, + qr{Squat skipping mailbox}); - $self->{instance}->getsyslog(); - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v', '-s', '0'); - $self->assert_syslog_matches($self->{instance}, - qr{Squat skipping mailbox}); + $self->{instance}->getsyslog(); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-v', '-s', '0'); + $self->assert_syslog_matches($self->{instance}, qr{Squat skipping mailbox}); } sub test_nonincremental - :SearchEngineSquat -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); - my $n = 0; - - for (1..5) { - # make a new message - $self->make_message(); - $n++; - - # do a full reindex - my (undef, $err) = $self->run_squatter('-vv'); - - # better have indexed them all, not just the new one! - $self->assert_matches(qr{indexed $n messages}, $err); - } - - # make a message with no subject, to, or from - $self->make_message(undef, to => undef, from => undef); + : SearchEngineSquat { + my ($self) = @_; + my $imap = $self->{store}->get_client(); + my $n = 0; + + for (1 .. 5) { + # make a new message + $self->make_message(); $n++; # do a full reindex @@ -280,275 +284,281 @@ sub test_nonincremental # better have indexed them all, not just the new one! $self->assert_matches(qr{indexed $n messages}, $err); -} - -sub test_incremental - :SearchEngineSquat -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); - my $err; - - # some initial messages - enough to definitely force a doc_ID_map realloc - # when incrementally reindexing later - for (1..50) { - $self->make_message(); - } - - # make a message with no subject, to, or from - # this used to trigger an indexing bug and produce a corrupt index, - # which would lead to a crash during incremental reindex - my $weird = $self->make_message(undef, to => undef, from => undef); - xlog "weird message:\n" . $weird->as_string(); - - sleep(1); - - # initial non-incremental index - (undef, $err) = $self->run_squatter('-vv'); - $self->assert_matches(qr{indexed 51 messages}, $err); - - # incremental reindex with no changes to mailbox - (undef, $err) = $self->run_squatter('-i', '-vv'); - $self->assert_matches(qr{indexed 0 messages}, $err); - - # delete, expunge, and cyr_expire some messages - # n.b. this does not unindex the message in any way - $imap->store('5', '+flags', '(\\Deleted)'); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); - $imap->expunge(); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); - $self->{instance}->run_command({cyrus => 1}, 'cyr_expire', '-X', '0'); - - # incremental reindex after one message expunged - (undef, $err) = $self->run_squatter('-i', '-vv'); - $self->assert_matches(qr{indexed 0 messages}, $err); - - # make one new message - for (1) { - $self->make_message(); - } - sleep(1); + } - # incremental reindex after one new message - (undef, $err) = $self->run_squatter('-i', '-vv'); - $self->assert_matches(qr{indexed 1 messages}, $err); + # make a message with no subject, to, or from + $self->make_message(undef, to => undef, from => undef); + $n++; - # incremental reindex with no changes to mailbox - (undef, $err) = $self->run_squatter('-i', '-vv'); - $self->assert_matches(qr{indexed 0 messages}, $err); + # do a full reindex + my (undef, $err) = $self->run_squatter('-vv'); - # make some new messages - for (1..10) { - $self->make_message(); - } - sleep(1); - - # incremental reindex after new messages - (undef, $err) = $self->run_squatter('-i', '-vv'); - $self->assert_matches(qr{indexed 10 messages}, $err); + # better have indexed them all, not just the new one! + $self->assert_matches(qr{indexed $n messages}, $err); +} - # incremental reindex with no changes to mailbox - (undef, $err) = $self->run_squatter('-i', '-vv'); - $self->assert_matches(qr{indexed 0 messages}, $err); +sub test_incremental + : SearchEngineSquat { + my ($self) = @_; + my $imap = $self->{store}->get_client(); + my $err; + + # some initial messages - enough to definitely force a doc_ID_map realloc + # when incrementally reindexing later + for (1 .. 50) { + $self->make_message(); + } + + # make a message with no subject, to, or from + # this used to trigger an indexing bug and produce a corrupt index, + # which would lead to a crash during incremental reindex + my $weird = $self->make_message(undef, to => undef, from => undef); + xlog "weird message:\n" . $weird->as_string(); + + sleep(1); + + # initial non-incremental index + (undef, $err) = $self->run_squatter('-vv'); + $self->assert_matches(qr{indexed 51 messages}, $err); + + # incremental reindex with no changes to mailbox + (undef, $err) = $self->run_squatter('-i', '-vv'); + $self->assert_matches(qr{indexed 0 messages}, $err); + + # delete, expunge, and cyr_expire some messages + # n.b. this does not unindex the message in any way + $imap->store('5', '+flags', '(\\Deleted)'); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); + $imap->expunge(); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X', '0'); + + # incremental reindex after one message expunged + (undef, $err) = $self->run_squatter('-i', '-vv'); + $self->assert_matches(qr{indexed 0 messages}, $err); + + # make one new message + for (1) { + $self->make_message(); + } + sleep(1); + + # incremental reindex after one new message + (undef, $err) = $self->run_squatter('-i', '-vv'); + $self->assert_matches(qr{indexed 1 messages}, $err); + + # incremental reindex with no changes to mailbox + (undef, $err) = $self->run_squatter('-i', '-vv'); + $self->assert_matches(qr{indexed 0 messages}, $err); + + # make some new messages + for (1 .. 10) { + $self->make_message(); + } + sleep(1); + + # incremental reindex after new messages + (undef, $err) = $self->run_squatter('-i', '-vv'); + $self->assert_matches(qr{indexed 10 messages}, $err); + + # incremental reindex with no changes to mailbox + (undef, $err) = $self->run_squatter('-i', '-vv'); + $self->assert_matches(qr{indexed 0 messages}, $err); } sub test_relocate_legacy_searchdb - :DelayedDelete :min_version_3_6 :MailboxLegacyDirs - :Admin :SearchEngineSquat :NoAltNamespace :VirtDomains -{ - my ($self) = @_; + : DelayedDelete : min_version_3_6 : MailboxLegacyDirs + : Admin : SearchEngineSquat : NoAltNamespace : VirtDomains { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser\@example.com"; - my $subfolder = "user.magicuser.foo\@example.com"; + my $inbox = "user.magicuser\@example.com"; + my $subfolder = "user.magicuser.foo\@example.com"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, 'relocate_by_id', '-u' => "magicuser\@example.com" ); + $self->{instance}->run_command({ cyrus => 1 }, + 'relocate_by_id', '-u' => "magicuser\@example.com"); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); } sub test_relocate_legacy_nosearchdb - :DelayedDelete :min_version_3_6 :MailboxLegacyDirs - :Admin :SearchEngineSquat :NoAltNamespace :VirtDomains -{ - my ($self) = @_; + : DelayedDelete : min_version_3_6 : MailboxLegacyDirs + : Admin : SearchEngineSquat : NoAltNamespace : VirtDomains { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser\@example.com"; - my $subfolder = "user.magicuser.foo\@example.com"; + my $inbox = "user.magicuser\@example.com"; + my $subfolder = "user.magicuser.foo\@example.com"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Don't create the search database! - # A user who's never been indexed should still relocate cleanly + # Don't create the search database! + # A user who's never been indexed should still relocate cleanly - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, 'relocate_by_id', '-u' => "magicuser\@example.com" ); + $self->{instance}->run_command({ cyrus => 1 }, + 'relocate_by_id', '-u' => "magicuser\@example.com"); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); } sub test_unindexed - :SearchEngineSquat :min_version_3_4 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); + : SearchEngineSquat : min_version_3_4 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); - $self->make_message("needle 1", body => "needle") || die; - $self->make_message("xxxxxx 2", body => "xxxxxx") || die; + $self->make_message("needle 1", body => "needle") || die; + $self->make_message("xxxxxx 2", body => "xxxxxx") || die; - $self->run_squatter; + $self->run_squatter; - my $uids = $imap->search('text', 'needle'); - $self->assert_deep_equals([1], $uids); + my $uids = $imap->search('text', 'needle'); + $self->assert_deep_equals([1], $uids); - $self->make_message("needle 3", body => "needle") || die; - $self->make_message("xxxxxx 4", body => "xxxxxx") || die; + $self->make_message("needle 3", body => "needle") || die; + $self->make_message("xxxxxx 4", body => "xxxxxx") || die; - # Do not rerun squatter. Make sure search only returns - # a matching unindexed message. + # Do not rerun squatter. Make sure search only returns + # a matching unindexed message. - $uids = $imap->search('text', 'needle'); - $self->assert_deep_equals([1,3], $uids); + $uids = $imap->search('text', 'needle'); + $self->assert_deep_equals([ 1, 3 ], $uids); } sub test_unindexed_fuzzy - :SearchEngineSquat :min_version_3_4 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); + : SearchEngineSquat : min_version_3_4 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); - $self->make_message("needle 1", body => "needle") || die; - $self->make_message("xxxxxx 2", body => "xxxxxx") || die; + $self->make_message("needle 1", body => "needle") || die; + $self->make_message("xxxxxx 2", body => "xxxxxx") || die; - $self->run_squatter; + $self->run_squatter; - my $uids = $imap->search('fuzzy', 'body', 'needle'); - $self->assert_deep_equals([1], $uids); + my $uids = $imap->search('fuzzy', 'body', 'needle'); + $self->assert_deep_equals([1], $uids); - $self->make_message("needle 3", body => "needle") || die; - $self->make_message("xxxxxx 4", body => "xxxxxx") || die; + $self->make_message("needle 3", body => "needle") || die; + $self->make_message("xxxxxx 4", body => "xxxxxx") || die; - # Do not rerun squatter. Make sure search only returns - # a matching unindexed message. + # Do not rerun squatter. Make sure search only returns + # a matching unindexed message. - $uids = $imap->search('fuzzy', 'body', 'needle'); - $self->assert_deep_equals([1,3], $uids); + $uids = $imap->search('fuzzy', 'body', 'needle'); + $self->assert_deep_equals([ 1, 3 ], $uids); } sub test_unindexed_since - :SearchEngineSquat :min_version_3_4 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); + : SearchEngineSquat : min_version_3_4 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); - my $past_dt = DateTime->last_day_of_month(year => 2023, month => 12); + my $past_dt = DateTime->last_day_of_month(year => 2023, month => 12); - $self->make_message("needle 1", body => "needle") || die; - $self->make_message("xxxxxx 2", body => "xxxxxx") || die; - $self->make_message("old 3", date => $past_dt, body => "needle") || die; + $self->make_message("needle 1", body => "needle") || die; + $self->make_message("xxxxxx 2", body => "xxxxxx") || die; + $self->make_message("old 3", date => $past_dt, body => "needle") || die; - $self->run_squatter; + $self->run_squatter; - my $uids = $imap->search('text', 'needle', 'since', '1-Feb-2024'); - $self->assert_deep_equals([1], $uids); + my $uids = $imap->search('text', 'needle', 'since', '1-Feb-2024'); + $self->assert_deep_equals([1], $uids); - $uids = $imap->search('text', 'needle', 'not', 'since', '1-Feb-2024'); - $self->assert_deep_equals([3], $uids); + $uids = $imap->search('text', 'needle', 'not', 'since', '1-Feb-2024'); + $self->assert_deep_equals([3], $uids); - $self->make_message("needle 4", body => "needle") || die; - $self->make_message("xxxxxx 5", body => "xxxxxx") || die; - $self->make_message("old 6", date => $past_dt, body => "needle") || die; + $self->make_message("needle 4", body => "needle") || die; + $self->make_message("xxxxxx 5", body => "xxxxxx") || die; + $self->make_message("old 6", date => $past_dt, body => "needle") || die; - # Do not rerun squatter. Make sure search only returns - # a matching unindexed message. + # Do not rerun squatter. Make sure search only returns + # a matching unindexed message. - $uids = $imap->search('text', 'needle', 'since', '1-Feb-2024'); - $self->assert_deep_equals([1,4], $uids); + $uids = $imap->search('text', 'needle', 'since', '1-Feb-2024'); + $self->assert_deep_equals([ 1, 4 ], $uids); - $uids = $imap->search('text', 'needle', 'not', 'since', '1-Feb-2024'); - $self->assert_deep_equals([3, 6], $uids); + $uids = $imap->search('text', 'needle', 'not', 'since', '1-Feb-2024'); + $self->assert_deep_equals([ 3, 6 ], $uids); } sub test_since - :SearchEngineSquat :min_version_3_4 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); + : SearchEngineSquat : min_version_3_4 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); - my $past_dt = DateTime->last_day_of_month(year => 2023, month => 12); + my $past_dt = DateTime->last_day_of_month(year => 2023, month => 12); - $self->make_message("needle 1", body => "needle") || die; - $self->make_message("xxxxxx 2", body => "xxxxxx") || die; - $self->make_message("old 3", date => $past_dt, body => "needle") || die; + $self->make_message("needle 1", body => "needle") || die; + $self->make_message("xxxxxx 2", body => "xxxxxx") || die; + $self->make_message("old 3", date => $past_dt, body => "needle") || die; - $self->run_squatter; + $self->run_squatter; - my $uids = $imap->search('since', '1-Feb-2024'); - $self->assert_deep_equals([1,2], $uids); + my $uids = $imap->search('since', '1-Feb-2024'); + $self->assert_deep_equals([ 1, 2 ], $uids); - $uids = $imap->search('not', 'since', '1-Feb-2024'); - $self->assert_deep_equals([3], $uids); + $uids = $imap->search('not', 'since', '1-Feb-2024'); + $self->assert_deep_equals([3], $uids); - $self->make_message("needle 4", body => "needle") || die; - $self->make_message("xxxxxx 5", body => "xxxxxx") || die; - $self->make_message("old 6", date => $past_dt, body => "needle") || die; + $self->make_message("needle 4", body => "needle") || die; + $self->make_message("xxxxxx 5", body => "xxxxxx") || die; + $self->make_message("old 6", date => $past_dt, body => "needle") || die; - # Do not rerun squatter. Make sure search only returns - # a matching unindexed message. + # Do not rerun squatter. Make sure search only returns + # a matching unindexed message. - $uids = $imap->search('since', '1-Feb-2024'); - $self->assert_deep_equals([1,2,4,5], $uids); + $uids = $imap->search('since', '1-Feb-2024'); + $self->assert_deep_equals([ 1, 2, 4, 5 ], $uids); - $uids = $imap->search('not', 'since', '1-Feb-2024'); - $self->assert_deep_equals([3,6], $uids); + $uids = $imap->search('not', 'since', '1-Feb-2024'); + $self->assert_deep_equals([ 3, 6 ], $uids); } 1; diff --git a/cassandane/Cassandane/Cyrus/Sieve.pm b/cassandane/Cassandane/Cyrus/Sieve.pm index b2cb8062d4..e4e999fc2f 100644 --- a/cassandane/Cassandane/Cyrus/Sieve.pm +++ b/cassandane/Cassandane/Cyrus/Sieve.pm @@ -51,327 +51,330 @@ use Date::Parse; use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -use Encode qw(decode); +use Encode qw(decode); use MIME::Base64 qw(encode_base64); use Data::Dumper; -sub new -{ - my $class = shift; - my $config = Cassandane::Config->default()->clone(); - - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj == 3 && $min == 0) { - # need to explicitly add 'body' to sieve_extensions for 3.0 - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags mailbox mboxmetadata servermetadata variables " . - "body"); - } - elsif ($maj < 3) { - # also for 2.5 (the earliest Cyrus that Cassandane can test) - $config->set(sieve_extensions => - "fileinto reject vacation vacation-seconds imap4flags notify " . - "envelope relational regex subaddress copy date index " . - "imap4flags body"); - } - $config->set(sievenotifier => 'mailto'); - $config->set(caldav_realm => 'Cassandane'); - $config->set(httpmodules => ['caldav', 'jmap']); - $config->set(calendar_user_address_set => 'example.com'); - $config->set(httpallowcompress => 'no'); - $config->set(caldav_historical_age => -1); - $config->set(icalendar_max_size => 100000); - $config->set(virtdomains => 'no'); - $config->set(jmap_nonstandard_extensions => 'yes'); - $config->set(conversations => 'yes'); - - return $class->SUPER::new({ - config => $config, - deliver => 1, - jmap => 1, - services => [ 'imap', 'sieve' ], - adminstore => 1, - }, @_); +sub new { + my $class = shift; + my $config = Cassandane::Config->default()->clone(); + + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj == 3 && $min == 0) { + # need to explicitly add 'body' to sieve_extensions for 3.0 + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags mailbox mboxmetadata servermetadata variables " + . "body"); + } elsif ($maj < 3) { + # also for 2.5 (the earliest Cyrus that Cassandane can test) + $config->set(sieve_extensions => + "fileinto reject vacation vacation-seconds imap4flags notify " + . "envelope relational regex subaddress copy date index " + . "imap4flags body"); + } + $config->set(sievenotifier => 'mailto'); + $config->set(caldav_realm => 'Cassandane'); + $config->set(httpmodules => [ 'caldav', 'jmap' ]); + $config->set(calendar_user_address_set => 'example.com'); + $config->set(httpallowcompress => 'no'); + $config->set(caldav_historical_age => -1); + $config->set(icalendar_max_size => 100000); + $config->set(virtdomains => 'no'); + $config->set(jmap_nonstandard_extensions => 'yes'); + $config->set(conversations => 'yes'); + + return $class->SUPER::new( + { + config => $config, + deliver => 1, + jmap => 1, + services => [ 'imap', 'sieve' ], + adminstore => 1, + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); - if ($self->{jmap}) { - $self->{jmap}->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'urn:ietf:params:jmap:calendars:preferences', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - ]); - } +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + if ($self->{jmap}) { + $self->{jmap}->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'urn:ietf:params:jmap:calendars:preferences', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + ]); + } } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub read_errors -{ - my ($filename) = @_; - - my @errors; - if ( -f $filename ) - { - open FH, '<', $filename - or die "Cannot open $filename for reading: $!"; - @errors = readline(FH); - close FH; - if (get_verbose) - { - xlog "errors: "; - map { xlog $_ } @errors; - } - # Hack to remove spurious junk generated when - # running coveraged code under ggcov-run - @errors = grep { ! m/libggcov:/ && ! m/profiling:/ } @errors; +sub read_errors { + my ($filename) = @_; + + my @errors; + if (-f $filename) { + open FH, '<', $filename + or die "Cannot open $filename for reading: $!"; + @errors = readline(FH); + close FH; + if (get_verbose) { + xlog "errors: "; + map { xlog $_ } @errors; } - return @errors; + # Hack to remove spurious junk generated when + # running coveraged code under ggcov-run + @errors = grep { !m/libggcov:/ && !m/profiling:/ } @errors; + } + return @errors; } -sub compile_sievec -{ - my ($self, $name, $script) = @_; +sub compile_sievec { + my ($self, $name, $script) = @_; - my $basedir = $self->{instance}->{basedir}; + my $basedir = $self->{instance}->{basedir}; - xlog $self, "Checking preconditions for compiling sieve script $name"; + xlog $self, "Checking preconditions for compiling sieve script $name"; - $self->assert_not_file_test("$basedir/$name.script", '-f'); - $self->assert_not_file_test("$basedir/$name.bc", '-f'); - $self->assert_not_file_test("$basedir/$name.errors", '-f'); + $self->assert_not_file_test("$basedir/$name.script", '-f'); + $self->assert_not_file_test("$basedir/$name.bc", '-f'); + $self->assert_not_file_test("$basedir/$name.errors", '-f'); - open(FH, '>', "$basedir/$name.script") - or die "Cannot open $basedir/$name.script for writing: $!"; - print FH $script; - close(FH); + open(FH, '>', "$basedir/$name.script") + or die "Cannot open $basedir/$name.script for writing: $!"; + print FH $script; + close(FH); - xlog $self, "Running sievec on script $name"; - my $result = $self->{instance}->run_command( - { - cyrus => 1, - redirects => { stderr => "$basedir/$name.errors" }, - handlers => { - exited_normally => sub { return 'success'; }, - exited_abnormally => sub { return 'failure'; }, - }, - }, - "sievec", "$basedir/$name.script", "$basedir/$name.bc"); - - # Read the errors file in @errors - my (@errors) = read_errors("$basedir/$name.errors"); - - if ($result eq 'success') + xlog $self, "Running sievec on script $name"; + my $result = $self->{instance}->run_command( { - xlog $self, "Checking that sievec wrote the output .bc file"; - $self->assert_file_test("$basedir/$name.bc", '-f'); - xlog $self, "Checking that sievec didn't write anything to stderr"; - $self->assert_equals(0, scalar(@errors)); - } - elsif ($result eq 'failure') - { - xlog $self, "Checking that sievec didn't write the output .bc file"; - $self->assert_not_file_test("$basedir/$name.bc", '-f'); - } + cyrus => 1, + redirects => { stderr => "$basedir/$name.errors" }, + handlers => { + exited_normally => sub { return 'success'; }, + exited_abnormally => sub { return 'failure'; }, + }, + }, + "sievec", + "$basedir/$name.script", + "$basedir/$name.bc" + ); + + # Read the errors file in @errors + my (@errors) = read_errors("$basedir/$name.errors"); + + if ($result eq 'success') { + xlog $self, "Checking that sievec wrote the output .bc file"; + $self->assert_file_test("$basedir/$name.bc", '-f'); + xlog $self, "Checking that sievec didn't write anything to stderr"; + $self->assert_equals(0, scalar(@errors)); + } elsif ($result eq 'failure') { + xlog $self, "Checking that sievec didn't write the output .bc file"; + $self->assert_not_file_test("$basedir/$name.bc", '-f'); + } - return ($result, join("\n", @errors)); + return ($result, join("\n", @errors)); } -sub compile_timsieved -{ - my ($self, $name, $script) = @_; +sub compile_timsieved { + my ($self, $name, $script) = @_; - my $basedir = $self->{instance}->{basedir}; - my $bindir = $self->{instance}->{cyrus_destdir} . - $self->{instance}->{cyrus_prefix} . '/bin'; - my $srv = $self->{instance}->get_service('sieve'); + my $basedir = $self->{instance}->{basedir}; + my $bindir = $self->{instance}->{cyrus_destdir} + . $self->{instance}->{cyrus_prefix} . '/bin'; + my $srv = $self->{instance}->get_service('sieve'); - xlog $self, "Checking preconditions for compiling sieve script $name"; + xlog $self, "Checking preconditions for compiling sieve script $name"; - $self->assert_not_file_test("$basedir/$name.script", '-f'); - $self->assert_not_file_test("$basedir/$name.errors", '-f'); + $self->assert_not_file_test("$basedir/$name.script", '-f'); + $self->assert_not_file_test("$basedir/$name.errors", '-f'); - open(FH, '>', "$basedir/$name.script") - or die "Cannot open $basedir/$name.script for writing: $!"; - print FH $script; - close(FH); + open(FH, '>', "$basedir/$name.script") + or die "Cannot open $basedir/$name.script for writing: $!"; + print FH $script; + close(FH); - if (! -f "$basedir/sieve.passwd" ) - { - open(FH, '>', "$basedir/sieve.passwd") - or die "Cannot open $basedir/sieve.passwd for writing: $!"; - print FH "\ntestpw\n"; - close(FH); - } + if (!-f "$basedir/sieve.passwd") { + open(FH, '>', "$basedir/sieve.passwd") + or die "Cannot open $basedir/sieve.passwd for writing: $!"; + print FH "\ntestpw\n"; + close(FH); + } - xlog $self, "Running installsieve on script $name"; - my $result = $self->{instance}->run_command({ - redirects => { - # No cyrus => 1 as installsieve is a Perl - # script which doesn't need Valgrind and - # doesn't understand the Cyrus -C option - stdin => "$basedir/sieve.passwd", - stderr => "$basedir/$name.errors" - }, - handlers => { - exited_normally => sub { return 'success'; }, - exited_abnormally => sub { return 'failure'; }, - }, - }, - "$bindir/installsieve", - "-i", "$basedir/$name.script", - "-u", "cassandane", - $srv->host() . ":" . $srv->port()); - - # Read the errors file in @errors - my (@errors) = read_errors("$basedir/$name.errors"); - - if ($result eq 'success') + xlog $self, "Running installsieve on script $name"; + my $result = $self->{instance}->run_command( { - xlog $self, "Checking that installsieve didn't write anything to stderr"; - $self->assert_equals(0, scalar(@errors)); - } - - return ($result, join("\n", @errors)); + redirects => { + # No cyrus => 1 as installsieve is a Perl + # script which doesn't need Valgrind and + # doesn't understand the Cyrus -C option + stdin => "$basedir/sieve.passwd", + stderr => "$basedir/$name.errors" + }, + handlers => { + exited_normally => sub { return 'success'; }, + exited_abnormally => sub { return 'failure'; }, + }, + }, + "$bindir/installsieve", + "-i", + "$basedir/$name.script", + "-u", + "cassandane", + $srv->host() . ":" . $srv->port() + ); + + # Read the errors file in @errors + my (@errors) = read_errors("$basedir/$name.errors"); + + if ($result eq 'success') { + xlog $self, "Checking that installsieve didn't write anything to stderr"; + $self->assert_equals(0, scalar(@errors)); + } + + return ($result, join("\n", @errors)); } -sub compile_sieve_script -{ - my ($self, $name, $script) = @_; +sub compile_sieve_script { + my ($self, $name, $script) = @_; - my $meth = 'compile_' . $self->{compile_method}; - return $self->$meth($name, $script); + my $meth = 'compile_' . $self->{compile_method}; + return $self->$meth($name, $script); } -sub badscript_common -{ - my ($self) = @_; - - my $res; - my $errs; - - ($res, $errs) = $self->compile_sieve_script('badrequire', - "require [\"nonesuch\"];\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/Unsupported feature.*nonesuch/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badreject1', - "reject \"foo\"\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/reject.*MUST be enabled/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badreject2', - "require [\"reject\"];\nreject\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/syntax error/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badreject3', - "require [\"reject\"];\nreject 42\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/syntax error/, $errs); - - # TODO: test UTF-8 verification of the string parameter - - ($res, $errs) = $self->compile_sieve_script('badfileinto1', - "fileinto \"foo\"\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/fileinto.*MUST be enabled/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badfileinto2', - "require [\"fileinto\"];\nfileinto\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/syntax error/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badfileinto3', - "require [\"fileinto\"];\nfileinto 42\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/syntax error/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badfileinto4', - "require [\"fileinto\"];\nfileinto :copy \"foo\"\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/copy.*MUST be enabled/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badfileinto5', - "require [\"fileinto\",\"copy\"];\nfileinto \"foo\"\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/syntax error/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badfileinto6', - "require [\"fileinto\",\"copy\"];\nfileinto :copy \"foo\"\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/syntax error/, $errs); - - ($res, $errs) = $self->compile_sieve_script('badchar1', - "require [\"fileinto\"];\n☃;\nfileinto \"foo\";\n"); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/non-ASCII/, $errs); - - ($res, $errs) = $self->compile_sieve_script('goodfileinto7', - "require [\"fileinto\",\"copy\"];\nfileinto \"foo\";\n"); - $self->assert_str_equals('success', $res); - - ($res, $errs) = $self->compile_sieve_script('goodfileinto8', - "require [\"fileinto\",\"copy\"];\nfileinto :copy \"foo\";\n"); - $self->assert_str_equals('success', $res); - - my $badregex1 = << 'EOF'; +sub badscript_common { + my ($self) = @_; + + my $res; + my $errs; + + ($res, $errs) + = $self->compile_sieve_script('badrequire', "require [\"nonesuch\"];\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/Unsupported feature.*nonesuch/, $errs); + + ($res, $errs) = $self->compile_sieve_script('badreject1', "reject \"foo\"\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/reject.*MUST be enabled/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('badreject2', + "require [\"reject\"];\nreject\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/syntax error/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('badreject3', + "require [\"reject\"];\nreject 42\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/syntax error/, $errs); + + # TODO: test UTF-8 verification of the string parameter + + ($res, $errs) + = $self->compile_sieve_script('badfileinto1', "fileinto \"foo\"\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/fileinto.*MUST be enabled/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('badfileinto2', + "require [\"fileinto\"];\nfileinto\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/syntax error/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('badfileinto3', + "require [\"fileinto\"];\nfileinto 42\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/syntax error/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('badfileinto4', + "require [\"fileinto\"];\nfileinto :copy \"foo\"\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/copy.*MUST be enabled/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('badfileinto5', + "require [\"fileinto\",\"copy\"];\nfileinto \"foo\"\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/syntax error/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('badfileinto6', + "require [\"fileinto\",\"copy\"];\nfileinto :copy \"foo\"\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/syntax error/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('badchar1', + "require [\"fileinto\"];\n☃;\nfileinto \"foo\";\n"); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/non-ASCII/, $errs); + + ($res, $errs) + = $self->compile_sieve_script('goodfileinto7', + "require [\"fileinto\",\"copy\"];\nfileinto \"foo\";\n"); + $self->assert_str_equals('success', $res); + + ($res, $errs) + = $self->compile_sieve_script('goodfileinto8', + "require [\"fileinto\",\"copy\"];\nfileinto :copy \"foo\";\n"); + $self->assert_str_equals('success', $res); + + my $badregex1 = << 'EOF'; require ["regex"]; if header :regex "Subject" "Message (x)?(.*" { stop; } EOF - ($res, $errs) = $self->compile_sieve_script('badregex1', $badregex1); - $self->assert_str_equals('failure', $res); - $self->assert_matches(qr/unbalanced/, $errs); + ($res, $errs) = $self->compile_sieve_script('badregex1', $badregex1); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/unbalanced/, $errs); - # TODO: test UTF-8 verification of the string parameter + # TODO: test UTF-8 verification of the string parameter } # Disabled for now - addflag does not work # on shared mailboxes in 2.5. # https://github.com/cyrusimap/cyrus-imapd/issues/1453 sub XXXtest_shared_delivery_addflag - :Admin - :needs_component_sieve -{ - my ($self) = @_; - - xlog $self, "Testing setting a flag on a sieve script on a"; - xlog $self, "shared folder. Bug 3617 / issue #1453"; - - my $imaptalk = $self->{store}->get_client(); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create the target folder"; - my $admintalk = $self->{adminstore}->get_client(); - my $target = "shared.departments.cis"; - $admintalk->create($target) - or die "Cannot create folder \"$target\": $@"; - $admintalk->setacl($target, admin => 'lrswipkxtecda') - or die "Cannot setacl for \"$target\": $@"; - $admintalk->setacl($target, 'cassandane' => 'lrswipkxtecd') - or die "Cannot setacl for \"$target\": $@"; - $admintalk->setacl($target, 'anyone' => 'p') - or die "Cannot setacl for \"$target\": $@"; - - xlog $self, "Install the sieve script"; - my $scriptname = 'cosbySweater'; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create the target folder"; + my $admintalk = $self->{adminstore}->get_client(); + my $target = "shared.departments.cis"; + $admintalk->create($target) + or die "Cannot create folder \"$target\": $@"; + $admintalk->setacl($target, admin => 'lrswipkxtecda') + or die "Cannot setacl for \"$target\": $@"; + $admintalk->setacl($target, 'cassandane' => 'lrswipkxtecd') + or die "Cannot setacl for \"$target\": $@"; + $admintalk->setacl($target, 'anyone' => 'p') + or die "Cannot setacl for \"$target\": $@"; + + xlog $self, "Install the sieve script"; + my $scriptname = 'cosbySweater'; + $self->{instance}->install_sieve_script( + < undef, - name => $scriptname); - - xlog $self, "Tell the folder to run the sieve script"; - $admintalk->setmetadata($target, "/shared/vendor/cmu/cyrus-imapd/sieve", $scriptname) - or die "Cannot set metadata: $@"; - - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "quinoa"); - $self->{instance}->deliver($msg1, users => [], folder => $target); - - xlog $self, "Check that the message made it to target"; - $self->{store}->set_folder($target); - $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged' ]); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + , + username => undef, + name => $scriptname + ); + + xlog $self, "Tell the folder to run the sieve script"; + $admintalk->setmetadata($target, "/shared/vendor/cmu/cyrus-imapd/sieve", + $scriptname) + or die "Cannot set metadata: $@"; + + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "quinoa"); + $self->{instance}->deliver($msg1, users => [], folder => $target); + + xlog $self, "Check that the message made it to target"; + $self->{store}->set_folder($target); + $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged' ]); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } use Cassandane::Tiny::Loader 'tiny-tests/Sieve'; diff --git a/cassandane/Cassandane/Cyrus/Simple.pm b/cassandane/Cassandane/Cyrus/Simple.pm index d07d1cf41c..4022d5b5a3 100644 --- a/cassandane/Cassandane/Cyrus/Simple.pm +++ b/cassandane/Cassandane/Cyrus/Simple.pm @@ -49,243 +49,230 @@ use Cassandane::Util::Log; $Data::Dumper::Sortkeys = 1; -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # # Test APPEND of messages to IMAP # -sub test_append -{ - my ($self) = @_; +sub test_append { + my ($self) = @_; - my %exp; + my %exp; - xlog $self, "generating message A"; - $exp{A} = $self->make_message("Message A"); - $self->check_messages(\%exp); + xlog $self, "generating message A"; + $exp{A} = $self->make_message("Message A"); + $self->check_messages(\%exp); - xlog $self, "generating message B"; - $exp{B} = $self->make_message("Message B"); - $self->check_messages(\%exp); + xlog $self, "generating message B"; + $exp{B} = $self->make_message("Message B"); + $self->check_messages(\%exp); - xlog $self, "generating message C"; - $exp{C} = $self->make_message("Message C"); - $self->check_messages(\%exp); + xlog $self, "generating message C"; + $exp{C} = $self->make_message("Message C"); + $self->check_messages(\%exp); - xlog $self, "generating message D"; - $exp{D} = $self->make_message("Message D"); - $self->check_messages(\%exp); + xlog $self, "generating message D"; + $exp{D} = $self->make_message("Message D"); + $self->check_messages(\%exp); } sub test_appendlimit_default - :min_version_3_6 -{ - my ($self) = @_; + : min_version_3_6 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $capa = $imaptalk->capability(); - my @appendlimits = grep { m/^appendlimit/ } keys %{$capa}; + my $capa = $imaptalk->capability(); + my @appendlimits = grep { m/^appendlimit/ } keys %{$capa}; - # should be only one appendlimit - $self->assert_num_equals(1, scalar @appendlimits); + # should be only one appendlimit + $self->assert_num_equals(1, scalar @appendlimits); - # we do not support per-mailbox limits, so it must have a value too - $self->assert_matches(qr{^appendlimit=\d+$}, $appendlimits[0]); + # we do not support per-mailbox limits, so it must have a value too + $self->assert_matches(qr{^appendlimit=\d+$}, $appendlimits[0]); - # and since we haven't configured it, it ought to be the default - # value, which is BYTESIZE_UNLIMITED (2147483647). - $self->assert_str_equals("appendlimit=2147483647", $appendlimits[0]); + # and since we haven't configured it, it ought to be the default + # value, which is BYTESIZE_UNLIMITED (2147483647). + $self->assert_str_equals("appendlimit=2147483647", $appendlimits[0]); } sub test_appendlimit_configured - :min_version_3_6 :NoStartInstances -{ - my ($self) = @_; + : min_version_3_6 : NoStartInstances { + my ($self) = @_; - my $desired_limit = "52428800"; # based on known failure + my $desired_limit = "52428800"; # based on known failure - $self->{instance}->{config}->set('maxmessagesize' => $desired_limit); - $self->_start_instances(); + $self->{instance}->{config}->set('maxmessagesize' => $desired_limit); + $self->_start_instances(); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $capa = $imaptalk->capability(); - my @appendlimits = grep { m/^appendlimit/ } keys %{$capa}; + my $capa = $imaptalk->capability(); + my @appendlimits = grep { m/^appendlimit/ } keys %{$capa}; - # should be only one appendlimit - $self->assert_num_equals(1, scalar @appendlimits); + # should be only one appendlimit + $self->assert_num_equals(1, scalar @appendlimits); - # we do not support per-mailbox limits, so it must have a value too - $self->assert_matches(qr{^appendlimit=\d+$}, $appendlimits[0]); + # we do not support per-mailbox limits, so it must have a value too + $self->assert_matches(qr{^appendlimit=\d+$}, $appendlimits[0]); - # and since we've configured it, it'd better be what we asked for! - $self->assert_str_equals("appendlimit=$desired_limit", $appendlimits[0]); + # and since we've configured it, it'd better be what we asked for! + $self->assert_str_equals("appendlimit=$desired_limit", $appendlimits[0]); } -sub test_select -{ - my ($self) = @_; +sub test_select { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "SELECTing INBOX"; - $imaptalk->select("INBOX"); - $self->assert(!$imaptalk->get_last_error()); + xlog $self, "SELECTing INBOX"; + $imaptalk->select("INBOX"); + $self->assert(!$imaptalk->get_last_error()); - xlog $self, "SELECTing inbox"; - $imaptalk->select("inbox"); - $self->assert(!$imaptalk->get_last_error()); + xlog $self, "SELECTing inbox"; + $imaptalk->select("inbox"); + $self->assert(!$imaptalk->get_last_error()); - xlog $self, "CREATEing sub folders"; - $imaptalk->create("INBOX.sub"); - $self->assert(!$imaptalk->get_last_error()); - $imaptalk->create("inbox.blub"); - $self->assert(!$imaptalk->get_last_error()); + xlog $self, "CREATEing sub folders"; + $imaptalk->create("INBOX.sub"); + $self->assert(!$imaptalk->get_last_error()); + $imaptalk->create("inbox.blub"); + $self->assert(!$imaptalk->get_last_error()); - xlog $self, "SELECTing subfolders"; - $imaptalk->select("inbox.sub"); - $self->assert(!$imaptalk->get_last_error()); - $imaptalk->select("INbOX.blub"); - $self->assert(!$imaptalk->get_last_error()); + xlog $self, "SELECTing subfolders"; + $imaptalk->select("inbox.sub"); + $self->assert(!$imaptalk->get_last_error()); + $imaptalk->select("INbOX.blub"); + $self->assert(!$imaptalk->get_last_error()); } sub test_cmdtimer_sessionid - :min_version_3_5 :NoStartInstances -{ - my ($self) = @_; + : min_version_3_5 : NoStartInstances { + my ($self) = @_; - # log the timing for anything that takes longer than zero seconds - $self->{instance}->{config}->set('commandmintimer', '0'); - $self->_start_instances(); + # log the timing for anything that takes longer than zero seconds + $self->{instance}->{config}->set('commandmintimer', '0'); + $self->_start_instances(); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # put a bunch of messages in inbox to make sure fetch isn't instantaneous - my %msgs; - foreach my $n (1..5) { - $msgs{$n} = $self->make_message("message $n"); - } + # put a bunch of messages in inbox to make sure fetch isn't instantaneous + my %msgs; + foreach my $n (1 .. 5) { + $msgs{$n} = $self->make_message("message $n"); + } - $imaptalk->select("INBOX"); - $self->assert_str_equals("ok", $imaptalk->get_last_completion_response()); + $imaptalk->select("INBOX"); + $self->assert_str_equals("ok", $imaptalk->get_last_completion_response()); - # discard buffered syslog output from setup - $self->{instance}->getsyslog(); + # discard buffered syslog output from setup + $self->{instance}->getsyslog(); - # fetch some things that will take a little while - $imaptalk->fetch('1:*', '(uid flags body[])'); - $self->assert_str_equals("ok", $imaptalk->get_last_completion_response()); + # fetch some things that will take a little while + $imaptalk->fetch('1:*', '(uid flags body[])'); + $self->assert_str_equals("ok", $imaptalk->get_last_completion_response()); - # should have logged some timer output, which should include the sess id - if ($self->{instance}->{have_syslog_replacement}) { - # make sure that the connection is ended so that imapd reset happens - $imaptalk->logout(); - undef $imaptalk; + # should have logged some timer output, which should include the sess id + if ($self->{instance}->{have_syslog_replacement}) { + # make sure that the connection is ended so that imapd reset happens + $imaptalk->logout(); + undef $imaptalk; - my @lines = $self->{instance}->getsyslog(); + my @lines = $self->{instance}->getsyslog(); - my @timer_lines = grep { m/\bcmdtimer:/ } @lines; - $self->assert_num_gte(1, scalar @timer_lines); - foreach my $line (@timer_lines) { - $self->assert_matches(qr/sessionid=<[^ >]+>/, $line); - } + my @timer_lines = grep { m/\bcmdtimer:/ } @lines; + $self->assert_num_gte(1, scalar @timer_lines); + foreach my $line (@timer_lines) { + $self->assert_matches(qr/sessionid=<[^ >]+>/, $line); + } - my (@behavior_lines) = grep { /session ended/ } @lines; + my (@behavior_lines) = grep { /session ended/ } @lines; - $self->assert_num_gte(1, scalar @behavior_lines); - } + $self->assert_num_gte(1, scalar @behavior_lines); + } } sub test_toggleable_debug_logging - :min_version_3_9 -{ - my ($self) = @_; + : min_version_3_9 { + my ($self) = @_; - my $config_debug = $self->{instance}->{config}->get_bool('debug', 'no'); - my $imaptalk = $self->{store}->get_client(); + my $config_debug = $self->{instance}->{config}->get_bool('debug', 'no'); + my $imaptalk = $self->{store}->get_client(); - # can't do anything without captured syslog - if (!$self->{instance}->{have_syslog_replacement}) { - xlog $self, "can't examine syslog, test is useless"; - return; - } + # can't do anything without captured syslog + if (!$self->{instance}->{have_syslog_replacement}) { + xlog $self, "can't examine syslog, test is useless"; + return; + } - # find our imapd pid from syslog - my $loginpat = qr{ + # find our imapd pid from syslog + my $loginpat = qr{ \bimap\[(\d+)\]:\slogin: \s\S+\s\[[\d\.]+\]\scassandane\splaintext \sUser\slogged\sin }x; - my @logins = $self->{instance}->getsyslog($loginpat); - $self->assert_num_equals(1, scalar @logins); - $logins[0] =~ m/$loginpat/; - my $imapd_pid = $1; - - for (1..5) { - $imaptalk->unselect(); - my $res = $imaptalk->select('INBOX'); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); - - # this is really looking at cassandane's own injected syslog - # output, so it depends on the injected syslog doing the right - # thing with masking - my $selectpat = qr/open: user cassandane opened INBOX/; - my @lines = $self->{instance}->getsyslog($selectpat); - if ($config_debug) { - $self->assert_num_equals(1, scalar @lines); - $self->assert_matches(qr/imap\[$imapd_pid\]:/, $lines[0]); - } - else { - $self->assert_num_equals(0, scalar @lines); - } - - $config_debug = !$config_debug; - - # toggle debug logging by sending SIGUSR1 - my $count = kill 'SIGUSR1', $imapd_pid; - $self->assert_num_equals(1, $count); - - # we can also look for the message logged by cyrus at the - # time it toggles the value - my $statuspat = qr/debug logging turned (on|off)/; - @lines = $self->{instance}->getsyslog($statuspat); - $self->assert_num_equals(1, scalar @lines); - $self->assert_matches(qr/imap\[$imapd_pid\]:/, $lines[0]); - $lines[0] =~ $statuspat; - my $status = $1; - if ($config_debug) { - $self->assert_str_equals('on', $status); - } - else { - $self->assert_str_equals('off', $status); - } + my @logins = $self->{instance}->getsyslog($loginpat); + $self->assert_num_equals(1, scalar @logins); + $logins[0] =~ m/$loginpat/; + my $imapd_pid = $1; + + for (1 .. 5) { + $imaptalk->unselect(); + my $res = $imaptalk->select('INBOX'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + # this is really looking at cassandane's own injected syslog + # output, so it depends on the injected syslog doing the right + # thing with masking + my $selectpat = qr/open: user cassandane opened INBOX/; + my @lines = $self->{instance}->getsyslog($selectpat); + if ($config_debug) { + $self->assert_num_equals(1, scalar @lines); + $self->assert_matches(qr/imap\[$imapd_pid\]:/, $lines[0]); + } else { + $self->assert_num_equals(0, scalar @lines); + } + + $config_debug = !$config_debug; + + # toggle debug logging by sending SIGUSR1 + my $count = kill 'SIGUSR1', $imapd_pid; + $self->assert_num_equals(1, $count); + + # we can also look for the message logged by cyrus at the + # time it toggles the value + my $statuspat = qr/debug logging turned (on|off)/; + @lines = $self->{instance}->getsyslog($statuspat); + $self->assert_num_equals(1, scalar @lines); + $self->assert_matches(qr/imap\[$imapd_pid\]:/, $lines[0]); + $lines[0] =~ $statuspat; + my $status = $1; + if ($config_debug) { + $self->assert_str_equals('on', $status); + } else { + $self->assert_str_equals('off', $status); } + } } -sub test_append_binary -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); +sub test_append_binary { + my ($self) = @_; + my $imap = $self->{store}->get_client(); - my $mime = <<'EOF' =~ s/\n/\r\n/gr; + my $mime = <<'EOF' =~ s/\n/\r\n/gr; To: to@local From: from@local Subject: test @@ -294,12 +281,12 @@ Content-Transfer-Encoding:binary test EOF - $imap->append("INBOX", { Binary => $mime }); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); + $imap->append("INBOX", { Binary => $mime }); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); - $imap->select('INBOX'); - my $res = $imap->fetch('1', '(BINARY[1])'); - $self->assert_str_equals("test\r\n", $res->{1}{binary}); + $imap->select('INBOX'); + my $res = $imap->fetch('1', '(BINARY[1])'); + $self->assert_str_equals("test\r\n", $res->{1}{binary}); } 1; diff --git a/cassandane/Cassandane/Cyrus/Specialuse.pm b/cassandane/Cassandane/Cyrus/Specialuse.pm index 1e822de1df..49f98e2a2a 100644 --- a/cassandane/Cassandane/Cyrus/Specialuse.pm +++ b/cassandane/Cassandane/Cyrus/Specialuse.pm @@ -46,227 +46,212 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # Test that you can rename a special use folder sub test_rename_toplevel - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Junk", "(USE (\\Junk))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Junk", "(USE (\\Junk))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->rename("INBOX.Junk", "INBOX.Other"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->rename("INBOX.Junk", "INBOX.Other"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); } sub test_rename_tosub - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Junk", "(USE (\\Junk))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Junk", "(USE (\\Junk))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->create("INBOX.Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - # can't rename to a deep folder - $imaptalk->rename("INBOX.Junk", "INBOX.Trash.Junk"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + # can't rename to a deep folder + $imaptalk->rename("INBOX.Junk", "INBOX.Trash.Junk"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); } sub test_create_multiple - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Rubbish", "(USE (\\Junk \\Trash \\Sent))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Rubbish", "(USE (\\Junk \\Trash \\Sent))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); } sub test_create_dupe - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Rubbish", "(USE (\\Trash))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Rubbish", "(USE (\\Trash))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); } -sub test_annot -{ - my ($self) = @_; +sub test_annot { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); } sub test_annot_dupe - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Rubbish", "(USE (\\Trash))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Rubbish", "(USE (\\Trash))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->create("INBOX.Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", "\\Trash"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", "\\Trash"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); } sub test_delete_imm - :ImmediateDelete :NoAltNameSpace -{ - my ($self) = @_; + : ImmediateDelete : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->delete("INBOX.Trash"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + $imaptalk->delete("INBOX.Trash"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); } sub test_delete_delay - :DelayedDelete :NoAltNameSpace -{ - my ($self) = @_; + : DelayedDelete : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->delete("INBOX.Trash"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + $imaptalk->delete("INBOX.Trash"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); } sub test_delete_removed_imm - :ImmediateDelete :NoAltNameSpace -{ - my ($self) = @_; + : ImmediateDelete : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", undef); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", undef); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->delete("INBOX.Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->delete("INBOX.Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); } sub test_delete_removed_delay - :DelayedDelete :NoAltNameSpace -{ - my ($self) = @_; + : DelayedDelete : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", undef); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", undef); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->delete("INBOX.Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->delete("INBOX.Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); } sub test_important - :min_version_3_1 :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Important", "(USE (\\Important))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Important", "(USE (\\Important))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->delete("INBOX.Important"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + $imaptalk->delete("INBOX.Important"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); } sub test_nochildren - :min_version_3_7 :NoStartInstances :NoAltNamespace -{ - my ($self) = @_; + : min_version_3_7 : NoStartInstances : NoAltNamespace { + my ($self) = @_; - $self->{instance}->{config}->set('specialuse_nochildren' => '\\Trash'); - $self->_start_instances(); + $self->{instance}->{config}->set('specialuse_nochildren' => '\\Trash'); + $self->_start_instances(); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - # should not be able to create a child - $imaptalk->create("INBOX.Trash.child"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + # should not be able to create a child + $imaptalk->create("INBOX.Trash.child"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); - # should not be able to create a grandchild either - $imaptalk->create("INBOX.Trash.child.grandchild"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + # should not be able to create a grandchild either + $imaptalk->create("INBOX.Trash.child.grandchild"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); - # better not have accidentally created anything - $imaptalk->select("INBOX.Trash.child"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + # better not have accidentally created anything + $imaptalk->select("INBOX.Trash.child"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); - $imaptalk->select("INBOX.Trash.child.grandchild"); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + $imaptalk->select("INBOX.Trash.child.grandchild"); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); - # what if we remove the annotation - $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", undef); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + # what if we remove the annotation + $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", undef); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - # should be able to create a child - $imaptalk->create("INBOX.Trash.child"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + # should be able to create a child + $imaptalk->create("INBOX.Trash.child"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - # should not be able to add the annotation back - $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", '\\Trash'); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + # should not be able to add the annotation back + $imaptalk->setmetadata("INBOX.Trash", "/private/specialuse", '\\Trash'); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); } # compile diff --git a/cassandane/Cassandane/Cyrus/SyncProto.pm b/cassandane/Cassandane/Cyrus/SyncProto.pm index 90b2561d54..1874970cce 100644 --- a/cassandane/Cassandane/Cyrus/SyncProto.pm +++ b/cassandane/Cassandane/Cyrus/SyncProto.pm @@ -49,56 +49,52 @@ use Data::Dumper; use Cyrus::SyncProto; use Cyrus::AccountSync; -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_syncproto_dump_restore -{ - my ($self) = @_; +sub test_syncproto_dump_restore { + my ($self) = @_; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("subfolder"); - $imaptalk->subscribe("subfolder"); - $imaptalk->create("Sent"); - $imaptalk->setmetadata("Sent", "/private/specialuse", "\\Sent"); + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create("subfolder"); + $imaptalk->subscribe("subfolder"); + $imaptalk->create("Sent"); + $imaptalk->setmetadata("Sent", "/private/specialuse", "\\Sent"); - xlog $self, "Create messages"; - $self->make_message("Message A"); - $self->{store}->set_folder("INBOX.subfolder"); - $self->make_message("Message B"); - $self->make_message("Message C"); + xlog $self, "Create messages"; + $self->make_message("Message A"); + $self->{store}->set_folder("INBOX.subfolder"); + $self->make_message("Message B"); + $self->make_message("Message C"); - $imaptalk->select("INBOX"); - $imaptalk->store("1", "+flags", "\\Seen"); - $imaptalk->store("1", "+flags", "aflag"); + $imaptalk->select("INBOX"); + $imaptalk->store("1", "+flags", "\\Seen"); + $imaptalk->store("1", "+flags", "aflag"); - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); - my $sp = Cyrus::SyncProto->new($admintalk); - my $as = Cyrus::AccountSync->new($sp); - my $data = $as->dump_user(username => 'cassandane'); - $self->assert_null($as->dump_user(username => 'newuser')); - $as->undump_user(username => 'newuser', data => $data); - my $newdata = $as->dump_user(username => 'newuser'); - $self->assert_deep_equals($data, $newdata); - $as->delete_user(username => 'newuser'); - $self->assert_null($as->dump_user(username => 'newuser')); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); + my $sp = Cyrus::SyncProto->new($admintalk); + my $as = Cyrus::AccountSync->new($sp); + my $data = $as->dump_user(username => 'cassandane'); + $self->assert_null($as->dump_user(username => 'newuser')); + $as->undump_user(username => 'newuser', data => $data); + my $newdata = $as->dump_user(username => 'newuser'); + $self->assert_deep_equals($data, $newdata); + $as->delete_user(username => 'newuser'); + $self->assert_null($as->dump_user(username => 'newuser')); } 1; diff --git a/cassandane/Cassandane/Cyrus/T116.pm b/cassandane/Cassandane/Cyrus/T116.pm index 298b5592f2..c97c8be6ab 100644 --- a/cassandane/Cassandane/Cyrus/T116.pm +++ b/cassandane/Cassandane/Cyrus/T116.pm @@ -46,48 +46,48 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Instance; -sub new -{ - my $class = shift; - return $class->SUPER::new({ adminstore => 1 }, @_); +sub new { + my $class = shift; + return $class->SUPER::new({ adminstore => 1 }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -Cassandane::Cyrus::TestCase::magic(T116 => sub { +Cassandane::Cyrus::TestCase::magic( + T116 => sub { my ($testcase) = @_; $testcase->config_set(virtdomains => 'userid'); -}); + } +); sub test_list_inbox - :T116 -{ - my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + : T116 { + my ($self) = @_; + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - xlog $self, "Test Cyrus extension which renames a user to a different partition"; + xlog $self, + "Test Cyrus extension which renames a user to a different partition"; - # create and prepare the user - $admintalk->create('user.test@inbox.com'); - $admintalk->setacl('user.test@inbox.com', 'admin', 'lrswipkxtecda'); + # create and prepare the user + $admintalk->create('user.test@inbox.com'); + $admintalk->setacl('user.test@inbox.com', 'admin', 'lrswipkxtecda'); - $admintalk->create('user.test@inbox2.com'); - $admintalk->setacl('user.test@inbox2.com', 'admin', 'lrswipkxtecda'); + $admintalk->create('user.test@inbox2.com'); + $admintalk->setacl('user.test@inbox2.com', 'admin', 'lrswipkxtecda'); - my @list = $admintalk->list('', '*'); - my @items = sort map { $_->[2] } @list; - $self->assert_deep_equals(\@items, ['user.cassandane', 'user.test@inbox.com', 'user.test@inbox2.com']); + my @list = $admintalk->list('', '*'); + my @items = sort map { $_->[2] } @list; + $self->assert_deep_equals(\@items, + [ 'user.cassandane', 'user.test@inbox.com', 'user.test@inbox2.com' ]); } 1; diff --git a/cassandane/Cassandane/Cyrus/TestCase.pm b/cassandane/Cassandane/Cyrus/TestCase.pm index 449b581768..b8aa1e7062 100644 --- a/cassandane/Cassandane/Cyrus/TestCase.pm +++ b/cassandane/Cassandane/Cyrus/TestCase.pm @@ -43,10 +43,10 @@ use warnings; use attributes; use Data::Dumper; use Scalar::Util qw(refaddr); -use List::Util qw(uniq); +use List::Util qw(uniq); use Digest::file qw(digest_file_hex); -use File::Temp qw(tempfile); -use File::Path qw(rmtree); +use File::Temp qw(tempfile); +use File::Path qw(rmtree); use lib '.'; use base qw(Cassandane::Unit::TestCase); @@ -61,9 +61,9 @@ use Cassandane::PortManager; use Cyrus::CheckReplication; my @stores = qw(store adminstore - replica_store replica_adminstore - frontend_store frontend_adminstore - backend2_store backend2_adminstore); + replica_store replica_adminstore + frontend_store frontend_adminstore + backend2_store backend2_adminstore); my %magic_handlers; # This code for storing function attributes is from @@ -71,115 +71,108 @@ my %magic_handlers; my %attrs; # package variable to store attribute lists by coderef address -sub MODIFY_CODE_ATTRIBUTES -{ - my ($package, $subref, @attrs) = @_; - $attrs{refaddr $subref} = \@attrs; - return; +sub MODIFY_CODE_ATTRIBUTES { + my ($package, $subref, @attrs) = @_; + $attrs{ refaddr $subref } = \@attrs; + return; } -sub FETCH_CODE_ATTRIBUTES -{ - my ($package, $subref) = @_; - my $attrs = $attrs{refaddr $subref} || []; - return @$attrs; +sub FETCH_CODE_ATTRIBUTES { + my ($package, $subref) = @_; + my $attrs = $attrs{ refaddr $subref } || []; + return @$attrs; } -sub new -{ - my ($class, $params, @args) = @_; - - my $want = { - instance => 1, - replica => 0, - imapmurder => 0, - httpmurder => 0, - backups => 0, - start_instances => 1, - services => [ 'imap' ], - store => 1, - adminstore => 0, - gen => 1, - deliver => 0, - jmap => 0, - install_certificates => 0, - squatter => 0, - }; - map { - $want->{$_} = delete $params->{$_} - if defined $params->{$_}; - - } keys %$want; - $want->{folder} = delete $params->{folder} - if defined $params->{folder}; - - my $instance_params = {}; - foreach my $p (qw(config)) - { - $instance_params->{$p} = delete $params->{$p} - if defined $params->{$p}; - } - - # should have consumed all of the $params hash; if - # not something is awry. - my $leftovers = join(' ', keys %$params); - die "Unexpected configuration parameters: $leftovers" - if length($leftovers); - - my $self = $class->SUPER::new(@args); - $self->{_name} = $args[0] || 'unknown'; - $self->{_want} = $want; - $self->{_instance_params} = $instance_params; - - return $self; +sub new { + my ($class, $params, @args) = @_; + + my $want = { + instance => 1, + replica => 0, + imapmurder => 0, + httpmurder => 0, + backups => 0, + start_instances => 1, + services => ['imap'], + store => 1, + adminstore => 0, + gen => 1, + deliver => 0, + jmap => 0, + install_certificates => 0, + squatter => 0, + }; + map { + $want->{$_} = delete $params->{$_} + if defined $params->{$_}; + + } keys %$want; + $want->{folder} = delete $params->{folder} + if defined $params->{folder}; + + my $instance_params = {}; + foreach my $p (qw(config)) { + $instance_params->{$p} = delete $params->{$p} + if defined $params->{$p}; + } + + # should have consumed all of the $params hash; if + # not something is awry. + my $leftovers = join(' ', keys %$params); + die "Unexpected configuration parameters: $leftovers" + if length($leftovers); + + my $self = $class->SUPER::new(@args); + $self->{_name} = $args[0] || 'unknown'; + $self->{_want} = $want; + $self->{_instance_params} = $instance_params; + + return $self; } # return an id for use by xlog -sub id -{ - my ($self) = @_; - return $self->{_name}; # XXX something cleverer? +sub id { + my ($self) = @_; + return $self->{_name}; # XXX something cleverer? } -sub filter -{ - my ($self) = @_; +sub filter { + my ($self) = @_; - my $filter = $self->SUPER::filter(@_); - - $filter->{enable_wanted_properties} = sub { - return if not exists $self->{_name}; - my $sub = $self->can($self->{_name}); - return if not defined $sub; - - # n.b. cannot be used to unwant, sorry - foreach my $attr (attributes::get($sub)) { - next if $attr !~ m/^want(_service)?_(\w+)$/; - - # XXX Since this is a 'filter', it could also check - # XXX whether the required components are configured - # XXX ala :needs_foo, and skip the test if they're - # XXX missing, rather than failing to start them. - # XXX That is, :want_service_http could be taken to - # XXX imply :needs_component_httpd, and the test - # XXX skipped if it's unavailable. But note that - # XXX there isn't a clean mapping between the names! - # XXX For now, tests will need to be annotated with - # XXX both attributes in these cases. - - $self->{_current_magic} = "Test function attribute ':$attr'"; - if (defined $1 && $1 eq '_service') { - $self->want_services($2); - } - else { - $self->want($2); - } - $self->{_current_magic} = undef; - } - return; - }; + my $filter = $self->SUPER::filter(@_); + + $filter->{enable_wanted_properties} = sub { + return if not exists $self->{_name}; + my $sub = $self->can($self->{_name}); + return if not defined $sub; + + # n.b. cannot be used to unwant, sorry + foreach my $attr (attributes::get($sub)) { + next if $attr !~ m/^want(_service)?_(\w+)$/; + + # XXX Since this is a 'filter', it could also check + # XXX whether the required components are configured + # XXX ala :needs_foo, and skip the test if they're + # XXX missing, rather than failing to start them. + # XXX That is, :want_service_http could be taken to + # XXX imply :needs_component_httpd, and the test + # XXX skipped if it's unavailable. But note that + # XXX there isn't a clean mapping between the names! + # XXX For now, tests will need to be annotated with + # XXX both attributes in these cases. + + $self->{_current_magic} = "Test function attribute ':$attr'"; + if (defined $1 && $1 eq '_service') { + $self->want_services($2); + } else { + $self->want($2); + } + $self->{_current_magic} = undef; + } + return; + }; - return $filter; + return $filter; } # will magically cause some special actions to be taken during test @@ -189,139 +182,182 @@ sub filter # before Cyrus instances are created, and can call want() to add to the # set of features wanted by this test, or config_set() to set additional # imapd.conf variables for all instances. -sub magic -{ - my ($name, $handler) = @_; - $name = lc($name); - die "Magic \"$name\" registered twice" - if defined $magic_handlers{$name}; - $magic_handlers{$name} = $handler; +sub magic { + my ($name, $handler) = @_; + $name = lc($name); + die "Magic \"$name\" registered twice" + if defined $magic_handlers{$name}; + $magic_handlers{$name} = $handler; } -sub _who_wants_it -{ - my ($self) = @_; - return $self->{_current_magic} - if defined $self->{_current_magic} ; - return "Test " . $self->{_name}; +sub _who_wants_it { + my ($self) = @_; + return $self->{_current_magic} + if defined $self->{_current_magic}; + return "Test " . $self->{_name}; } -sub want -{ - my ($self, $name, $value) = @_; - $value = 1 if !defined $value; - $self->{_want}->{$name} = $value; - xlog $self->_who_wants_it() . " wants $name = $value"; +sub want { + my ($self, $name, $value) = @_; + $value = 1 if !defined $value; + $self->{_want}->{$name} = $value; + xlog $self->_who_wants_it() . " wants $name = $value"; } -sub want_services -{ - my ($self, @services) = @_; +sub want_services { + my ($self, @services) = @_; - @{$self->{_want}->{services}} = uniq(@{$self->{_want}->{services}}, - @services); - xlog $self->_who_wants_it() . " wants services " . join(', ', @services); + @{ $self->{_want}->{services} } + = uniq(@{ $self->{_want}->{services} }, @services); + xlog $self->_who_wants_it() . " wants services " . join(', ', @services); } -sub config_set -{ - my ($self, %pairs) = @_; - $self->{_config}->set(%pairs); - while (my ($n, $v) = each %pairs) - { - xlog $self->_who_wants_it() . " sets config $n = $v"; - } +sub config_set { + my ($self, %pairs) = @_; + $self->{_config}->set(%pairs); + while (my ($n, $v) = each %pairs) { + xlog $self->_who_wants_it() . " sets config $n = $v"; + } } # XXX put these in alphabetical order someday -magic(ReverseACLs => sub { +magic( + ReverseACLs => sub { shift->config_set(reverseacls => 1); -}); -magic(RightNow => sub { + } +); +magic( + RightNow => sub { shift->config_set(sync_rightnow_channel => '""'); -}); -magic(SyncLog => sub { + } +); +magic( + SyncLog => sub { shift->config_set(sync_log => 1); -}); + } +); magic(Replication => sub { shift->want('replica'); }); -magic(CSyncReplication => sub { +magic( + CSyncReplication => sub { my ($self) = @_; $self->want('csyncreplica'); $self->config_set('sync_try_imap' => 0); -}); + } +); magic(IMAPMurder => sub { shift->want('imapmurder'); }); magic(HTTPMurder => sub { shift->want('httpmurder'); }); -magic(Backups => sub { shift->want('backups'); }); -magic(AnnotationAllowUndefined => sub { +magic(Backups => sub { shift->want('backups'); }); +magic( + AnnotationAllowUndefined => sub { shift->config_set(annotation_allow_undefined => 1); -}); -magic(AllowDeleted => sub { + } +); +magic( + AllowDeleted => sub { shift->config_set(allowdeleted => 1); -}); -magic(ImmediateDelete => sub { + } +); +magic( + ImmediateDelete => sub { shift->config_set(delete_mode => 'immediate'); -}); -magic(DelayedDelete => sub { + } +); +magic( + DelayedDelete => sub { shift->config_set(delete_mode => 'delayed'); -}); -magic(UnixHierarchySep => sub { + } +); +magic( + UnixHierarchySep => sub { shift->config_set(unixhierarchysep => 'yes'); -}); -magic(ImmediateExpunge => sub { + } +); +magic( + ImmediateExpunge => sub { shift->config_set(expunge_mode => 'immediate'); -}); -magic(SemidelayedExpunge => sub { + } +); +magic( + SemidelayedExpunge => sub { my $semidelayed = 'semidelayed'; my ($maj, $min) = Cassandane::Instance->get_version(); if ($maj < 3 || ($maj == 3 && $min < 1)) { - # this value used to be called 'default' in 3.0 and earlier - $semidelayed = 'default'; + # this value used to be called 'default' in 3.0 and earlier + $semidelayed = 'default'; } shift->config_set(expunge_mode => $semidelayed); -}); -magic(DelayedExpunge => sub { + } +); +magic( + DelayedExpunge => sub { shift->config_set(expunge_mode => 'delayed'); -}); -magic(VirtDomains => sub { + } +); +magic( + VirtDomains => sub { shift->config_set(virtdomains => 'userid'); -}); -magic(NoVirtDomains => sub { + } +); +magic( + NoVirtDomains => sub { shift->config_set(virtdomains => 'off'); -}); -magic(AltNamespace => sub { + } +); +magic( + AltNamespace => sub { shift->config_set(altnamespace => 'yes'); -}); -magic(NoAltNamespace => sub { + } +); +magic( + NoAltNamespace => sub { shift->config_set(altnamespace => 'no'); -}); -magic(NoMailboxLegacyDirs => sub { + } +); +magic( + NoMailboxLegacyDirs => sub { shift->config_set(mailbox_legacy_dirs => 'no'); -}); -magic(MailboxLegacyDirs => sub { + } +); +magic( + MailboxLegacyDirs => sub { shift->config_set(mailbox_legacy_dirs => 'yes'); -}); -magic(CrossDomains => sub { + } +); +magic( + CrossDomains => sub { shift->config_set(crossdomains => 'yes'); -}); -magic(Conversations => sub { + } +); +magic( + Conversations => sub { shift->config_set(conversations => 'yes'); -}); -magic(ConversationsQuota => sub { + } +); +magic( + ConversationsQuota => sub { # this setting is new in 3.3.0 -- any test using this magic # also needs :min_version_3_3 shift->config_set(quota_use_conversations => 'yes'); -}); -magic(Admin => sub { + } +); +magic( + Admin => sub { shift->want('adminstore'); -}); -magic(AllowMoves => sub { + } +); +magic( + AllowMoves => sub { shift->config_set('allowusermoves' => 'yes'); -}); -magic(DisconnectOnVanished => sub { + } +); +magic( + DisconnectOnVanished => sub { shift->config_set('disconnect_on_vanished_mailbox' => 'yes'); -}); -magic(NoStartInstances => sub { + } +); +magic( + NoStartInstances => sub { # If you use this magic, you must call $self->_start_instances() # and optionally $self->_setup_http_service_objects() # yourself from your test function once you're ready for Cyrus @@ -329,1451 +365,1469 @@ magic(NoStartInstances => sub { # If any test function in your suite uses this magic, then # your suite's set_up function cannot assume Cyrus is running! shift->want('start_instances' => 0); -}); -magic(MagicPlus => sub { + } +); +magic( + MagicPlus => sub { shift->config_set('imapmagicplus' => 'yes'); -}); -magic(FastMailSharing => sub { + } +); +magic( + FastMailSharing => sub { shift->config_set('fastmailsharing' => 'true'); -}); -magic(Partition2 => sub { + } +); +magic( + Partition2 => sub { shift->config_set('partition-p2' => '@basedir@/data-p2'); -}); -magic(FastMailEvent => sub { + } +); +magic( + FastMailEvent => sub { shift->config_set( - event_content_inclusion_mode => 'standard', - event_content_size => 1, # just the first byte - event_exclude_specialuse => '\\Junk', - event_extra_params => 'modseq vnd.fastmail.clientId service uidnext vnd.fastmail.sessionId vnd.cmu.envelope vnd.fastmail.convUnseen vnd.fastmail.convExists vnd.fastmail.cid vnd.cmu.mbtype vnd.cmu.davFilename vnd.cmu.davUid vnd.cmu.mailboxACL vnd.fastmail.counters messages vnd.cmu.unseenMessages flagNames vnd.cmu.visibleUsers', - event_groups => 'mailbox message flags calendar applepushservice', + event_content_inclusion_mode => 'standard', + event_content_size => 1, # just the first byte + event_exclude_specialuse => '\\Junk', + event_extra_params => + 'modseq vnd.fastmail.clientId service uidnext vnd.fastmail.sessionId vnd.cmu.envelope vnd.fastmail.convUnseen vnd.fastmail.convExists vnd.fastmail.cid vnd.cmu.mbtype vnd.cmu.davFilename vnd.cmu.davUid vnd.cmu.mailboxACL vnd.fastmail.counters messages vnd.cmu.unseenMessages flagNames vnd.cmu.visibleUsers', + event_groups => 'mailbox message flags calendar applepushservice', ); -}); -magic(NoMunge8Bit => sub { + } +); +magic( + NoMunge8Bit => sub { shift->config_set(munge8bit => 'no'); -}); -magic(RFC2047_UTF8 => sub { + } +); +magic( + RFC2047_UTF8 => sub { shift->config_set(rfc2047_utf8 => 'yes'); -}); -magic(JMAPSearchDBLegacy => sub { + } +); +magic( + JMAPSearchDBLegacy => sub { # XXX Needed for JMAPEmail.email_query_..._legacy (3.1-3.4). # XXX Don't use in newer tests, and remove this someday when 3.4 is # XXX obsolete. - shift->config_set('jmap_emailsearch_db_path' => - '@basedir@/search/jmap_emailsearch.db'); -}); -magic(JMAPQueryCacheMaxAge1s => sub { + shift->config_set( + 'jmap_emailsearch_db_path' => '@basedir@/search/jmap_emailsearch.db'); + } +); +magic( + JMAPQueryCacheMaxAge1s => sub { shift->config_set('jmap_querycache_max_age' => '1s'); -}); -magic(JMAPNoHasAttachment => sub { + } +); +magic( + JMAPNoHasAttachment => sub { shift->config_set('jmap_set_has_attachment' => 'no'); -}); -magic(JMAPExtensions => sub { + } +); +magic( + JMAPExtensions => sub { shift->config_set('jmap_nonstandard_extensions' => 'yes'); -}); -magic(SearchAttachmentExtractor => sub { + } +); +magic( + SearchAttachmentExtractor => sub { my $port = Cassandane::PortManager::alloc("localhost"); my $self = shift; - $self->config_set('search_attachment_extractor_url' => - "http://localhost:$port/extractor"); + $self->config_set( + 'search_attachment_extractor_url' => "http://localhost:$port/extractor"); $self->config_set('search_attachment_extractor_request_timeout' => '3s'); - $self->config_set('search_attachment_extractor_idle_timeout' => '3s'); -}); -magic(SearchLanguage => sub { + $self->config_set('search_attachment_extractor_idle_timeout' => '3s'); + } +); +magic( + SearchLanguage => sub { shift->config_set('search_index_language' => 'yes'); -}); -magic(SieveUTF8Fileinto => sub { + } +); +magic( + SieveUTF8Fileinto => sub { shift->config_set('sieve_utf8fileinto' => 'yes'); -}); -magic(SearchSetForceScanMode => sub { + } +); +magic( + SearchSetForceScanMode => sub { shift->config_set(search_queryscan => '1'); -}); -magic(SearchFuzzyAlways => sub { + } +); +magic( + SearchFuzzyAlways => sub { shift->config_set(search_fuzzy_always => '1'); -}); -magic(SearchEngineSquat => sub { + } +); +magic( + SearchEngineSquat => sub { shift->config_set(search_engine => 'squat'); -}); -magic(SearchNormalizationMax20000 => sub { + } +); +magic( + SearchNormalizationMax20000 => sub { shift->config_set(search_normalisation_max => 20000); -}); -magic(SearchMaxtime1Sec => sub { + } +); +magic( + SearchMaxtime1Sec => sub { shift->config_set(search_maxtime => 1); -}); -magic(SearchMaxSize4k => sub { + } +); +magic( + SearchMaxSize4k => sub { shift->config_set(search_maxsize => 4); -}); -magic(TLS => sub { + } +); +magic( + TLS => sub { # XXX Here be dragons. Check existing tests that use this magic # XXX for some of the hoops you may still need to jump through! my $self = shift; $self->config_set(tls_server_cert => '@basedir@/conf/certs/cert.pem'); - $self->config_set(tls_server_key => '@basedir@/conf/certs/key.pem'); + $self->config_set(tls_server_key => '@basedir@/conf/certs/key.pem'); $self->want('install_certificates'); $self->want_services('imaps'); -}); -magic(LowEmailLimits => sub { + } +); +magic( + LowEmailLimits => sub { # these settings are new in 3.3.0 -- any test using this magic # also needs :min_version_3_3 shift->config_set( - conversations_max_guidrecords => 10, - conversations_max_guidexists => 5, - conversations_max_guidinfolder => 2, + conversations_max_guidrecords => 10, + conversations_max_guidexists => 5, + conversations_max_guidinfolder => 2, ); -}); -magic(HttpJWTAuthRSA => sub { + } +); +magic( + HttpJWTAuthRSA => sub { my $self = shift; $self->config_set(http_jwt_key_dir => '@basedir@/conf/certs/http_jwt'); $self->want('install_certificates'); -}); -magic(iCalendarMaxSize10k => sub { + } +); +magic( + iCalendarMaxSize10k => sub { shift->config_set(icalendar_max_size => 100000); -}); -magic(CalDAVNoDefaultCalendar => sub { + } +); +magic( + CalDAVNoDefaultCalendar => sub { + shift->config_set(caldav_create_default => 'no',); + } +); +magic( + AltPTSDBPath => sub { shift->config_set( - caldav_create_default => 'no', - ); -}); -magic(AltPTSDBPath => sub { - shift->config_set( - 'ptscache_db_path' => '@basedir@/conf/non-default-ptscache.db' - ); -}); -magic(ArchivePartition => sub { + 'ptscache_db_path' => '@basedir@/conf/non-default-ptscache.db'); + } +); +magic( + ArchivePartition => sub { my $conf = shift; $conf->config_set('archivepartition-default' => '@basedir@/archive'); - $conf->config_set('archive_enabled' => 'yes'); - $conf->config_set('archive_after' => '7d'); -}); -magic(ArchiveNow => sub { + $conf->config_set('archive_enabled' => 'yes'); + $conf->config_set('archive_after' => '7d'); + } +); +magic( + ArchiveNow => sub { my $conf = shift; $conf->config_set('archivepartition-default' => '@basedir@/archive'); - $conf->config_set('archive_enabled' => 'yes'); - $conf->config_set('archive_after' => '0d'); -}); -magic(AllowCalendarAdmin => sub { + $conf->config_set('archive_enabled' => 'yes'); + $conf->config_set('archive_after' => '0d'); + } +); +magic( + AllowCalendarAdmin => sub { my $conf = shift; $conf->config_set('caldav_allowcalendaradmin' => 'yes'); -}); -magic(NoCheckSyslog => sub { + } +); +magic( + NoCheckSyslog => sub { my $self = shift; $self->{no_check_syslog} = 1; -}); -magic(JmapMaxCalendarEventNotifs => sub { + } +); +magic( + JmapMaxCalendarEventNotifs => sub { my $conf = shift; # set to some small number $conf->config_set('jmap_max_calendareventnotifs' => 10); -}); -magic(NoReplicaonly => sub { + } +); +magic( + NoReplicaonly => sub { my $self = shift; $self->{no_replicaonly} = 1; -}); -magic(ConversationMaxThread10 => sub { + } +); +magic( + ConversationMaxThread10 => sub { my $self = shift; $self->config_set('conversation_max_thread' => 10); -}); -magic(SlowIO => sub { + } +); +magic( + SlowIO => sub { my $self = shift; $self->config_set('debug_slowio' => 'yes'); -}); -magic(Mboxgroups => sub { + } +); +magic( + Mboxgroups => sub { my $self = shift; $self->config_set('auth_mech' => 'mboxgroups'); -}); + } +); # Run any magic handlers indicated by the test name or attributes -sub _run_magic -{ - my ($self) = @_; - - my %seen; - - foreach my $m (split(/_/, $self->{_name})) - { - next if $seen{$m}; - next unless defined $magic_handlers{$m}; - $self->{_current_magic} = "Magic word $m in name"; - $magic_handlers{$m}->($self); - $self->{_current_magic} = undef; - $seen{$m} = 1; - } - - my $sub = $self->can($self->{_name}); - if (defined $sub) { - foreach my $a (attributes::get($sub)) - { - my $m = lc($a); - # ignore min/max version attribution here - next if $a =~ m/^(?:min|max)_version_/; - # ignore feature test attribution here - next if $a =~ m/^needs_/; - # ignore want attribution here - next if $a =~ m/^want_/; - die "Unknown attribute $a" - unless defined $magic_handlers{$m}; - next if $seen{$m}; - $self->{_current_magic} = "Magic attribute $a"; - $magic_handlers{$m}->($self); - $self->{_current_magic} = undef; - $seen{$m} = 1; - } +sub _run_magic { + my ($self) = @_; + + my %seen; + + foreach my $m (split(/_/, $self->{_name})) { + next if $seen{$m}; + next unless defined $magic_handlers{$m}; + $self->{_current_magic} = "Magic word $m in name"; + $magic_handlers{$m}->($self); + $self->{_current_magic} = undef; + $seen{$m} = 1; + } + + my $sub = $self->can($self->{_name}); + if (defined $sub) { + foreach my $a (attributes::get($sub)) { + my $m = lc($a); + # ignore min/max version attribution here + next if $a =~ m/^(?:min|max)_version_/; + # ignore feature test attribution here + next if $a =~ m/^needs_/; + # ignore want attribution here + next if $a =~ m/^want_/; + die "Unknown attribute $a" + unless defined $magic_handlers{$m}; + next if $seen{$m}; + $self->{_current_magic} = "Magic attribute $a"; + $magic_handlers{$m}->($self); + $self->{_current_magic} = undef; + $seen{$m} = 1; } + } } -sub _create_instances -{ - my ($self) = @_; - my $sync_port; - my $mupdate_port; - my $frontend_service_port; - my $backend1_service_port; - my $backend2_service_port; - my $backupd_port; - - $self->{_config} = $self->{_instance_params}->{config} || Cassandane::Config->default(); - $self->{_config} = $self->{_config}->clone(); - - $self->_run_magic(); - - my $want = $self->{_want}; - my %instance_params = %{$self->{_instance_params}}; - - my $cassini = Cassandane::Cassini->instance(); - - if ($want->{imapmurder} && $want->{httpmurder}) { - # XXX Murder is implemented assuming that everything is on standard - # XXX ports, but Cassandane needs to use high port numbers. - # XXX We fudge a workaround here by embedding the service port - # XXX number in the server name, which tricks Murder into using - # XXX that port number instead of the standard one, but that means - # XXX we can only have one proxied service per instance. - die "Cannot enable murder for both IMAP and JMAP at the same time"; +sub _create_instances { + my ($self) = @_; + my $sync_port; + my $mupdate_port; + my $frontend_service_port; + my $backend1_service_port; + my $backend2_service_port; + my $backupd_port; + + $self->{_config} + = $self->{_instance_params}->{config} || Cassandane::Config->default(); + $self->{_config} = $self->{_config}->clone(); + + $self->_run_magic(); + + my $want = $self->{_want}; + my %instance_params = %{ $self->{_instance_params} }; + + my $cassini = Cassandane::Cassini->instance(); + + if ($want->{imapmurder} && $want->{httpmurder}) { + # XXX Murder is implemented assuming that everything is on standard + # XXX ports, but Cassandane needs to use high port numbers. + # XXX We fudge a workaround here by embedding the service port + # XXX number in the server name, which tricks Murder into using + # XXX that port number instead of the standard one, but that means + # XXX we can only have one proxied service per instance. + die "Cannot enable murder for both IMAP and JMAP at the same time"; + } + + if ($want->{instance}) { + my $conf = $self->{_config}->clone(); + + if ($want->{replica} || $want->{csyncreplica}) { + $sync_port = Cassandane::PortManager::alloc("localhost"); + $conf->set( + # sync_client will find the port in the config + sync_host => 'localhost', + sync_port => $sync_port, + # tell sync_client how to login + sync_authname => 'repluser', + sync_password => 'replpass', + ); } - if ($want->{instance}) - { - my $conf = $self->{_config}->clone(); - - if ($want->{replica} || $want->{csyncreplica}) - { - $sync_port = Cassandane::PortManager::alloc("localhost"); - $conf->set( - # sync_client will find the port in the config - sync_host => 'localhost', - sync_port => $sync_port, - # tell sync_client how to login - sync_authname => 'repluser', - sync_password => 'replpass', - ); - } - - if ($want->{imapmurder} || $want->{httpmurder}) - { - $mupdate_port = Cassandane::PortManager::alloc("localhost"); - $backend1_service_port = Cassandane::PortManager::alloc("localhost"); - - $conf->set( - servername => "localhost:$backend1_service_port", - mupdate_server => "localhost:$mupdate_port", - # XXX documentation says to use mupdate_port, but - # XXX this doesn't work -- need to embed port number in - # XXX mupdate_server setting instead. - #mupdate_port => $mupdate_port, - mupdate_username => 'mupduser', - mupdate_authname => 'mupduser', - mupdate_password => 'mupdpass', - proxyservers => 'mailproxy', - lmtp_admins => 'mailproxy', - proxy_authname => 'mailproxy', - proxy_password => 'mailproxy', - ); - } - - if ($want->{backups}) - { - $backupd_port = Cassandane::PortManager::alloc("localhost"); - $conf->set( - backup_sync_host => "localhost", - backup_sync_port => $backupd_port, - backup_sync_authname => 'repluser', - backup_sync_password => 'repluser', - backup_sync_try_imap => 'no', - xbackup_enabled => 'yes', - ); - } - - my $sub = $self->{_name}; - if ($sub =~ s/^test_/config_/ && $self->can($sub)) - { - die 'Use of config_ subs is not supported anymore'; - } - - $instance_params{config} = $conf; - $instance_params{install_certificates} = $want->{install_certificates}; - - $instance_params{description} = "main instance for test $self->{_name}"; - $self->{instance} = Cassandane::Instance->new(%instance_params); - $self->{instance}->add_services(@{$want->{services}}); - $self->{instance}->_setup_for_deliver() - if ($want->{deliver}); - - if ($want->{squatter}) { - $self->{instance}->add_daemon( - name => 'squatter', - argv => [ - 'squatter', - '-R', - ], - wait => 'y', - ); - } - - if ($want->{replica} || $want->{csyncreplica}) - { - my %replica_params = %instance_params; - $replica_params{config} = $conf->clone(); - $replica_params{config}->set(sync_rightnow_channel => undef); - unless ($self->{no_replicaonly}) { - $replica_params{config}->set(replicaonly => 'yes'); - } - my $cyrus_replica_prefix = $cassini->val('cyrus replica', 'prefix'); - if (defined $cyrus_replica_prefix and -d $cyrus_replica_prefix) { - xlog $self, "replica instance: using [cyrus replica] configuration"; - $replica_params{installation} = 'replica'; - } - - $replica_params{description} = "replica instance for test $self->{_name}"; - $self->{replica} = Cassandane::Instance->new(%replica_params, - setup_mailbox => 0); - my ($v) = Cassandane::Instance->get_version($replica_params{installation}); - if ($v < 3 || $want->{csyncreplica}) { - $self->{replica}->add_service(name => 'sync', - port => $sync_port, - argv => ['sync_server']); - } - else { - $self->{replica}->add_service(name => 'sync', port => $sync_port); - } - $self->{replica}->add_services(@{$want->{services}}); - $self->{replica}->_setup_for_deliver() - if ($want->{deliver}); - } - - if ($want->{imapmurder} || $want->{httpmurder}) - { - $frontend_service_port = Cassandane::PortManager::alloc("localhost"); - $backend2_service_port = Cassandane::PortManager::alloc("localhost"); - - # set up a front end on which we also run the mupdate master - my $frontend_conf = $self->{_config}->clone(); - $frontend_conf->set( - servername => "localhost:$frontend_service_port", - mupdate_server => "localhost:$mupdate_port", - # XXX documentation says to use mupdate_port, but - # XXX this doesn't work -- need to embed port number in - # XXX mupdate_server setting instead. - #mupdate_port => $mupdate_port, - mupdate_username => 'mupduser', - mupdate_authname => 'mupduser', - mupdate_password => 'mupdpass', - serverlist => - "localhost:$backend1_service_port localhost:$backend2_service_port", - proxy_authname => 'mailproxy', - proxy_password => 'mailproxy', - ); - - my $cyrus_murder_prefix = $cassini->val('cyrus murder', 'prefix'); - if (defined $cyrus_murder_prefix and -d $cyrus_murder_prefix) { - xlog $self, "murder instance: using [cyrus murder] configuration"; - $instance_params{installation} = 'murder'; - } - - $instance_params{description} = "murder frontend for test $self->{_name}"; - $instance_params{config} = $frontend_conf; - $self->{frontend} = Cassandane::Instance->new(%instance_params, - setup_mailbox => 0); - $self->{frontend}->add_service(name => 'mupdate', - port => $mupdate_port, - argv => ['mupdate', '-m'], - prefork => 1); - $self->{frontend}->add_services(@{$want->{services}}); - $self->{frontend}->_setup_for_deliver() - if ($want->{deliver}); - - # arrange for frontend service to run on a known port - if ($want->{imapmurder}) { - $self->{frontend}->remove_service('imap'); - $self->{frontend}->add_service(name => 'imap', - port => $frontend_service_port); - } - elsif ($want->{httpmurder}) { - $self->{frontend}->remove_service('http'); - $self->{frontend}->add_service(name => 'http', - port => $frontend_service_port); - } - else { - die "shouldn't get here!"; - } - - # arrange for backend1 to push to mupdate on startup - $self->{instance}->add_start(name => 'mupdatepush', - argv => ['ctl_mboxlist', '-m']); - - # arrange for backend1 service to run on a known port - if ($want->{imapmurder}) { - $self->{instance}->remove_service('imap'); - $self->{instance}->add_service(name => 'imap', - port => $backend1_service_port); - } - elsif ($want->{httpmurder}) { - $self->{instance}->remove_service('http'); - $self->{instance}->add_service(name => 'http', - port => $backend1_service_port); - } - else { - die "shouldn't get here!"; - } - - # set up a second backend - my $backend2_conf = $self->{_config}->clone(); - $backend2_conf->set( - servername => "localhost:$backend2_service_port", - mupdate_server => "localhost:$mupdate_port", - # XXX documentation says to use mupdate_port, but - # XXX this doesn't work -- need to embed port number in - # XXX mupdate_server setting instead. - #mupdate_port => $mupdate_port, - mupdate_username => 'mupduser', - mupdate_authname => 'mupduser', - mupdate_password => 'mupdpass', - proxyservers => 'mailproxy', - lmtp_admins => 'mailproxy', - sasl_mech_list => 'PLAIN', - proxy_authname => 'mailproxy', - proxy_password => 'mailproxy', - ); - - $instance_params{description} = "murder backend2 for test $self->{_name}"; - $instance_params{config} = $backend2_conf; - $self->{backend2} = Cassandane::Instance->new(%instance_params, - setup_mailbox => 0); # XXX ? - $self->{backend2}->add_services(@{$want->{services}}); - - # arrange for backend2 to push to mupdate on startup - $self->{backend2}->add_start(name => 'mupdatepush', - argv => ['ctl_mboxlist', '-m']); - - # arrange for backend2 service to run on a known port - if ($want->{imapmurder}) { - $self->{backend2}->remove_service('imap'); - $self->{backend2}->add_service(name => 'imap', - port => $backend2_service_port); - } - elsif ($want->{httpmurder}) { - $self->{backend2}->remove_service('http'); - $self->{backend2}->add_service(name => 'http', - port => $backend2_service_port); - } - else { - die "shouldn't get here!"; - } - - $self->{backend2}->_setup_for_deliver() - if ($want->{deliver}); - } - - if ($want->{backups}) - { - # set up a backup server - my $backup_conf = $self->{_config}->clone(); - $backup_conf->set( - temp_path => '@basedir@/tmp', - backup_keep_previous => 'yes', - 'backuppartition-default' => '@basedir@/data/backup', - ); - - my $cyrus_backup_prefix = $cassini->val('cyrus backup', 'prefix'); - if (defined $cyrus_backup_prefix and -d $cyrus_backup_prefix) { - xlog $self, "backup instance: using [cyrus backup] configuration"; - $instance_params{installation} = 'backup'; - } - - $instance_params{description} = "backup server for test $self->{_name}"; - $instance_params{config} = $backup_conf; - - $self->{backups} = Cassandane::Instance->new(%instance_params, - setup_mailbox => 0); - $self->{backups}->add_service(name => 'backup', - port => $backupd_port, - argv => ['backupd']); - } + if ($want->{imapmurder} || $want->{httpmurder}) { + $mupdate_port = Cassandane::PortManager::alloc("localhost"); + $backend1_service_port = Cassandane::PortManager::alloc("localhost"); + + $conf->set( + servername => "localhost:$backend1_service_port", + mupdate_server => "localhost:$mupdate_port", + # XXX documentation says to use mupdate_port, but + # XXX this doesn't work -- need to embed port number in + # XXX mupdate_server setting instead. + #mupdate_port => $mupdate_port, + mupdate_username => 'mupduser', + mupdate_authname => 'mupduser', + mupdate_password => 'mupdpass', + proxyservers => 'mailproxy', + lmtp_admins => 'mailproxy', + proxy_authname => 'mailproxy', + proxy_password => 'mailproxy', + ); } - if ($want->{gen}) - { - $self->{gen} = Cassandane::Generator->new(); + if ($want->{backups}) { + $backupd_port = Cassandane::PortManager::alloc("localhost"); + $conf->set( + backup_sync_host => "localhost", + backup_sync_port => $backupd_port, + backup_sync_authname => 'repluser', + backup_sync_password => 'repluser', + backup_sync_try_imap => 'no', + xbackup_enabled => 'yes', + ); } -} -sub _setup_http_service_objects -{ - my ($self) = @_; + my $sub = $self->{_name}; + if ($sub =~ s/^test_/config_/ && $self->can($sub)) { + die 'Use of config_ subs is not supported anymore'; + } - # nothing to do if no http service - require Mail::JMAPTalk; - require Net::CalDAVTalk; - require Net::CardDAVTalk; + $instance_params{config} = $conf; + $instance_params{install_certificates} = $want->{install_certificates}; + + $instance_params{description} = "main instance for test $self->{_name}"; + $self->{instance} = Cassandane::Instance->new(%instance_params); + $self->{instance}->add_services(@{ $want->{services} }); + $self->{instance}->_setup_for_deliver() + if ($want->{deliver}); + + if ($want->{squatter}) { + $self->{instance}->add_daemon( + name => 'squatter', + argv => [ 'squatter', '-R', ], + wait => 'y', + ); + } - my $service = $self->{instance}->get_service("http"); - return if !$service; - - if ($self->{instance}->{config}->get_bit('httpmodules', 'carddav')) { - require Net::CardDAVTalk; - $self->{carddav} = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, + if ($want->{replica} || $want->{csyncreplica}) { + my %replica_params = %instance_params; + $replica_params{config} = $conf->clone(); + $replica_params{config}->set(sync_rightnow_channel => undef); + unless ($self->{no_replicaonly}) { + $replica_params{config}->set(replicaonly => 'yes'); + } + my $cyrus_replica_prefix = $cassini->val('cyrus replica', 'prefix'); + if (defined $cyrus_replica_prefix and -d $cyrus_replica_prefix) { + xlog $self, "replica instance: using [cyrus replica] configuration"; + $replica_params{installation} = 'replica'; + } + + $replica_params{description} = "replica instance for test $self->{_name}"; + $self->{replica} + = Cassandane::Instance->new(%replica_params, setup_mailbox => 0); + my ($v) + = Cassandane::Instance->get_version($replica_params{installation}); + if ($v < 3 || $want->{csyncreplica}) { + $self->{replica}->add_service( + name => 'sync', + port => $sync_port, + argv => ['sync_server'] ); + } else { + $self->{replica}->add_service(name => 'sync', port => $sync_port); + } + $self->{replica}->add_services(@{ $want->{services} }); + $self->{replica}->_setup_for_deliver() + if ($want->{deliver}); } - if ($self->{instance}->{config}->get_bit('httpmodules', 'caldav')) { - require Net::CalDAVTalk; - $self->{caldav} = Net::CalDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, + + if ($want->{imapmurder} || $want->{httpmurder}) { + $frontend_service_port = Cassandane::PortManager::alloc("localhost"); + $backend2_service_port = Cassandane::PortManager::alloc("localhost"); + + # set up a front end on which we also run the mupdate master + my $frontend_conf = $self->{_config}->clone(); + $frontend_conf->set( + servername => "localhost:$frontend_service_port", + mupdate_server => "localhost:$mupdate_port", + # XXX documentation says to use mupdate_port, but + # XXX this doesn't work -- need to embed port number in + # XXX mupdate_server setting instead. + #mupdate_port => $mupdate_port, + mupdate_username => 'mupduser', + mupdate_authname => 'mupduser', + mupdate_password => 'mupdpass', + serverlist => + "localhost:$backend1_service_port localhost:$backend2_service_port", + proxy_authname => 'mailproxy', + proxy_password => 'mailproxy', + ); + + my $cyrus_murder_prefix = $cassini->val('cyrus murder', 'prefix'); + if (defined $cyrus_murder_prefix and -d $cyrus_murder_prefix) { + xlog $self, "murder instance: using [cyrus murder] configuration"; + $instance_params{installation} = 'murder'; + } + + $instance_params{description} = "murder frontend for test $self->{_name}"; + $instance_params{config} = $frontend_conf; + $self->{frontend} + = Cassandane::Instance->new(%instance_params, setup_mailbox => 0); + $self->{frontend}->add_service( + name => 'mupdate', + port => $mupdate_port, + argv => [ 'mupdate', '-m' ], + prefork => 1 + ); + $self->{frontend}->add_services(@{ $want->{services} }); + $self->{frontend}->_setup_for_deliver() + if ($want->{deliver}); + + # arrange for frontend service to run on a known port + if ($want->{imapmurder}) { + $self->{frontend}->remove_service('imap'); + $self->{frontend}->add_service( + name => 'imap', + port => $frontend_service_port ); - $self->{caldav}->UpdateAddressSet("Test User", - "cassandane\@example.com"); - } - if ($self->{instance}->{config}->get_bit('httpmodules', 'jmap')) { - require Mail::JMAPTalk; - $ENV{DEBUGJMAP} = 1; - $self->{jmap} = Mail::JMAPTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', + } elsif ($want->{httpmurder}) { + $self->{frontend}->remove_service('http'); + $self->{frontend}->add_service( + name => 'http', + port => $frontend_service_port ); - } - - xlog $self, "http service objects setup complete!"; -} - -sub set_up -{ - my ($self) = @_; + } else { + die "shouldn't get here!"; + } + + # arrange for backend1 to push to mupdate on startup + $self->{instance}->add_start( + name => 'mupdatepush', + argv => [ 'ctl_mboxlist', '-m' ] + ); + + # arrange for backend1 service to run on a known port + if ($want->{imapmurder}) { + $self->{instance}->remove_service('imap'); + $self->{instance}->add_service( + name => 'imap', + port => $backend1_service_port + ); + } elsif ($want->{httpmurder}) { + $self->{instance}->remove_service('http'); + $self->{instance}->add_service( + name => 'http', + port => $backend1_service_port + ); + } else { + die "shouldn't get here!"; + } + + # set up a second backend + my $backend2_conf = $self->{_config}->clone(); + $backend2_conf->set( + servername => "localhost:$backend2_service_port", + mupdate_server => "localhost:$mupdate_port", + # XXX documentation says to use mupdate_port, but + # XXX this doesn't work -- need to embed port number in + # XXX mupdate_server setting instead. + #mupdate_port => $mupdate_port, + mupdate_username => 'mupduser', + mupdate_authname => 'mupduser', + mupdate_password => 'mupdpass', + proxyservers => 'mailproxy', + lmtp_admins => 'mailproxy', + sasl_mech_list => 'PLAIN', + proxy_authname => 'mailproxy', + proxy_password => 'mailproxy', + ); + + $instance_params{description} = "murder backend2 for test $self->{_name}"; + $instance_params{config} = $backend2_conf; + $self->{backend2} + = Cassandane::Instance->new(%instance_params, setup_mailbox => 0) + ; # XXX ? + $self->{backend2}->add_services(@{ $want->{services} }); + + # arrange for backend2 to push to mupdate on startup + $self->{backend2}->add_start( + name => 'mupdatepush', + argv => [ 'ctl_mboxlist', '-m' ] + ); + + # arrange for backend2 service to run on a known port + if ($want->{imapmurder}) { + $self->{backend2}->remove_service('imap'); + $self->{backend2}->add_service( + name => 'imap', + port => $backend2_service_port + ); + } elsif ($want->{httpmurder}) { + $self->{backend2}->remove_service('http'); + $self->{backend2}->add_service( + name => 'http', + port => $backend2_service_port + ); + } else { + die "shouldn't get here!"; + } - xlog "---------- BEGIN $self->{_name} ----------"; - - $self->_create_instances(); - if ($self->{_want}->{start_instances}) { - eval { - $self->_start_instances(); - $self->_setup_http_service_objects() if defined $self->{instance}; - }; - if ($@) { - my $e = $@; - $self->tear_down(); - die $e; - } - } - else { - xlog $self, "Instances not started due to :NoStartInstances magic!"; - xlog $self, "HTTP service objects not setup due to :NoStartInstances" - . " magic!"; + $self->{backend2}->_setup_for_deliver() + if ($want->{deliver}); } - if ($self->{no_check_syslog}) { - xlog $self, "Disabling syslog checks for test instance"; + if ($want->{backups}) { + # set up a backup server + my $backup_conf = $self->{_config}->clone(); + $backup_conf->set( + temp_path => '@basedir@/tmp', + backup_keep_previous => 'yes', + 'backuppartition-default' => '@basedir@/data/backup', + ); + + my $cyrus_backup_prefix = $cassini->val('cyrus backup', 'prefix'); + if (defined $cyrus_backup_prefix and -d $cyrus_backup_prefix) { + xlog $self, "backup instance: using [cyrus backup] configuration"; + $instance_params{installation} = 'backup'; + } + + $instance_params{description} = "backup server for test $self->{_name}"; + $instance_params{config} = $backup_conf; + + $self->{backups} + = Cassandane::Instance->new(%instance_params, setup_mailbox => 0); + $self->{backups}->add_service( + name => 'backup', + port => $backupd_port, + argv => ['backupd'] + ); } + } - xlog $self, "Calling test function"; + if ($want->{gen}) { + $self->{gen} = Cassandane::Generator->new(); + } } -sub _start_instances -{ - my ($self) = @_; +sub _setup_http_service_objects { + my ($self) = @_; - $self->{frontend}->start() - if (defined $self->{frontend}); - $self->{instance}->start() - if (defined $self->{instance}); - $self->{backend2}->start() - if (defined $self->{backend2}); - $self->{replica}->start() - if (defined $self->{replica}); - $self->{backups}->start() - if (defined $self->{backups}); - - $self->{store} = undef; - $self->{adminstore} = undef; - $self->{master_store} = undef; - $self->{master_adminstore} = undef; - $self->{replica_store} = undef; - $self->{replica_adminstore} = undef; - $self->{frontend_store} = undef; - $self->{frontend_adminstore} = undef; - $self->{backend1_store} = undef; - $self->{backend1_adminstore} = undef; - $self->{backend2_store} = undef; - $self->{backend2_adminstore} = undef; - - # Run the replication engine to create the user mailbox - # in the replica. Doing it this way avoids issues with - # mismatched mailbox uniqueids. - $self->run_replication() - if (defined $self->{replica}); - - my %store_params; - $store_params{folder} = $self->{_want}->{folder} - if defined $self->{_want}->{folder}; - - my %adminstore_params = ( %store_params, username => 'admin' ); - # The admin stores need an extra parameter to force their - # default folder because otherwise they will default to 'INBOX' - # which refers to user.admin not user.cassandane - $adminstore_params{folder} ||= 'INBOX'; - $adminstore_params{folder} = 'user.cassandane' - if ($adminstore_params{folder} =~ m/^inbox$/i); - - if (defined $self->{instance}) - { - my $svc = $self->{instance}->get_service('imap'); - if (defined $svc) - { - $self->{store} = $svc->create_store(%store_params) - if ($self->{_want}->{store}); - $self->{adminstore} = $svc->create_store(%adminstore_params) - if ($self->{_want}->{adminstore}); - } - } - if (defined $self->{replica}) - { - # aliases for the master's store(s) - $self->{master_store} = $self->{store}; - $self->{master_adminstore} = $self->{adminstore}; - - my $svc = $self->{replica}->get_service('imap'); - if (defined $svc) - { - $self->{replica_store} = $svc->create_store(%store_params) - if ($self->{_want}->{store}); - $self->{replica_adminstore} = $svc->create_store(%adminstore_params) - if ($self->{_want}->{adminstore}); - } - } - if (defined $self->{frontend}) - { - # aliases for first backend store - $self->{backend1_store} = $self->{store}; - $self->{backend1_adminstore} = $self->{adminstore}; - - my $svc = $self->{frontend}->get_service('imap'); - if (defined $svc) - { - $self->{frontend_store} = $svc->create_store(%store_params) - if ($self->{_want}->{store}); - $self->{frontend_adminstore} = $svc->create_store(%adminstore_params) - if ($self->{_want}->{adminstore}); - } - } - if (defined $self->{backend2}) - { - my $svc = $self->{backend2}->get_service('imap'); - if (defined $svc) - { - $self->{backend2_store} = $svc->create_store(%store_params) - if ($self->{_want}->{store}); - $self->{backend2_adminstore} = $svc->create_store(%adminstore_params) - if ($self->{_want}->{adminstore}); - } - } + # nothing to do if no http service + require Mail::JMAPTalk; + require Net::CalDAVTalk; + require Net::CardDAVTalk; + + my $service = $self->{instance}->get_service("http"); + return if !$service; + + if ($self->{instance}->{config}->get_bit('httpmodules', 'carddav')) { + require Net::CardDAVTalk; + $self->{carddav} = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + } + if ($self->{instance}->{config}->get_bit('httpmodules', 'caldav')) { + require Net::CalDAVTalk; + $self->{caldav} = Net::CalDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + $self->{caldav}->UpdateAddressSet("Test User", "cassandane\@example.com"); + } + if ($self->{instance}->{config}->get_bit('httpmodules', 'jmap')) { + require Mail::JMAPTalk; + $ENV{DEBUGJMAP} = 1; + $self->{jmap} = Mail::JMAPTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + } + + xlog $self, "http service objects setup complete!"; } -sub tear_down -{ - my ($self) = @_; +sub set_up { + my ($self) = @_; - xlog $self, "Beginning tear_down"; + xlog "---------- BEGIN $self->{_name} ----------"; - foreach my $s (@stores) - { - if (defined $self->{$s}) - { - $self->{$s}->disconnect(); - $self->{$s} = undef; - } + $self->_create_instances(); + if ($self->{_want}->{start_instances}) { + eval { + $self->_start_instances(); + $self->_setup_http_service_objects() if defined $self->{instance}; + }; + if ($@) { + my $e = $@; + $self->tear_down(); + die $e; } - $self->{master_store} = undef; - $self->{master_adminstore} = undef; - $self->{backend1_store} = undef; - $self->{backend1_adminstore} = undef; + } else { + xlog $self, "Instances not started due to :NoStartInstances magic!"; + xlog $self, + "HTTP service objects not setup due to :NoStartInstances" . " magic!"; + } - my @stop_errors; - my @basedirs; + if ($self->{no_check_syslog}) { + xlog $self, "Disabling syslog checks for test instance"; + } - if (defined $self->{instance}) - { - eval { - push @stop_errors, $self->{instance}->stop( - no_check_syslog => defined $self->{no_check_syslog} - ); - }; - push @basedirs, $self->{instance}->get_basedir(); - $self->{instance} = undef; + xlog $self, "Calling test function"; +} + +sub _start_instances { + my ($self) = @_; + + $self->{frontend}->start() + if (defined $self->{frontend}); + $self->{instance}->start() + if (defined $self->{instance}); + $self->{backend2}->start() + if (defined $self->{backend2}); + $self->{replica}->start() + if (defined $self->{replica}); + $self->{backups}->start() + if (defined $self->{backups}); + + $self->{store} = undef; + $self->{adminstore} = undef; + $self->{master_store} = undef; + $self->{master_adminstore} = undef; + $self->{replica_store} = undef; + $self->{replica_adminstore} = undef; + $self->{frontend_store} = undef; + $self->{frontend_adminstore} = undef; + $self->{backend1_store} = undef; + $self->{backend1_adminstore} = undef; + $self->{backend2_store} = undef; + $self->{backend2_adminstore} = undef; + + # Run the replication engine to create the user mailbox + # in the replica. Doing it this way avoids issues with + # mismatched mailbox uniqueids. + $self->run_replication() + if (defined $self->{replica}); + + my %store_params; + $store_params{folder} = $self->{_want}->{folder} + if defined $self->{_want}->{folder}; + + my %adminstore_params = (%store_params, username => 'admin'); + # The admin stores need an extra parameter to force their + # default folder because otherwise they will default to 'INBOX' + # which refers to user.admin not user.cassandane + $adminstore_params{folder} ||= 'INBOX'; + $adminstore_params{folder} = 'user.cassandane' + if ($adminstore_params{folder} =~ m/^inbox$/i); + + if (defined $self->{instance}) { + my $svc = $self->{instance}->get_service('imap'); + if (defined $svc) { + $self->{store} = $svc->create_store(%store_params) + if ($self->{_want}->{store}); + $self->{adminstore} = $svc->create_store(%adminstore_params) + if ($self->{_want}->{adminstore}); } - if (defined $self->{backups}) - { - eval { - push @stop_errors, $self->{backups}->stop( - no_check_syslog => defined $self->{no_check_syslog} - ); - }; - push @basedirs, $self->{backups}->get_basedir(); - $self->{backups} = undef; + } + if (defined $self->{replica}) { + # aliases for the master's store(s) + $self->{master_store} = $self->{store}; + $self->{master_adminstore} = $self->{adminstore}; + + my $svc = $self->{replica}->get_service('imap'); + if (defined $svc) { + $self->{replica_store} = $svc->create_store(%store_params) + if ($self->{_want}->{store}); + $self->{replica_adminstore} = $svc->create_store(%adminstore_params) + if ($self->{_want}->{adminstore}); } - if (defined $self->{backend2}) - { - eval { - push @stop_errors, $self->{backend2}->stop( - no_check_syslog => defined $self->{no_check_syslog} - ); - }; - push @basedirs, $self->{backend2}->get_basedir(); - $self->{backend2} = undef; + } + if (defined $self->{frontend}) { + # aliases for first backend store + $self->{backend1_store} = $self->{store}; + $self->{backend1_adminstore} = $self->{adminstore}; + + my $svc = $self->{frontend}->get_service('imap'); + if (defined $svc) { + $self->{frontend_store} = $svc->create_store(%store_params) + if ($self->{_want}->{store}); + $self->{frontend_adminstore} = $svc->create_store(%adminstore_params) + if ($self->{_want}->{adminstore}); } - if (defined $self->{replica}) - { - eval { - push @stop_errors, $self->{replica}->stop( - no_check_syslog => defined $self->{no_check_syslog} - ); - }; - push @basedirs, $self->{replica}->get_basedir(); - $self->{replica} = undef; + } + if (defined $self->{backend2}) { + my $svc = $self->{backend2}->get_service('imap'); + if (defined $svc) { + $self->{backend2_store} = $svc->create_store(%store_params) + if ($self->{_want}->{store}); + $self->{backend2_adminstore} = $svc->create_store(%adminstore_params) + if ($self->{_want}->{adminstore}); } - if (defined $self->{frontend}) - { - eval { - push @stop_errors, $self->{frontend}->stop( - no_check_syslog => defined $self->{no_check_syslog} - ); - }; - push @basedirs, $self->{frontend}->get_basedir(); - $self->{frontend} = undef; + } +} + +sub tear_down { + my ($self) = @_; + + xlog $self, "Beginning tear_down"; + + foreach my $s (@stores) { + if (defined $self->{$s}) { + $self->{$s}->disconnect(); + $self->{$s} = undef; } + } + $self->{master_store} = undef; + $self->{master_adminstore} = undef; + $self->{backend1_store} = undef; + $self->{backend1_adminstore} = undef; + + my @stop_errors; + my @basedirs; + + if (defined $self->{instance}) { + eval { + push @stop_errors, $self->{instance} + ->stop(no_check_syslog => defined $self->{no_check_syslog}); + }; + push @basedirs, $self->{instance}->get_basedir(); + $self->{instance} = undef; + } + if (defined $self->{backups}) { + eval { + push @stop_errors, $self->{backups} + ->stop(no_check_syslog => defined $self->{no_check_syslog}); + }; + push @basedirs, $self->{backups}->get_basedir(); + $self->{backups} = undef; + } + if (defined $self->{backend2}) { + eval { + push @stop_errors, $self->{backend2} + ->stop(no_check_syslog => defined $self->{no_check_syslog}); + }; + push @basedirs, $self->{backend2}->get_basedir(); + $self->{backend2} = undef; + } + if (defined $self->{replica}) { + eval { + push @stop_errors, $self->{replica} + ->stop(no_check_syslog => defined $self->{no_check_syslog}); + }; + push @basedirs, $self->{replica}->get_basedir(); + $self->{replica} = undef; + } + if (defined $self->{frontend}) { + eval { + push @stop_errors, $self->{frontend} + ->stop(no_check_syslog => defined $self->{no_check_syslog}); + }; + push @basedirs, $self->{frontend}->get_basedir(); + $self->{frontend} = undef; + } - $self->{cleanup_basedirs} = [@basedirs]; + $self->{cleanup_basedirs} = [@basedirs]; - # maybe there's multiple errors, but we can only die for one of them... - die $stop_errors[0] if scalar @stop_errors; + # maybe there's multiple errors, but we can only die for one of them... + die $stop_errors[0] if scalar @stop_errors; - xlog "---------- END $self->{_name} ----------"; + xlog "---------- END $self->{_name} ----------"; } -sub post_tear_down -{ - my ($self, $result) = @_; - - if ($result eq 'pass' - && ref $self->{cleanup_basedirs} - && Cassandane::Cassini->instance()->bool_val('cassandane', 'cleanup') - ) { - foreach my $basedir (@{$self->{cleanup_basedirs}}) { - xlog $self, "Cleaning up basedir " . $basedir; - rmtree $basedir; - } +sub post_tear_down { + my ($self, $result) = @_; + + if ( $result eq 'pass' + && ref $self->{cleanup_basedirs} + && Cassandane::Cassini->instance()->bool_val('cassandane', 'cleanup')) + { + foreach my $basedir (@{ $self->{cleanup_basedirs} }) { + xlog $self, "Cleaning up basedir " . $basedir; + rmtree $basedir; } + } - die "Found some stray processes" - if (Cassandane::GenericListener::kill_processes_on_ports( - Cassandane::PortManager::free_all())); + die "Found some stray processes" + if ( + Cassandane::GenericListener::kill_processes_on_ports( + Cassandane::PortManager::free_all() + ) + ); } -sub _save_message -{ - my ($self, $msg, $store) = @_; +sub _save_message { + my ($self, $msg, $store) = @_; - $store ||= $self->{store}; + $store ||= $self->{store}; - $store->write_begin(); - $store->write_message($msg); - $store->write_end(); + $store->write_begin(); + $store->write_message($msg); + $store->write_end(); } -sub make_message -{ - my ($self, $subject, %attrs) = @_; +sub make_message { + my ($self, $subject, %attrs) = @_; - my $store = $attrs{store}; # may be undef - delete $attrs{store}; + my $store = $attrs{store}; # may be undef + delete $attrs{store}; - my $msg = $self->{gen}->generate(subject => $subject, %attrs); - $msg->remove_headers('subject') if !defined $subject; - $msg->remove_headers('to') if exists $attrs{to} and not $attrs{to}; - $msg->remove_headers('from') if exists $attrs{from} and not $attrs{from}; - $self->_save_message($msg, $store); + my $msg = $self->{gen}->generate(subject => $subject, %attrs); + $msg->remove_headers('subject') if !defined $subject; + $msg->remove_headers('to') if exists $attrs{to} and not $attrs{to}; + $msg->remove_headers('from') if exists $attrs{from} and not $attrs{from}; + $self->_save_message($msg, $store); - return $msg; + return $msg; } -sub make_random_data -{ - my ($self, $kb, %params) = @_; - my $data = ''; - $params{minreps} = 10 - unless defined $params{minreps}; - $params{maxreps} = 100 - unless defined $params{maxreps}; - $params{separators} = ' ' - unless defined $params{separators}; - my $sepidx = 0; - while (!defined $kb || length($data) < 1024*$kb) - { - my $word = random_word(); - my $count = $params{minreps} + - rand($params{maxreps} - $params{minreps}); - while ($count > 0) - { - my $sep = substr($params{separators}, - $sepidx % length($params{separators}), 1); - $sepidx++; - $data .= $sep . $word; - $count--; - } - last unless defined $kb; +sub make_random_data { + my ($self, $kb, %params) = @_; + my $data = ''; + $params{minreps} = 10 + unless defined $params{minreps}; + $params{maxreps} = 100 + unless defined $params{maxreps}; + $params{separators} = ' ' + unless defined $params{separators}; + my $sepidx = 0; + while (!defined $kb || length($data) < 1024 * $kb) { + my $word = random_word(); + my $count = $params{minreps} + rand($params{maxreps} - $params{minreps}); + while ($count > 0) { + my $sep + = substr($params{separators}, $sepidx % length($params{separators}), 1); + $sepidx++; + $data .= $sep . $word; + $count--; } - return $data; + last unless defined $kb; + } + return $data; } -sub check_messages -{ - my ($self, $expected, %params) = @_; - my $actual = $params{actual}; - my $check_guid = $params{check_guid}; - $check_guid = 1 unless defined $check_guid; - my $keyed_on = $params{keyed_on} || 'subject'; +sub check_messages { + my ($self, $expected, %params) = @_; + my $actual = $params{actual}; + my $check_guid = $params{check_guid}; + $check_guid = 1 unless defined $check_guid; + my $keyed_on = $params{keyed_on} || 'subject'; + + xlog $self, "check_messages: " . join(' ', %params); + + if (!defined $actual) { + my $store = $params{store} || $self->{store}; + $actual = {}; + $store->read_begin(); + while (my $msg = $store->read_message()) { + my $key = $msg->$keyed_on(); + $self->assert(!defined $actual->{$key}); + $actual->{$key} = $msg; + } + $store->read_end(); + } - xlog $self, "check_messages: " . join(' ', %params); + $self->assert_num_equals(scalar keys %$expected, scalar keys %$actual); - if (!defined $actual) - { - my $store = $params{store} || $self->{store}; - $actual = {}; - $store->read_begin(); - while (my $msg = $store->read_message()) - { - my $key = $msg->$keyed_on(); - $self->assert(!defined $actual->{$key}); - $actual->{$key} = $msg; - } - $store->read_end(); - } + foreach my $expmsg (values %$expected) { + my $key = $expmsg->$keyed_on(); + xlog $self, "message \"$key\""; + my $actmsg = $actual->{$key}; - $self->assert_num_equals(scalar keys %$expected, scalar keys %$actual); + $self->assert_not_null($actmsg); - foreach my $expmsg (values %$expected) - { - my $key = $expmsg->$keyed_on(); - xlog $self, "message \"$key\""; - my $actmsg = $actual->{$key}; - - $self->assert_not_null($actmsg); - - if ($check_guid) - { - xlog $self, "checking guid"; - $self->assert_str_equals($expmsg->get_guid(), - $actmsg->get_guid()); - } - - # Check required headers - foreach my $h (qw(x-cassandane-unique)) - { - xlog $self, "checking header $h"; - $self->assert_not_null($actmsg->get_header($h)); - $self->assert_str_equals($expmsg->get_header($h), - $actmsg->get_header($h)); - } - - # if there were optional headers we wished to check, do it here - - # check optional string attributes - foreach my $a (qw(id uid cid)) - { - next unless defined $expmsg->get_attribute($a); - xlog $self, "checking attribute $a"; - $self->assert_str_equals($expmsg->get_attribute($a), - $actmsg->get_attribute($a)); - } - - # check optional structured attributes - foreach my $a (qw(modseq)) - { - next unless defined $expmsg->get_attribute($a); - xlog $self, "checking attribute $a"; - $self->assert_deep_equals($expmsg->get_attribute($a), - $actmsg->get_attribute($a)); - } - - # check optional order-agnostic attributes - foreach my $a (qw(flags)) - { - next unless defined $expmsg->get_attribute($a); - xlog $self, "checking attribute $a"; - - my $exp = $expmsg->get_attribute($a); - my $act = $actmsg->get_attribute($a); - - if (ref $exp eq 'ARRAY') { - $exp = [ sort @{$exp} ]; - } - if (ref $act eq 'ARRAY') { - $act = [ sort @{$act} ]; - } - - $self->assert_deep_equals($exp, $act); - } - - # check annotations - foreach my $ea ($expmsg->list_annotations()) - { - xlog $self, "checking annotation ($ea->{entry} $ea->{attrib})"; - $self->assert($actmsg->has_annotation($ea)); - my $expval = $expmsg->get_annotation($ea); - my $actval = $actmsg->get_annotation($ea); - if (defined $expval) - { - $self->assert_not_null($actval); - $self->assert_str_equals($expval, $actval); - } - else - { - $self->assert_null($actval); - } - } + if ($check_guid) { + xlog $self, "checking guid"; + $self->assert_str_equals($expmsg->get_guid(), $actmsg->get_guid()); } - return $actual; -} + # Check required headers + foreach my $h (qw(x-cassandane-unique)) { + xlog $self, "checking header $h"; + $self->assert_not_null($actmsg->get_header($h)); + $self->assert_str_equals($expmsg->get_header($h), + $actmsg->get_header($h)); + } -sub _disconnect_all -{ - my ($self) = @_; + # if there were optional headers we wished to check, do it here - foreach my $s (@stores) - { - $self->{$s}->disconnect() - if defined $self->{$s}; + # check optional string attributes + foreach my $a (qw(id uid cid)) { + next unless defined $expmsg->get_attribute($a); + xlog $self, "checking attribute $a"; + $self->assert_str_equals($expmsg->get_attribute($a), + $actmsg->get_attribute($a)); } -} -sub _reconnect_all -{ - my ($self) = @_; + # check optional structured attributes + foreach my $a (qw(modseq)) { + next unless defined $expmsg->get_attribute($a); + xlog $self, "checking attribute $a"; + $self->assert_deep_equals($expmsg->get_attribute($a), + $actmsg->get_attribute($a)); + } - foreach my $s (@stores) - { - if (defined $self->{$s}) - { - $self->{$s}->connect(); - $self->{$s}->_select(); - } + # check optional order-agnostic attributes + foreach my $a (qw(flags)) { + next unless defined $expmsg->get_attribute($a); + xlog $self, "checking attribute $a"; + + my $exp = $expmsg->get_attribute($a); + my $act = $actmsg->get_attribute($a); + + if (ref $exp eq 'ARRAY') { + $exp = [ sort @{$exp} ]; + } + if (ref $act eq 'ARRAY') { + $act = [ sort @{$act} ]; + } + + $self->assert_deep_equals($exp, $act); } -} -sub run_replication -{ - my ($self, %opts) = @_; - - # Parse options from caller - my $server = $self->{replica}->get_service('sync')->store_params()->{host}; - $server = delete $opts{server} if exists $opts{server}; - # $server might be undef at this point - my $channel = delete $opts{channel}; - my $inputfile = delete $opts{inputfile}; - - # mode options - my $nmodes = 0; - my $user = delete $opts{user}; - my $rolling = delete $opts{rolling}; - my $mailbox = delete $opts{mailbox}; - my $meta = delete $opts{meta}; - my $nosyncback = delete $opts{nosyncback}; - my $allusers = delete $opts{allusers}; - my $stagetoarchive = delete $opts{stagetoarchive}; - $nmodes++ if $user; - $nmodes++ if $rolling; - $nmodes++ if $mailbox; - $nmodes++ if $meta; - $nmodes++ if $allusers; - - # pass through run_command options - my $handlers = delete $opts{handlers}; - my $redirects = delete $opts{redirects}; - - # historical default for Cassandane tests is user mode - $user = 'cassandane' if ($nmodes == 0); - die "Too many mode options" if ($nmodes > 1); - - die "Unrecognised options: " . join(' ', keys %opts) if (scalar %opts); - - xlog $self, "running replication"; - - # Disconnect during replication to ensure no imapd - # is locking the mailbox, which gives us a spurious - # error which is ignored in real world scenarios. - $self->_disconnect_all(); - - # build sync_client command line - my @cmd = ('sync_client', '-v', '-v', '-o'); - push(@cmd, '-S', $server) if defined $server; - push(@cmd, '-n', $channel) if defined $channel; - push(@cmd, '-f', $inputfile) if defined $inputfile; - push(@cmd, '-R') if defined $rolling; - push(@cmd, '-s') if defined $meta; - push(@cmd, '-O') if defined $nosyncback; - push(@cmd, '-u', $user) if defined $user; - push(@cmd, '-m', $mailbox) if defined $mailbox; - push(@cmd, '-A') if $allusers; # n.b. boolean - push(@cmd, '-a') if $stagetoarchive; # n.b. boolean - - my %run_options; - $run_options{cyrus} = 1; - $run_options{handlers} = $handlers if defined $handlers; - $run_options{redirects} = $redirects if defined $redirects; - $self->{instance}->run_command(\%run_options, @cmd); - - $self->_reconnect_all(); -} + # check annotations + foreach my $ea ($expmsg->list_annotations()) { + xlog $self, "checking annotation ($ea->{entry} $ea->{attrib})"; + $self->assert($actmsg->has_annotation($ea)); + my $expval = $expmsg->get_annotation($ea); + my $actval = $actmsg->get_annotation($ea); + if (defined $expval) { + $self->assert_not_null($actval); + $self->assert_str_equals($expval, $actval); + } else { + $self->assert_null($actval); + } + } + } -sub check_replication { - my ($self, $user) = @_; + return $actual; +} - # get store connections as the user +sub _disconnect_all { + my ($self) = @_; - my $mastersvc = $self->{instance}->get_service('imap'); - my $masterstore = $mastersvc->create_store(username => $user); + foreach my $s (@stores) { + $self->{$s}->disconnect() + if defined $self->{$s}; + } +} - my $replicasvc = $self->{replica}->get_service('imap'); - my $replicastore = $replicasvc->create_store(username => $user); +sub _reconnect_all { + my ($self) = @_; - my $CR = Cyrus::CheckReplication->new( - IMAPs1 => $masterstore->get_client(), - IMAPs2 => $replicastore->get_client(), - CyrusName => $user, - SleepTime => 0, - Repeats => 1, - CheckConversations => 1, - CheckAnnotations => 1, - CheckMetadata => 1, - ); - $CR->CheckUserReplication(2); - if ($CR->HasError()) { - my @Messages = $CR->GetMessages(); - $self->assert(0, "GOT ERRORS " . join(', ', @Messages)); + foreach my $s (@stores) { + if (defined $self->{$s}) { + $self->{$s}->connect(); + $self->{$s}->_select(); } + } } -sub run_delayed_expunge -{ - my ($self) = @_; +sub run_replication { + my ($self, %opts) = @_; + + # Parse options from caller + my $server = $self->{replica}->get_service('sync')->store_params()->{host}; + $server = delete $opts{server} if exists $opts{server}; + # $server might be undef at this point + my $channel = delete $opts{channel}; + my $inputfile = delete $opts{inputfile}; + + # mode options + my $nmodes = 0; + my $user = delete $opts{user}; + my $rolling = delete $opts{rolling}; + my $mailbox = delete $opts{mailbox}; + my $meta = delete $opts{meta}; + my $nosyncback = delete $opts{nosyncback}; + my $allusers = delete $opts{allusers}; + my $stagetoarchive = delete $opts{stagetoarchive}; + $nmodes++ if $user; + $nmodes++ if $rolling; + $nmodes++ if $mailbox; + $nmodes++ if $meta; + $nmodes++ if $allusers; + + # pass through run_command options + my $handlers = delete $opts{handlers}; + my $redirects = delete $opts{redirects}; + + # historical default for Cassandane tests is user mode + $user = 'cassandane' if ($nmodes == 0); + die "Too many mode options" if ($nmodes > 1); + + die "Unrecognised options: " . join(' ', keys %opts) if (scalar %opts); + + xlog $self, "running replication"; + + # Disconnect during replication to ensure no imapd + # is locking the mailbox, which gives us a spurious + # error which is ignored in real world scenarios. + $self->_disconnect_all(); + + # build sync_client command line + my @cmd = ('sync_client', '-v', '-v', '-o'); + push(@cmd, '-S', $server) if defined $server; + push(@cmd, '-n', $channel) if defined $channel; + push(@cmd, '-f', $inputfile) if defined $inputfile; + push(@cmd, '-R') if defined $rolling; + push(@cmd, '-s') if defined $meta; + push(@cmd, '-O') if defined $nosyncback; + push(@cmd, '-u', $user) if defined $user; + push(@cmd, '-m', $mailbox) if defined $mailbox; + push(@cmd, '-A') if $allusers; # n.b. boolean + push(@cmd, '-a') if $stagetoarchive; # n.b. boolean + + my %run_options; + $run_options{cyrus} = 1; + $run_options{handlers} = $handlers if defined $handlers; + $run_options{redirects} = $redirects if defined $redirects; + $self->{instance}->run_command(\%run_options, @cmd); + + $self->_reconnect_all(); +} - xlog $self, "Performing delayed expunge"; +sub check_replication { + my ($self, $user) = @_; + + # get store connections as the user + + my $mastersvc = $self->{instance}->get_service('imap'); + my $masterstore = $mastersvc->create_store(username => $user); + + my $replicasvc = $self->{replica}->get_service('imap'); + my $replicastore = $replicasvc->create_store(username => $user); + + my $CR = Cyrus::CheckReplication->new( + IMAPs1 => $masterstore->get_client(), + IMAPs2 => $replicastore->get_client(), + CyrusName => $user, + SleepTime => 0, + Repeats => 1, + CheckConversations => 1, + CheckAnnotations => 1, + CheckMetadata => 1, + ); + $CR->CheckUserReplication(2); + if ($CR->HasError()) { + my @Messages = $CR->GetMessages(); + $self->assert(0, "GOT ERRORS " . join(', ', @Messages)); + } +} - $self->_disconnect_all(); +sub run_delayed_expunge { + my ($self) = @_; - my @cmd = ( 'cyr_expire', '-E', '1', '-X', '0', '-D', '0' ); - push(@cmd, '-v') - if get_verbose; - $self->{instance}->run_command({ cyrus => 1 }, @cmd); + xlog $self, "Performing delayed expunge"; - $self->_reconnect_all(); -} + $self->_disconnect_all(); -sub check_conversations -{ - my ($self) = @_; - my $filename = $self->{instance}{basedir} . "/ctl_conversationsdb.out"; - $self->{instance}->run_command({ - cyrus => 1, - redirects => {stdout => $filename}, - }, 'ctl_conversationsdb', '-A', '-r', '-v'); + my @cmd = ('cyr_expire', '-E', '1', '-X', '0', '-D', '0'); + push(@cmd, '-v') + if get_verbose; + $self->{instance}->run_command({ cyrus => 1 }, @cmd); - my $str = slurp_file($filename); + $self->_reconnect_all(); +} - xlog $self, "RESULT: $str"; - $self->assert_matches(qr/is OK/, $str); - $self->assert_does_not_match(qr/is BROKEN/, $str); +sub check_conversations { + my ($self) = @_; + my $filename = $self->{instance}{basedir} . "/ctl_conversationsdb.out"; + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { stdout => $filename }, + }, + 'ctl_conversationsdb', + '-A', '-r', '-v' + ); + + my $str = slurp_file($filename); + + xlog $self, "RESULT: $str"; + $self->assert_matches(qr/is OK/, $str); + $self->assert_does_not_match(qr/is BROKEN/, $str); } # Set up a mailbox structure from data # See List.pm tests for examples of how to drive this -sub setup_mailbox_structure -{ - my ($self, $client, $test_data) = @_; - - foreach my $row (@{$test_data}) { - my ($cmd, $arg) = @{$row}; - if (ref $arg) { - foreach (@{$arg}) { - $client->$cmd($_) || die "$cmd '$_': $@"; - } - } - else { - $client->$cmd($arg) || die "$cmd '$_': $@"; - } +sub setup_mailbox_structure { + my ($self, $client, $test_data) = @_; + + foreach my $row (@{$test_data}) { + my ($cmd, $arg) = @{$row}; + if (ref $arg) { + foreach (@{$arg}) { + $client->$cmd($_) || die "$cmd '$_': $@"; + } + } else { + $client->$cmd($arg) || die "$cmd '$_': $@"; } + } } # Assert that the provided LIST response match the expected data # See List.pm tests for examples of how to drive this -sub assert_mailbox_structure -{ - my ($self, $actual, $expected_hiersep, $expected_mailbox_flags, - $strict, $msg) = @_; - - # rearrange list output into order-agnostic format - my %actual_hash; - foreach my $row (@{$actual}) { - my ($flags, $hiersep, $mailbox) = @{$row}; - - $actual_hash{$mailbox} = { - flags => { map { (lc($_) => 1) } @{$flags} }, - hiersep => $hiersep, - mailbox => $mailbox, - } +sub assert_mailbox_structure { + my ($self, $actual, $expected_hiersep, $expected_mailbox_flags, $strict, $msg) + = @_; + + # rearrange list output into order-agnostic format + my %actual_hash; + foreach my $row (@{$actual}) { + my ($flags, $hiersep, $mailbox) = @{$row}; + + $actual_hash{$mailbox} = { + flags => { map { (lc($_) => 1) } @{$flags} }, + hiersep => $hiersep, + mailbox => $mailbox, + }; + } + + # check that expected data exists + foreach my $mailbox (sort keys %{$expected_mailbox_flags}) { + xlog $self, "expect mailbox: $mailbox"; + $self->assert(exists $actual_hash{$mailbox}, + "'$mailbox': mailbox not found"); + + $self->assert_str_equals( + $actual_hash{$mailbox}->{hiersep}, + $expected_hiersep, + "'$mailbox': got hierarchy separator '" + . $actual_hash{$mailbox}->{hiersep} + . "', expected '$expected_hiersep'" + ); + + my %expected_flags; + if (ref $expected_mailbox_flags->{$mailbox}) { + %expected_flags + = map { (lc($_) => 1) } @{ $expected_mailbox_flags->{$mailbox} }; + } else { + %expected_flags = map { (lc($_) => 1) } + split / /, $expected_mailbox_flags->{$mailbox}; } - # check that expected data exists - foreach my $mailbox (sort keys %{$expected_mailbox_flags}) { - xlog $self, "expect mailbox: $mailbox"; + # look for expected flags + foreach my $flag (sort keys %expected_flags) { + # https://tools.ietf.org/html/rfc5258#section-3.4: + # \NoInferiors implies \HasNoChildren + # \NonExistent implies \NoSelect + if ($flag eq "\\hasnochildren") { $self->assert( - exists $actual_hash{$mailbox}, - "'$mailbox': mailbox not found" + ( + exists $actual_hash{$mailbox}->{flags}->{$flag} + || exists $actual_hash{$mailbox}->{flags}->{"\\noinferiors"} + ), + "'$mailbox': missing flag '$flag'" ); - - $self->assert_str_equals( - $actual_hash{$mailbox}->{hiersep}, - $expected_hiersep, - "'$mailbox': got hierarchy separator '" - . $actual_hash{$mailbox}->{hiersep} - . "', expected '$expected_hiersep'" + } elsif ($flag eq "\\noselect") { + $self->assert( + ( + exists $actual_hash{$mailbox}->{flags}->{$flag} + || exists $actual_hash{$mailbox}->{flags}->{"\\nonexistent"} + ), + "'$mailbox': missing flag '$flag'" ); - - my %expected_flags; - if (ref $expected_mailbox_flags->{$mailbox}) { - %expected_flags = map { (lc($_) => 1) } - @{$expected_mailbox_flags->{$mailbox}}; - } - else { - %expected_flags = map { (lc($_) => 1) } - split / /, $expected_mailbox_flags->{$mailbox}; - } - - # look for expected flags - foreach my $flag (sort keys %expected_flags) { - # https://tools.ietf.org/html/rfc5258#section-3.4: - # \NoInferiors implies \HasNoChildren - # \NonExistent implies \NoSelect - if ($flag eq "\\hasnochildren") { - $self->assert( - (exists $actual_hash{$mailbox}->{flags}->{$flag} - || exists $actual_hash{$mailbox}->{flags}->{"\\noinferiors"}), - "'$mailbox': missing flag '$flag'" - ); - } - elsif ($flag eq "\\noselect") { - $self->assert( - (exists $actual_hash{$mailbox}->{flags}->{$flag} - || exists $actual_hash{$mailbox}->{flags}->{"\\nonexistent"}), - "'$mailbox': missing flag '$flag'" - ); - } - else { - $self->assert( - exists $actual_hash{$mailbox}->{flags}->{$flag}, - "'$mailbox': missing flag '$flag'" - ); - } - } - - next if not $strict; - - # look for unexpected flags - foreach my $flag (sort keys %{$actual_hash{$mailbox}->{flags}}) { - if ($flag eq "\\noinferiors") { - $self->assert( - (exists $actual_hash{$mailbox}->{flags}->{$flag} - || exists $actual_hash{$mailbox}->{flags}->{"\\hasnochildren"}), - "'$mailbox': found unexpected flag '$flag'" - ); - } - elsif ($flag eq "\\nonexistent") { - $self->assert( - (exists $actual_hash{$mailbox}->{flags}->{$flag} - || exists $actual_hash{$mailbox}->{flags}->{"\\noselect"}), - "'$mailbox': found unexpected flag '$flag'" - ); - } - else { - $self->assert( - exists $expected_flags{$flag}, - "'$mailbox': found unexected flag '$flag'" - ); - } - } + } else { + $self->assert(exists $actual_hash{$mailbox}->{flags}->{$flag}, + "'$mailbox': missing flag '$flag'"); + } } - # check that unexpected data does not exist - foreach my $mailbox (sort keys %actual_hash) { + next if not $strict; + + # look for unexpected flags + foreach my $flag (sort keys %{ $actual_hash{$mailbox}->{flags} }) { + if ($flag eq "\\noinferiors") { + $self->assert( + ( + exists $actual_hash{$mailbox}->{flags}->{$flag} + || exists $actual_hash{$mailbox}->{flags}->{"\\hasnochildren"} + ), + "'$mailbox': found unexpected flag '$flag'" + ); + } elsif ($flag eq "\\nonexistent") { $self->assert( - exists $expected_mailbox_flags->{$mailbox}, - "'$mailbox': found unexpected extra mailbox" + ( + exists $actual_hash{$mailbox}->{flags}->{$flag} + || exists $actual_hash{$mailbox}->{flags}->{"\\noselect"} + ), + "'$mailbox': found unexpected flag '$flag'" ); + } else { + $self->assert(exists $expected_flags{$flag}, + "'$mailbox': found unexected flag '$flag'"); + } } + } + + # check that unexpected data does not exist + foreach my $mailbox (sort keys %actual_hash) { + $self->assert( + exists $expected_mailbox_flags->{$mailbox}, + "'$mailbox': found unexpected extra mailbox" + ); + } } -sub assert_sieve_exists -{ - my ($self, $instance, $user, $scriptname, $bc_only) = @_; +sub assert_sieve_exists { + my ($self, $instance, $user, $scriptname, $bc_only) = @_; - my $sieve_dir = $instance->get_sieve_script_dir($user); + my $sieve_dir = $instance->get_sieve_script_dir($user); - $self->assert(( -f "$sieve_dir/$scriptname.bc" ), - "$sieve_dir/$scriptname.bc: file not found"); + $self->assert((-f "$sieve_dir/$scriptname.bc"), + "$sieve_dir/$scriptname.bc: file not found"); - if ($bc_only == 0) { - $self->assert(( -f "$sieve_dir/$scriptname.script" ), - "$sieve_dir/$scriptname.script: file not found"); - } + if ($bc_only == 0) { + $self->assert((-f "$sieve_dir/$scriptname.script"), + "$sieve_dir/$scriptname.script: file not found"); + } } -sub assert_sieve_not_exists -{ - my ($self, $instance, $user, $scriptname, $bc_only) = @_; +sub assert_sieve_not_exists { + my ($self, $instance, $user, $scriptname, $bc_only) = @_; - my $sieve_dir = $instance->get_sieve_script_dir($user); + my $sieve_dir = $instance->get_sieve_script_dir($user); - $self->assert(( ! -f "$sieve_dir/$scriptname.bc" ), - "$sieve_dir/$scriptname.bc: file exists"); + $self->assert( + (!-f "$sieve_dir/$scriptname.bc"), + "$sieve_dir/$scriptname.bc: file exists" + ); - if ($bc_only == 0) { - $self->assert(( ! -f "$sieve_dir/$scriptname.script" ), - "$sieve_dir/$scriptname.script: file exists"); - } + if ($bc_only == 0) { + $self->assert( + (!-f "$sieve_dir/$scriptname.script"), + "$sieve_dir/$scriptname.script: file exists" + ); + } } -sub assert_sieve_active -{ - my ($self, $instance, $user, $scriptname) = @_; +sub assert_sieve_active { + my ($self, $instance, $user, $scriptname) = @_; - my $sieve_dir = $instance->get_sieve_script_dir($user); + my $sieve_dir = $instance->get_sieve_script_dir($user); - $self->assert(( -l "$sieve_dir/defaultbc" ), - "$sieve_dir/defaultbc: missing or not a symlink"); - $self->assert_str_equals("$scriptname.bc", readlink "$sieve_dir/defaultbc"); + $self->assert((-l "$sieve_dir/defaultbc"), + "$sieve_dir/defaultbc: missing or not a symlink"); + $self->assert_str_equals("$scriptname.bc", readlink "$sieve_dir/defaultbc"); } -sub assert_sieve_noactive -{ - my ($self, $instance, $user) = @_; +sub assert_sieve_noactive { + my ($self, $instance, $user) = @_; - my $sieve_dir = $instance->get_sieve_script_dir($user); + my $sieve_dir = $instance->get_sieve_script_dir($user); - $self->assert(( ! -e "$sieve_dir/defaultbc" ), - "$sieve_dir/defaultbc exists"); - $self->assert(( ! -l "$sieve_dir/defaultbc" ), - "dangling $sieve_dir/defaultbc symlink exists"); + $self->assert((!-e "$sieve_dir/defaultbc"), "$sieve_dir/defaultbc exists"); + $self->assert((!-l "$sieve_dir/defaultbc"), + "dangling $sieve_dir/defaultbc symlink exists"); } -sub assert_sieve_matches -{ - my ($self, $instance, $user, $scriptname, $scriptcontent) = @_; +sub assert_sieve_matches { + my ($self, $instance, $user, $scriptname, $scriptcontent) = @_; - my $sieve_dir = $instance->get_sieve_script_dir($user); + my $sieve_dir = $instance->get_sieve_script_dir($user); - my $bcname = "$sieve_dir/$scriptname.bc"; + my $bcname = "$sieve_dir/$scriptname.bc"; - $self->assert(( -f $bcname ), - "$sieve_dir/$scriptname.bc: file not found"); + $self->assert((-f $bcname), "$sieve_dir/$scriptname.bc: file not found"); - # compile $scriptcontent and compare digests of bytecode - my (undef, $tmp) = tempfile('scriptXXXXX', OPEN => 0, - DIR => $instance->{basedir} . "/tmp"); - open my $f, '>', $tmp or die "open: $!"; - print $f $scriptcontent; - close $f; + # compile $scriptcontent and compare digests of bytecode + my (undef, $tmp) = tempfile( + 'scriptXXXXX', + OPEN => 0, + DIR => $instance->{basedir} . "/tmp" + ); + open my $f, '>', $tmp or die "open: $!"; + print $f $scriptcontent; + close $f; - my (undef, $filename) = tempfile('tmpXXXXXX', OPEN => 0, - DIR => $instance->{basedir} . "/tmp"); + my (undef, $filename) = tempfile( + 'tmpXXXXXX', + OPEN => 0, + DIR => $instance->{basedir} . "/tmp" + ); - $instance->run_command({ redirects => {stdin => \$scriptcontent}, - cyrus => 1, - }, - 'sievec', $tmp, "$filename"); - $self->assert_str_equals(digest_file_hex($bcname, "MD5"), - digest_file_hex($filename, "MD5")); + $instance->run_command( + { + redirects => { stdin => \$scriptcontent }, + cyrus => 1, + }, + 'sievec', $tmp, + "$filename" + ); + $self->assert_str_equals(digest_file_hex($bcname, "MD5"), + digest_file_hex($filename, "MD5")); } -sub assert_syslog_matches -{ - my ($self, $instance, $pattern) = @_; +sub assert_syslog_matches { + my ($self, $instance, $pattern) = @_; - if ($instance->{have_syslog_replacement}) { - $self->assert((scalar $instance->getsyslog($pattern) >= 1), - "syslog does not match pattern $pattern"); - } + if ($instance->{have_syslog_replacement}) { + $self->assert( + (scalar $instance->getsyslog($pattern) >= 1), + "syslog does not match pattern $pattern" + ); + } } -sub assert_syslog_does_not_match -{ - my ($self, $instance, $pattern) = @_; +sub assert_syslog_does_not_match { + my ($self, $instance, $pattern) = @_; - if ($instance->{have_syslog_replacement}) { - $self->assert((scalar $instance->getsyslog($pattern) == 0), - "syslog matches pattern $pattern"); - } + if ($instance->{have_syslog_replacement}) { + $self->assert((scalar $instance->getsyslog($pattern) == 0), + "syslog matches pattern $pattern"); + } } # create a bunch of mailboxes and messages with various flags and annots, # returning a hash of what to expect to find there later -sub populate_user -{ - my ($self, $instance, $store, $folders) = @_; - - my $created = {}; - - my @specialuse = qw(Drafts Junk Sent Trash); - - foreach my $folder (@{$folders}) { - $store->set_folder($folder); - - # create some messages - foreach my $n (1 .. 20) { - my $msg = $self->make_message("Message $n", store => $store); - $created->{mailboxes}->{$folder}->{messages}->{$msg->uid()} = $msg; - } - - # fizzbuzz some flags - my $talk = $store->get_client(); - $talk->select($folder); - my $n = 1; - while (my ($uid, $msg) - = each %{$created->{mailboxes}->{$folder}->{messages}}) - { - my @flags; - - if ($n % 3 == 0) { - # fizz - $talk->store("$uid", '+flags', '(\\Flagged)'); - $self->assert_str_equals( - 'ok', $talk->get_last_completion_response() - ); - push @flags, '\\Flagged'; - } - if ($n % 5 == 0) { - # buzz - $talk->store("$uid", '+flags', '(\\Deleted)'); - $self->assert_str_equals( - 'ok', $talk->get_last_completion_response() - ); - push @flags, '\\Deleted'; - } - - $msg->set_attribute('flags', \@flags) if scalar @flags; - $n++; - } - - # make sure the messages are as expected - $store->set_fetch_attributes('uid', 'flags'); - $self->check_messages($created->{mailboxes}->{$folder}->{messages}, - store => $store, - check_guid => 0, - keyed_on => 'uid'); - - # maybe set a special use annotation if the folder name is such - my ($suflag, @extra) = grep { - lc $folder =~ m{^(?:INBOX[./])?$_$} - } @specialuse; - if ($suflag and not scalar @extra) { - $talk->setmetadata($folder, '/private/specialuse', "\\$suflag"); - $self->assert_str_equals('ok', - $talk->get_last_completion_response()); - $created->{mailboxes}->{$folder}->{specialuse} = "\\$suflag"; - } - } +sub populate_user { + my ($self, $instance, $store, $folders) = @_; + + my $created = {}; - # XXX ought to be conditional on whether $instance was built with sieve - # XXX support, but Cassandane::BuildInfo doesn't currently support - # XXX choosing an instance to ask about... - my $scriptname = random_word(); - my $scriptcontent = 'keep;'; + my @specialuse = qw(Drafts Junk Sent Trash); - $instance->install_sieve_script($scriptcontent, - name => $scriptname, - username => $store->{username}); - $self->assert_sieve_exists($instance, $store->{username}, $scriptname); - $self->assert_sieve_active($instance, $store->{username}, $scriptname); + foreach my $folder (@{$folders}) { + $store->set_folder($folder); - $created->{sieve}->{scripts}->{$scriptname} = $scriptcontent; - $created->{sieve}->{active} = $scriptname; + # create some messages + foreach my $n (1 .. 20) { + my $msg = $self->make_message("Message $n", store => $store); + $created->{mailboxes}->{$folder}->{messages}->{ $msg->uid() } = $msg; + } - return $created; + # fizzbuzz some flags + my $talk = $store->get_client(); + $talk->select($folder); + my $n = 1; + while (my ($uid, $msg) + = each %{ $created->{mailboxes}->{$folder}->{messages} }) + { + my @flags; + + if ($n % 3 == 0) { + # fizz + $talk->store("$uid", '+flags', '(\\Flagged)'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + push @flags, '\\Flagged'; + } + if ($n % 5 == 0) { + # buzz + $talk->store("$uid", '+flags', '(\\Deleted)'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + push @flags, '\\Deleted'; + } + + $msg->set_attribute('flags', \@flags) if scalar @flags; + $n++; + } + + # make sure the messages are as expected + $store->set_fetch_attributes('uid', 'flags'); + $self->check_messages( + $created->{mailboxes}->{$folder}->{messages}, + store => $store, + check_guid => 0, + keyed_on => 'uid' + ); + + # maybe set a special use annotation if the folder name is such + my ($suflag, @extra) + = grep { lc $folder =~ m{^(?:INBOX[./])?$_$} } @specialuse; + if ($suflag and not scalar @extra) { + $talk->setmetadata($folder, '/private/specialuse', "\\$suflag"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $created->{mailboxes}->{$folder}->{specialuse} = "\\$suflag"; + } + } + + # XXX ought to be conditional on whether $instance was built with sieve + # XXX support, but Cassandane::BuildInfo doesn't currently support + # XXX choosing an instance to ask about... + my $scriptname = random_word(); + my $scriptcontent = 'keep;'; + + $instance->install_sieve_script( + $scriptcontent, + name => $scriptname, + username => $store->{username} + ); + $self->assert_sieve_exists($instance, $store->{username}, $scriptname); + $self->assert_sieve_active($instance, $store->{username}, $scriptname); + + $created->{sieve}->{scripts}->{$scriptname} = $scriptcontent; + $created->{sieve}->{active} = $scriptname; + + return $created; } # check that the contents of the store match the data returned by # populate_user() -sub check_user -{ - my ($self, $instance, $store, $expected) = @_; - - die "bad expected hash" if ref $expected ne 'HASH'; - - foreach my $folder (keys %{$expected->{mailboxes}}) { - $store->set_folder($folder); - $store->set_fetch_attributes('uid', 'flags'); - $self->check_messages($expected->{mailboxes}->{$folder}->{messages}, - store => $store, - check_guid => 0, - keyed_on => 'uid'); - - my $specialuse = $expected->{mailboxes}->{$folder}->{specialuse}; - if ($specialuse) { - my $talk = $store->get_client(); - my $res = $talk->getmetadata($folder, '/private/specialuse'); - $self->assert_str_equals('ok', - $talk->get_last_completion_response()); - $self->assert_not_null($res); - - $self->assert_str_equals($specialuse, - $res->{$folder}->{'/private/specialuse'}); - } +sub check_user { + my ($self, $instance, $store, $expected) = @_; + + die "bad expected hash" if ref $expected ne 'HASH'; + + foreach my $folder (keys %{ $expected->{mailboxes} }) { + $store->set_folder($folder); + $store->set_fetch_attributes('uid', 'flags'); + $self->check_messages( + $expected->{mailboxes}->{$folder}->{messages}, + store => $store, + check_guid => 0, + keyed_on => 'uid' + ); + + my $specialuse = $expected->{mailboxes}->{$folder}->{specialuse}; + if ($specialuse) { + my $talk = $store->get_client(); + my $res = $talk->getmetadata($folder, '/private/specialuse'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + + $self->assert_str_equals($specialuse, + $res->{$folder}->{'/private/specialuse'}); } + } - if (exists $expected->{sieve}) { - while (my ($scriptname, $scriptcontent) - = each %{$expected->{sieve}->{scripts}}) - { - $self->assert_sieve_exists($instance, $store->{username}, - $scriptname, 1); - $self->assert_sieve_matches($instance, $store->{username}, - $scriptname, $scriptcontent); - } - if ($expected->{sieve}->{active}) { - $self->assert_sieve_active($instance, $store->{username}, - $expected->{sieve}->{active}); - } + if (exists $expected->{sieve}) { + while (my ($scriptname, $scriptcontent) + = each %{ $expected->{sieve}->{scripts} }) + { + $self->assert_sieve_exists($instance, $store->{username}, $scriptname, 1); + $self->assert_sieve_matches($instance, $store->{username}, + $scriptname, $scriptcontent); + } + if ($expected->{sieve}->{active}) { + $self->assert_sieve_active($instance, $store->{username}, + $expected->{sieve}->{active}); } + } } 1; diff --git a/cassandane/Cassandane/Cyrus/TesterCalDAV.pm b/cassandane/Cassandane/Cyrus/TesterCalDAV.pm index 98e748162a..27a2d7fbdb 100644 --- a/cassandane/Cassandane/Cyrus/TesterCalDAV.pm +++ b/cassandane/Cassandane/Cyrus/TesterCalDAV.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::TesterCalDAV; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use JSON::XS; @@ -1452,242 +1452,237 @@ well-known/Simple PROPFIND tests/5 | 1 well-known/Simple PROPFIND tests/6 | 1 EOF -sub init -{ - my $cassini = Cassandane::Cassini->instance(); - $basedir = $cassini->val('caldavtester', 'basedir'); - return unless defined $basedir; - $basedir = abs_path($basedir); +sub init { + my $cassini = Cassandane::Cassini->instance(); + $basedir = $cassini->val('caldavtester', 'basedir'); + return unless defined $basedir; + $basedir = abs_path($basedir); - my $supp = $cassini->val('caldavtester', 'suppress-caldav', - ''); - map { $suppressed{$_} = 1; } split(/\s+/, $supp); + my $supp = $cassini->val('caldavtester', 'suppress-caldav', ''); + map { $suppressed{$_} = 1; } split(/\s+/, $supp); - foreach my $row (split /\n/, $KNOWN_ERRORS) { - next if $row =~ m/^\s*\#/; - next unless $row =~ m/\S/; - my ($key, @items) = split /\s*\|\s*/, $row; - $expected{$key} = \@items; - } + foreach my $row (split /\n/, $KNOWN_ERRORS) { + next if $row =~ m/^\s*\#/; + next unless $row =~ m/\S/; + my ($key, @items) = split /\s*\|\s*/, $row; + $expected{$key} = \@items; + } - $binary = "$basedir/testcaldav.py"; - $testdir = "$basedir/scripts/tests/CalDAV"; + $binary = "$basedir/testcaldav.py"; + $testdir = "$basedir/scripts/tests/CalDAV"; } init; -sub new -{ - my $class = shift; +sub new { + my $class = shift; - my $buildinfo = Cassandane::BuildInfo->new(); + my $buildinfo = Cassandane::BuildInfo->new(); - if (not defined $basedir or not $buildinfo->get('component', 'httpd')) { - # don't bother setting up, we're not running tests anyway - return $class->SUPER::new({}, @_); - } + if (not defined $basedir or not $buildinfo->get('component', 'httpd')) { + # don't bother setting up, we're not running tests anyway + return $class->SUPER::new({}, @_); + } - my $config = Cassandane::Config->default()->clone(); - $config->set(servername => "127.0.0.1"); # urlauth needs matching servername - $config->set(caldav_realm => 'Cassandane'); - $config->set(httpmodules => 'caldav'); - $config->set(httpallowcompress => 'no'); + my $config = Cassandane::Config->default()->clone(); + $config->set(servername => "127.0.0.1"); # urlauth needs matching servername + $config->set(caldav_realm => 'Cassandane'); + $config->set(httpmodules => 'caldav'); + $config->set(httpallowcompress => 'no'); - return $class->SUPER::new({ - config => $config, - adminstore => 1, - services => ['imap', 'http'], - }, @_); + return $class->SUPER::new( + { + config => $config, + adminstore => 1, + services => [ 'imap', 'http' ], + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); - if (not defined $basedir - or not $self->{instance}->{buildinfo}->get('component', 'httpd')) - { - # don't bother setting up further, we're not running tests anyway - return; - } + if ( not defined $basedir + or not $self->{instance}->{buildinfo}->get('component', 'httpd')) + { + # don't bother setting up further, we're not running tests anyway + return; + } - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - $ENV{JMAP_ALWAYS_FULL} = 1; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + $ENV{JMAP_ALWAYS_FULL} = 1; - for (1..40) { - my $name = sprintf("user%02d", $_); - my $displayname = sprintf("User %02d", $_); - $admintalk->create("user.$name"); - $admintalk->setacl("user.$name", admin => 'lrswipkxtecda'); - $admintalk->setacl("user.$name", $name => 'lrswipkxtecd'); + for (1 .. 40) { + my $name = sprintf("user%02d", $_); + my $displayname = sprintf("User %02d", $_); + $admintalk->create("user.$name"); + $admintalk->setacl("user.$name", admin => 'lrswipkxtecda'); + $admintalk->setacl("user.$name", $name => 'lrswipkxtecd'); - my $CalDAV = Net::CalDAVTalk->new( - user => $name, - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $CalDAV = Net::CalDAVTalk->new( + user => $name, + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - eval { - # this fails on older Cyruses -- but don't crash during set_up! - $CalDAV->UpdateAddressSet($displayname, "$name\@example.com"); - }; - } + eval { + # this fails on older Cyruses -- but don't crash during set_up! + $CalDAV->UpdateAddressSet($displayname, "$name\@example.com"); + }; + } } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub list_tests -{ - my @tests; +sub list_tests { + my @tests; - if (!defined $basedir) - { - return ( 'test_warning_caldavtester_is_not_installed' ); - } + if (!defined $basedir) { + return ('test_warning_caldavtester_is_not_installed'); + } - open(FH, "-|", 'find', $testdir, '-name' => '*.xml'); - while () - { - chomp; - next unless s{^$testdir/}{}; - next unless s{\.xml$}{}; - next if $suppressed{$_}; - push(@tests, "test_$_"); - } - close(FH); + open(FH, "-|", 'find', $testdir, '-name' => '*.xml'); + while () { + chomp; + next unless s{^$testdir/}{}; + next unless s{\.xml$}{}; + next if $suppressed{$_}; + push(@tests, "test_$_"); + } + close(FH); - return @tests; + return @tests; } -sub run_test -{ - my ($self) = @_; +sub run_test { + my ($self) = @_; - if (!defined $basedir) - { - xlog "CalDAVTester tests are not enabled. To enabled them, please"; - xlog "install CalDAVTester from http://calendarserver.org/wiki/CalDAVTester"; - xlog "and edit [caldavtester]basedir in cassandane.ini"; - xlog "This is not a failure"; - return; - } + if (!defined $basedir) { + xlog "CalDAVTester tests are not enabled. To enabled them, please"; + xlog + "install CalDAVTester from http://calendarserver.org/wiki/CalDAVTester"; + xlog "and edit [caldavtester]basedir in cassandane.ini"; + xlog "This is not a failure"; + return; + } - my $name = $self->name(); - $name =~ s/^test_//; - my $testname = $name; - $testname .= ".xml"; + my $name = $self->name(); + $name =~ s/^test_//; + my $testname = $name; + $testname .= ".xml"; - my $logdir = "$self->{instance}->{basedir}/rawlog/"; - mkdir($logdir); + my $logdir = "$self->{instance}->{basedir}/rawlog/"; + mkdir($logdir); - my $svc = $self->{instance}->get_service('http'); - my $params = $svc->store_params(); + my $svc = $self->{instance}->get_service('http'); + my $params = $svc->store_params(); - my $rundir = "$self->{instance}->{basedir}/run"; - mkdir($rundir); + my $rundir = "$self->{instance}->{basedir}/run"; + mkdir($rundir); - system('ln', '-s', "$testdir", "$rundir/tests"); - system('ln', '-s', "$basedir", "$rundir/data"); + system('ln', '-s', "$testdir", "$rundir/tests"); + system('ln', '-s', "$basedir", "$rundir/data"); - # XXX - make the config file! - my $configfile = "$rundir/serverinfo.xml"; - { - my $config = slurp_file(abs_path("data/caldavtester-serverinfo-template.xml")); - $config =~ s/SERVICE_HOST/$params->{host}/g; - $config =~ s/SERVICE_PORT/$params->{port}/g; - - open(FH, ">", $configfile); - print FH $config; - close(FH); - } + # XXX - make the config file! + my $configfile = "$rundir/serverinfo.xml"; + { + my $config + = slurp_file(abs_path("data/caldavtester-serverinfo-template.xml")); + $config =~ s/SERVICE_HOST/$params->{host}/g; + $config =~ s/SERVICE_PORT/$params->{port}/g; - my $errfile = $self->{instance}->{basedir} . "/$name.errors"; - my $outfile = $self->{instance}->{basedir} . "/$name.stdout"; - my $status; - my @verbose; - if (get_verbose) { - push @verbose, "--always-print-request", "--always-print-response"; - } - $self->{instance}->run_command({ - redirects => { stderr => $errfile, stdout => $outfile }, - workingdir => $logdir, - handlers => { - exited_normally => sub { $status = 1; }, - exited_abnormally => sub { $status = 0; }, - }, - }, - $binary, - "--basedir" => $rundir, - "--observer=jsondump", - @verbose, - $testname); + open(FH, ">", $configfile); + print FH $config; + close(FH); + } - my $json; + my $errfile = $self->{instance}->{basedir} . "/$name.errors"; + my $outfile = $self->{instance}->{basedir} . "/$name.stdout"; + my $status; + my @verbose; + if (get_verbose) { + push @verbose, "--always-print-request", "--always-print-response"; + } + $self->{instance}->run_command( { - my $output = slurp_file($outfile); - $output =~ s/^.*?\[/[/s; - $json = decode_json($output); - } + redirects => { stderr => $errfile, stdout => $outfile }, + workingdir => $logdir, + handlers => { + exited_normally => sub { $status = 1; }, + exited_abnormally => sub { $status = 0; }, + }, + }, + $binary, + "--basedir" => $rundir, + "--observer=jsondump", + @verbose, + $testname + ); - if (0 && (!$status || get_verbose)) { - foreach my $file ($errfile) { - next unless -f $file; - xlog $self, slurp_file($file); - } + my $json; + { + my $output = slurp_file($outfile); + $output =~ s/^.*?\[/[/s; + $json = decode_json($output); + } + + if (0 && (!$status || get_verbose)) { + foreach my $file ($errfile) { + next unless -f $file; + xlog $self, slurp_file($file); } + } - $json->[0]{name} = $name; # short name at top level - $self->assert(_check_result($name, $json->[0])); + $json->[0]{name} = $name; # short name at top level + $self->assert(_check_result($name, $json->[0])); } sub _check_result { - my $name = shift; - my $json = shift; - my $res = 1; + my $name = shift; + my $json = shift; + my $res = 1; - if (defined $json->{result}) { - if ($json->{result} == 0) { - xlog "$name [OK]"; - } - elsif ($json->{result} == 1) { - xlog "$name [FAILED]"; - $res = 0; - } - elsif ($json->{result} == 3) { - xlog "$name [SKIPPED]"; - } - if (exists $expected{$name}) { - if ($json->{result} == $expected{$name}[0]) { - xlog "EXPECTED RESULT FOR $name"; - $res = 1; - } - else { - xlog "UNEXPECTED RESULT FOR $name: " . $expected{$name}[1] if $expected{$name}[1]; - $res = 0; # yep, even if we succeeded - } - } - xlog $json->{details} if $json->{result}; + if (defined $json->{result}) { + if ($json->{result} == 0) { + xlog "$name [OK]"; + } elsif ($json->{result} == 1) { + xlog "$name [FAILED]"; + $res = 0; + } elsif ($json->{result} == 3) { + xlog "$name [SKIPPED]"; + } + if (exists $expected{$name}) { + if ($json->{result} == $expected{$name}[0]) { + xlog "EXPECTED RESULT FOR $name"; + $res = 1; + } else { + xlog "UNEXPECTED RESULT FOR $name: " . $expected{$name}[1] + if $expected{$name}[1]; + $res = 0; # yep, even if we succeeded + } } + xlog $json->{details} if $json->{result}; + } - xlog "FAILED WHEN NOT EXPECTED $name" unless $res; + xlog "FAILED WHEN NOT EXPECTED $name" unless $res; - if ($json->{tests}) { - foreach my $test (@{$json->{tests}}) { - $res = 0 unless _check_result("$name/$test->{name}", $test); - } + if ($json->{tests}) { + foreach my $test (@{ $json->{tests} }) { + $res = 0 unless _check_result("$name/$test->{name}", $test); } + } - return $res; + return $res; } 1; diff --git a/cassandane/Cassandane/Cyrus/TesterCardDAV.pm b/cassandane/Cassandane/Cyrus/TesterCardDAV.pm index af85999660..a36bf36854 100644 --- a/cassandane/Cassandane/Cyrus/TesterCardDAV.pm +++ b/cassandane/Cassandane/Cyrus/TesterCardDAV.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::TesterCardDAV; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use JSON::XS; @@ -143,218 +143,213 @@ well-known/Simple PROPFIND tests/5 | 1 well-known/Simple PROPFIND tests/6 | 1 EOF -sub init -{ - my $cassini = Cassandane::Cassini->instance(); - $basedir = $cassini->val('caldavtester', 'basedir'); - return unless defined $basedir; - $basedir = abs_path($basedir); - - my $supp = $cassini->val('caldavtester', 'suppress-carddav', - ''); - map { $suppressed{$_} = 1; } split(/\s+/, $supp); - - foreach my $row (split /\n/, $KNOWN_ERRORS) { - next if $row =~ m/\s*\#/; - next unless $row =~ m/\S/; - my ($key, @items) = split /\s*\|\s*/, $row; - $expected{$key} = \@items; - } +sub init { + my $cassini = Cassandane::Cassini->instance(); + $basedir = $cassini->val('caldavtester', 'basedir'); + return unless defined $basedir; + $basedir = abs_path($basedir); + + my $supp = $cassini->val('caldavtester', 'suppress-carddav', ''); + map { $suppressed{$_} = 1; } split(/\s+/, $supp); - $binary = "$basedir/testcaldav.py"; - $testdir = "$basedir/scripts/tests/CardDAV"; + foreach my $row (split /\n/, $KNOWN_ERRORS) { + next if $row =~ m/\s*\#/; + next unless $row =~ m/\S/; + my ($key, @items) = split /\s*\|\s*/, $row; + $expected{$key} = \@items; + } + + $binary = "$basedir/testcaldav.py"; + $testdir = "$basedir/scripts/tests/CardDAV"; } init; -sub new -{ - my $class = shift; - - my $buildinfo = Cassandane::BuildInfo->new(); +sub new { + my $class = shift; - if (not defined $basedir or not $buildinfo->get('component', 'httpd')) { - # don't bother setting up, we're not running tests anyway - return $class->SUPER::new({}, @_); - } + my $buildinfo = Cassandane::BuildInfo->new(); - my $config = Cassandane::Config->default()->clone(); - $config->set(servername => "127.0.0.1"); # urlauth needs matching servername - $config->set(caldav_realm => 'Cassandane'); - $config->set(httpmodules => 'carddav'); - $config->set(httpallowcompress => 'no'); - - return $class->SUPER::new({ - config => $config, - adminstore => 1, - services => ['imap', 'http'], - }, @_); -} + if (not defined $basedir or not $buildinfo->get('component', 'httpd')) { + # don't bother setting up, we're not running tests anyway + return $class->SUPER::new({}, @_); + } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); + my $config = Cassandane::Config->default()->clone(); + $config->set(servername => "127.0.0.1"); # urlauth needs matching servername + $config->set(caldav_realm => 'Cassandane'); + $config->set(httpmodules => 'carddav'); + $config->set(httpallowcompress => 'no'); - if (not defined $basedir - or not $self->{instance}->{buildinfo}->get('component', 'httpd')) + return $class->SUPER::new( { - # don't bother setting up further, we're not running tests anyway - return; - } - - my $admintalk = $self->{adminstore}->get_client(); - - for (1..40) { - my $name = sprintf("user%02d", $_); - $admintalk->create("user.$name"); - $admintalk->setacl("user.$name", admin => 'lrswipkxtecda'); - $admintalk->setacl("user.$name", $name => 'lrswipkxtecd'); - } + config => $config, + adminstore => 1, + services => [ 'imap', 'http' ], + }, + @_ + ); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); + + if ( not defined $basedir + or not $self->{instance}->{buildinfo}->get('component', 'httpd')) + { + # don't bother setting up further, we're not running tests anyway + return; + } + + my $admintalk = $self->{adminstore}->get_client(); + + for (1 .. 40) { + my $name = sprintf("user%02d", $_); + $admintalk->create("user.$name"); + $admintalk->setacl("user.$name", admin => 'lrswipkxtecda'); + $admintalk->setacl("user.$name", $name => 'lrswipkxtecd'); + } } -sub list_tests -{ - my @tests; - - if (!defined $basedir) - { - return ( 'test_warning_caldavtester_is_not_installed' ); - } - - open(FH, "-|", 'find', $testdir, '-name' => '*.xml'); - while () - { - chomp; - next unless s{^$testdir/}{}; - next unless s{\.xml$}{}; - next if $suppressed{$_}; - push(@tests, "test_$_"); - } - close(FH); - - return @tests; +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub run_test -{ - my ($self) = @_; - - if (!defined $basedir) - { - xlog "CalDAVTester tests are not enabled. To enabled them, please"; - xlog "install CalDAVTester from http://calendarserver.org/wiki/CalDAVTester"; - xlog "and edit [caldavtester]basedir in cassandane.ini"; - xlog "This is not a failure"; - return; - } - - my $name = $self->name(); - $name =~ s/^test_//; - my $testname = $name; - $testname .= ".xml"; - - my $logdir = "$self->{instance}->{basedir}/rawlog/"; - mkdir($logdir); +sub list_tests { + my @tests; - my $svc = $self->{instance}->get_service('http'); - my $params = $svc->store_params(); + if (!defined $basedir) { + return ('test_warning_caldavtester_is_not_installed'); + } - my $rundir = "$self->{instance}->{basedir}/run"; - mkdir($rundir); + open(FH, "-|", 'find', $testdir, '-name' => '*.xml'); + while () { + chomp; + next unless s{^$testdir/}{}; + next unless s{\.xml$}{}; + next if $suppressed{$_}; + push(@tests, "test_$_"); + } + close(FH); - system('ln', '-s', "$testdir", "$rundir/tests"); - system('ln', '-s', "$basedir", "$rundir/data"); + return @tests; +} - # XXX - make the config file! - my $configfile = "$rundir/serverinfo.xml"; +sub run_test { + my ($self) = @_; + + if (!defined $basedir) { + xlog "CalDAVTester tests are not enabled. To enabled them, please"; + xlog + "install CalDAVTester from http://calendarserver.org/wiki/CalDAVTester"; + xlog "and edit [caldavtester]basedir in cassandane.ini"; + xlog "This is not a failure"; + return; + } + + my $name = $self->name(); + $name =~ s/^test_//; + my $testname = $name; + $testname .= ".xml"; + + my $logdir = "$self->{instance}->{basedir}/rawlog/"; + mkdir($logdir); + + my $svc = $self->{instance}->get_service('http'); + my $params = $svc->store_params(); + + my $rundir = "$self->{instance}->{basedir}/run"; + mkdir($rundir); + + system('ln', '-s', "$testdir", "$rundir/tests"); + system('ln', '-s', "$basedir", "$rundir/data"); + + # XXX - make the config file! + my $configfile = "$rundir/serverinfo.xml"; + { + my $config + = slurp_file(abs_path("data/caldavtester-serverinfo-template.xml")); + $config =~ s/SERVICE_HOST/$params->{host}/g; + $config =~ s/SERVICE_PORT/$params->{port}/g; + + open(FH, ">", $configfile); + print FH $config; + close(FH); + } + + my $errfile = $self->{instance}->{basedir} . "/$name.errors"; + my $outfile = $self->{instance}->{basedir} . "/$name.stdout"; + my $status; + my @verbose; + if (get_verbose) { + push @verbose, "--always-print-request", "--always-print-response"; + } + $self->{instance}->run_command( { - my $config = slurp_file(abs_path("data/caldavtester-serverinfo-template.xml")); - $config =~ s/SERVICE_HOST/$params->{host}/g; - $config =~ s/SERVICE_PORT/$params->{port}/g; - - open(FH, ">", $configfile); - print FH $config; - close(FH); + redirects => { stderr => $errfile, stdout => $outfile }, + workingdir => $logdir, + handlers => { + exited_normally => sub { $status = 1; }, + exited_abnormally => sub { $status = 0; }, + }, + }, + $binary, + "--basedir" => $rundir, + "--observer=jsondump", + @verbose, + $testname + ); + + my $json = decode_json(slurp_file($outfile)); + + if (0 && (!$status || get_verbose)) { + foreach my $file ($errfile) { + next unless -f $file; + xlog $self, slurp_file($file); } + } - my $errfile = $self->{instance}->{basedir} . "/$name.errors"; - my $outfile = $self->{instance}->{basedir} . "/$name.stdout"; - my $status; - my @verbose; - if (get_verbose) { - push @verbose, "--always-print-request", "--always-print-response"; - } - $self->{instance}->run_command({ - redirects => { stderr => $errfile, stdout => $outfile }, - workingdir => $logdir, - handlers => { - exited_normally => sub { $status = 1; }, - exited_abnormally => sub { $status = 0; }, - }, - }, - $binary, - "--basedir" => $rundir, - "--observer=jsondump", - @verbose, - $testname); - - my $json = decode_json(slurp_file($outfile)); - - if (0 && (!$status || get_verbose)) { - foreach my $file ($errfile) { - next unless -f $file; - xlog $self, slurp_file($file); - } - } - - $json->[0]{name} = $name; # short name at top level - $self->assert(_check_result($name, $json->[0])); + $json->[0]{name} = $name; # short name at top level + $self->assert(_check_result($name, $json->[0])); } sub _check_result { - my $name = shift; - my $json = shift; - my $res = 1; - - if (defined $json->{result}) { - if ($json->{result} == 0) { - xlog "$name [OK]"; - } - elsif ($json->{result} == 1) { - xlog "$name [FAILED]"; - $res = 0; - } - elsif ($json->{result} == 3) { - xlog "$name [SKIPPED]"; - } - if (exists $expected{$name}) { - if ($json->{result} == $expected{$name}[0]) { - xlog "EXPECTED RESULT FOR $name"; - $res = 1; - } - else { - xlog "UNEXPECTED RESULT FOR $name: " . $expected{$name}[1] if $expected{$name}[1]; - $res = 0; # yep, even if we succeeded - } - } - xlog $json->{details} if $json->{result}; + my $name = shift; + my $json = shift; + my $res = 1; + + if (defined $json->{result}) { + if ($json->{result} == 0) { + xlog "$name [OK]"; + } elsif ($json->{result} == 1) { + xlog "$name [FAILED]"; + $res = 0; + } elsif ($json->{result} == 3) { + xlog "$name [SKIPPED]"; + } + if (exists $expected{$name}) { + if ($json->{result} == $expected{$name}[0]) { + xlog "EXPECTED RESULT FOR $name"; + $res = 1; + } else { + xlog "UNEXPECTED RESULT FOR $name: " . $expected{$name}[1] + if $expected{$name}[1]; + $res = 0; # yep, even if we succeeded + } } + xlog $json->{details} if $json->{result}; + } - xlog "FAILED WHEN NOT EXPECTED $name" unless $res; + xlog "FAILED WHEN NOT EXPECTED $name" unless $res; - if ($json->{tests}) { - foreach my $test (@{$json->{tests}}) { - $res = 0 unless _check_result("$name/$test->{name}", $test); - } + if ($json->{tests}) { + foreach my $test (@{ $json->{tests} }) { + $res = 0 unless _check_result("$name/$test->{name}", $test); } + } - return $res; + return $res; } 1; diff --git a/cassandane/Cassandane/Cyrus/Thread.pm b/cassandane/Cassandane/Cyrus/Thread.pm index 392cf969ec..6dc500212a 100644 --- a/cassandane/Cassandane/Cyrus/Thread.pm +++ b/cassandane/Cassandane/Cyrus/Thread.pm @@ -47,306 +47,306 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub test_unrelated -{ - my ($self) = @_; - - xlog $self, "test THREAD with no inter-message references"; - xlog $self, "and all different subjects"; - my $talk = $self->{store}->get_client(); - my $res; - - xlog $self, "append some messages"; - my %exp; - my $N = 20; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); - - xlog $self, "The REFERENCES algorithm gives each message in a singleton thread"; - $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); - $self->assert_deep_equals([ map { [ $_ ] } (1..$N) ], $res); - - xlog $self, "The ORDEREDSUBJECT algorithm gives each message in a singleton thread"; - $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); - $self->assert_deep_equals([ map { [ $_ ] } (1..$N) ], $res); - - xlog $self, "Double-check the messages are still there"; - $self->check_messages(\%exp); +sub test_unrelated { + my ($self) = @_; + + xlog $self, "test THREAD with no inter-message references"; + xlog $self, "and all different subjects"; + my $talk = $self->{store}->get_client(); + my $res; + + xlog $self, "append some messages"; + my %exp; + my $N = 20; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); + + xlog $self, + "The REFERENCES algorithm gives each message in a singleton thread"; + $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); + $self->assert_deep_equals([ map { [$_] } (1 .. $N) ], $res); + + xlog $self, + "The ORDEREDSUBJECT algorithm gives each message in a singleton thread"; + $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); + $self->assert_deep_equals([ map { [$_] } (1 .. $N) ], $res); + + xlog $self, "Double-check the messages are still there"; + $self->check_messages(\%exp); } -sub test_subjects -{ - my ($self) = @_; - - xlog $self, "test THREAD with no inter-message references"; - xlog $self, "but apparently similar subjects"; - my $talk = $self->{store}->get_client(); - my $res; - - xlog $self, "append some messages"; - my %exp; - my %exp_by_sub; - my $N = 20; - my @subjects = ( 'quinoa', 'selvedge', 'messenger bag' ); - for (1..$N) - { - my $sub = $subjects[($_ - 1) % scalar(@subjects)]; - $exp_by_sub{$sub} ||= []; - my $msg = $self->make_message(("Re: " x scalar(@{$exp_by_sub{$sub}})) . $sub); - push(@{$exp_by_sub{$sub}}, $msg); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); - - my @expthreads; - foreach my $sub (@subjects) - { - my @thread = ( map { $_->uid } @{$exp_by_sub{$sub}} ); - my $parent = shift(@thread); - push(@expthreads, [ $parent, map { [ $_ ] } @thread ] ); - } - - xlog $self, "The REFERENCES algorithm gives one thread per subject, even"; - xlog $self, "though the References headers are completely missing"; - $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); - $self->assert_deep_equals(\@expthreads, $res); - - xlog $self, "The ORDEREDSUBJECT algorithm gives one thread per subject"; - $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); - $self->assert_deep_equals(\@expthreads, $res); - - xlog $self, "Double-check the messages are still there"; - $self->check_messages(\%exp); +sub test_subjects { + my ($self) = @_; + + xlog $self, "test THREAD with no inter-message references"; + xlog $self, "but apparently similar subjects"; + my $talk = $self->{store}->get_client(); + my $res; + + xlog $self, "append some messages"; + my %exp; + my %exp_by_sub; + my $N = 20; + my @subjects = ('quinoa', 'selvedge', 'messenger bag'); + for (1 .. $N) { + my $sub = $subjects[ ($_ - 1) % scalar(@subjects) ]; + $exp_by_sub{$sub} ||= []; + my $msg + = $self->make_message(("Re: " x scalar(@{ $exp_by_sub{$sub} })) . $sub); + push(@{ $exp_by_sub{$sub} }, $msg); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); + + my @expthreads; + foreach my $sub (@subjects) { + my @thread = (map { $_->uid } @{ $exp_by_sub{$sub} }); + my $parent = shift(@thread); + push(@expthreads, [ $parent, map { [$_] } @thread ]); + } + + xlog $self, "The REFERENCES algorithm gives one thread per subject, even"; + xlog $self, "though the References headers are completely missing"; + $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); + $self->assert_deep_equals(\@expthreads, $res); + + xlog $self, "The ORDEREDSUBJECT algorithm gives one thread per subject"; + $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); + $self->assert_deep_equals(\@expthreads, $res); + + xlog $self, "Double-check the messages are still there"; + $self->check_messages(\%exp); } -sub test_references_chain -{ - my ($self) = @_; - - xlog $self, "test THREAD with a linear chain of inter-message references"; - xlog $self, "and apparently similar subjects"; - my $talk = $self->{store}->get_client(); - my $res; - - xlog $self, "append some messages"; - my %exp; - my %exp_by_sub; - my $N = 20; - my @subjects = ( 'cosby sweater', 'brooklyn', 'portland' ); - for (1..$N) - { - my $sub = $subjects[($_ - 1) % scalar(@subjects)]; - $exp_by_sub{$sub} ||= []; - my $msg; - if (scalar @{$exp_by_sub{$sub}}) - { - my $parent = $exp_by_sub{$sub}->[-1]; - $msg = $self->make_message("Re: " . $parent->subject, - references => [ $parent ]); - } - else - { - $msg = $self->make_message($sub); - } - push(@{$exp_by_sub{$sub}}, $msg); - $exp{$_} = $msg; +sub test_references_chain { + my ($self) = @_; + + xlog $self, "test THREAD with a linear chain of inter-message references"; + xlog $self, "and apparently similar subjects"; + my $talk = $self->{store}->get_client(); + my $res; + + xlog $self, "append some messages"; + my %exp; + my %exp_by_sub; + my $N = 20; + my @subjects = ('cosby sweater', 'brooklyn', 'portland'); + for (1 .. $N) { + my $sub = $subjects[ ($_ - 1) % scalar(@subjects) ]; + $exp_by_sub{$sub} ||= []; + my $msg; + if (scalar @{ $exp_by_sub{$sub} }) { + my $parent = $exp_by_sub{$sub}->[-1]; + $msg = $self->make_message("Re: " . $parent->subject, + references => [$parent]); + } else { + $msg = $self->make_message($sub); } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); - - my @expthreads; - - xlog $self, "The REFERENCES algorithm gives the true thread structure which is deep"; - foreach my $sub (@subjects) - { - push(@expthreads, [ map { $_->uid } @{$exp_by_sub{$sub}} ]); - } - $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); - $self->assert_deep_equals(\@expthreads, $res); - -# From RFC5256 -# The top level or "root" in ORDEREDSUBJECT threading contains -# the first message of every thread. All messages in the root -# are siblings of each other. The second message of a thread is -# the child of the first message, and subsequent messages of the -# thread are siblings of the second message and hence children of -# the message at the root. Hence, there are no grandchildren in -# ORDEREDSUBJECT threading. - xlog $self, "The ORDEREDSUBJECT algorithm gives a false more flat view of the structure"; - @expthreads = (); - foreach my $sub (@subjects) - { - my @thread = ( map { $_->uid } @{$exp_by_sub{$sub}} ); - my $parent = shift(@thread); - push(@expthreads, [ $parent, map { [ $_ ] } @thread ] ); - } - $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); - $self->assert_deep_equals(\@expthreads, $res); - - xlog $self, "Double-check the messages are still there"; - $self->check_messages(\%exp); + push(@{ $exp_by_sub{$sub} }, $msg); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); + + my @expthreads; + + xlog $self, + "The REFERENCES algorithm gives the true thread structure which is deep"; + foreach my $sub (@subjects) { + push(@expthreads, [ map { $_->uid } @{ $exp_by_sub{$sub} } ]); + } + $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); + $self->assert_deep_equals(\@expthreads, $res); + + # From RFC5256 + # The top level or "root" in ORDEREDSUBJECT threading contains + # the first message of every thread. All messages in the root + # are siblings of each other. The second message of a thread is + # the child of the first message, and subsequent messages of the + # thread are siblings of the second message and hence children of + # the message at the root. Hence, there are no grandchildren in + # ORDEREDSUBJECT threading. + xlog $self, + "The ORDEREDSUBJECT algorithm gives a false more flat view of the structure"; + @expthreads = (); + foreach my $sub (@subjects) { + my @thread = (map { $_->uid } @{ $exp_by_sub{$sub} }); + my $parent = shift(@thread); + push(@expthreads, [ $parent, map { [$_] } @thread ]); + } + $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); + $self->assert_deep_equals(\@expthreads, $res); + + xlog $self, "Double-check the messages are still there"; + $self->check_messages(\%exp); } -sub test_references_star -{ - my ($self) = @_; - - xlog $self, "test THREAD with a star configuration of inter-message references"; - xlog $self, "and apparently similar subjects"; - my $talk = $self->{store}->get_client(); - my $res; - - xlog $self, "append some messages"; - my %exp; - my %exp_by_sub; - my $N = 20; - my @subjects = ( 'cosby sweater', 'brooklyn', 'portland' ); - foreach my $uid (1..$N) - { - my $sub = $subjects[($uid - 1) % scalar(@subjects)]; - $exp_by_sub{$sub} ||= []; - my $msg; - if (scalar @{$exp_by_sub{$sub}}) - { - my $parent = $exp_by_sub{$sub}->[0]; - $msg = $self->make_message("Re: " . $parent->subject, - references => [ $parent ]); - } - else - { - $msg = $self->make_message($sub); - } - push(@{$exp_by_sub{$sub}}, $msg); - $exp{$uid} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp, keyed_on => 'uid'); - - my @expthreads; - foreach my $sub (@subjects) - { - my @thread = ( map { $_->uid } @{$exp_by_sub{$sub}} ); - my $parent = shift(@thread); - push(@expthreads, [ $parent, map { [ $_ ] } @thread ] ); +sub test_references_star { + my ($self) = @_; + + xlog $self, + "test THREAD with a star configuration of inter-message references"; + xlog $self, "and apparently similar subjects"; + my $talk = $self->{store}->get_client(); + my $res; + + xlog $self, "append some messages"; + my %exp; + my %exp_by_sub; + my $N = 20; + my @subjects = ('cosby sweater', 'brooklyn', 'portland'); + foreach my $uid (1 .. $N) { + my $sub = $subjects[ ($uid - 1) % scalar(@subjects) ]; + $exp_by_sub{$sub} ||= []; + my $msg; + if (scalar @{ $exp_by_sub{$sub} }) { + my $parent = $exp_by_sub{$sub}->[0]; + $msg = $self->make_message("Re: " . $parent->subject, + references => [$parent]); + } else { + $msg = $self->make_message($sub); } - - xlog $self, "The REFERENCES algorithm gives the true thread structure which is flat"; - $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); - $self->assert_deep_equals(\@expthreads, $res); - - xlog $self, "The ORDEREDSUBJECT algorithm gives the same flat view"; - $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); - $self->assert_deep_equals(\@expthreads, $res); - - xlog $self, "Double-check the messages are still there"; - $self->check_messages(\%exp, keyed_on => 'uid'); + push(@{ $exp_by_sub{$sub} }, $msg); + $exp{$uid} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp, keyed_on => 'uid'); + + my @expthreads; + foreach my $sub (@subjects) { + my @thread = (map { $_->uid } @{ $exp_by_sub{$sub} }); + my $parent = shift(@thread); + push(@expthreads, [ $parent, map { [$_] } @thread ]); + } + + xlog $self, + "The REFERENCES algorithm gives the true thread structure which is flat"; + $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); + $self->assert_deep_equals(\@expthreads, $res); + + xlog $self, "The ORDEREDSUBJECT algorithm gives the same flat view"; + $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); + $self->assert_deep_equals(\@expthreads, $res); + + xlog $self, "Double-check the messages are still there"; + $self->check_messages(\%exp, keyed_on => 'uid'); } -sub test_references_missing_parent -{ - my ($self) = @_; - - xlog $self, "test THREAD with two messages which share a common parent"; - xlog $self, "which is not seen on the server"; - my $talk = $self->{store}->get_client(); - my $res; - my %exp; - - xlog $self, "Message A is never seen by the server"; - my $msgA = $self->{gen}->generate(subject => "put a bird on it"); - - xlog $self, "Generate message B, which References message A"; - my $msgB = $self->make_message("Re: " . $msgA->subject, - uid => 1, - references => [ $msgA ]); - $exp{1} = $msgB; - - xlog $self, "Generate message C, which References message A"; - my $msgC = $self->make_message("Re: " . $msgA->subject, - uid => 2, - references => [ $msgA ]); - $exp{2} = $msgC; - - xlog $self, "check the messages got there"; - $self->check_messages(\%exp, keyed_on => 'uid'); - - xlog $self, "The REFERENCES algorithm gives the true thread"; - xlog $self, "structure which is flat with a missing common parent"; - $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); - $self->assert_deep_equals([[[1],[2]]], $res); - - xlog $self, "The ORDEREDSUBJECT algorithm gives a false more flat view of the structure"; - $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); - $self->assert_deep_equals([[1, 2]], $res); - - xlog $self, "Double-check the messages are still there"; - $self->check_messages(\%exp, keyed_on => 'uid'); +sub test_references_missing_parent { + my ($self) = @_; + + xlog $self, "test THREAD with two messages which share a common parent"; + xlog $self, "which is not seen on the server"; + my $talk = $self->{store}->get_client(); + my $res; + my %exp; + + xlog $self, "Message A is never seen by the server"; + my $msgA = $self->{gen}->generate(subject => "put a bird on it"); + + xlog $self, "Generate message B, which References message A"; + my $msgB = $self->make_message( + "Re: " . $msgA->subject, + uid => 1, + references => [$msgA] + ); + $exp{1} = $msgB; + + xlog $self, "Generate message C, which References message A"; + my $msgC = $self->make_message( + "Re: " . $msgA->subject, + uid => 2, + references => [$msgA] + ); + $exp{2} = $msgC; + + xlog $self, "check the messages got there"; + $self->check_messages(\%exp, keyed_on => 'uid'); + + xlog $self, "The REFERENCES algorithm gives the true thread"; + xlog $self, "structure which is flat with a missing common parent"; + $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); + $self->assert_deep_equals([ [ [1], [2] ] ], $res); + + xlog $self, + "The ORDEREDSUBJECT algorithm gives a false more flat view of the structure"; + $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); + $self->assert_deep_equals([ [ 1, 2 ] ], $res); + + xlog $self, "Double-check the messages are still there"; + $self->check_messages(\%exp, keyed_on => 'uid'); } - -sub test_references_loop -{ - my ($self) = @_; - - xlog $self, "test THREAD with a loop configuration of inter-message references"; - xlog $self, "and a missing common parent (Bug 3784)"; - my $talk = $self->{store}->get_client(); - my $res; - my %exp; - - xlog $self, "Generate message B, which References itself and some other messages"; - my $msgB = $self->{gen}->generate(subject => "Re: put a bird on it", uid => 1); - $msgB->set_headers('Message-Id', '<477CBE0D020000330001972A@gwia1.boku.ac.at>'); - $msgB->set_headers('References', - '<477CB3AF0200001E00003B58@gwia1.boku.ac.at>' . "\n" . - '<477CBA030200003300019722@gwia1.boku.ac.at>' . "\n" . - '<477CBD530200003300019726@gwia1.boku.ac.at>' . "\n" . - '<477CBE0D020000330001972A@gwia1.boku.ac.at>'); - $msgB->set_headers('In-Reply-To', '<477CBE0D020000330001972A@gwia1.boku.ac.at>'); - $self->_save_message($msgB); - $exp{1} = $msgB; - - xlog $self, "Generate message C, which References itself and some other messages"; - my $msgC = $self->{gen}->generate(subject => "Re: put a bird on it", uid => 2); - $msgC->set_headers('Message-Id', '<478B52E10200003300019E06@gwia1.boku.ac.at>'); - $msgC->set_headers('References', - '<477CB3AF0200001E00003B58@gwia1.boku.ac.at>' . "\n" . - '<478B2D7F0200003300019DA2@gwia1.boku.ac.at>' . "\n" . - '<478B2E9F0200003300019DA5@gwia1.boku.ac.at>' . "\n" . - '<478B2F0E0200003300019DA8@gwia1.boku.ac.at>' . "\n" . - '<478B32C40200003300019DB1@gwia1.boku.ac.at>' . "\n" . - '<478B38C40200003300019DBD@gwia1.boku.ac.at>' . "\n" . - '<478B52E10200003300019E06@gwia1.boku.ac.at>'); - $msgC->set_headers('In-Reply-To', '<478B52E10200003300019E06@gwia1.boku.ac.at>'); - $self->_save_message($msgC); - $exp{2} = $msgC; - - xlog $self, "check the messages got there"; - $self->check_messages(\%exp, keyed_on => 'uid'); - - xlog $self, "The REFERENCES algorithm gives the true thread"; - xlog $self, "structure which is flat"; - $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); - $self->assert_deep_equals([[[1], [2]]], $res); - - xlog $self, "The ORDEREDSUBJECT algorithm gives a false more flat view of the structure"; - $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); - $self->assert_deep_equals([[1, 2]], $res); - - xlog $self, "Double-check the messages are still there"; - $self->check_messages(\%exp, keyed_on => 'uid'); +sub test_references_loop { + my ($self) = @_; + + xlog $self, + "test THREAD with a loop configuration of inter-message references"; + xlog $self, "and a missing common parent (Bug 3784)"; + my $talk = $self->{store}->get_client(); + my $res; + my %exp; + + xlog $self, + "Generate message B, which References itself and some other messages"; + my $msgB + = $self->{gen}->generate(subject => "Re: put a bird on it", uid => 1); + $msgB->set_headers('Message-Id', + '<477CBE0D020000330001972A@gwia1.boku.ac.at>'); + $msgB->set_headers('References', + '<477CB3AF0200001E00003B58@gwia1.boku.ac.at>' . "\n" + . '<477CBA030200003300019722@gwia1.boku.ac.at>' . "\n" + . '<477CBD530200003300019726@gwia1.boku.ac.at>' . "\n" + . '<477CBE0D020000330001972A@gwia1.boku.ac.at>'); + $msgB->set_headers('In-Reply-To', + '<477CBE0D020000330001972A@gwia1.boku.ac.at>'); + $self->_save_message($msgB); + $exp{1} = $msgB; + + xlog $self, + "Generate message C, which References itself and some other messages"; + my $msgC + = $self->{gen}->generate(subject => "Re: put a bird on it", uid => 2); + $msgC->set_headers('Message-Id', + '<478B52E10200003300019E06@gwia1.boku.ac.at>'); + $msgC->set_headers('References', + '<477CB3AF0200001E00003B58@gwia1.boku.ac.at>' . "\n" + . '<478B2D7F0200003300019DA2@gwia1.boku.ac.at>' . "\n" + . '<478B2E9F0200003300019DA5@gwia1.boku.ac.at>' . "\n" + . '<478B2F0E0200003300019DA8@gwia1.boku.ac.at>' . "\n" + . '<478B32C40200003300019DB1@gwia1.boku.ac.at>' . "\n" + . '<478B38C40200003300019DBD@gwia1.boku.ac.at>' . "\n" + . '<478B52E10200003300019E06@gwia1.boku.ac.at>'); + $msgC->set_headers('In-Reply-To', + '<478B52E10200003300019E06@gwia1.boku.ac.at>'); + $self->_save_message($msgC); + $exp{2} = $msgC; + + xlog $self, "check the messages got there"; + $self->check_messages(\%exp, keyed_on => 'uid'); + + xlog $self, "The REFERENCES algorithm gives the true thread"; + xlog $self, "structure which is flat"; + $res = $talk->thread('REFERENCES', 'US-ASCII', 'ALL'); + $self->assert_deep_equals([ [ [1], [2] ] ], $res); + + xlog $self, + "The ORDEREDSUBJECT algorithm gives a false more flat view of the structure"; + $res = $talk->thread('ORDEREDSUBJECT', 'US-ASCII', 'ALL'); + $self->assert_deep_equals([ [ 1, 2 ] ], $res); + + xlog $self, "Double-check the messages are still there"; + $self->check_messages(\%exp, keyed_on => 'uid'); } 1; diff --git a/cassandane/Cassandane/Cyrus/UIDonly.pm b/cassandane/Cassandane/Cyrus/UIDonly.pm index f505731c55..71c1ce983e 100644 --- a/cassandane/Cassandane/Cyrus/UIDonly.pm +++ b/cassandane/Cassandane/Cyrus/UIDonly.pm @@ -55,66 +55,63 @@ use Cassandane::Util::Log; use charnames ':full'; -sub new -{ - my ($class, @args) = @_; - - my $config = Cassandane::Config->default()->clone(); - - $config->set(conversations => 'yes'); - - return $class->SUPER::new({ - config => $config, - deliver => 1, - adminstore => 1, - services => [ 'imap', 'sieve' ] - }, @args); +sub new { + my ($class, @args) = @_; + + my $config = Cassandane::Config->default()->clone(); + + $config->set(conversations => 'yes'); + + return $class->SUPER::new( + { + config => $config, + deliver => 1, + adminstore => 1, + services => [ 'imap', 'sieve' ] + }, + @args + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub uidonly_cmd -{ - my $self = shift; - my $imaptalk = shift; - my $cmd = shift; - - my %fetched; - my %handlers = - ( - uidfetch => sub - { - my (undef, $items, $uid) = @_; - - if (ref($items) ne 'HASH') { - # IMAPTalk < 4.06. Convert the key/value list into a hash - my %hash; - my $kvlist = $imaptalk->_next_atom(); - while (@$kvlist) { - my ($key, $val) = (shift @$kvlist, shift @$kvlist); - $hash{lc($key)} = $val; - } - $items = \%hash; - } - - $fetched{$uid} = $items; - }, - ); - - $imaptalk->_imap_cmd($cmd, 0, \%handlers, @_); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - return %fetched; +sub uidonly_cmd { + my $self = shift; + my $imaptalk = shift; + my $cmd = shift; + + my %fetched; + my %handlers = ( + uidfetch => sub { + my (undef, $items, $uid) = @_; + + if (ref($items) ne 'HASH') { + # IMAPTalk < 4.06. Convert the key/value list into a hash + my %hash; + my $kvlist = $imaptalk->_next_atom(); + while (@$kvlist) { + my ($key, $val) = (shift @$kvlist, shift @$kvlist); + $hash{ lc($key) } = $val; + } + $items = \%hash; + } + + $fetched{$uid} = $items; + }, + ); + + $imaptalk->_imap_cmd($cmd, 0, \%handlers, @_); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + return %fetched; } use Cassandane::Tiny::Loader 'tiny-tests/UIDonly'; diff --git a/cassandane/Cassandane/Cyrus/URLAuth.pm b/cassandane/Cassandane/Cyrus/URLAuth.pm index 3078811fed..7ed2414812 100644 --- a/cassandane/Cassandane/Cyrus/URLAuth.pm +++ b/cassandane/Cassandane/Cyrus/URLAuth.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::URLAuth; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use Data::Dumper; @@ -50,71 +50,65 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Util::NetString; +sub new { + my $class = shift; -sub new -{ - my $class = shift; + my $config = Cassandane::Config->default()->clone(); + $config->set(servername => "127.0.0.1"); # urlauth needs matching servername - my $config = Cassandane::Config->default()->clone(); - $config->set(servername => "127.0.0.1"); # urlauth needs matching servername - - return $class->SUPER::new({ - config => $config, - adminstore => 1, - services => ['imap'] - }, @_); + return $class->SUPER::new( + { + config => $config, + adminstore => 1, + services => ['imap'] + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub test_urlfetch -{ - my ($self) = @_; +sub test_urlfetch { + my ($self) = @_; - my %exp_sub; - my $store = $self->{store}; - my $talk = $store->get_client(); + my %exp_sub; + my $store = $self->{store}; + my $talk = $store->get_client(); - $store->set_folder("INBOX"); - $store->_select(); - $self->{gen}->set_next_uid(1); + $store->set_folder("INBOX"); + $store->_select(); + $self->{gen}->set_next_uid(1); - my $body; + my $body; - # Subpart 1 - $body = "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "\r\n" - . "body1" - . "\r\n"; + # Subpart 1 + $body = "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" . "\r\n" . "body1" . "\r\n"; - # Subpart 2 - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: multipart/mixed;boundary=frontier\r\n" - . "\r\n"; + # Subpart 2 + $body .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: multipart/mixed;boundary=frontier\r\n" . "\r\n"; - # Subpart 2.1 - $body .= "--frontier\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body21" - . "\r\n"; + # Subpart 2.1 + $body + .= "--frontier\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "body21" . "\r\n"; - # End subpart 2 - $body .= "--frontier--\r\n"; + # End subpart 2 + $body .= "--frontier--\r\n"; - # Subpart 3 - my $msg3 = "" + # Subpart 3 + my $msg3 + = "" . "Return-Path: \r\n" . "Mime-Version: 1.0\r\n" . "Content-Type: text/plain\r\n" @@ -123,42 +117,42 @@ sub test_urlfetch . "From: Ava T. Nguyen \r\n" . "Message-ID: \r\n" . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "body3"; - - $body .= "--047d7b33dd729737fe04d3bde348\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . $msg3 - . "\r\n"; - - # End body - $body .= "--047d7b33dd729737fe04d3bde348--"; - - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body - ); - - my $data; - my %handlers = - ( - urlfetch => sub - { - my ($cmd, $params) = @_; - $data = ${$params}[1]; - }, - ); - - my $url = $talk->_imap_cmd('genurlauth', 0, "genurlauth", - "imap://cassandane\@127.0.0.1/INBOX/;uid=1/;section=3.TEXT;partial=1.3;urlauth=user+cassandane", - 'INTERNAL'); - - my $res = $talk->_imap_cmd('urlfetch', 0, \%handlers, substr($url, 1, -1)); - - $self->assert_str_equals($data, "ody"); + . "To: Test User \r\n" . "\r\n" . "body3"; + + $body + .= "--047d7b33dd729737fe04d3bde348\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . $msg3 . "\r\n"; + + # End body + $body .= "--047d7b33dd729737fe04d3bde348--"; + + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body + ); + + my $data; + my %handlers = ( + urlfetch => sub { + my ($cmd, $params) = @_; + $data = ${$params}[1]; + }, + ); + + my $url = $talk->_imap_cmd( + 'genurlauth', + 0, + "genurlauth", + "imap://cassandane\@127.0.0.1/INBOX/;uid=1/;section=3.TEXT;partial=1.3;urlauth=user+cassandane", + 'INTERNAL' + ); + + my $res = $talk->_imap_cmd('urlfetch', 0, \%handlers, substr($url, 1, -1)); + + $self->assert_str_equals($data, "ody"); } 1; diff --git a/cassandane/Cassandane/Cyrus/UTF8Accept.pm b/cassandane/Cassandane/Cyrus/UTF8Accept.pm index 7ee62dea47..f5d86a6a07 100644 --- a/cassandane/Cassandane/Cyrus/UTF8Accept.pm +++ b/cassandane/Cassandane/Cyrus/UTF8Accept.pm @@ -40,7 +40,7 @@ package Cassandane::Cyrus::UTF8Accept; use strict; use warnings; -use Cwd qw(abs_path); +use Cwd qw(abs_path); use File::Path qw(mkpath); use DateTime; use Data::Dumper; @@ -50,177 +50,175 @@ use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; use Cassandane::Util::NetString; +sub new { + my $class = shift; + my $config = Cassandane::Config->default()->clone(); -sub new -{ - my $class = shift; - my $config = Cassandane::Config->default()->clone(); + # Make sure the server will advertise support for UTF8=ACCEPT + $config->set(reject8bit => 'off'); + $config->set(munge8bit => 'off'); - # Make sure the server will advertise support for UTF8=ACCEPT - $config->set(reject8bit => 'off'); - $config->set(munge8bit => 'off'); - - return $class->SUPER::new({ services => ['imap'], config => $config }, @_); + return $class->SUPER::new({ services => ['imap'], config => $config }, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_mboxname - :NoAltNameSpace :min_version_3_9 -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - - xlog $self, "Create mailbox with mUTF7 encoded name"; - my $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.&JgA-"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "ENABLE UTF8=ACCEPT"; - $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "UTF8=ACCEPT"); - $self->assert_num_equals(1, $res->{'utf8=accept'}); - - xlog $self, "Create a mailbox with denormalized mailbox name"; - $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.Å"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Create a child mailbox with normalized mailbox name"; - $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.Å.B"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that LIST responses use UTF8 mailbox names"; - $res = $talk->list("", "*"); - $self->assert_mailbox_structure($res, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.☀' => [qw( \\HasNoChildren )], - 'INBOX.Å' => [qw( \\HasChildren )], - "INBOX.Å.B" => [qw( \\HasNoChildren )], - }); - - xlog $self, "EXAMINE mailbox with UTF8 mailbox name"; - $res = $talk->_imap_cmd('EXAMINE', 0, "", "INBOX.☀"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $talk->unselect(); - - xlog $self, "RENAME mailbox with denormalized mailbox names"; - $res = $talk->_imap_cmd('RENAME', 0, "", "INBOX.Å", "INBOX.Ω"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "DELETE a child mailbox with normalized mailbox name"; - $res = $talk->_imap_cmd('DELETE', 0, "", "INBOX.Ω.B"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Verify that LIST responses use UTF8 mailbox names"; - $res = $talk->list("", "*"); - $self->assert_mailbox_structure($res, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.☀' => [qw( \\HasNoChildren )], - "INBOX.Ω" => [qw( \\HasNoChildren )], - }); + : NoAltNameSpace : min_version_3_9 { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + + xlog $self, "Create mailbox with mUTF7 encoded name"; + my $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.&JgA-"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "ENABLE UTF8=ACCEPT"; + $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "UTF8=ACCEPT"); + $self->assert_num_equals(1, $res->{'utf8=accept'}); + + xlog $self, "Create a mailbox with denormalized mailbox name"; + $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.Å"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Create a child mailbox with normalized mailbox name"; + $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.Å.B"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that LIST responses use UTF8 mailbox names"; + $res = $talk->list("", "*"); + $self->assert_mailbox_structure( + $res, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.☀' => [qw( \\HasNoChildren )], + 'INBOX.Å' => [qw( \\HasChildren )], + "INBOX.Å.B" => [qw( \\HasNoChildren )], + } + ); + + xlog $self, "EXAMINE mailbox with UTF8 mailbox name"; + $res = $talk->_imap_cmd('EXAMINE', 0, "", "INBOX.☀"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->unselect(); + + xlog $self, "RENAME mailbox with denormalized mailbox names"; + $res = $talk->_imap_cmd('RENAME', 0, "", "INBOX.Å", "INBOX.Ω"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "DELETE a child mailbox with normalized mailbox name"; + $res = $talk->_imap_cmd('DELETE', 0, "", "INBOX.Ω.B"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Verify that LIST responses use UTF8 mailbox names"; + $res = $talk->list("", "*"); + $self->assert_mailbox_structure( + $res, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.☀' => [qw( \\HasNoChildren )], + "INBOX.Ω" => [qw( \\HasNoChildren )], + } + ); } sub test_append - :NoAltNameSpace :min_version_3_9 -{ - my ($self) = @_; + : NoAltNameSpace : min_version_3_9 { + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $MsgTxt = <_imap_cmd('CREATE', 0, "", "INBOX.&JgA-"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - # Using UTF8 before UTF8=ACCEPT should fail - xlog $self, "Attempt to append message with UTF-8 header to mailbox"; - $res = $talk->_imap_cmd('APPEND', 0, "", "INBOX.&JgA-", - 'UTF8', [ { Literal => $MsgTxt } ]); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); - - xlog $self, "ENABLE UTF8=ACCEPT"; - $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "UTF8=ACCEPT"); - $self->assert_num_equals(1, $res->{'utf8=accept'}); - - xlog $self, "Append message with UTF-8 header to mailbox"; - $res = $talk->_imap_cmd('APPEND', 0, "", "INBOX.☀", - 'UTF8', [ { Literal => $MsgTxt } ]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Catenate message with UTF-8 header to mailbox"; - $res = $talk->_imap_cmd('APPEND', 0, "", "INBOX.☀", - 'CATENATE', [ 'UTF8', [ { Literal => $MsgTxt } ] ]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $MsgTxt =~ s/\n/\015\012/g; + + xlog $self, "Create mailbox with mUTF7 encoded name"; + my $res = $talk->_imap_cmd('CREATE', 0, "", "INBOX.&JgA-"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + # Using UTF8 before UTF8=ACCEPT should fail + xlog $self, "Attempt to append message with UTF-8 header to mailbox"; + $res = $talk->_imap_cmd('APPEND', 0, "", "INBOX.&JgA-", + 'UTF8', [ { Literal => $MsgTxt } ]); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); + + xlog $self, "ENABLE UTF8=ACCEPT"; + $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "UTF8=ACCEPT"); + $self->assert_num_equals(1, $res->{'utf8=accept'}); + + xlog $self, "Append message with UTF-8 header to mailbox"; + $res = $talk->_imap_cmd('APPEND', 0, "", "INBOX.☀", + 'UTF8', [ { Literal => $MsgTxt } ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Catenate message with UTF-8 header to mailbox"; + $res = $talk->_imap_cmd('APPEND', 0, "", "INBOX.☀", + 'CATENATE', [ 'UTF8', [ { Literal => $MsgTxt } ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); } sub test_search_sort_thread - :NoAltNameSpace :min_version_3_9 -{ - my ($self) = @_; - - xlog $self, "Make some messages"; - my $uid = 1; - my %msgs; - for (1..10) - { - $msgs{$uid} = $self->make_message("Message $uid"); - $msgs{$uid}->set_attribute('uid', $uid); - $uid++; - } + : NoAltNameSpace : min_version_3_9 { + my ($self) = @_; + + xlog $self, "Make some messages"; + my $uid = 1; + my %msgs; + for (1 .. 10) { + $msgs{$uid} = $self->make_message("Message $uid"); + $msgs{$uid}->set_attribute('uid', $uid); + $uid++; + } - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - # Verify that pre-ENABLE search/sort/thread work as expected - my $uids = $talk->search('charset', 'us-ascii', 'all'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + # Verify that pre-ENABLE search/sort/thread work as expected + my $uids = $talk->search('charset', 'us-ascii', 'all'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $uids = $talk->sort('(size)', 'us-ascii', 'all'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uids = $talk->sort('(size)', 'us-ascii', 'all'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $uids = $talk->thread('orderedsubject', 'us-ascii', 'all'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uids = $talk->thread('orderedsubject', 'us-ascii', 'all'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "ENABLE UTF8=ACCEPT"; - my $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "UTF8=ACCEPT"); - $self->assert_num_equals(1, $res->{'utf8=accept'}); + xlog $self, "ENABLE UTF8=ACCEPT"; + my $res = $talk->_imap_cmd('ENABLE', 0, "enabled", "UTF8=ACCEPT"); + $self->assert_num_equals(1, $res->{'utf8=accept'}); - # Using CHARSET after UTF8=ACCEPT should fail - $uids = $talk->search('charset', 'us-ascii', 'all'); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); + # Using CHARSET after UTF8=ACCEPT should fail + $uids = $talk->search('charset', 'us-ascii', 'all'); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); - $uids = $talk->search('all'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uids = $talk->search('all'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - # Using CHARSET other than UTF-8 after UTF8=ACCEPT should fail - $uids = $talk->sort('(size)', 'us-ascii', 'all'); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); + # Using CHARSET other than UTF-8 after UTF8=ACCEPT should fail + $uids = $talk->sort('(size)', 'us-ascii', 'all'); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); - $uids = $talk->sort('(size)', 'utf8', 'all'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uids = $talk->sort('(size)', 'utf8', 'all'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - # Using CHARSET other than UTF-8 after UTF8=ACCEPT should fail - $uids = $talk->thread('orderedsubject', 'us-ascii', 'all'); - $self->assert_str_equals('bad', $talk->get_last_completion_response()); + # Using CHARSET other than UTF-8 after UTF8=ACCEPT should fail + $uids = $talk->thread('orderedsubject', 'us-ascii', 'all'); + $self->assert_str_equals('bad', $talk->get_last_completion_response()); - $uids = $talk->thread('orderedsubject', 'utf8', 'all'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uids = $talk->thread('orderedsubject', 'utf8', 'all'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); } 1; diff --git a/cassandane/Cassandane/Cyrus/Userid.pm b/cassandane/Cassandane/Cyrus/Userid.pm index 3a7f4f6899..cef7926792 100644 --- a/cassandane/Cassandane/Cyrus/Userid.pm +++ b/cassandane/Cassandane/Cyrus/Userid.pm @@ -45,116 +45,118 @@ use lib '.'; use base qw(Cassandane::Cyrus::TestCase); use Cassandane::Util::Log; -Cassandane::Cyrus::TestCase::magic(NoAutocreate => sub { +Cassandane::Cyrus::TestCase::magic( + NoAutocreate => sub { shift->config_set('autocreate_users' => 'nobody'); -}); -Cassandane::Cyrus::TestCase::magic(PopUseACL => sub { + } +); +Cassandane::Cyrus::TestCase::magic( + PopUseACL => sub { shift->config_set('popuseacl' => 'yes'); -}); + } +); - -sub new -{ - my $class = shift; - return $class->SUPER::new({ - adminstore => 1, - services => ['imap', 'pop3'] - }, @_); +sub new { + my $class = shift; + return $class->SUPER::new( + { + adminstore => 1, + services => [ 'imap', 'pop3' ] + }, + @_ + ); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } # Tests userid with dots and unix hierarchy separator sub test_dots_unix - :UnixHierarchySep NoAutocreate PopUseACL -{ - my ($self) = @_; - - my $user = 'userid.with.dots'; - # We will also play a bit with the internal form of this userid - (my $user_internal = $user) =~ s/\./^/g; - - # Create user per instance->create_user() - see builds 1170-1176 - $self->{instance}->create_user($user); - - # There should only be ACLs for (external) userid - my $adminclient = $self->{adminstore}->get_client(); - my $mb = Cassandane::Mboxname->new( - config => $self->{instance}->{config}, - userid => $user - )->to_external(); - - my %acls = $adminclient->getacl($mb); - $self->assert(defined($acls{$user})); - $self->assert(!defined($acls{$user_internal})); - - - # User should be able to login and enter its INBOX - my $store = $self->{instance}->get_service('imap')->create_store(username => $user); - my $client = $store->get_client(); - $client->select('INBOX'); - $self->assert_str_equals('ok', $client->get_last_completion_response()); + : UnixHierarchySep NoAutocreate PopUseACL { + my ($self) = @_; + + my $user = 'userid.with.dots'; + # We will also play a bit with the internal form of this userid + (my $user_internal = $user) =~ s/\./^/g; + + # Create user per instance->create_user() - see builds 1170-1176 + $self->{instance}->create_user($user); + + # There should only be ACLs for (external) userid + my $adminclient = $self->{adminstore}->get_client(); + my $mb = Cassandane::Mboxname->new( + config => $self->{instance}->{config}, + userid => $user + )->to_external(); + + my %acls = $adminclient->getacl($mb); + $self->assert(defined($acls{$user})); + $self->assert(!defined($acls{$user_internal})); + + # User should be able to login and enter its INBOX + my $store + = $self->{instance}->get_service('imap')->create_store(username => $user); + my $client = $store->get_client(); + $client->select('INBOX'); + $self->assert_str_equals('ok', $client->get_last_completion_response()); + $store->disconnect(); + + # Same thing in POP + $store + = $self->{instance}->get_service('pop3')->create_store(username => $user); + $client = $store->get_client(); + $store->disconnect(); + + # Internal userid shall not be able to enter its INBOX + $store = $self->{instance}->get_service('imap') + ->create_store(username => $user_internal); + $client = $store->get_client(); + $client->select('INBOX'); + $self->assert_str_equals('no', $client->get_last_completion_response()); + $store->disconnect(); + + # Same thing in POP + $store = $self->{instance}->get_service('pop3') + ->create_store(username => $user_internal); + { + # shut up + local $SIG{__DIE__}; + local $SIG{__WARN__} = sub { 1 }; + + eval { $client = $store->get_client(); }; + my $Err = $@; $store->disconnect(); - - # Same thing in POP - $store = $self->{instance}->get_service('pop3')->create_store(username => $user); - $client = $store->get_client(); - $store->disconnect(); - - - # Internal userid shall not be able to enter its INBOX - $store = $self->{instance}->get_service('imap')->create_store(username => $user_internal); - $client = $store->get_client(); - $client->select('INBOX'); - $self->assert_str_equals('no', $client->get_last_completion_response()); - $store->disconnect(); - - # Same thing in POP - $store = $self->{instance}->get_service('pop3')->create_store(username => $user_internal); - { - # shut up - local $SIG{__DIE__}; - local $SIG{__WARN__} = sub { 1 }; - - eval { $client = $store->get_client(); }; - my $Err = $@; - $store->disconnect(); - $self->assert_matches(qr/Cannot login via POP3/, $Err); - }; - - - # We should be able to set ACLs for internal userid - $adminclient->setacl($mb, $user_internal => 'lrswipkxtecd') - or die "Cannot setacl for $mb: $@"; - %acls = $adminclient->getacl($mb); - $self->assert(defined($acls{$user_internal})); - - # XXX - In an ideal world, internal userid should still not be able to enter - # its INBOX. However, since we set its rights, it will be able to access the - # external userid mailbox: the '^' character is not forbidden, and since it - # is converted to itself in internal form, the code is such that both - # external and internal userid have the same mailbox name. - - - # We should be able to delete ACLs for external/internal userid - # But external one, as mailbox owner, should still keep some - $adminclient->deleteacl($mb, $user) - or die "Cannot deleteacl for $mb: $@"; - $adminclient->deleteacl($mb, $user_internal) - or die "Cannot deleteacl for $mb: $@"; - %acls = $adminclient->getacl($mb); - $self->assert(defined($acls{$user})); - $self->assert(!defined($acls{$user_internal})); + $self->assert_matches(qr/Cannot login via POP3/, $Err); + }; + + # We should be able to set ACLs for internal userid + $adminclient->setacl($mb, $user_internal => 'lrswipkxtecd') + or die "Cannot setacl for $mb: $@"; + %acls = $adminclient->getacl($mb); + $self->assert(defined($acls{$user_internal})); + + # XXX - In an ideal world, internal userid should still not be able to enter + # its INBOX. However, since we set its rights, it will be able to access the + # external userid mailbox: the '^' character is not forbidden, and since it + # is converted to itself in internal form, the code is such that both + # external and internal userid have the same mailbox name. + + # We should be able to delete ACLs for external/internal userid + # But external one, as mailbox owner, should still keep some + $adminclient->deleteacl($mb, $user) + or die "Cannot deleteacl for $mb: $@"; + $adminclient->deleteacl($mb, $user_internal) + or die "Cannot deleteacl for $mb: $@"; + %acls = $adminclient->getacl($mb); + $self->assert(defined($acls{$user})); + $self->assert(!defined($acls{$user_internal})); } 1; diff --git a/cassandane/Cassandane/Generator.pm b/cassandane/Cassandane/Generator.pm index 3b141fd552..f349b9a869 100644 --- a/cassandane/Cassandane/Generator.pm +++ b/cassandane/Cassandane/Generator.pm @@ -40,7 +40,7 @@ package Cassandane::Generator; use strict; use warnings; -use feature qw(state); +use feature qw(state); use Digest::MD5 qw(md5_hex); use lib '.'; @@ -52,228 +52,213 @@ use Cassandane::Util::SHA; our $admin = 'qa@cyrus.works'; our @girls_forenames = ( - # Top 10 girl baby names in 2006 according to - # http://www.babyhold.com/babynames/Popular/Popular_girl_names_in_the_US_for_2006/ - 'Emily', - 'Emma', - 'Madison', - 'Abigail', - 'Olivia', - 'Isabella', - 'Hannah', - 'Samantha', - 'Ava', - 'Ashley' +# Top 10 girl baby names in 2006 according to +# http://www.babyhold.com/babynames/Popular/Popular_girl_names_in_the_US_for_2006/ + 'Emily', + 'Emma', + 'Madison', + 'Abigail', + 'Olivia', + 'Isabella', + 'Hannah', + 'Samantha', + 'Ava', + 'Ashley' ); our @surnames = ( - # Top 10 common surnames in Australia according to - # http://genealogy.about.com/od/australia/tp/common_surnames.htm - 'Smith', - 'Jones', - 'Williams', - 'Brown', - 'Wilson', - 'Taylor', - 'Nguyen', - 'Johnson', - 'Martin', - 'White' + # Top 10 common surnames in Australia according to + # http://genealogy.about.com/od/australia/tp/common_surnames.htm + 'Smith', + 'Jones', + 'Williams', + 'Brown', + 'Wilson', + 'Taylor', + 'Nguyen', + 'Johnson', + 'Martin', + 'White' ); our @domains = ( - # Pulled out of my hat. - 'fastmail.fm', - 'gmail.com', - 'hotmail.com', - 'yahoo.com' + # Pulled out of my hat. + 'fastmail.fm', + 'gmail.com', + 'hotmail.com', + 'yahoo.com' ); our @localpart_styles = ( - sub($$$) - { - my ($forename, $initial, $surname) = @_; - return "$forename.$surname"; - }, - sub($$$) - { - my ($forename, $initial, $surname) = @_; - return lc(substr($forename,0,1) . $initial . $surname); - }, - sub($$$) - { - my ($forename, $initial, $surname) = @_; - return lc(substr($forename,0,1) . $initial . substr($surname,0,1)); - } + sub($$$) { + my ($forename, $initial, $surname) = @_; + return "$forename.$surname"; + }, + sub($$$) { + my ($forename, $initial, $surname) = @_; + return lc(substr($forename, 0, 1) . $initial . $surname); + }, + sub($$$) { + my ($forename, $initial, $surname) = @_; + return lc(substr($forename, 0, 1) . $initial . substr($surname, 0, 1)); + } ); -sub new -{ - my ($class, %params) = @_; +sub new { + my ($class, %params) = @_; - my $self = { - next_uid => 1, - min_extra_lines => $params{min_extra_lines} || 0, - max_extra_lines => $params{max_extra_lines} || 0, - }; + my $self = { + next_uid => 1, + min_extra_lines => $params{min_extra_lines} || 0, + max_extra_lines => $params{max_extra_lines} || 0, + }; - bless $self, $class; - return $self; + bless $self, $class; + return $self; } -sub _generate_uid -{ - my ($self) = @_; - my $uid = $self->{next_uid}++; - return $uid; +sub _generate_uid { + my ($self) = @_; + my $uid = $self->{next_uid}++; + return $uid; } -sub set_next_uid -{ - my ($self, $uid) = @_; - $self->{next_uid} = 0+$uid; +sub set_next_uid { + my ($self, $uid) = @_; + $self->{next_uid} = 0 + $uid; } -sub make_random_address -{ - my (%params) = @_; - - my $i = int(rand(scalar(@girls_forenames))); - my $forename = delete $params{forename}; - $forename = $girls_forenames[$i] if !defined $forename; +sub make_random_address { + my (%params) = @_; - $i = int(rand(scalar(@surnames))); - my $surname = delete $params{surname}; - $surname = $surnames[$i] if !defined $surname; + my $i = int(rand(scalar(@girls_forenames))); + my $forename = delete $params{forename}; + $forename = $girls_forenames[$i] if !defined $forename; - my $digest = md5_hex("$forename $surname"); + $i = int(rand(scalar(@surnames))); + my $surname = delete $params{surname}; + $surname = $surnames[$i] if !defined $surname; - $i = oct("0x" . substr($digest,0,4)) % scalar(@domains); - my $domain = delete $params{domain}; - $domain = $domains[$i] if !defined $domain; + my $digest = md5_hex("$forename $surname"); - $i = oct("0x" . substr($digest,4,4)) % 26; - my $initial = delete $params{initial}; - $initial = substr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", $i, 1) - if !defined $initial; + $i = oct("0x" . substr($digest, 0, 4)) % scalar(@domains); + my $domain = delete $params{domain}; + $domain = $domains[$i] if !defined $domain; - $i = oct("0x" . substr($digest,8,4)) % scalar(@localpart_styles); - my $localpart = delete $params{localpart}; - $localpart = $localpart_styles[$i]->($forename, $initial, $surname) - if !defined $localpart; + $i = oct("0x" . substr($digest, 4, 4)) % 26; + my $initial = delete $params{initial}; + $initial = substr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", $i, 1) + if !defined $initial; - my $extra = delete $params{extra}; - $extra = '' if !defined $extra; + $i = oct("0x" . substr($digest, 8, 4)) % scalar(@localpart_styles); + my $localpart = delete $params{localpart}; + $localpart = $localpart_styles[$i]->($forename, $initial, $surname) + if !defined $localpart; - return Cassandane::Address->new( - name => "$forename $initial. $surname$extra", - localpart => $localpart, - domain => $domain - ); -} + my $extra = delete $params{extra}; + $extra = '' if !defined $extra; -sub _generate_from -{ - my ($self, $params) = @_; - return make_random_address(); + return Cassandane::Address->new( + name => "$forename $initial. $surname$extra", + localpart => $localpart, + domain => $domain + ); } -sub _generate_to -{ - my ($self, $params) = @_; - return Cassandane::Address->new( - name => "Test User", - localpart => 'test', - domain => 'vmtom.com' - ); +sub _generate_from { + my ($self, $params) = @_; + return make_random_address(); } -sub _generate_messageid -{ - my ($self, $params) = @_; - state $counter = 0; - my $idsalt = int(rand(65536)); - - return sprintf 'fake.%d.%d.%d@%s', - $params->{date}->epoch(), - ++ $counter, - $idsalt, - $params->{from}->domain(); +sub _generate_to { + my ($self, $params) = @_; + return Cassandane::Address->new( + name => "Test User", + localpart => 'test', + domain => 'vmtom.com' + ); } -sub _params_defaults -{ - my $self = shift; - my $params = { @_ }; - - # Note: no error checking, e.g. for unknown parameters. Sorry. - # - $params->{date} = DateTime->now() - unless defined $params->{date}; - $params->{date} = from_iso8601($params->{date}) - if ref $params->{date} eq ''; - die "Bad date: " . ref $params->{date} - unless ref $params->{date} eq 'DateTime'; - - $params->{from} = $self->_generate_from($params) - unless defined $params->{from}; - die "Bad from: " . ref $params->{from} - unless ref $params->{from} eq 'Cassandane::Address'; - - $params->{subject} = "Generated test email" - unless defined $params->{subject}; - - $params->{to} = $self->_generate_to($params) - unless defined $params->{to}; - die "Bad to: " . ref $params->{to} - unless ref $params->{to} eq 'Cassandane::Address'; - - $params->{messageid} = $self->_generate_messageid($params) - unless defined $params->{messageid}; - - # Allow 'references' to be an array of Message objects - # which is really handy for generating conversation data - if (defined $params->{references} && - ref $params->{references} eq 'ARRAY') - { - my @refs; - map { - if (ref($_) eq 'Cassandane::Message') - { - push(@refs, $_->messageid()) - } - else - { - push(@refs, "" . $_); - } - } @{$params->{references}}; - $params->{references} = join(', ', @refs); - } - - $params->{uid} = $self->_generate_uid() - unless defined $params->{uid}; - - $params->{body} = "This is a generated test email. " . - "If received, please notify $admin\r\n" - unless defined $params->{body}; +sub _generate_messageid { + my ($self, $params) = @_; + state $counter = 0; + my $idsalt = int(rand(65536)); - $params->{extra_lines} = int($self->{min_extra_lines} + - rand($self->{max_extra_lines} - - $self->{min_extra_lines})) - unless defined $params->{extra_lines}; - - $params->{mime_encoding} = '7bit' - unless defined $params->{mime_encoding}; - $params->{mime_type} = 'text/plain' - unless defined $params->{mime_type}; - $params->{mime_charset} = 'us-ascii' - unless defined $params->{mime_charset}; - $params->{mime_boundary} = 'Apple-Mail-1-798269008' - unless defined $params->{mime_boundary}; + return sprintf 'fake.%d.%d.%d@%s', + $params->{date}->epoch(), ++$counter, + $idsalt, + $params->{from}->domain(); +} - return $params; +sub _params_defaults { + my $self = shift; + my $params = {@_}; + + # Note: no error checking, e.g. for unknown parameters. Sorry. + # + $params->{date} = DateTime->now() + unless defined $params->{date}; + $params->{date} = from_iso8601($params->{date}) + if ref $params->{date} eq ''; + die "Bad date: " . ref $params->{date} + unless ref $params->{date} eq 'DateTime'; + + $params->{from} = $self->_generate_from($params) + unless defined $params->{from}; + die "Bad from: " . ref $params->{from} + unless ref $params->{from} eq 'Cassandane::Address'; + + $params->{subject} = "Generated test email" + unless defined $params->{subject}; + + $params->{to} = $self->_generate_to($params) + unless defined $params->{to}; + die "Bad to: " . ref $params->{to} + unless ref $params->{to} eq 'Cassandane::Address'; + + $params->{messageid} = $self->_generate_messageid($params) + unless defined $params->{messageid}; + + # Allow 'references' to be an array of Message objects + # which is really handy for generating conversation data + if (defined $params->{references} + && ref $params->{references} eq 'ARRAY') + { + my @refs; + map { + if (ref($_) eq 'Cassandane::Message') { + push(@refs, $_->messageid()); + } else { + push(@refs, "" . $_); + } + } @{ $params->{references} }; + $params->{references} = join(', ', @refs); + } + + $params->{uid} = $self->_generate_uid() + unless defined $params->{uid}; + + $params->{body} + = "This is a generated test email. " + . "If received, please notify $admin\r\n" + unless defined $params->{body}; + + $params->{extra_lines} + = int($self->{min_extra_lines} + + rand($self->{max_extra_lines} - $self->{min_extra_lines})) + unless defined $params->{extra_lines}; + + $params->{mime_encoding} = '7bit' + unless defined $params->{mime_encoding}; + $params->{mime_type} = 'text/plain' + unless defined $params->{mime_type}; + $params->{mime_charset} = 'us-ascii' + unless defined $params->{mime_charset}; + $params->{mime_boundary} = 'Apple-Mail-1-798269008' + unless defined $params->{mime_boundary}; + + return $params; } -sub _generate_unique -{ - return sha1_hex("" . int(rand(65536))); +sub _generate_unique { + return sha1_hex("" . int(rand(65536))); } # @@ -281,83 +266,89 @@ sub _generate_unique # Args: Generator, (param-key => param-value ... ) # Returns: Message ref # -sub generate -{ - my ($self, @aparams) = @_; - my $params = $self->_params_defaults(@aparams); - my $datestr = to_rfc822($params->{date}); - my $from = $params->{from}; - my $to = $params->{to}; - my $extra_lines = $params->{extra_lines}; - my $extra = ''; - if ($extra_lines) { - $extra .= "This is an extra line\r\n" x $extra_lines; +sub generate { + my ($self, @aparams) = @_; + my $params = $self->_params_defaults(@aparams); + my $datestr = to_rfc822($params->{date}); + my $from = $params->{from}; + my $to = $params->{to}; + my $extra_lines = $params->{extra_lines}; + my $extra = ''; + if ($extra_lines) { + $extra .= "This is an extra line\r\n" x $extra_lines; + } + my $size = $params->{size}; + my $msg = Cassandane::Message->new(); + + $msg->add_header("Return-Path", "<" . $from->address() . ">"); + # TODO: two minutes ago + $msg->add_header("Received", + "from gateway (gateway." + . $to->domain() + . " [10.0.0.1])\r\n" + . "\tby ahost (ahost." + . $to->domain() + . "[10.0.0.2]); $datestr"); + $msg->add_header("Received", + "from mail." + . $from->domain() + . " (mail." + . $from->domain() + . " [192.168.0.1])\r\n" + . "\tby gateway." + . $to->domain() + . " (gateway." + . $to->domain() + . " [10.0.0.1]); $datestr"); + $msg->add_header("MIME-Version", "1.0"); + my $mimetype = $params->{mime_type}; + if ($mimetype =~ m/multipart\//i) { + $mimetype .= "; boundary=\"$params->{mime_boundary}\""; + } else { + $mimetype .= "; charset=\"$params->{mime_charset}\"" + if $params->{mime_charset} ne ''; + } + $msg->add_header("Content-Type", $mimetype); + $msg->add_header("Content-Transfer-Encoding", $params->{mime_encoding}); + $msg->add_header("Subject", $params->{subject}); + $msg->add_header("From", $from); + $msg->add_header("Message-ID", "<" . $params->{messageid} . ">"); + $msg->add_header("References", $params->{references}) + if defined $params->{references}; + $msg->add_header("Date", $datestr); + $msg->add_header("To", $to); + $msg->add_header("Cc", $params->{cc}) if defined $params->{cc}; + $msg->add_header("Bcc", $params->{bcc}) if defined $params->{bcc}; + + if (defined($params->{extra_headers})) { + foreach my $extra_header (@{ $params->{extra_headers} }) { + $msg->add_header(@{$extra_header}); } - my $size = $params->{size}; - my $msg = Cassandane::Message->new(); - - $msg->add_header("Return-Path", "<" . $from->address() . ">"); - # TODO: two minutes ago - $msg->add_header("Received", - "from gateway (gateway." . $to->domain() . " [10.0.0.1])\r\n" . - "\tby ahost (ahost." . $to->domain() . "[10.0.0.2]); $datestr"); - $msg->add_header("Received", - "from mail." . $from->domain() . " (mail." . $from->domain() . " [192.168.0.1])\r\n" . - "\tby gateway." . $to->domain() . " (gateway." . $to->domain() . " [10.0.0.1]); $datestr"); - $msg->add_header("MIME-Version", "1.0"); - my $mimetype = $params->{mime_type}; - if ($mimetype =~ m/multipart\//i) - { - $mimetype .= "; boundary=\"$params->{mime_boundary}\"" + } + $msg->add_header('X-Cassandane-Unique', _generate_unique()); + if (defined $size) { + my $padding = "ton bear\r\n"; + my $msg_size = $msg->size() + length($params->{body}) + length($extra); + my $needs = $size - $msg_size; + die "size $size cannot be achieved, message is already $msg_size bytes long" + if $needs < 0; + my $npad = int($needs / length($padding)) - 1; + if ($npad > 0) { + $extra .= $padding x $npad; + $needs -= length($padding) * $npad; } - else - { - $mimetype .= "; charset=\"$params->{mime_charset}\"" - if $params->{mime_charset} ne ''; - } - $msg->add_header("Content-Type", $mimetype); - $msg->add_header("Content-Transfer-Encoding", $params->{mime_encoding}); - $msg->add_header("Subject", $params->{subject}); - $msg->add_header("From", $from); - $msg->add_header("Message-ID", "<" . $params->{messageid} . ">"); - $msg->add_header("References", $params->{references}) - if defined $params->{references}; - $msg->add_header("Date", $datestr); - $msg->add_header("To", $to); - $msg->add_header("Cc", $params->{cc}) if defined $params->{cc}; - $msg->add_header("Bcc", $params->{bcc}) if defined $params->{bcc}; - if (defined($params->{extra_headers})) { - foreach my $extra_header (@{$params->{extra_headers}}) { - $msg->add_header(@{$extra_header}); - } - } - $msg->add_header('X-Cassandane-Unique', _generate_unique()); - if (defined $size) - { - my $padding = "ton bear\r\n"; - my $msg_size = $msg->size() + length($params->{body}) + length($extra); - my $needs = $size - $msg_size; - die "size $size cannot be achieved, message is already $msg_size bytes long" - if $needs < 0; - my $npad = int($needs / length($padding)) - 1; - if ($npad > 0) - { - $extra .= $padding x $npad; - $needs -= length($padding) * $npad; - } - $extra .= 'X' x ($needs - 2) if ($needs >= 2); - $extra .= "\r\n"; - } - $msg->set_body($params->{body} . $extra); - $msg->set_attributes(uid => $params->{uid}); - $msg->set_internaldate($params->{date}); - - if ($params->{flags}) { - $msg->set_attribute(flags => $params->{flags}); - } - - return $msg; + $extra .= 'X' x ($needs - 2) if ($needs >= 2); + $extra .= "\r\n"; + } + $msg->set_body($params->{body} . $extra); + $msg->set_attributes(uid => $params->{uid}); + $msg->set_internaldate($params->{date}); + + if ($params->{flags}) { + $msg->set_attribute(flags => $params->{flags}); + } + + return $msg; } - 1; diff --git a/cassandane/Cassandane/GenericListener.pm b/cassandane/Cassandane/GenericListener.pm index 1b59f7aa1e..f314d2b1b5 100644 --- a/cassandane/Cassandane/GenericListener.pm +++ b/cassandane/Cassandane/GenericListener.pm @@ -45,424 +45,388 @@ use lib '.'; use Cassandane::Util::Log; use Cassandane::PortManager; -sub new -{ - my ($class, %params) = @_; - - my $host = '127.0.0.1'; - $host = delete $params{host} - if (exists $params{host}); - my $port = delete $params{port}; - my $config = delete $params{config}; - my $argv = delete $params{argv}; - my $name = delete $params{name}; - - die "Unexpected parameters: " . join(" ", keys %params) - if scalar %params; - - return bless - { - name => $name, - host => $host, - port => $port, - config => $config, - argv => $argv, - }, $class; +sub new { + my ($class, %params) = @_; + + my $host = '127.0.0.1'; + $host = delete $params{host} + if (exists $params{host}); + my $port = delete $params{port}; + my $config = delete $params{config}; + my $argv = delete $params{argv}; + my $name = delete $params{name}; + + die "Unexpected parameters: " . join(" ", keys %params) + if scalar %params; + + return bless { + name => $name, + host => $host, + port => $port, + config => $config, + argv => $argv, + }, $class; } -sub set_config -{ - my ($self, $config) = @_; - $self->{config} = $config; +sub set_config { + my ($self, $config) = @_; + $self->{config} = $config; } # Return the host -sub host -{ - my ($self) = @_; - return $self->{host}; +sub host { + my ($self) = @_; + return $self->{host}; } # Return the port -sub port -{ - my ($self) = @_; +sub port { + my ($self) = @_; - $self->set_port($self->{port}); + $self->set_port($self->{port}); - return $self->{port}; + return $self->{port}; } -sub set_port -{ - my ($self, $port) = @_; +sub set_port { + my ($self, $port) = @_; - if (defined $port && - defined $self->{config}) - { - # expand @basedir@ et al - $port = $self->{config}->substitute($port); - } + if ( defined $port + && defined $self->{config}) + { + # expand @basedir@ et al + $port = $self->{config}->substitute($port); + } - $port ||= Cassandane::PortManager::alloc($self->host); - $self->{port} = $port; + $port ||= Cassandane::PortManager::alloc($self->host); + $self->{port} = $port; } # Return a hash of parameters for connecting to the listener. # These will ultimately go through to MessageStoreFactory::create. -sub connection_params -{ - my ($self, %params) = @_; - - $params{address_family} = $self->{address_family}; - $params{host} = $self->host(); - $params{port} = $self->port(); - $params{verbose} ||= get_verbose(); - return \%params; +sub connection_params { + my ($self, %params) = @_; + + $params{address_family} = $self->{address_family}; + $params{host} = $self->host(); + $params{port} = $self->port(); + $params{verbose} ||= get_verbose(); + return \%params; } -sub address -{ - my ($self) = @_; - my @parts; - - my $port = $self->port(); - if (defined $self->{host} && !($port =~ m/^\//)) - { - # Cyrus uses the syntax '[ipv6address]:port' to specify - # an IPv6 address (which will contain the : character) - # as the host part. - push(@parts, '[') if ($self->{host} =~ m/:/); - push(@parts, $self->{host}); - push(@parts, ']') if ($self->{host} =~ m/:/); - push(@parts, ':'); - } - push(@parts, $port); - return join('', @parts); +sub address { + my ($self) = @_; + my @parts; + + my $port = $self->port(); + if (defined $self->{host} && !($port =~ m/^\//)) { + # Cyrus uses the syntax '[ipv6address]:port' to specify + # an IPv6 address (which will contain the : character) + # as the host part. + push(@parts, '[') if ($self->{host} =~ m/:/); + push(@parts, $self->{host}); + push(@parts, ']') if ($self->{host} =~ m/:/); + push(@parts, ':'); + } + push(@parts, $port); + return join('', @parts); } -sub parse_address -{ - my ($s) = @_; - my $host; - my $port; - - if ($s =~ m/^\//) - { - # UNIX domain socket - $port = $s; - } - if (!defined $port) - { - # syntax '[ipv6address]:port' - ($host, $port) = ($s =~ m/^\[([^]]+)\]:([^:]+)$/); - } - if (!defined $port) - { - # syntax 'host:port' - ($host, $port) = ($s =~ m/^([^:]+):([^:]+)$/); - } - if (!defined $port) - { - # syntax 'port' - ($port) = ($s =~ m/^([^:]+)$/); - } - if (!defined $port) - { - die "Cannot parse \"$s\" as socket address" - } - - return { host => $host, port => $port }; +sub parse_address { + my ($s) = @_; + my $host; + my $port; + + if ($s =~ m/^\//) { + # UNIX domain socket + $port = $s; + } + if (!defined $port) { + # syntax '[ipv6address]:port' + ($host, $port) = ($s =~ m/^\[([^]]+)\]:([^:]+)$/); + } + if (!defined $port) { + # syntax 'host:port' + ($host, $port) = ($s =~ m/^([^:]+):([^:]+)$/); + } + if (!defined $port) { + # syntax 'port' + ($port) = ($s =~ m/^([^:]+)$/); + } + if (!defined $port) { + die "Cannot parse \"$s\" as socket address"; + } + + return { host => $host, port => $port }; } my %netstat_parse = ( - # # netstat -ln -Ainet - # Active Internet connections (only servers) - # Proto Recv-Q Send-Q Local Address Foreign Address State - # tcp 0 0 0.0.0.0:56686 0.0.0.0:* LISTEN - # - # # netstat -lnp -Ainet - # Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name - # tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1058/sshd - inet => sub - { - my ($line, $wantpid) = @_; - - my @a = split(/\s+/, $line); - return unless scalar(@a) == 6 + ($wantpid ? 1 : 0); - - my ($addr, $port) = ($a[3] =~ m/^(.*):([0-9]+)$/); - return unless defined $port; - $addr = 'any' if ($addr eq '0.0.0.0'); - $addr = 'localhost' if ($addr eq '127.0.0.1'); - - my $pid; - my $cmd; - ($pid, $cmd) = ($a[6] =~ m/^([0-9]+)\/(.*)$/) if ($wantpid); - - return { - address_family => 'inet', - protocol => $a[0], # 'tcp' - state => $a[5], # 'LISTEN' - local_addr => $addr, # numeric - local_port => $port, # numeric - pid => $pid, # numeric or undef - cmd => $cmd, # string or undef - }; - }, - - # # netstat -ln -Ainet6 - # Active Internet connections (only servers) - # Proto Recv-Q Send-Q Local Address Foreign Address State - # tcp6 0 0 :::22 :::* LISTEN - # - # # netstat -lnp -Ainet6 - # Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name - # tcp6 0 0 :::22 :::* LISTEN 1058/sshd - inet6 => sub - { - my ($line, $wantpid) = @_; - - my @a = split(/\s+/, $line); - return unless scalar(@a) == 6 + ($wantpid ? 1 : 0); - - my $prot = $a[0]; # tcp or tcp6 - $prot =~ s/6$//; - - my ($addr, $port) = ($a[3] =~ m/^(.*):([0-9]+)$/); - return unless defined $port; - $addr = 'any' if ($addr eq '::'); - $addr = 'localhost' if ($addr eq '::1'); - - my $pid; - my $cmd; - ($pid, $cmd) = ($a[6] =~ m/^([0-9]+)\/(.*)$/) if ($wantpid); - - return { - address_family => 'inet6', - protocol => $prot, # 'tcp' - state => $a[5], # 'LISTEN' - local_addr => $addr, # numeric - local_port => $port, # numeric - pid => $pid, # numeric or undef - cmd => $cmd, # string or undef - }; - }, - - # # netstat -ln -Aunix - # Active UNIX domain sockets (only servers) - # Proto RefCnt Flags Type State I-Node Path - # unix 2 [ ACC ] STREAM LISTENING 7941 /var/run/dbus/system_bus_socket - # - # # netstat -lnp -Aunix - # Active UNIX domain sockets (only servers) - # Proto RefCnt Flags Type State I-Node PID/Program name Path - # unix 2 [ ACC ] STREAM LISTENING 13317 2016/gconf-helper /tmp/orbit-gnb/linc-7e0-0-6044c14eae22e - unix => sub - { - my ($line, $wantpid) = @_; - - # Compress the Flags field to eliminate spaces and make split() - # return a predictable number of fields. - $line =~ s/\[[^]]*\]/[]/; - - my @a = split(/\s+/, $line); - return unless scalar(@a) == 7 + ($wantpid ? 1 : 0); - - my $state = $a[4]; - $state =~ s/^LISTENING$/LISTEN/; - - return if $a[0] ne 'unix'; - my $prot; - $prot = 'tcp' if ($a[3] eq 'STREAM'); - $prot = 'udp' if ($a[3] eq 'DGRAM'); - return if !defined $prot; - - my $pid; - my $cmd; - ($pid, $cmd) = ($a[6] =~ m/^([0-9]+)\/(.*)$/) if ($wantpid); - - return { - address_family => 'unix', - protocol => $prot, # 'tcp' - state => $state, # 'LISTEN' - local_addr => 'any', - local_port => $a[-1], # bound socket path - pid => $pid, # numeric or undef - cmd => $cmd, # string or undef - }; - }, +# # netstat -ln -Ainet +# Active Internet connections (only servers) +# Proto Recv-Q Send-Q Local Address Foreign Address State +# tcp 0 0 0.0.0.0:56686 0.0.0.0:* LISTEN +# +# # netstat -lnp -Ainet +# Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name +# tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1058/sshd + inet => sub { + my ($line, $wantpid) = @_; + + my @a = split(/\s+/, $line); + return unless scalar(@a) == 6 + ($wantpid ? 1 : 0); + + my ($addr, $port) = ($a[3] =~ m/^(.*):([0-9]+)$/); + return unless defined $port; + $addr = 'any' if ($addr eq '0.0.0.0'); + $addr = 'localhost' if ($addr eq '127.0.0.1'); + + my $pid; + my $cmd; + ($pid, $cmd) = ($a[6] =~ m/^([0-9]+)\/(.*)$/) if ($wantpid); + + return { + address_family => 'inet', + protocol => $a[0], # 'tcp' + state => $a[5], # 'LISTEN' + local_addr => $addr, # numeric + local_port => $port, # numeric + pid => $pid, # numeric or undef + cmd => $cmd, # string or undef + }; + }, + +# # netstat -ln -Ainet6 +# Active Internet connections (only servers) +# Proto Recv-Q Send-Q Local Address Foreign Address State +# tcp6 0 0 :::22 :::* LISTEN +# +# # netstat -lnp -Ainet6 +# Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name +# tcp6 0 0 :::22 :::* LISTEN 1058/sshd + inet6 => sub { + my ($line, $wantpid) = @_; + + my @a = split(/\s+/, $line); + return unless scalar(@a) == 6 + ($wantpid ? 1 : 0); + + my $prot = $a[0]; # tcp or tcp6 + $prot =~ s/6$//; + + my ($addr, $port) = ($a[3] =~ m/^(.*):([0-9]+)$/); + return unless defined $port; + $addr = 'any' if ($addr eq '::'); + $addr = 'localhost' if ($addr eq '::1'); + + my $pid; + my $cmd; + ($pid, $cmd) = ($a[6] =~ m/^([0-9]+)\/(.*)$/) if ($wantpid); + + return { + address_family => 'inet6', + protocol => $prot, # 'tcp' + state => $a[5], # 'LISTEN' + local_addr => $addr, # numeric + local_port => $port, # numeric + pid => $pid, # numeric or undef + cmd => $cmd, # string or undef + }; + }, + +# # netstat -ln -Aunix +# Active UNIX domain sockets (only servers) +# Proto RefCnt Flags Type State I-Node Path +# unix 2 [ ACC ] STREAM LISTENING 7941 /var/run/dbus/system_bus_socket +# +# # netstat -lnp -Aunix +# Active UNIX domain sockets (only servers) +# Proto RefCnt Flags Type State I-Node PID/Program name Path +# unix 2 [ ACC ] STREAM LISTENING 13317 2016/gconf-helper /tmp/orbit-gnb/linc-7e0-0-6044c14eae22e + unix => sub { + my ($line, $wantpid) = @_; + + # Compress the Flags field to eliminate spaces and make split() + # return a predictable number of fields. + $line =~ s/\[[^]]*\]/[]/; + + my @a = split(/\s+/, $line); + return unless scalar(@a) == 7 + ($wantpid ? 1 : 0); + + my $state = $a[4]; + $state =~ s/^LISTENING$/LISTEN/; + + return if $a[0] ne 'unix'; + my $prot; + $prot = 'tcp' if ($a[3] eq 'STREAM'); + $prot = 'udp' if ($a[3] eq 'DGRAM'); + return if !defined $prot; + + my $pid; + my $cmd; + ($pid, $cmd) = ($a[6] =~ m/^([0-9]+)\/(.*)$/) if ($wantpid); + + return { + address_family => 'unix', + protocol => $prot, # 'tcp' + state => $state, # 'LISTEN' + local_addr => 'any', + local_port => $a[-1], # bound socket path + pid => $pid, # numeric or undef + cmd => $cmd, # string or undef + }; + }, ); -sub address_family -{ - my ($self) = @_; - my $h = $self->host(); - my $p = $self->port(); - - # port being a UNIX domain socket is ok - return 'unix' if ($p =~ m/^\//); - - # otherwise, the port has to be numeric - die "Sorry, the port \"$p\" must be a numeric TCP port or unix path" - unless ($p =~ m/^\d+$/); - - # undefined host is ok = inet, IPADDR_ANY - return 'inet' if !defined $h; - # IPv4 address is ok - return 'inet' if ($h =~ m/^\d+\.\d+\.\d+\.\d+$/); - # full IPv6 address is ok - return 'inet6' if ($h =~ m/^([[:xdigit:]]{1,4}::?)+[[:xdigit:]]{1,4}$/); - # IPv6 forms xxxx::x and ::x are ok - return 'inet6' if ($h =~ m/^[[:xdigit:]]{4}::[[:xdigit:]]{1,4}$/); - return 'inet6' if ($h =~ m/^::[[:xdigit:]]{1,4}$/); - # others, not so much - die "Sorry, the host argument \"$h\" must be a numeric IPv4 or IPv6 address"; +sub address_family { + my ($self) = @_; + my $h = $self->host(); + my $p = $self->port(); + + # port being a UNIX domain socket is ok + return 'unix' if ($p =~ m/^\//); + + # otherwise, the port has to be numeric + die "Sorry, the port \"$p\" must be a numeric TCP port or unix path" + unless ($p =~ m/^\d+$/); + + # undefined host is ok = inet, IPADDR_ANY + return 'inet' if !defined $h; + # IPv4 address is ok + return 'inet' if ($h =~ m/^\d+\.\d+\.\d+\.\d+$/); + # full IPv6 address is ok + return 'inet6' if ($h =~ m/^([[:xdigit:]]{1,4}::?)+[[:xdigit:]]{1,4}$/); + # IPv6 forms xxxx::x and ::x are ok + return 'inet6' if ($h =~ m/^[[:xdigit:]]{4}::[[:xdigit:]]{1,4}$/); + return 'inet6' if ($h =~ m/^::[[:xdigit:]]{1,4}$/); + # others, not so much + die "Sorry, the host argument \"$h\" must be a numeric IPv4 or IPv6 address"; } -sub _is_listening_af -{ - my ($self, $af) = @_; +sub _is_listening_af { + my ($self, $af) = @_; + + my @cmd = ( + 'netstat', + '-l', # listening ports only + '-n', # numeric output + ); + my $parser = $netstat_parse{$af}; + my $found = 0; + open NETSTAT, '-|', @cmd + or die "Cannot run netstat to check for service: $!"; + + my $host = $self->{host}; + $host = 'any' if !defined $host; + $host = 'localhost' if $host eq '127.0.0.1'; + $host = 'localhost' if $host eq '::1'; + + while () { + chomp; + my $ii = $parser->($_, 0); + next unless $ii; + next if ($ii->{protocol} ne 'tcp'); + next if ($ii->{state} ne 'LISTEN'); + next if ($ii->{local_port} ne "$self->{port}"); + next if ($ii->{local_addr} ne $host && $ii->{local_addr} ne 'any'); + $found = 1; + last; + } + close NETSTAT; + + xlog "is_listening: service $self->{name} is " + . "listening on " + . $self->address() + if ($found); + + return $found; +} - my @cmd = ( - 'netstat', - '-l', # listening ports only - '-n', # numeric output - ); - my $parser = $netstat_parse{$af}; - my $found = 0; - open NETSTAT,'-|',@cmd - or die "Cannot run netstat to check for service: $!"; - - my $host = $self->{host}; - $host = 'any' if !defined $host; - $host = 'localhost' if $host eq '127.0.0.1'; - $host = 'localhost' if $host eq '::1'; - - while () - { - chomp; - my $ii = $parser->($_, 0); - next unless $ii; - next if ($ii->{protocol} ne 'tcp'); - next if ($ii->{state} ne 'LISTEN'); - next if ($ii->{local_port} ne "$self->{port}"); - next if ($ii->{local_addr} ne $host && $ii->{local_addr} ne 'any'); - $found = 1; - last; - } - close NETSTAT; +sub is_listening { + my ($self) = @_; - xlog "is_listening: service $self->{name} is " . - "listening on " . $self->address() - if ($found); + my @afs; + my $af = $self->address_family(); + push(@afs, $af); + push(@afs, 'inet6') + if ($af eq 'inet' && !defined $self->host()); - return $found; + foreach my $af (@afs) { + return 0 if (!$self->_is_listening_af($af)); + } + return 1; } -sub is_listening -{ - my ($self) = @_; +sub kill_processes_on_ports { + my (@ports) = @_; - my @afs; - my $af = $self->address_family(); - push(@afs, $af); - push(@afs, 'inet6') - if ($af eq 'inet' && !defined $self->host()); + return if !scalar(@ports); + xlog "checking for stray processes on ports: " . join(' ', @ports); - foreach my $af (@afs) - { - return 0 if (!$self->_is_listening_af($af)); - } - return 1; -} + my %portshash; + map { $portshash{$_} = 1; } @ports; -sub kill_processes_on_ports -{ - my (@ports) = @_; - - return if !scalar(@ports); - xlog "checking for stray processes on ports: " . join(' ', @ports); - - my %portshash; - map { $portshash{$_} = 1; } @ports; - - # We don't care about UNIX sockets here - # although we probably should - my @found; - foreach my $af ('inet', 'inet6') - { - # Silly netstat -p on Linux prints a warning to stderr - # -n numeric output - # -p show pid & program - my $cmd = "netstat -np 2>/dev/null"; - - my $parser = $netstat_parse{$af}; - open NETSTAT,'-|',$cmd - or die "Cannot run netstat to check for stray processes: $!"; - - while () - { - chomp; - my $ii = $parser->($_, 1); - next unless $ii; - next unless $portshash{$ii->{local_port}}; -# xlog "XXX stray socket: " . Data::Dumper::Dumper($ii); - next if !defined $ii->{pid}; # we don't have permission, - # or there is no process, - # e.g. in TIME_WAIT state - push(@found, $ii); - } - close NETSTAT; - } + # We don't care about UNIX sockets here + # although we probably should + my @found; + foreach my $af ('inet', 'inet6') { + # Silly netstat -p on Linux prints a warning to stderr + # -n numeric output + # -p show pid & program + my $cmd = "netstat -np 2>/dev/null"; - foreach my $ii (@found) - { - xlog "ERROR!! killing stray process $ii->{cmd} on port $ii->{local_port}"; - Cassandane::Instance::_stop_pid($ii->{pid}); + my $parser = $netstat_parse{$af}; + open NETSTAT, '-|', $cmd + or die "Cannot run netstat to check for stray processes: $!"; + + while () { + chomp; + my $ii = $parser->($_, 1); + next unless $ii; + next unless $portshash{ $ii->{local_port} }; + # xlog "XXX stray socket: " . Data::Dumper::Dumper($ii); + next if !defined $ii->{pid}; # we don't have permission, + # or there is no process, + # e.g. in TIME_WAIT state + push(@found, $ii); } - return scalar(@found); + close NETSTAT; + } + + foreach my $ii (@found) { + xlog "ERROR!! killing stray process $ii->{cmd} on port $ii->{local_port}"; + Cassandane::Instance::_stop_pid($ii->{pid}); + } + return scalar(@found); } -sub describe -{ - my ($self) = @_; +sub describe { + my ($self) = @_; - printf "%s listening on %s\n", - $self->{name}, - $self->address(); + printf "%s listening on %s\n", $self->{name}, $self->address(); } -sub set_argv -{ - my ($self, @args) = @_; - $self->{argv} = [ @args ]; +sub set_argv { + my ($self, @args) = @_; + $self->{argv} = [@args]; } -sub get_argv -{ - my ($self) = @_; +sub get_argv { + my ($self) = @_; - my $aa = $self->{argv}; - die "No command" if (!defined $aa); - my @argv; + my $aa = $self->{argv}; + die "No command" if (!defined $aa); + my @argv; - if (ref $aa eq 'CODE') - { - @argv = $aa->($self); - } - elsif (ref $aa eq 'ARRAY') - { - @argv = @$aa; - } - else - { - die "Unexpected command type"; - } + if (ref $aa eq 'CODE') { + @argv = $aa->($self); + } elsif (ref $aa eq 'ARRAY') { + @argv = @$aa; + } else { + die "Unexpected command type"; + } - map { $_ = $self->{config}->substitute($_); } @argv; + map { $_ = $self->{config}->substitute($_); } @argv; - return @argv; + return @argv; } 1; diff --git a/cassandane/Cassandane/IMAPMessageStore.pm b/cassandane/Cassandane/IMAPMessageStore.pm index b8673f9ba9..6e2f579b05 100644 --- a/cassandane/Cassandane/IMAPMessageStore.pm +++ b/cassandane/Cassandane/IMAPMessageStore.pm @@ -54,390 +54,360 @@ use Cassandane::Util::Socket; our $BATCHSIZE = 10; -sub new -{ - my ($class, %params) = @_; - my %bits = ( - address_family => delete $params{address_family} || 'inet', - host => delete $params{host} || 'localhost', - port => 0 + (delete $params{port} || 143), - folder => delete $params{folder} || 'INBOX', - username => delete $params{username}, - password => delete $params{password}, - client => undef, - banner => undef, - # state for streaming read - next_uid => undef, - last_uid => undef, - last_batch_uid => undef, - batch => undef, - fetch_attrs => { uid => 1, 'body.peek[]' => 1 }, - # state for XCONVFETCH - fetched => undef, - ssl => delete $params{ssl} || 0, - ); - my $self = $class->SUPER::new(%params); - map { $self->{$_} = $bits{$_}; } keys %bits; - return $self; +sub new { + my ($class, %params) = @_; + my %bits = ( + address_family => delete $params{address_family} || 'inet', + host => delete $params{host} || 'localhost', + port => 0 + (delete $params{port} || 143), + folder => delete $params{folder} || 'INBOX', + username => delete $params{username}, + password => delete $params{password}, + client => undef, + banner => undef, + # state for streaming read + next_uid => undef, + last_uid => undef, + last_batch_uid => undef, + batch => undef, + fetch_attrs => { uid => 1, 'body.peek[]' => 1 }, + # state for XCONVFETCH + fetched => undef, + ssl => delete $params{ssl} || 0, + ); + my $self = $class->SUPER::new(%params); + map { $self->{$_} = $bits{$_}; } keys %bits; + return $self; } -sub connect -{ - my ($self, %params) = @_; - - # if already successfully connected, do nothing - return - if (defined $self->{client} && - ($self->{client}->state() == Mail::IMAPTalk::Authenticated || - $self->{client}->state() == Mail::IMAPTalk::Selected)); - - $self->disconnect(); - - my $client; - - if ($self->{ssl}) { - my $ca_file = abs_path("data/certs/cacert.pem"); - # XXX https://github.com/noxxi/p5-io-socket-ssl/issues/121 - # XXX With newer IO::Socket::SSL, hostname verification fails - # XXX because our hostname is an IP address and the certificate - # XXX CN does not contain the IP address. - # XXX Turning hostname verification back off again for now with - # XXX `SSL_verifycn_scheme => 'none'`. The better fix would be - # XXX to generate new certificates for 127.0.0.1 and ::1, but - # XXX I don't remember how... - $client = Mail::IMAPTalk->new( - Server => $self->{host}, - Port => $self->{port}, - UseSSL => $self->{ssl}, - SSL_ca_file => $ca_file, - SSL_verifycn_scheme => 'none', - UseBlocking => 1, # must be blocking for SSL - Pedantic => 1, - PreserveINBOX => 1, - Uid => 0, - NoLiteralPlus => delete $params{NoLiteralPlus} || 0, - ) - or die "Cannot connect to '$self->{host}:$self->{port}': $@"; - } - else { - my $sock = create_client_socket( - $self->{address_family}, - $self->{host}, $self->{port}) - or die "Cannot create client socket: $@"; - - $client = Mail::IMAPTalk->new( - Socket => $sock, - Pedantic => 1, - PreserveINBOX => 1, - Uid => 0, - NoLiteralPlus => delete $params{NoLiteralPlus} || 0, - ) - or die "Cannot connect to server: $@"; - } - - $client->set_tracing(1) - if $self->{verbose}; - - my $banner = $client->get_response_code('remainder'); - $client->login($self->{username}, $self->{password}) - or die "Cannot login to server \"$self->{host}:$self->{port}\": $@"; +sub connect { + my ($self, %params) = @_; - # Make Mail::IMAPTalk just stfu - $client->set_unicode_folders(1); - - $client->parse_mode(Envelope => 1); + # if already successfully connected, do nothing + return + if ( + defined $self->{client} + && ( $self->{client}->state() == Mail::IMAPTalk::Authenticated + || $self->{client}->state() == Mail::IMAPTalk::Selected) + ); - $self->{client} = $client; - $self->{banner} = $banner; + $self->disconnect(); + + my $client; + + if ($self->{ssl}) { + my $ca_file = abs_path("data/certs/cacert.pem"); + # XXX https://github.com/noxxi/p5-io-socket-ssl/issues/121 + # XXX With newer IO::Socket::SSL, hostname verification fails + # XXX because our hostname is an IP address and the certificate + # XXX CN does not contain the IP address. + # XXX Turning hostname verification back off again for now with + # XXX `SSL_verifycn_scheme => 'none'`. The better fix would be + # XXX to generate new certificates for 127.0.0.1 and ::1, but + # XXX I don't remember how... + $client = Mail::IMAPTalk->new( + Server => $self->{host}, + Port => $self->{port}, + UseSSL => $self->{ssl}, + SSL_ca_file => $ca_file, + SSL_verifycn_scheme => 'none', + UseBlocking => 1, # must be blocking for SSL + Pedantic => 1, + PreserveINBOX => 1, + Uid => 0, + NoLiteralPlus => delete $params{NoLiteralPlus} || 0, + ) or die "Cannot connect to '$self->{host}:$self->{port}': $@"; + } else { + my $sock = create_client_socket($self->{address_family}, + $self->{host}, $self->{port}) + or die "Cannot create client socket: $@"; + + $client = Mail::IMAPTalk->new( + Socket => $sock, + Pedantic => 1, + PreserveINBOX => 1, + Uid => 0, + NoLiteralPlus => delete $params{NoLiteralPlus} || 0, + ) or die "Cannot connect to server: $@"; + } + + $client->set_tracing(1) + if $self->{verbose}; + + my $banner = $client->get_response_code('remainder'); + $client->login($self->{username}, $self->{password}) + or die "Cannot login to server \"$self->{host}:$self->{port}\": $@"; + + # Make Mail::IMAPTalk just stfu + $client->set_unicode_folders(1); + + $client->parse_mode(Envelope => 1); + + $self->{client} = $client; + $self->{banner} = $banner; } -sub disconnect -{ - my ($self) = @_; - - # We don't care if the LOGOUT fails. Really. - eval - { - local $SIG{__DIE__}; - $self->{client}->logout() - if defined $self->{client}; - }; - $self->{client} = undef; +sub disconnect { + my ($self) = @_; + + # We don't care if the LOGOUT fails. Really. + eval { + local $SIG{__DIE__}; + $self->{client}->logout() + if defined $self->{client}; + }; + $self->{client} = undef; } -sub _select -{ - my ($self) = @_; +sub _select { + my ($self) = @_; - if ($self->{client}->state() == Mail::IMAPTalk::Selected) - { - $self->{client}->unselect() - or die "Cannot unselect: $@"; - } - return $self->{client}->select($self->{folder}); + if ($self->{client}->state() == Mail::IMAPTalk::Selected) { + $self->{client}->unselect() + or die "Cannot unselect: $@"; + } + return $self->{client}->select($self->{folder}); } -sub write_begin -{ - my ($self) = @_; - my $r; +sub write_begin { + my ($self) = @_; + my $r; - $self->connect(); + $self->connect(); - $r = $self->_select(); - if (!defined $r) - { - die "Cannot select folder \"$self->{folder}\": $@" - unless $self->{client}->get_last_error() =~ m/does not exist/; - $self->{client}->create($self->{folder}) - or die "Cannot create folder \"$self->{folder}\": $@" - } + $r = $self->_select(); + if (!defined $r) { + die "Cannot select folder \"$self->{folder}\": $@" + unless $self->{client}->get_last_error() =~ m/does not exist/; + $self->{client}->create($self->{folder}) + or die "Cannot create folder \"$self->{folder}\": $@"; + } } -sub write_message -{ - my ($self, $msg, %opts) = @_; +sub write_message { + my ($self, $msg, %opts) = @_; - my @extra; - my @flags; + my @extra; + my @flags; - if ($opts{flags}) { - push @flags, @{$opts{flags}}; - } + if ($opts{flags}) { + push @flags, @{ $opts{flags} }; + } - if ($msg->has_attribute('flags')) { - push @flags, @{$msg->get_attribute('flags')}; - } + if ($msg->has_attribute('flags')) { + push @flags, @{ $msg->get_attribute('flags') }; + } - if (@flags) { - push @extra, '(' . join(' ', @flags) . ')'; - } + if (@flags) { + push @extra, '(' . join(' ', @flags) . ')'; + } - if ($msg->has_attribute('internaldate')) { - push @extra, $msg->get_attribute('internaldate'); - } + if ($msg->has_attribute('internaldate')) { + push @extra, $msg->get_attribute('internaldate'); + } - $self->{client}->append($self->{folder}, @extra, - { Literal => $msg->as_string() } ) - || die "$@"; + $self->{client} + ->append($self->{folder}, @extra, { Literal => $msg->as_string() }) + || die "$@"; - # if we know the uid and uidvalidity, update the msg object - my $appenduid = $self->{client}->get_response_code('appenduid'); - if (defined $appenduid and ref $appenduid eq 'ARRAY') { - $msg->set_attribute(uidvalidity => $appenduid->[0]); - $msg->set_attribute(uid => $appenduid->[1]); - } + # if we know the uid and uidvalidity, update the msg object + my $appenduid = $self->{client}->get_response_code('appenduid'); + if (defined $appenduid and ref $appenduid eq 'ARRAY') { + $msg->set_attribute(uidvalidity => $appenduid->[0]); + $msg->set_attribute(uid => $appenduid->[1]); + } } -sub write_end -{ - my ($self) = @_; +sub write_end { + my ($self) = @_; } -sub set_fetch_attributes -{ - my ($self, @attrs) = @_; - - $self->{fetch_attrs} = { uid => 1, 'body.peek[]' => 1 }; - foreach my $attr (@attrs) - { - $attr = lc($attr); - die "Bad fetch attribute \"$attr\"" - unless ($attr =~ m/^annotation\s+\(\S+\s+value\.(shared|priv)\)$/i || - $attr =~ m/^[a-z0-9.\[\]<>]+$/); - next - if ($attr =~ m/^body/); - $self->{fetch_attrs}->{$attr} = 1; - } +sub set_fetch_attributes { + my ($self, @attrs) = @_; + + $self->{fetch_attrs} = { uid => 1, 'body.peek[]' => 1 }; + foreach my $attr (@attrs) { + $attr = lc($attr); + die "Bad fetch attribute \"$attr\"" + unless ($attr =~ m/^annotation\s+\(\S+\s+value\.(shared|priv)\)$/i + || $attr =~ m/^[a-z0-9.\[\]<>]+$/); + next + if ($attr =~ m/^body/); + $self->{fetch_attrs}->{$attr} = 1; + } } -sub read_begin -{ - my ($self) = @_; +sub read_begin { + my ($self) = @_; - $self->connect(); + $self->connect(); - $self->_select() - or die "Cannot select folder \"$self->{folder}\": $@"; + $self->_select() + or die "Cannot select folder \"$self->{folder}\": $@"; - $self->{next_uid} = 1; - $self->{last_uid} = -1 + $self->{client}->get_response_code('uidnext'); - $self->{last_batch_uid} = undef; - $self->{batch} = undef; + $self->{next_uid} = 1; + $self->{last_uid} = -1 + $self->{client}->get_response_code('uidnext'); + $self->{last_batch_uid} = undef; + $self->{batch} = undef; } -sub read_message -{ - my ($self, $msg) = @_; - - for (;;) - { - while (defined $self->{batch}) - { - my $uid = $self->{next_uid}; - last if $uid > $self->{last_batch_uid}; - $self->{next_uid}++; - my $rr = $self->{batch}->{$uid}; - next unless defined $rr; - delete $self->{batch}->{$uid}; - - # xlog "found uid=$uid in batch"; - # xlog "rr=" . Dumper($rr); - my $raw = $rr->{'body'}; - delete $rr->{'body'}; - return Cassandane::Message->new(raw => $raw, - attrs => { id => $uid, %$rr }); - } - $self->{batch} = undef; - - # xlog "batch empty or no batch available"; - - for (;;) - { - my $first_uid = $self->{next_uid}; - return undef - if $first_uid > $self->{last_uid}; # EOF - my $last_uid = $first_uid + $BATCHSIZE - 1; - $last_uid = $self->{last_uid} - if $last_uid > $self->{last_uid}; - # xlog "fetching batch range $first_uid:$last_uid"; - my $attrs = join(' ', keys %{$self->{fetch_attrs}}); - $self->{batch} = $self->{client}->fetch("$first_uid:$last_uid", - "($attrs)"); - $self->{last_batch_uid} = $last_uid; - last if (defined $self->{batch} && scalar $self->{batch} > 0); - $self->{next_uid} = $last_uid + 1; - } - # xlog "have a batch, next_uid=$self->{next_uid}"; +sub read_message { + my ($self, $msg) = @_; + + for (;;) { + while (defined $self->{batch}) { + my $uid = $self->{next_uid}; + last if $uid > $self->{last_batch_uid}; + $self->{next_uid}++; + my $rr = $self->{batch}->{$uid}; + next unless defined $rr; + delete $self->{batch}->{$uid}; + + # xlog "found uid=$uid in batch"; + # xlog "rr=" . Dumper($rr); + my $raw = $rr->{'body'}; + delete $rr->{'body'}; + return Cassandane::Message->new( + raw => $raw, + attrs => { id => $uid, %$rr } + ); } + $self->{batch} = undef; - return undef; + # xlog "batch empty or no batch available"; + + for (;;) { + my $first_uid = $self->{next_uid}; + return undef + if $first_uid > $self->{last_uid}; # EOF + my $last_uid = $first_uid + $BATCHSIZE - 1; + $last_uid = $self->{last_uid} + if $last_uid > $self->{last_uid}; + # xlog "fetching batch range $first_uid:$last_uid"; + my $attrs = join(' ', keys %{ $self->{fetch_attrs} }); + $self->{batch} + = $self->{client}->fetch("$first_uid:$last_uid", "($attrs)"); + $self->{last_batch_uid} = $last_uid; + last if (defined $self->{batch} && scalar $self->{batch} > 0); + $self->{next_uid} = $last_uid + 1; + } + # xlog "have a batch, next_uid=$self->{next_uid}"; + } + + return undef; } -sub read_end -{ - my ($self) = @_; +sub read_end { + my ($self) = @_; - $self->{next_uid} = undef; - $self->{last_uid} = undef; - $self->{last_batch_uid} = undef; - $self->{batch} = undef; + $self->{next_uid} = undef; + $self->{last_uid} = undef; + $self->{last_batch_uid} = undef; + $self->{batch} = undef; } -sub remove -{ - my ($self) = @_; +sub remove { + my ($self) = @_; - $self->connect(); - my $r = $self->{client}->delete($self->{folder}); - die "IMAP DELETE failed: $@" - if (!defined $r && !($self->{client}->get_last_error() =~ m/does not exist/)); + $self->connect(); + my $r = $self->{client}->delete($self->{folder}); + die "IMAP DELETE failed: $@" + if (!defined $r + && !($self->{client}->get_last_error() =~ m/does not exist/)); } -sub get_client -{ - my ($self, %params) = @_; +sub get_client { + my ($self, %params) = @_; - $self->connect(%params); - return $self->{client}; + $self->connect(%params); + return $self->{client}; } -sub get_server_name -{ - my ($self) = @_; +sub get_server_name { + my ($self) = @_; - $self->connect(); + $self->connect(); - # Cyrus returns the servername config variable in the first - # word of the untagged OK reponse sent on connection. We - # Capture the non-response code part of that in {banner}. - # which looks like - # slott02 Cyrus IMAP git2.5.0+0-git-work-6640 server ready - my ($servername) = ($self->{banner} =~ m/^(\S+)\s+Cyrus\s+IMAP\s+/); - return $servername; + # Cyrus returns the servername config variable in the first + # word of the untagged OK reponse sent on connection. We + # Capture the non-response code part of that in {banner}. + # which looks like + # slott02 Cyrus IMAP git2.5.0+0-git-work-6640 server ready + my ($servername) = ($self->{banner} =~ m/^(\S+)\s+Cyrus\s+IMAP\s+/); + return $servername; } -sub as_string -{ - my ($self) = @_; +sub as_string { + my ($self) = @_; - return 'imap://' . $self->{host} . ':' . $self->{port} . '/' . $self->{folder}; + return + 'imap://' + . $self->{host} . ':' + . $self->{port} . '/' + . $self->{folder}; } -sub set_folder -{ - my ($self, $folder) = @_; +sub set_folder { + my ($self, $folder) = @_; - if ($self->{folder} ne $folder) - { - $self->{folder} = $folder; - } + if ($self->{folder} ne $folder) { + $self->{folder} = $folder; + } } -sub _kvlist_to_hash -{ - my (@kvlist) = @_; - my $h = {}; - while (my $k = shift @kvlist) - { - my $v = shift @kvlist; - $h->{lc($k)} = $v; - } - return $h; +sub _kvlist_to_hash { + my (@kvlist) = @_; + my $h = {}; + while (my $k = shift @kvlist) { + my $v = shift @kvlist; + $h->{ lc($k) } = $v; + } + return $h; } -sub xconvfetch_begin -{ - my ($self, $cid, $changedsince) = @_; - my @args = ( $cid, $changedsince || 0, [ keys %{$self->{fetch_attrs}} ] ); - - my $results = - { - xconvmeta => {}, - }; - $self->{fetched} = undef; - my %handlers = - ( - xconvmeta => sub - { - # expecting: * XCONVMETA d55a42549e674b82 (MODSEQ 29) - my ($response, $rr) = @_; -# xlog "XCONVMETA rr=" . Dumper($rr); - $results->{xconvmeta}->{$rr->[0]} = _kvlist_to_hash(@{$rr->[1]}); - }, - fetch => sub - { - my ($response, $rr) = @_; -# xlog "FETCH rr=" . Dumper($rr); - push(@{$self->{fetched}}, $rr); - } - ); +sub xconvfetch_begin { + my ($self, $cid, $changedsince) = @_; + my @args = ($cid, $changedsince || 0, [ keys %{ $self->{fetch_attrs} } ]); + + my $results = { xconvmeta => {}, }; + $self->{fetched} = undef; + my %handlers = ( + xconvmeta => sub { + # expecting: * XCONVMETA d55a42549e674b82 (MODSEQ 29) + my ($response, $rr) = @_; + # xlog "XCONVMETA rr=" . Dumper($rr); + $results->{xconvmeta}->{ $rr->[0] } = _kvlist_to_hash(@{ $rr->[1] }); + }, + fetch => sub { + my ($response, $rr) = @_; + # xlog "FETCH rr=" . Dumper($rr); + push(@{ $self->{fetched} }, $rr); + } + ); - $self->connect(); + $self->connect(); - $self->{client}->_imap_cmd("xconvfetch", 0, \%handlers, @args) - or return undef; + $self->{client}->_imap_cmd("xconvfetch", 0, \%handlers, @args) + or return undef; - return $results; + return $results; } -sub xconvfetch_message -{ - my ($self) = @_; +sub xconvfetch_message { + my ($self) = @_; - my $rr = shift @{$self->{fetched}}; - return undef - if !defined $rr; + my $rr = shift @{ $self->{fetched} }; + return undef + if !defined $rr; - my $raw = $rr->{'body'}; - delete $rr->{'body'}; - return Cassandane::Message->new(raw => $raw, attrs => $rr); + my $raw = $rr->{'body'}; + delete $rr->{'body'}; + return Cassandane::Message->new(raw => $raw, attrs => $rr); } -sub xconvfetch_end -{ - my ($self) = @_; - $self->{fetched} = undef; +sub xconvfetch_end { + my ($self) = @_; + $self->{fetched} = undef; } # @@ -445,45 +415,43 @@ sub xconvfetch_end # respond with the "+ idling" response. Returns undef and sets $@ # on error, or dies on timeout. # -sub idle_begin -{ - my ($self) = @_; +sub idle_begin { + my ($self) = @_; - my $talk = $self->get_client(); + my $talk = $self->get_client(); - $talk->_send_cmd('idle'); + $talk->_send_cmd('idle'); - my $got_idling = 0; - my $handlers = { idling => sub { $got_idling = 1; } }; + my $got_idling = 0; + my $handlers = { idling => sub { $got_idling = 1; } }; - # Await the "+ idling" response + # Await the "+ idling" response - # temporarily set Timeout - my $old_tout = $talk->{Timeout}; - $talk->{Timeout} = 5; + # temporarily set Timeout + my $old_tout = $talk->{Timeout}; + $talk->{Timeout} = 5; - # hack to force a line read - $talk->{ReadLine} = undef; + # hack to force a line read + $talk->{ReadLine} = undef; - # temporarily replace CmdId with '+' so that - # _parse_response() will think it's a tag. - my $cmd_id = $talk->{CmdId}; - $talk->{CmdId} = '+'; + # temporarily replace CmdId with '+' so that + # _parse_response() will think it's a tag. + my $cmd_id = $talk->{CmdId}; + $talk->{CmdId} = '+'; - # Will die if timedout - failing the test - $talk->_parse_response($handlers); + # Will die if timedout - failing the test + $talk->_parse_response($handlers); - # replace CmdId, Timeout - $talk->{CmdId} = $cmd_id; - $talk->{Timeout} = $old_tout; + # replace CmdId, Timeout + $talk->{CmdId} = $cmd_id; + $talk->{Timeout} = $old_tout; - if (!$got_idling) - { - $@ = "Did not receive expected \"idling\" response"; - return undef; - } + if (!$got_idling) { + $@ = "Did not receive expected \"idling\" response"; + return undef; + } - return 1; + return 1; } # @@ -493,50 +461,47 @@ sub idle_begin # no responses have yet been received, with 0 specifically meaning "just # poll, do not block". Returns true if at a response was read. # -sub idle_response -{ - my ($self, $handlers, $tout) = @_; - my $talk = $self->get_client(); - - # Temporarily set the Timeout for _parse_response - my $old_tout = $talk->{Timeout}; - $talk->{Timeout} = $tout; - - # Temporarily set CmdId to fool _parse_response into returning as - # soon as it sees the first unsolicited response instead of waiting - # for the actual tagged response, which might be a very long time - # coming. - my $cmd_id = $talk->{CmdId}; - $talk->{CmdId} = '*'; - - my $got = 0; - eval - { - $talk->_parse_response($handlers); - $got = 1; - }; - - # Restore old values of CmdId and Timeout - $talk->{CmdId} = $cmd_id; - $talk->{Timeout} = $old_tout; - - return $got; +sub idle_response { + my ($self, $handlers, $tout) = @_; + my $talk = $self->get_client(); + + # Temporarily set the Timeout for _parse_response + my $old_tout = $talk->{Timeout}; + $talk->{Timeout} = $tout; + + # Temporarily set CmdId to fool _parse_response into returning as + # soon as it sees the first unsolicited response instead of waiting + # for the actual tagged response, which might be a very long time + # coming. + my $cmd_id = $talk->{CmdId}; + $talk->{CmdId} = '*'; + + my $got = 0; + eval { + $talk->_parse_response($handlers); + $got = 1; + }; + + # Restore old values of CmdId and Timeout + $talk->{CmdId} = $cmd_id; + $talk->{Timeout} = $old_tout; + + return $got; } -sub idle_end -{ - my ($self, $handlers) = @_; +sub idle_end { + my ($self, $handlers) = @_; - my $talk = $self->get_client(); + my $talk = $self->get_client(); - # Send the "DONE" continuation which cancels the IDLE command - $talk->_imap_socket_out("DONE\n"); + # Send the "DONE" continuation which cancels the IDLE command + $talk->_imap_socket_out("DONE\n"); - # Get the final tagged response including any unsolicited responses not yet seen - $talk->_parse_response($handlers); +# Get the final tagged response including any unsolicited responses not yet seen + $talk->_parse_response($handlers); - # Prepare for the next command - $talk->{CmdId}++; + # Prepare for the next command + $talk->{CmdId}++; } 1; diff --git a/cassandane/Cassandane/IMAPService.pm b/cassandane/Cassandane/IMAPService.pm index ff5145d8f8..9056b76334 100644 --- a/cassandane/Cassandane/IMAPService.pm +++ b/cassandane/Cassandane/IMAPService.pm @@ -45,24 +45,22 @@ use lib '.'; use base qw(Cassandane::Service); use Cassandane::Util::Log; -sub new -{ - my ($class, %params) = @_; - my $ssl = scalar grep { $_ eq '-s' } @{$params{argv}}; - my $type = $ssl ? 'imaps' : 'imap'; - my $self = $class->SUPER::new(type => $type, %params); - return $self; +sub new { + my ($class, %params) = @_; + my $ssl = scalar grep { $_ eq '-s' } @{ $params{argv} }; + my $type = $ssl ? 'imaps' : 'imap'; + my $self = $class->SUPER::new(type => $type, %params); + return $self; } # Return a hash of parameters suitable for passing # to MessageStoreFactory::create. -sub store_params -{ - my ($self, %inparams) = @_; +sub store_params { + my ($self, %inparams) = @_; - my $outparams = $self->SUPER::store_params(%inparams); - $outparams->{folder} ||= 'inbox'; - return $outparams; + my $outparams = $self->SUPER::store_params(%inparams); + $outparams->{folder} ||= 'inbox'; + return $outparams; } 1; diff --git a/cassandane/Cassandane/Instance.pm b/cassandane/Cassandane/Instance.pm index 4b51cb754f..4f804168d9 100644 --- a/cassandane/Cassandane/Instance.pm +++ b/cassandane/Cassandane/Instance.pm @@ -61,7 +61,7 @@ use JSON; use HTTP::Daemon; use DBI; use Time::HiRes qw(usleep); -use List::Util qw(uniqstr); +use List::Util qw(uniqstr); use lib '.'; use Cassandane::Util::DateTime qw(to_iso8601); @@ -88,946 +88,848 @@ my $__cached_rootdir; my $stamp; my $next_unique = 1; -sub new -{ - my $class = shift; - my %params = @_; - - my $cassini = Cassandane::Cassini->instance(); - - my $self = { - name => undef, - buildinfo => undef, - basedir => undef, - installation => 'default', - cyrus_prefix => undef, - cyrus_destdir => undef, - config => Cassandane::Config->default()->clone(), - starts => [], - services => {}, - events => [], - daemons => {}, - generic_listeners => {}, - re_use_dir => 0, - setup_mailbox => 1, - persistent => 0, - authdaemon => 1, - _children => {}, - _stopped => 0, - description => 'unknown', - _shutdowncallbacks => [], - _started => 0, - _pwcheck => $cassini->val('cassandane', 'pwcheck', 'alwaystrue'), - install_certificates => 0, - _pid => $$, - }; - - $self->{name} = $params{name} - if defined $params{name}; - $self->{basedir} = $params{basedir} - if defined $params{basedir}; - $self->{installation} = $params{installation} - if defined $params{installation}; - $self->{cyrus_prefix} = $cassini->val("cyrus $self->{installation}", - 'prefix', '/usr/cyrus'); - $self->{cyrus_prefix} = $params{cyrus_prefix} - if defined $params{cyrus_prefix}; - $self->{cyrus_destdir} = $cassini->val("cyrus $self->{installation}", - 'destdir', ''); - $self->{cyrus_destdir} = $params{cyrus_destdir} - if defined $params{cyrus_destdir}; - $self->{config} = $params{config}->clone() - if defined $params{config}; - $self->{re_use_dir} = $params{re_use_dir} - if defined $params{re_use_dir}; - $self->{setup_mailbox} = $params{setup_mailbox} - if defined $params{setup_mailbox}; - $self->{persistent} = $params{persistent} - if defined $params{persistent}; - $self->{authdaemon} = $params{authdaemon} - if defined $params{authdaemon}; - $self->{description} = $params{description} - if defined $params{description}; - $self->{pwcheck} = $params{pwcheck} - if defined $params{pwcheck}; - $self->{install_certificates} = $params{install_certificates} - if defined $params{install_certificates}; - - # XXX - get testcase name from caller, to apply even finer - # configuration from cassini ? - return bless $self, $class; +sub new { + my $class = shift; + my %params = @_; + + my $cassini = Cassandane::Cassini->instance(); + + my $self = { + name => undef, + buildinfo => undef, + basedir => undef, + installation => 'default', + cyrus_prefix => undef, + cyrus_destdir => undef, + config => Cassandane::Config->default()->clone(), + starts => [], + services => {}, + events => [], + daemons => {}, + generic_listeners => {}, + re_use_dir => 0, + setup_mailbox => 1, + persistent => 0, + authdaemon => 1, + _children => {}, + _stopped => 0, + description => 'unknown', + _shutdowncallbacks => [], + _started => 0, + _pwcheck => $cassini->val('cassandane', 'pwcheck', 'alwaystrue'), + install_certificates => 0, + _pid => $$, + }; + + $self->{name} = $params{name} + if defined $params{name}; + $self->{basedir} = $params{basedir} + if defined $params{basedir}; + $self->{installation} = $params{installation} + if defined $params{installation}; + $self->{cyrus_prefix} + = $cassini->val("cyrus $self->{installation}", 'prefix', '/usr/cyrus'); + $self->{cyrus_prefix} = $params{cyrus_prefix} + if defined $params{cyrus_prefix}; + $self->{cyrus_destdir} + = $cassini->val("cyrus $self->{installation}", 'destdir', ''); + $self->{cyrus_destdir} = $params{cyrus_destdir} + if defined $params{cyrus_destdir}; + $self->{config} = $params{config}->clone() + if defined $params{config}; + $self->{re_use_dir} = $params{re_use_dir} + if defined $params{re_use_dir}; + $self->{setup_mailbox} = $params{setup_mailbox} + if defined $params{setup_mailbox}; + $self->{persistent} = $params{persistent} + if defined $params{persistent}; + $self->{authdaemon} = $params{authdaemon} + if defined $params{authdaemon}; + $self->{description} = $params{description} + if defined $params{description}; + $self->{pwcheck} = $params{pwcheck} + if defined $params{pwcheck}; + $self->{install_certificates} = $params{install_certificates} + if defined $params{install_certificates}; + + # XXX - get testcase name from caller, to apply even finer + # configuration from cassini ? + return bless $self, $class; } # Class method! Need to be able to interrogate the Cyrus version # being tested without actually instantiating a Cassandane::Instance. # This also means we have to do a few things here the direct way, # rather than using helper methods... -my %cached_version = (); +my %cached_version = (); my %cached_sversion = (); -sub get_version -{ - my ($class, $installation) = @_; - $installation = 'default' if not defined $installation; - - if (exists $cached_version{$installation}) { - return @{$cached_version{$installation}} if wantarray; - return $cached_sversion{$installation}; - } - - my $cassini = Cassandane::Cassini->instance(); - - # Need to check the named-installation directory AND the - # default installation directory, before falling back to the - # default-default - # Usually Cassandane::Cyrus::TestCase only initialises an Instance - # object with a non-default installation if that installation actually - # exists, but this is a class method, not an object method, so we - # don't have that protection and have to DIY. - my ($cyrus_prefix, $cyrus_destdir, $cyrus_master); - - INSTALLATION: foreach my $i (uniqstr($installation, 'default')) { - $cyrus_prefix = $cassini->val("cyrus $i", 'prefix', - $i eq 'default' ? '/usr/cyrus' : undef); - - # no prefix? non-default installation isn't configured, skip it - next INSTALLATION if not defined $cyrus_prefix; - - $cyrus_destdir = $cassini->val("cyrus $i", 'destdir', q{}); - - foreach my $d (qw( bin sbin libexec libexec/cyrus-imapd lib cyrus/bin )) - { - my $try = "$cyrus_destdir$cyrus_prefix/$d/master"; - if (-x $try) { - $cyrus_master = $try; - last INSTALLATION; - } - } - } - - die "unable to locate master binary" if not defined $cyrus_master; - - my $version; - { - open my $fh, '-|', "$cyrus_master -V" - or die "unable to execute '$cyrus_master -V': $!"; - local $/; - $version = <$fh>; - close $fh; - } - if (not $version) { - # Cyrus version might be too old for 'master -V' - # Try to squirrel a version out of libcyrus pkgconfig file - open my $fh, '<', "$cyrus_destdir$cyrus_prefix/lib/pkgconfig/libcyrus.pc"; - while (<$fh>) { - $version = $_ if m/^Version:/; - } - close $fh; - } - - #cyrus-imapd 3.0.0-beta3-114-g5fa1dbc-dirty - if ($version =~ m/^cyrus-imapd (\d+)\.(\d+).(\d+)(?:-(.*))?$/) { - my ($maj, $min, $rev, $extra) = ($1, $2, $3, $4); - my $pluscommits = 0; - if (defined $extra && $extra =~ m/(\d+)-g[a-fA-F0-9]+(?:-dirty)?$/) { - $pluscommits = $1; - } - $cached_version{$installation} = [ 0 + $maj, - 0 + $min, - 0 + $rev, - 0 + $pluscommits, - $extra ]; - } - elsif ($version =~ m/^Version: (\d+)\.(\d+).(\d+)(?:-(.*))?$/) { - my ($maj, $min, $rev, $extra) = ($1, $2, $3, $4); - my $pluscommits; - if ($extra =~ m/(\d+)-g[a-fA-F0-9]+(?:-dirty)?$/) { - $pluscommits = $1; - } - $cached_version{$installation} = [ 0 + $maj, - 0 + $min, - 0 + $rev, - 0 + $pluscommits, - $extra ]; - } - else { - $cached_version{$installation} = [0, 0, 0, 0, q{}]; - } +sub get_version { + my ($class, $installation) = @_; + $installation = 'default' if not defined $installation; - $cached_sversion{$installation} = join q{.}, - @{$cached_version{$installation}}[0..2]; - $cached_sversion{$installation} .= "-$cached_version{$installation}->[4]" - if $cached_version{$installation}->[4]; - - return @{$cached_version{$installation}} if wantarray; + if (exists $cached_version{$installation}) { + return @{ $cached_version{$installation} } if wantarray; return $cached_sversion{$installation}; + } + + my $cassini = Cassandane::Cassini->instance(); + + # Need to check the named-installation directory AND the + # default installation directory, before falling back to the + # default-default + # Usually Cassandane::Cyrus::TestCase only initialises an Instance + # object with a non-default installation if that installation actually + # exists, but this is a class method, not an object method, so we + # don't have that protection and have to DIY. + my ($cyrus_prefix, $cyrus_destdir, $cyrus_master); + + INSTALLATION: foreach my $i (uniqstr($installation, 'default')) { + $cyrus_prefix = $cassini->val("cyrus $i", 'prefix', + $i eq 'default' ? '/usr/cyrus' : undef); + + # no prefix? non-default installation isn't configured, skip it + next INSTALLATION if not defined $cyrus_prefix; + + $cyrus_destdir = $cassini->val("cyrus $i", 'destdir', q{}); + + foreach my $d (qw( bin sbin libexec libexec/cyrus-imapd lib cyrus/bin )) { + my $try = "$cyrus_destdir$cyrus_prefix/$d/master"; + if (-x $try) { + $cyrus_master = $try; + last INSTALLATION; + } + } + } + + die "unable to locate master binary" if not defined $cyrus_master; + + my $version; + { + open my $fh, '-|', "$cyrus_master -V" + or die "unable to execute '$cyrus_master -V': $!"; + local $/; + $version = <$fh>; + close $fh; + } + + if (not $version) { + # Cyrus version might be too old for 'master -V' + # Try to squirrel a version out of libcyrus pkgconfig file + open my $fh, '<', "$cyrus_destdir$cyrus_prefix/lib/pkgconfig/libcyrus.pc"; + while (<$fh>) { + $version = $_ if m/^Version:/; + } + close $fh; + } + + #cyrus-imapd 3.0.0-beta3-114-g5fa1dbc-dirty + if ($version =~ m/^cyrus-imapd (\d+)\.(\d+).(\d+)(?:-(.*))?$/) { + my ($maj, $min, $rev, $extra) = ($1, $2, $3, $4); + my $pluscommits = 0; + if (defined $extra && $extra =~ m/(\d+)-g[a-fA-F0-9]+(?:-dirty)?$/) { + $pluscommits = $1; + } + $cached_version{$installation} + = [ 0 + $maj, 0 + $min, 0 + $rev, 0 + $pluscommits, $extra ]; + } elsif ($version =~ m/^Version: (\d+)\.(\d+).(\d+)(?:-(.*))?$/) { + my ($maj, $min, $rev, $extra) = ($1, $2, $3, $4); + my $pluscommits; + if ($extra =~ m/(\d+)-g[a-fA-F0-9]+(?:-dirty)?$/) { + $pluscommits = $1; + } + $cached_version{$installation} + = [ 0 + $maj, 0 + $min, 0 + $rev, 0 + $pluscommits, $extra ]; + } else { + $cached_version{$installation} = [ 0, 0, 0, 0, q{} ]; + } + + $cached_sversion{$installation} = join q{.}, + @{ $cached_version{$installation} }[ 0 .. 2 ]; + $cached_sversion{$installation} .= "-$cached_version{$installation}->[4]" + if $cached_version{$installation}->[4]; + + return @{ $cached_version{$installation} } if wantarray; + return $cached_sversion{$installation}; +} + +sub _rootdir { + if (!defined $__cached_rootdir) { + my $cassini = Cassandane::Cassini->instance(); + $__cached_rootdir = $cassini->val('cassandane', 'rootdir', '/var/tmp/cass'); + } + return $__cached_rootdir; } -sub _rootdir -{ - if (!defined $__cached_rootdir) - { - my $cassini = Cassandane::Cassini->instance(); - $__cached_rootdir = - $cassini->val('cassandane', 'rootdir', '/var/tmp/cass'); - } - return $__cached_rootdir; -} - -sub _make_instance_info -{ - my ($name, $basedir) = @_; +sub _make_instance_info { + my ($name, $basedir) = @_; - die "Need either a name or a basename" - if !defined $name && !defined $basedir; - $name ||= basename($basedir); - $basedir ||= _rootdir() . '/' . $name; + die "Need either a name or a basename" + if !defined $name && !defined $basedir; + $name ||= basename($basedir); + $basedir ||= _rootdir() . '/' . $name; - my $sb = stat($basedir); - die "Cannot stat $basedir: $!" if !defined $sb && $! != ENOENT; + my $sb = stat($basedir); + die "Cannot stat $basedir: $!" if !defined $sb && $! != ENOENT; - return { - name => $name, - basedir => $basedir, - ctime => ($sb ? $sb->ctime : undef), - }; + return { + name => $name, + basedir => $basedir, + ctime => ($sb ? $sb->ctime : undef), + }; } -sub _make_unique_instance_info -{ - # This must be kept in sync with cleanup_leftovers, which expects - # to be able to recognise instance directories by name for cleanup. - if (!defined $stamp) - { - $stamp = to_iso8601(DateTime->now); - $stamp =~ s/.*T(\d+)Z/$1/; +sub _make_unique_instance_info { + # This must be kept in sync with cleanup_leftovers, which expects + # to be able to recognise instance directories by name for cleanup. + if (!defined $stamp) { + $stamp = to_iso8601(DateTime->now); + $stamp =~ s/.*T(\d+)Z/$1/; - my $workerid = $ENV{TEST_UNIT_WORKER_ID}; - die "Invalid TEST_UNIT_WORKER_ID - code not run in Worker context" - if (defined($workerid) && $workerid eq 'invalid'); - $stamp .= sprintf("%02X", $workerid) if defined $workerid; - } + my $workerid = $ENV{TEST_UNIT_WORKER_ID}; + die "Invalid TEST_UNIT_WORKER_ID - code not run in Worker context" + if (defined($workerid) && $workerid eq 'invalid'); + $stamp .= sprintf("%02X", $workerid) if defined $workerid; + } - my $rootdir = _rootdir(); + my $rootdir = _rootdir(); - my $name; - my $basedir; - for (;;) - { - $name = sprintf("%s%02X", $stamp, $next_unique); - $next_unique++; - $basedir = "$rootdir/$name"; - last if mkdir($basedir); - die "Cannot create $basedir: $!" if ($! != EEXIST); - } - return _make_instance_info($name, $basedir); + my $name; + my $basedir; + for (;;) { + $name = sprintf("%s%02X", $stamp, $next_unique); + $next_unique++; + $basedir = "$rootdir/$name"; + last if mkdir($basedir); + die "Cannot create $basedir: $!" if ($! != EEXIST); + } + return _make_instance_info($name, $basedir); } -sub list -{ - my $rootdir = _rootdir(); - opendir ROOT, $rootdir - or die "Cannot open $rootdir for reading: $!"; - my @instances; - while ($_ = readdir(ROOT)) - { - next unless m/^[0-9]+[A-Z]?$/; - push(@instances, _make_instance_info($_)); - } - closedir ROOT; - return @instances; +sub list { + my $rootdir = _rootdir(); + opendir ROOT, $rootdir + or die "Cannot open $rootdir for reading: $!"; + my @instances; + while ($_ = readdir(ROOT)) { + next unless m/^[0-9]+[A-Z]?$/; + push(@instances, _make_instance_info($_)); + } + closedir ROOT; + return @instances; } -sub exists -{ - my ($name) = @_; - return if ( ! -d _rootdir() . '/' . $name ); - return _make_instance_info($name); +sub exists { + my ($name) = @_; + return if (!-d _rootdir() . '/' . $name); + return _make_instance_info($name); } -sub _init_basedir_and_name -{ - my ($self) = @_; +sub _init_basedir_and_name { + my ($self) = @_; - my $info; - my $which = (defined $self->{name} ? 1 : 0) | - (defined $self->{basedir} ? 2 : 0); - if ($which == 0) - { - # have neither name nor basedir - # usual first time case for test instances - $info = _make_unique_instance_info(); - } - else - { - # have name but not basedir - # usual first time case for start-instance.pl - # or basedir but not name, which doesn't happen - $info = _make_instance_info($self->{name}, $self->{basedir}); - } - $self->{name} = $info->{name}; - $self->{basedir} = $info->{basedir}; + my $info; + my $which + = (defined $self->{name} ? 1 : 0) | (defined $self->{basedir} ? 2 : 0); + if ($which == 0) { + # have neither name nor basedir + # usual first time case for test instances + $info = _make_unique_instance_info(); + } else { + # have name but not basedir + # usual first time case for start-instance.pl + # or basedir but not name, which doesn't happen + $info = _make_instance_info($self->{name}, $self->{basedir}); + } + $self->{name} = $info->{name}; + $self->{basedir} = $info->{basedir}; } -sub get_basedir -{ - my ($self) = @_; +sub get_basedir { + my ($self) = @_; - return $self->{basedir} if $self->{basedir}; + return $self->{basedir} if $self->{basedir}; - $self->_init_basedir_and_name(); + $self->_init_basedir_and_name(); - return $self->{basedir}; + return $self->{basedir}; } # Remove on-disk traces of any previous instances -sub cleanup_leftovers -{ - my $rootdir = _rootdir(); - - return if (!-d $rootdir); - opendir ROOT, $rootdir - or die "Cannot open directory $rootdir for reading: $!"; - my @dirs; - while (my $e = readdir(ROOT)) - { - # This must be kept in sync with _make_unique_instance_info, - # which is what names and creates these directories. - my $basedirpat = qr{ +sub cleanup_leftovers { + my $rootdir = _rootdir(); + + return if (!-d $rootdir); + opendir ROOT, $rootdir + or die "Cannot open directory $rootdir for reading: $!"; + my @dirs; + while (my $e = readdir(ROOT)) { + # This must be kept in sync with _make_unique_instance_info, + # which is what names and creates these directories. + my $basedirpat = qr{ \d{6} # UTC timestamp as HHMMSS (?:[0-9A-F]{2,})? # optional worker ID as 2+ hex digits [0-9A-F]{2,} # unique number as 2+ hex digits }ax; - push(@dirs, $e) if $e =~ m/$basedirpat/; - } - closedir ROOT; + push(@dirs, $e) if $e =~ m/$basedirpat/; + } + closedir ROOT; - map - { - if (get_verbose) { - xlog "Cleaning up old basedir $rootdir/$_"; - } - rmtree "$rootdir/$_"; - } @dirs; -} - -sub add_service -{ - my ($self, %params) = @_; - - my $name = $params{name}; - die "Missing parameter 'name'" - unless defined $name; - die "Already have a service named \"$name\"" - if defined $self->{services}->{$name}; - - # Add a hardcoded recover START if we're doing an actual IMAP test. - if ($name =~ m/imap/) - { - $self->add_recover(); + map { + if (get_verbose) { + xlog "Cleaning up old basedir $rootdir/$_"; } - - my $srv = Cassandane::ServiceFactory->create(instance => $self, %params); - $self->{services}->{$name} = $srv; - return $srv; + rmtree "$rootdir/$_"; + } @dirs; } -sub add_services -{ - my ($self, @names) = @_; - map { $self->add_service(name => $_); } @names; -} +sub add_service { + my ($self, %params) = @_; -sub get_service -{ - my ($self, $name) = @_; - return $self->{services}->{$name}; -} + my $name = $params{name}; + die "Missing parameter 'name'" + unless defined $name; + die "Already have a service named \"$name\"" + if defined $self->{services}->{$name}; -sub remove_service -{ - my ($self, $name) = @_; - delete $self->{services}->{$name}; -} + # Add a hardcoded recover START if we're doing an actual IMAP test. + if ($name =~ m/imap/) { + $self->add_recover(); + } -sub add_start -{ - my ($self, %params) = @_; - push(@{$self->{starts}}, Cassandane::MasterStart->new(%params)); + my $srv = Cassandane::ServiceFactory->create(instance => $self, %params); + $self->{services}->{$name} = $srv; + return $srv; } -sub remove_start -{ - my ($self, $name) = @_; - $self->{starts} = [ grep { $_->{name} ne $name } @{$self->{starts}} ]; +sub add_services { + my ($self, @names) = @_; + map { $self->add_service(name => $_); } @names; } -sub add_recover -{ - my ($self) = @_; - - if (!grep { $_->{name} eq 'recover'; } @{$self->{starts}}) - { - $self->add_start(name => 'recover', - argv => [ qw(ctl_cyrusdb -r) ]); - } +sub get_service { + my ($self, $name) = @_; + return $self->{services}->{$name}; } -sub add_event -{ - my ($self, %params) = @_; - push(@{$self->{events}}, Cassandane::MasterEvent->new(%params)); +sub remove_service { + my ($self, $name) = @_; + delete $self->{services}->{$name}; } -sub add_daemon -{ - my ($self, %params) = @_; - - my $name = $params{name}; - die "Missing parameter 'name'" - unless defined $name; - die "Already have a daemon named \"$name\"" - if defined $self->{daemons}->{$name}; - - $self->{daemons}->{$name} = Cassandane::MasterDaemon->new(%params); +sub add_start { + my ($self, %params) = @_; + push(@{ $self->{starts} }, Cassandane::MasterStart->new(%params)); } -sub add_generic_listener -{ - my ($self, %params) = @_; - - my $name = delete $params{name}; - die "Missing parameter 'name'" - unless defined $name; - die "Already have a generic listener named \"$name\"" - if defined $self->{generic_listeners}->{$name}; +sub remove_start { + my ($self, $name) = @_; + $self->{starts} = [ grep { $_->{name} ne $name } @{ $self->{starts} } ]; +} - $params{config} //= $self->{config}; +sub add_recover { + my ($self) = @_; - my $listener = Cassandane::GenericListener->new( - name => $name, - %params + if (!grep { $_->{name} eq 'recover'; } @{ $self->{starts} }) { + $self->add_start( + name => 'recover', + argv => [qw(ctl_cyrusdb -r)] ); - - $self->{generic_listeners}->{$name} = $listener; - return $listener; + } } -sub set_config -{ - my ($self, $conf) = @_; - - $self->{config} = $conf; +sub add_event { + my ($self, %params) = @_; + push(@{ $self->{events} }, Cassandane::MasterEvent->new(%params)); } -sub _find_binary -{ - my ($self, $name) = @_; +sub add_daemon { + my ($self, %params) = @_; - my $cassini = Cassandane::Cassini->instance(); - my $name_override = $cassini->val("cyrus $self->{installation}", $name); - $name = $name_override if defined $name_override; - - return $name if $name =~ m/^\//; + my $name = $params{name}; + die "Missing parameter 'name'" + unless defined $name; + die "Already have a daemon named \"$name\"" + if defined $self->{daemons}->{$name}; - my $base = $self->{cyrus_destdir} . $self->{cyrus_prefix}; - - if ($name eq 'delve') { - my $lib = `ldd $base/libexec/imapd` || die "can't ldd imapd"; - $lib =~ m{(/\S+)/lib/libxapian-([0-9.]+)\.so}; - return "$1/bin/xapian-delve-$2"; - } - - foreach (qw( bin sbin libexec libexec/cyrus-imapd lib cyrus/bin )) - { - my $dir = "$base/$_"; - if (opendir my $dh, $dir) - { - if (grep { $_ eq $name } readdir $dh) { - xlog "Found binary $name in $dir"; - closedir $dh; - return "$dir/$name"; - } - closedir $dh; - } - else - { - xlog "Couldn't opendir $dir: $!" if $! != ENOENT; - next; - } - } - - die "Couldn't locate $name under $base"; + $self->{daemons}->{$name} = Cassandane::MasterDaemon->new(%params); } -sub _binary -{ - my ($self, $name) = @_; +sub add_generic_listener { + my ($self, %params) = @_; - my @cmd; - my $valground = 0; + my $name = delete $params{name}; + die "Missing parameter 'name'" + unless defined $name; + die "Already have a generic listener named \"$name\"" + if defined $self->{generic_listeners}->{$name}; - my $cassini = Cassandane::Cassini->instance(); + $params{config} //= $self->{config}; - if ($cassini->bool_val('valgrind', 'enabled') && - !($name =~ m/delve$/) && - !($name =~ m/\.pl$/) && - !($name =~ m/^\//)) - { - my $arguments = '-q --tool=memcheck --leak-check=full --run-libc-freeres=no'; - my $valgrind_logdir = $self->{basedir} . '/vglogs'; - my $valgrind_suppressions = - abs_path($cassini->val('valgrind', 'suppression', 'vg.supp')); - mkpath $valgrind_logdir - unless ( -d $valgrind_logdir ); - push(@cmd, - $cassini->val('valgrind', 'binary', '/usr/bin/valgrind'), - "--log-file=$valgrind_logdir/$name.%p", - "--suppressions=$valgrind_suppressions", - "--gen-suppressions=all", - split(/\s+/, $cassini->val('valgrind', 'arguments', $arguments)) - ); - $valground = 1; - } - - my $bin = $self->_find_binary($name); - push(@cmd, $bin); - - if (!$valground && $cassini->bool_val('gdb', $name)) - { - xlog "Will run binary $name under gdb due to cassandane.ini"; - xlog "Look in syslog for helpful instructions from gdbtramp"; - push(@cmd, '-D'); - } - - return @cmd; -} - -sub _imapd_conf -{ - my ($self, $prefix) = @_; + my $listener = Cassandane::GenericListener->new( + name => $name, + %params + ); - my $fname = $prefix ? "$prefix-imapd.conf" : 'imapd.conf'; - - return $self->{basedir} . "/conf/$fname"; + $self->{generic_listeners}->{$name} = $listener; + return $listener; } -sub _master_conf -{ - my ($self) = @_; - - return $self->{basedir} . '/conf/cyrus.conf'; -} - -sub _pid_file -{ - my ($self, $name) = @_; - - $name ||= 'master'; +sub set_config { + my ($self, $conf) = @_; - return $self->{basedir} . "/run/$name.pid"; + $self->{config} = $conf; } -sub _list_pid_files -{ - my ($self) = @_; +sub _find_binary { + my ($self, $name) = @_; - my $rundir = $self->{basedir} . "/run"; - if (!opendir(RUNDIR, $rundir)) { - return if $!{ENOENT}; # no run dir? never started - die "Cannot open run directory $rundir: $!"; - } + my $cassini = Cassandane::Cassini->instance(); + my $name_override = $cassini->val("cyrus $self->{installation}", $name); + $name = $name_override if defined $name_override; - my @pidfiles; - while ($_ = readdir(RUNDIR)) - { - my ($name) = m/^([^.].*)\.pid$/; - push(@pidfiles, $name) if defined $name; - } + return $name if $name =~ m/^\//; - closedir(RUNDIR); - - @pidfiles = sort { $a cmp $b } @pidfiles; - @pidfiles = ( 'master', grep { $_ ne 'master' } @pidfiles ); - - return @pidfiles; -} - -sub _build_skeleton -{ - my ($self) = @_; - - my @subdirs = - ( - 'conf', - 'conf/certs', - 'conf/cores', - 'conf/sieve', - 'conf/socket', - 'conf/proc', - 'conf/log', - 'conf/log/admin', - 'conf/log/cassandane', - 'conf/log/user2', - 'conf/log/foo', - 'conf/log/mailproxy', - 'conf/log/mupduser', - 'conf/log/postman', - 'conf/log/repluser', - 'conf/log/smtpclient.sendmail', - 'conf/log/smtpclient.host', - 'lock', - 'data', - 'meta', - 'run', - 'tmp', - ); - foreach my $sd (@subdirs) - { - my $d = $self->{basedir} . '/' . $sd; - mkpath $d - or die "Cannot make path $d: $!"; - } -} + my $base = $self->{cyrus_destdir} . $self->{cyrus_prefix}; -sub _generate_imapd_conf -{ - my ($self, $config, $prefix) = @_; + if ($name eq 'delve') { + my $lib = `ldd $base/libexec/imapd` || die "can't ldd imapd"; + $lib =~ m{(/\S+)/lib/libxapian-([0-9.]+)\.so}; + return "$1/bin/xapian-delve-$2"; + } - if (defined $self->{services}->{http}) { - my $davhost = $self->{services}->{http}->host; - if (defined $self->{services}->{http}->port) { - $davhost .= ':' . $self->{services}->{http}->port; - } - $config->set( - webdav_attachments_baseurl => "http://$davhost" - ); + foreach (qw( bin sbin libexec libexec/cyrus-imapd lib cyrus/bin )) { + my $dir = "$base/$_"; + if (opendir my $dh, $dir) { + if (grep { $_ eq $name } readdir $dh) { + xlog "Found binary $name in $dir"; + closedir $dh; + return "$dir/$name"; + } + closedir $dh; + } else { + xlog "Couldn't opendir $dir: $!" if $! != ENOENT; + next; } + } - my ($cyrus_major_version, $cyrus_minor_version) = - Cassandane::Instance->get_version($self->{installation}); - - $config->set_variables( - name => $self->{name}, - basedir => $self->{basedir}, - cyrus_prefix => $self->{cyrus_prefix}, - prefix => getcwd(), - ); - $config->set( - sasl_pwcheck_method => 'saslauthd', - sasl_saslauthd_path => "$self->{basedir}/run/mux", - notifysocket => "dlist:$self->{basedir}/run/notify", - event_notifier => 'pusher', - ); - if ($cyrus_major_version >= 3) { - $config->set(imipnotifier => 'imip'); - $config->set_bits('event_groups', 'mailbox message flags calendar'); - - if ($cyrus_major_version > 3 || $cyrus_minor_version >= 1) { - $config->set( - smtp_backend => 'host', - smtp_host => $self->{smtphost}, - ); - } - } - else { - $config->set_bits('event_groups', 'mailbox message flags'); - } - if ($self->{buildinfo}->get('search', 'xapian')) { - my %xapian_defaults = ( - search_engine => 'xapian', - search_index_headers => 'no', - search_batchsize => '8192', - defaultsearchtier => 't1', - 't1searchpartition-default' => "$self->{basedir}/search", - 't2searchpartition-default' => "$self->{basedir}/search2", - 't3searchpartition-default' => "$self->{basedir}/search3", - ); - while (my ($k, $v) = each %xapian_defaults) { - if (not defined $config->get($k)) { - $config->set($k => $v); - } - } - } - - $config->generate($self->_imapd_conf($prefix)); -} - -sub _emit_master_entry -{ - my ($self, $entry) = @_; - - my $params = $entry->master_params(); - my $name = delete $params->{name}; - my $config = delete $params->{config}; - - # if this master entry has its own confix, it will have a prefixed name - my $imapd_conf = $self->_imapd_conf($config ? $name : undef); - - # Convert ->{argv} to ->{cmd} - my $argv = delete $params->{argv}; - die "No argv argument" - unless defined $argv; - # do not alter original argv - my @args = @$argv; - my $bin = shift @args; - $params->{cmd} = join(' ', - $self->_binary($bin), - '-C', $imapd_conf, - @args - ); - - print MASTER " $name"; - while (my ($k, $v) = each %$params) - { - $v = "\"$v\"" - if ($v =~ m/\s/); - print MASTER " $k=$v"; - } - print MASTER "\n"; + die "Couldn't locate $name under $base"; } -sub _generate_master_conf -{ - my ($self) = @_; +sub _binary { + my ($self, $name) = @_; - my $filename = $self->_master_conf(); - open MASTER,'>',$filename - or die "Cannot open $filename for writing: $!"; + my @cmd; + my $valground = 0; - if (scalar @{$self->{starts}}) - { - print MASTER "START {\n"; - map { $self->_emit_master_entry($_); } @{$self->{starts}}; - print MASTER "}\n"; - } + my $cassini = Cassandane::Cassini->instance(); - if (scalar %{$self->{services}}) - { - print MASTER "SERVICES {\n"; - map { $self->_emit_master_entry($_); } values %{$self->{services}}; - print MASTER "}\n"; - } + if ( $cassini->bool_val('valgrind', 'enabled') + && !($name =~ m/delve$/) + && !($name =~ m/\.pl$/) + && !($name =~ m/^\//)) + { + my $arguments + = '-q --tool=memcheck --leak-check=full --run-libc-freeres=no'; + my $valgrind_logdir = $self->{basedir} . '/vglogs'; + my $valgrind_suppressions + = abs_path($cassini->val('valgrind', 'suppression', 'vg.supp')); + mkpath $valgrind_logdir + unless (-d $valgrind_logdir); + push(@cmd, + $cassini->val('valgrind', 'binary', '/usr/bin/valgrind'), + "--log-file=$valgrind_logdir/$name.%p", + "--suppressions=$valgrind_suppressions", + "--gen-suppressions=all", + split(/\s+/, $cassini->val('valgrind', 'arguments', $arguments))); + $valground = 1; + } - if (scalar @{$self->{events}}) - { - print MASTER "EVENTS {\n"; - map { $self->_emit_master_entry($_); } @{$self->{events}}; - print MASTER "}\n"; - } + my $bin = $self->_find_binary($name); + push(@cmd, $bin); - if (scalar %{$self->{daemons}}) - { - print MASTER "DAEMON {\n"; - $self->_emit_master_entry($_) for values %{$self->{daemons}}; - print MASTER "}\n"; - } + if (!$valground && $cassini->bool_val('gdb', $name)) { + xlog "Will run binary $name under gdb due to cassandane.ini"; + xlog "Look in syslog for helpful instructions from gdbtramp"; + push(@cmd, '-D'); + } - close MASTER; + return @cmd; } -sub _add_services_from_cyrus_conf -{ - my ($self) = @_; +sub _imapd_conf { + my ($self, $prefix) = @_; + + my $fname = $prefix ? "$prefix-imapd.conf" : 'imapd.conf'; - my $filename = $self->_master_conf(); - open MASTER,'<',$filename - or die "Cannot open $filename for reading: $!"; + return $self->{basedir} . "/conf/$fname"; +} + +sub _master_conf { + my ($self) = @_; + + return $self->{basedir} . '/conf/cyrus.conf'; +} + +sub _pid_file { + my ($self, $name) = @_; + + $name ||= 'master'; + + return $self->{basedir} . "/run/$name.pid"; +} + +sub _list_pid_files { + my ($self) = @_; + + my $rundir = $self->{basedir} . "/run"; + if (!opendir(RUNDIR, $rundir)) { + return if $!{ENOENT}; # no run dir? never started + die "Cannot open run directory $rundir: $!"; + } + + my @pidfiles; + while ($_ = readdir(RUNDIR)) { + my ($name) = m/^([^.].*)\.pid$/; + push(@pidfiles, $name) if defined $name; + } + + closedir(RUNDIR); + + @pidfiles = sort { $a cmp $b } @pidfiles; + @pidfiles = ('master', grep { $_ ne 'master' } @pidfiles); + + return @pidfiles; +} + +sub _build_skeleton { + my ($self) = @_; + + my @subdirs = ( + 'conf', 'conf/certs', + 'conf/cores', 'conf/sieve', + 'conf/socket', 'conf/proc', + 'conf/log', 'conf/log/admin', + 'conf/log/cassandane', 'conf/log/user2', + 'conf/log/foo', 'conf/log/mailproxy', + 'conf/log/mupduser', 'conf/log/postman', + 'conf/log/repluser', 'conf/log/smtpclient.sendmail', + 'conf/log/smtpclient.host', 'lock', + 'data', 'meta', + 'run', 'tmp', + ); + foreach my $sd (@subdirs) { + my $d = $self->{basedir} . '/' . $sd; + mkpath $d + or die "Cannot make path $d: $!"; + } +} + +sub _generate_imapd_conf { + my ($self, $config, $prefix) = @_; + + if (defined $self->{services}->{http}) { + my $davhost = $self->{services}->{http}->host; + if (defined $self->{services}->{http}->port) { + $davhost .= ':' . $self->{services}->{http}->port; + } + $config->set(webdav_attachments_baseurl => "http://$davhost"); + } + + my ($cyrus_major_version, $cyrus_minor_version) + = Cassandane::Instance->get_version($self->{installation}); + + $config->set_variables( + name => $self->{name}, + basedir => $self->{basedir}, + cyrus_prefix => $self->{cyrus_prefix}, + prefix => getcwd(), + ); + $config->set( + sasl_pwcheck_method => 'saslauthd', + sasl_saslauthd_path => "$self->{basedir}/run/mux", + notifysocket => "dlist:$self->{basedir}/run/notify", + event_notifier => 'pusher', + ); + if ($cyrus_major_version >= 3) { + $config->set(imipnotifier => 'imip'); + $config->set_bits('event_groups', 'mailbox message flags calendar'); - my $in; - while () - { - chomp; - s/\s*#.*//; # strip comments - next if m/^\s*$/; # skip empty lines - my ($m) = m/^(START|SERVICES|EVENTS|DAEMON)\s*{/; - if ($m) - { - $in = $m; - next; - } - if ($in && m/^\s*}\s*$/) - { - $in = undef; - next; - } - next if !defined $in; - - my ($name, $rem) = m/^\s*([a-zA-Z0-9]+)\s+(.*)$/; - $_ = $rem; - my %params; - while (length $_) - { - my ($k, $rem2) = m/^([a-zA-Z0-9]+)=(.*)/; - die "Bad parameter name" if !defined $k; - $_ = $rem2; - - my ($v, $rem3) = m/^"([^"]*)"(.*)/; - if (!defined $v) - { - ($v, $rem3) = m/^(\S*)(.*)/; - } - die "Bad parameter value" if !defined $v; - $_ = $rem3; - - if ($k eq 'listen') - { - my $aa = Cassandane::GenericListener::parse_address($v); - $params{host} = $aa->{host}; - $params{port} = $aa->{port}; - } - elsif ($k eq 'cmd') - { - $params{argv} = [ split(/\s+/, $v) ]; - } - else - { - $params{$k} = $v; - } - s/^\s+//; - } - if ($in eq 'SERVICES') - { - $self->add_service(instance => $self, name => $name, %params); - } - } - - close MASTER; -} - -sub _fix_ownership -{ - my ($self, $path) = @_; - - $path ||= $self->{basedir}; - - return if geteuid() != 0; - my $uid = getpwnam('cyrus'); - my $gid = getgrnam('root'); - - find(sub { chown($uid, $gid, $File::Find::name) }, $path); -} - -sub _read_pid_file -{ - my ($self, $name) = @_; - my $file = $self->_pid_file($name); - my $pid; + if ($cyrus_major_version > 3 || $cyrus_minor_version >= 1) { + $config->set( + smtp_backend => 'host', + smtp_host => $self->{smtphost}, + ); + } + } else { + $config->set_bits('event_groups', 'mailbox message flags'); + } + if ($self->{buildinfo}->get('search', 'xapian')) { + my %xapian_defaults = ( + search_engine => 'xapian', + search_index_headers => 'no', + search_batchsize => '8192', + defaultsearchtier => 't1', + 't1searchpartition-default' => "$self->{basedir}/search", + 't2searchpartition-default' => "$self->{basedir}/search2", + 't3searchpartition-default' => "$self->{basedir}/search3", + ); + while (my ($k, $v) = each %xapian_defaults) { + if (not defined $config->get($k)) { + $config->set($k => $v); + } + } + } + + $config->generate($self->_imapd_conf($prefix)); +} + +sub _emit_master_entry { + my ($self, $entry) = @_; + + my $params = $entry->master_params(); + my $name = delete $params->{name}; + my $config = delete $params->{config}; + + # if this master entry has its own confix, it will have a prefixed name + my $imapd_conf = $self->_imapd_conf($config ? $name : undef); + + # Convert ->{argv} to ->{cmd} + my $argv = delete $params->{argv}; + die "No argv argument" + unless defined $argv; + # do not alter original argv + my @args = @$argv; + my $bin = shift @args; + $params->{cmd} = join(' ', $self->_binary($bin), '-C', $imapd_conf, @args); + + print MASTER " $name"; + while (my ($k, $v) = each %$params) { + $v = "\"$v\"" + if ($v =~ m/\s/); + print MASTER " $k=$v"; + } + print MASTER "\n"; +} + +sub _generate_master_conf { + my ($self) = @_; - return undef if ( ! -f $file ); + my $filename = $self->_master_conf(); + open MASTER, '>', $filename + or die "Cannot open $filename for writing: $!"; + + if (scalar @{ $self->{starts} }) { + print MASTER "START {\n"; + map { $self->_emit_master_entry($_); } @{ $self->{starts} }; + print MASTER "}\n"; + } - open PID,'<',$file - or return undef; - while() - { - chomp; - ($pid) = m/^(\d+)$/; - last; - } - close PID; - - return undef unless defined $pid; - return undef unless $pid > 1; - return undef unless kill(0, $pid) > 0; - return $pid; -} - -sub _start_master -{ - my ($self) = @_; - - # First check that nothing is listening on any of the ports - # we expect to be able to use. That would indicate a failure - # of test containment - i.e. we failed to shut something down - # earlier. Or it might indicate that someone is trying to run - # a second set of Cassandane tests on this machine, which is - # also going to fail miserably. In any case we want to know. - foreach my $srv (values %{$self->{services}}, - values %{$self->{generic_listeners}}) - { - die "Some process is already listening on " . $srv->address() - if $srv->is_listening(); - } + if (scalar %{ $self->{services} }) { + print MASTER "SERVICES {\n"; + map { $self->_emit_master_entry($_); } values %{ $self->{services} }; + print MASTER "}\n"; + } + + if (scalar @{ $self->{events} }) { + print MASTER "EVENTS {\n"; + map { $self->_emit_master_entry($_); } @{ $self->{events} }; + print MASTER "}\n"; + } - # Now start the master process. - my @cmd = - ( - 'master', - # The following is added automatically by _fork_command: - # '-C', $self->_imapd_conf(), - '-l', '255', - '-p', $self->_pid_file(), - '-d', - '-M', $self->_master_conf(), + if (scalar %{ $self->{daemons} }) { + print MASTER "DAEMON {\n"; + $self->_emit_master_entry($_) for values %{ $self->{daemons} }; + print MASTER "}\n"; + } + + close MASTER; +} + +sub _add_services_from_cyrus_conf { + my ($self) = @_; + + my $filename = $self->_master_conf(); + open MASTER, '<', $filename + or die "Cannot open $filename for reading: $!"; + + my $in; + while () { + chomp; + s/\s*#.*//; # strip comments + next if m/^\s*$/; # skip empty lines + my ($m) = m/^(START|SERVICES|EVENTS|DAEMON)\s*{/; + if ($m) { + $in = $m; + next; + } + if ($in && m/^\s*}\s*$/) { + $in = undef; + next; + } + next if !defined $in; + + my ($name, $rem) = m/^\s*([a-zA-Z0-9]+)\s+(.*)$/; + $_ = $rem; + my %params; + while (length $_) { + my ($k, $rem2) = m/^([a-zA-Z0-9]+)=(.*)/; + die "Bad parameter name" if !defined $k; + $_ = $rem2; + + my ($v, $rem3) = m/^"([^"]*)"(.*)/; + if (!defined $v) { + ($v, $rem3) = m/^(\S*)(.*)/; + } + die "Bad parameter value" if !defined $v; + $_ = $rem3; + + if ($k eq 'listen') { + my $aa = Cassandane::GenericListener::parse_address($v); + $params{host} = $aa->{host}; + $params{port} = $aa->{port}; + } elsif ($k eq 'cmd') { + $params{argv} = [ split(/\s+/, $v) ]; + } else { + $params{$k} = $v; + } + s/^\s+//; + } + if ($in eq 'SERVICES') { + $self->add_service(instance => $self, name => $name, %params); + } + } + + close MASTER; +} + +sub _fix_ownership { + my ($self, $path) = @_; + + $path ||= $self->{basedir}; + + return if geteuid() != 0; + my $uid = getpwnam('cyrus'); + my $gid = getgrnam('root'); + + find(sub { chown($uid, $gid, $File::Find::name) }, $path); +} + +sub _read_pid_file { + my ($self, $name) = @_; + my $file = $self->_pid_file($name); + my $pid; + + return undef if (!-f $file); + + open PID, '<', $file + or return undef; + while () { + chomp; + ($pid) = m/^(\d+)$/; + last; + } + close PID; + + return undef unless defined $pid; + return undef unless $pid > 1; + return undef unless kill(0, $pid) > 0; + return $pid; +} + +sub _start_master { + my ($self) = @_; + + # First check that nothing is listening on any of the ports + # we expect to be able to use. That would indicate a failure + # of test containment - i.e. we failed to shut something down + # earlier. Or it might indicate that someone is trying to run + # a second set of Cassandane tests on this machine, which is + # also going to fail miserably. In any case we want to know. + foreach my $srv (values %{ $self->{services} }, + values %{ $self->{generic_listeners} }) + { + die "Some process is already listening on " . $srv->address() + if $srv->is_listening(); + } + + # Now start the master process. + my @cmd = ( + 'master', + # The following is added automatically by _fork_command: + # '-C', $self->_imapd_conf(), + '-l', '255', + '-p', $self->_pid_file(), + '-d', + '-M', $self->_master_conf(), + ); + if (get_verbose) { + my $logfile = $self->{basedir} . '/conf/master.log'; + xlog "_start_master: logging to $logfile"; + push(@cmd, '-L', $logfile); + } + unlink $self->_pid_file(); + # Start master daemon + $self->run_command({ cyrus => 1 }, @cmd); + + # wait until the pidfile exists and contains a PID + # that we can verify is still alive. + xlog "_start_master: waiting for PID file"; + timed_wait(sub { $self->_read_pid_file() }, + description => "the master PID file to exist"); + xlog "_start_master: PID file present and correct"; + + # Start any other defined listeners + foreach my $listener (values %{ $self->{generic_listeners} }) { + $self->run_command({ cyrus => 0 }, $listener->get_argv()); + } + + # Wait until all the defined services are reported as listening. + # That doesn't mean they're ready to use but it means that at least + # a client will be able to connect(), although the first response + # might be a bit slow. + xlog "_start_master: PID waiting for services"; + foreach my $srv (values %{ $self->{services} }, + values %{ $self->{generic_listeners} }) + { + timed_wait( + sub { + $self->is_running() + or die "Master no longer running"; + $srv->is_listening(); + }, + description => $srv->address() . " to be in LISTEN state" ); - if (get_verbose) { - my $logfile = $self->{basedir} . '/conf/master.log'; - xlog "_start_master: logging to $logfile"; - push(@cmd, '-L', $logfile); - } - unlink $self->_pid_file(); - # Start master daemon - $self->run_command({ cyrus => 1 }, @cmd); - - # wait until the pidfile exists and contains a PID - # that we can verify is still alive. - xlog "_start_master: waiting for PID file"; - timed_wait(sub { $self->_read_pid_file() }, - description => "the master PID file to exist"); - xlog "_start_master: PID file present and correct"; - - # Start any other defined listeners - foreach my $listener (values %{$self->{generic_listeners}}) - { - $self->run_command({ cyrus => 0 }, $listener->get_argv()); - } - - # Wait until all the defined services are reported as listening. - # That doesn't mean they're ready to use but it means that at least - # a client will be able to connect(), although the first response - # might be a bit slow. - xlog "_start_master: PID waiting for services"; - foreach my $srv (values %{$self->{services}}, - values %{$self->{generic_listeners}}) - { - timed_wait(sub - { - $self->is_running() - or die "Master no longer running"; - $srv->is_listening(); - }, - description => $srv->address() . " to be in LISTEN state"); - } - xlog "_start_master: all services listening"; + } + xlog "_start_master: all services listening"; } -sub _start_notifyd -{ - my ($self) = @_; +sub _start_notifyd { + my ($self) = @_; - my $basedir = $self->{basedir}; + my $basedir = $self->{basedir}; - my $notifypid = fork(); - unless ($notifypid) { - $SIG{TERM} = sub { POSIX::_exit(0) }; + my $notifypid = fork(); + unless ($notifypid) { + $SIG{TERM} = sub { POSIX::_exit(0) }; - POSIX::close( $_ ) for 3 .. 1024; ## Arbitrary upper bound + POSIX::close($_) for 3 .. 1024; ## Arbitrary upper bound - # child; - $0 = "cassandane notifyd: $basedir"; - notifyd("$basedir/run"); - POSIX::_exit(0); - } + # child; + $0 = "cassandane notifyd: $basedir"; + notifyd("$basedir/run"); + POSIX::_exit(0); + } - xlog "started notifyd for $basedir as $notifypid"; - push @{$self->{_shutdowncallbacks}}, sub { - local *__ANON__ = "kill_notifyd"; - my $self = shift; - xlog "killing notifyd $notifypid"; - kill(15, $notifypid); - waitpid($notifypid, 0); - }; + xlog "started notifyd for $basedir as $notifypid"; + push @{ $self->{_shutdowncallbacks} }, sub { + local *__ANON__ = "kill_notifyd"; + my $self = shift; + xlog "killing notifyd $notifypid"; + kill(15, $notifypid); + waitpid($notifypid, 0); + }; } # @@ -1042,594 +944,579 @@ sub _start_notifyd # # Returns void, or dies if something went wrong # -sub create_user -{ - my ($self, $user, %params) = @_; +sub create_user { + my ($self, $user, %params) = @_; - my $mb = Cassandane::Mboxname->new(config => $self->{config}, username => $user); + my $mb + = Cassandane::Mboxname->new(config => $self->{config}, username => $user); - xlog "create user $user"; + xlog "create user $user"; - my $srv = $self->get_service('imap'); - return - unless defined $srv; + my $srv = $self->get_service('imap'); + return + unless defined $srv; - my $adminstore = $srv->create_store(username => 'admin'); - my $adminclient = $adminstore->get_client(); + my $adminstore = $srv->create_store(username => 'admin'); + my $adminclient = $adminstore->get_client(); - my @mboxes = ( $mb->to_external() ); - map { push(@mboxes, $mb->make_child($_)->to_external()); } @{$params{subdirs}} - if ($params{subdirs}); + my @mboxes = ($mb->to_external()); + map { push(@mboxes, $mb->make_child($_)->to_external()); } + @{ $params{subdirs} } + if ($params{subdirs}); - foreach my $mb (@mboxes) - { - $adminclient->create($mb) - or die "Cannot create $mb: $@"; - $adminclient->setacl($mb, admin => 'lrswipkxtecdan') - or die "Cannot setacl for $mb: $@"; - $adminclient->setacl($mb, $user => 'lrswipkxtecdn') - or die "Cannot setacl for $mb: $@"; - $adminclient->setacl($mb, anyone => 'p') - or die "Cannot setacl for $mb: $@"; - } + foreach my $mb (@mboxes) { + $adminclient->create($mb) + or die "Cannot create $mb: $@"; + $adminclient->setacl($mb, admin => 'lrswipkxtecdan') + or die "Cannot setacl for $mb: $@"; + $adminclient->setacl($mb, $user => 'lrswipkxtecdn') + or die "Cannot setacl for $mb: $@"; + $adminclient->setacl($mb, anyone => 'p') + or die "Cannot setacl for $mb: $@"; + } } sub set_smtpd { - my ($self, $data) = @_; - my $basedir = $self->{basedir}; - if ($data) { - open(FH, ">$basedir/conf/smtpd.json"); - print FH encode_json($data); - close(FH); - } - else { - unlink("$basedir/conf/smtpd.json"); - } + my ($self, $data) = @_; + my $basedir = $self->{basedir}; + if ($data) { + open(FH, ">$basedir/conf/smtpd.json"); + print FH encode_json($data); + close(FH); + } else { + unlink("$basedir/conf/smtpd.json"); + } } -sub _start_smtpd -{ - my ($self) = @_; - - my $basedir = $self->{basedir}; +sub _start_smtpd { + my ($self) = @_; - my $host = 'localhost'; + my $basedir = $self->{basedir}; - my $port = Cassandane::PortManager::alloc($host); + my $host = 'localhost'; - my $smtppid = fork(); - unless ($smtppid) { - # Child process. - # XXX This child still has the whole test's process space - # XXX still mapped, and when it exits, all our destructors - # XXX will be called, leaving the test in who knows what - # XXX state... - $SIG{TERM} = sub { die "killed" }; + my $port = Cassandane::PortManager::alloc($host); - POSIX::close( $_ ) for 3 .. 1024; ## Arbitrary upper bound + my $smtppid = fork(); + unless ($smtppid) { + # Child process. + # XXX This child still has the whole test's process space + # XXX still mapped, and when it exits, all our destructors + # XXX will be called, leaving the test in who knows what + # XXX state... + $SIG{TERM} = sub { die "killed" }; - $0 = "cassandane smtpd: $basedir"; + POSIX::close($_) for 3 .. 1024; ## Arbitrary upper bound - my $smtpd = Cassandane::Net::SMTPServer->new({ - cass_verbose => 1, - xmtp_personality => 'smtp', - host => $host, - port => $port, - max_servers => 3, # default is 50, yikes - control_file => "$basedir/conf/smtpd.json", - }); - $smtpd->run() or die; - exit 0; # Never reached - } + $0 = "cassandane smtpd: $basedir"; - # Parent process. - - # give the child a moment to actually start up - sleep 1; - - # and then make sure it did! - my $waitstatus = waitpid($smtppid, WNOHANG); - if ($waitstatus == 0) { - $self->{smtphost} = $host . ':' . $port; - - xlog "started smtpd as $smtppid"; - push @{$self->{_shutdowncallbacks}}, sub { - local *__ANON__ = "kill_smtpd"; - my $self = shift; - xlog "killing smtpd $smtppid"; - kill(15, $smtppid); - waitpid($smtppid, 0); - }; - } - else { - # child process already exited, something has gone wrong - Cassandane::PortManager::free($port); - die "smtpd with pid=$smtppid failed to start"; - } + my $smtpd = Cassandane::Net::SMTPServer->new({ + cass_verbose => 1, + xmtp_personality => 'smtp', + host => $host, + port => $port, + max_servers => 3, # default is 50, yikes + control_file => "$basedir/conf/smtpd.json", + }); + $smtpd->run() or die; + exit 0; # Never reached + } + + # Parent process. + + # give the child a moment to actually start up + sleep 1; + + # and then make sure it did! + my $waitstatus = waitpid($smtppid, WNOHANG); + if ($waitstatus == 0) { + $self->{smtphost} = $host . ':' . $port; + + xlog "started smtpd as $smtppid"; + push @{ $self->{_shutdowncallbacks} }, sub { + local *__ANON__ = "kill_smtpd"; + my $self = shift; + xlog "killing smtpd $smtppid"; + kill(15, $smtppid); + waitpid($smtppid, 0); + }; + } else { + # child process already exited, something has gone wrong + Cassandane::PortManager::free($port); + die "smtpd with pid=$smtppid failed to start"; + } } sub start_httpd { - my ($self, $handler, $port) = @_; - - my $basedir = $self->{basedir}; - - my $host = 'localhost'; - $port ||= Cassandane::PortManager::alloc($host); - - my $httpdpid = fork(); - unless ($httpdpid) { - # Child process. - # XXX This child still has the whole test's process space - # XXX still mapped, and when it exits, all our destructors - # XXX will be called, leaving the test in who knows what - # XXX state... - $SIG{TERM} = sub { exit 0; }; - - POSIX::close( $_ ) for 3 .. 1024; ## Arbitrary upper bound - - $0 = "cassandane httpd: $basedir"; - - my $httpd = HTTP::Daemon->new( - LocalAddr => $host, - LocalPort => $port, - ReuseAddr => 1, # Reuse ports left in TIME_WAIT - ) || die; - while (my $conn = $httpd->accept) { - while (my $req = $conn->get_request) { - $handler->($conn, $req); - } - $conn->close; - undef($conn); - } - - exit 0; # Never reached - } - - # Parent process. - $self->{httpdhost} = $host . ':' . $port; + my ($self, $handler, $port) = @_; - xlog "started httpd as $httpdpid"; - push @{$self->{_shutdowncallbacks}}, sub { - local *__ANON__ = "kill_httpd"; - my $self = shift; - xlog "killing httpd $httpdpid"; - kill(15, $httpdpid); - waitpid($httpdpid, 0); - }; - - return $port; -} + my $basedir = $self->{basedir}; -sub start -{ - my ($self) = @_; + my $host = 'localhost'; + $port ||= Cassandane::PortManager::alloc($host); - my $created = 0; + my $httpdpid = fork(); + unless ($httpdpid) { + # Child process. + # XXX This child still has the whole test's process space + # XXX still mapped, and when it exits, all our destructors + # XXX will be called, leaving the test in who knows what + # XXX state... + $SIG{TERM} = sub { exit 0; }; - $self->_init_basedir_and_name(); - xlog "start $self->{description}: basedir $self->{basedir}"; + POSIX::close($_) for 3 .. 1024; ## Arbitrary upper bound - if ($self->{description} =~ m/^main instance for test /) { - # Start SMTP server before generating imapd config, we need to - # to set smtp_host to the auto-assigned TCP port it listens on. - $self->_start_smtpd(); - } + $0 = "cassandane httpd: $basedir"; - # arrange for fakesaslauthd to be started by master - my $fakesaslauthd_socket = "$self->{basedir}/run/mux"; - my $fakesaslauthd_isdaemon = 1; - if ($self->{authdaemon}) { - my ($maj, $min) = Cassandane::Instance->get_version( - $self->{installation}); - if ($maj < 3 || ($maj == 3 && $min < 4)) { - $self->add_start( - name => 'fakesaslauthd', - argv => [ - abs_path('utils/fakesaslauthd'), - '-p', $fakesaslauthd_socket, - ], - ); - $fakesaslauthd_isdaemon = 0; - } - elsif (not exists $self->{daemons}->{fakesaslauthd}) { - $self->add_daemon( - name => 'fakesaslauthd', - argv => [ - abs_path('utils/fakesaslauthd'), - '-p', $fakesaslauthd_socket, - ], - wait => 'y', - ); - } + my $httpd = HTTP::Daemon->new( + LocalAddr => $host, + LocalPort => $port, + ReuseAddr => 1, # Reuse ports left in TIME_WAIT + ) || die; + while (my $conn = $httpd->accept) { + while (my $req = $conn->get_request) { + $handler->($conn, $req); + } + $conn->close; + undef($conn); } - if (!$self->{re_use_dir} || ! -d $self->{basedir}) - { - $created = 1; - rmtree $self->{basedir}; - $self->_build_skeleton(); - # TODO: system("echo 1 >/proc/sys/kernel/core_uses_pid"); - # TODO: system("echo 1 >/proc/sys/fs/suid_dumpable"); - $self->{buildinfo} = Cassandane::BuildInfo->new($self->{cyrus_destdir}, - $self->{cyrus_prefix}); - - # the main imapd.conf - $self->_generate_imapd_conf($self->{config}); - - # individual prefix-imapd.conf for master entries that want one - foreach my $me (values %{$self->{services}}, - values %{$self->{daemons}}, - @{$self->{starts}}, - @{$self->{events}}) - { - if ($me->{config}) { - $self->_generate_imapd_conf($me->{config}, $me->{name}); - } - } + exit 0; # Never reached + } - $self->_generate_master_conf(); - $self->install_certificates() if $self->{install_certificates}; - $self->_fix_ownership(); - } - elsif (!scalar $self->{services}) - { - $self->_add_services_from_cyrus_conf(); - # XXX START, EVENTS, DAEMON entries will be missed here if reusing - # XXX the directory. Does it matter? Maybe not, since the master - # XXX conf already contains them, so they'll still run, just - # XXX cassandane won't know about it. - } - $self->setup_syslog_replacement(); - $self->_start_notifyd(); - $self->_uncompress_berkeley_crud(); - $self->_start_master(); - $self->{_stopped} = 0; - $self->{_started} = 1; - - # give fakesaslauthd a moment (but not more than 2s) to set up its - # socket before anything starts trying to connect to services - if ($self->{authdaemon} && !$fakesaslauthd_isdaemon) { - my $tries = 0; - while (not -S $fakesaslauthd_socket && $tries < 2_000_000) { - $tries += usleep(10_000); # 10ms as us - } - die "fakesaslauthd socket $fakesaslauthd_socket not ready after 2s!" - if not -S $fakesaslauthd_socket; - } + # Parent process. + $self->{httpdhost} = $host . ':' . $port; - if ($created && $self->{setup_mailbox}) - { - $self->create_user("cassandane"); - } + xlog "started httpd as $httpdpid"; + push @{ $self->{_shutdowncallbacks} }, sub { + local *__ANON__ = "kill_httpd"; + my $self = shift; + xlog "killing httpd $httpdpid"; + kill(15, $httpdpid); + waitpid($httpdpid, 0); + }; - xlog "started $self->{description}: cyrus version " - . Cassandane::Instance->get_version($self->{installation}); + return $port; } -sub _compress_berkeley_crud -{ - my ($self) = @_; +sub start { + my ($self) = @_; - my @files; - my $dbdir = $self->{basedir} . "/conf/db"; - if ( -d $dbdir ) - { - opendir DBDIR, $dbdir - or return "Cannot open directory $dbdir: $!"; - while (my $e = readdir DBDIR) - { - push(@files, "$dbdir/$e") - if ($e =~ m/^__db\.\d+$/); - } - closedir DBDIR; - } - - if (scalar @files) - { - xlog "Compressing Berkeley environment files: " . join(' ', @files); - system('/bin/bzip2', @files); - } - - return; -} + my $created = 0; -sub _uncompress_berkeley_crud -{ - my ($self) = @_; + $self->_init_basedir_and_name(); + xlog "start $self->{description}: basedir $self->{basedir}"; - my @files; - my $dbdir = $self->{basedir} . "/conf/db"; - if ( -d $dbdir ) - { - opendir DBDIR, $dbdir - or die "Cannot open directory $dbdir: $!"; - while (my $e = readdir DBDIR) - { - push(@files, "$dbdir/$e") - if ($e =~ m/^__db\.\d+\.bz2$/); - } - closedir DBDIR; - } + if ($self->{description} =~ m/^main instance for test /) { + # Start SMTP server before generating imapd config, we need to + # to set smtp_host to the auto-assigned TCP port it listens on. + $self->_start_smtpd(); + } - if (scalar @files) + # arrange for fakesaslauthd to be started by master + my $fakesaslauthd_socket = "$self->{basedir}/run/mux"; + my $fakesaslauthd_isdaemon = 1; + if ($self->{authdaemon}) { + my ($maj, $min) = Cassandane::Instance->get_version($self->{installation}); + if ($maj < 3 || ($maj == 3 && $min < 4)) { + $self->add_start( + name => 'fakesaslauthd', + argv => + [ abs_path('utils/fakesaslauthd'), '-p', $fakesaslauthd_socket, ], + ); + $fakesaslauthd_isdaemon = 0; + } elsif (not exists $self->{daemons}->{fakesaslauthd}) { + $self->add_daemon( + name => 'fakesaslauthd', + argv => + [ abs_path('utils/fakesaslauthd'), '-p', $fakesaslauthd_socket, ], + wait => 'y', + ); + } + } + + if (!$self->{re_use_dir} || !-d $self->{basedir}) { + $created = 1; + rmtree $self->{basedir}; + $self->_build_skeleton(); + # TODO: system("echo 1 >/proc/sys/kernel/core_uses_pid"); + # TODO: system("echo 1 >/proc/sys/fs/suid_dumpable"); + $self->{buildinfo} = Cassandane::BuildInfo->new($self->{cyrus_destdir}, + $self->{cyrus_prefix}); + + # the main imapd.conf + $self->_generate_imapd_conf($self->{config}); + + # individual prefix-imapd.conf for master entries that want one + foreach my $me ( + values %{ $self->{services} }, + values %{ $self->{daemons} }, + @{ $self->{starts} }, + @{ $self->{events} } + ) { - xlog "Uncompressing Berkeley environment files: " . join(' ', @files); - system('/bin/bunzip2', @files); + if ($me->{config}) { + $self->_generate_imapd_conf($me->{config}, $me->{name}); + } + } + + $self->_generate_master_conf(); + $self->install_certificates() if $self->{install_certificates}; + $self->_fix_ownership(); + } elsif (!scalar $self->{services}) { + $self->_add_services_from_cyrus_conf(); + # XXX START, EVENTS, DAEMON entries will be missed here if reusing + # XXX the directory. Does it matter? Maybe not, since the master + # XXX conf already contains them, so they'll still run, just + # XXX cassandane won't know about it. + } + $self->setup_syslog_replacement(); + $self->_start_notifyd(); + $self->_uncompress_berkeley_crud(); + $self->_start_master(); + $self->{_stopped} = 0; + $self->{_started} = 1; + + # give fakesaslauthd a moment (but not more than 2s) to set up its + # socket before anything starts trying to connect to services + if ($self->{authdaemon} && !$fakesaslauthd_isdaemon) { + my $tries = 0; + while (not -S $fakesaslauthd_socket && $tries < 2_000_000) { + $tries += usleep(10_000); # 10ms as us + } + die "fakesaslauthd socket $fakesaslauthd_socket not ready after 2s!" + if not -S $fakesaslauthd_socket; + } + + if ($created && $self->{setup_mailbox}) { + $self->create_user("cassandane"); + } + + xlog "started $self->{description}: cyrus version " + . Cassandane::Instance->get_version($self->{installation}); +} + +sub _compress_berkeley_crud { + my ($self) = @_; + + my @files; + my $dbdir = $self->{basedir} . "/conf/db"; + if (-d $dbdir) { + opendir DBDIR, $dbdir + or return "Cannot open directory $dbdir: $!"; + while (my $e = readdir DBDIR) { + push(@files, "$dbdir/$e") + if ($e =~ m/^__db\.\d+$/); + } + closedir DBDIR; + } + + if (scalar @files) { + xlog "Compressing Berkeley environment files: " . join(' ', @files); + system('/bin/bzip2', @files); + } + + return; +} + +sub _uncompress_berkeley_crud { + my ($self) = @_; + + my @files; + my $dbdir = $self->{basedir} . "/conf/db"; + if (-d $dbdir) { + opendir DBDIR, $dbdir + or die "Cannot open directory $dbdir: $!"; + while (my $e = readdir DBDIR) { + push(@files, "$dbdir/$e") + if ($e =~ m/^__db\.\d+\.bz2$/); + } + closedir DBDIR; + } + + if (scalar @files) { + xlog "Uncompressing Berkeley environment files: " . join(' ', @files); + system('/bin/bunzip2', @files); + } +} + +sub _check_valgrind_logs { + my ($self) = @_; + + return + unless Cassandane::Cassini->instance()->bool_val('valgrind', 'enabled'); + + my $valgrind_logdir = $self->{basedir} . '/vglogs'; + + return unless -d $valgrind_logdir; + opendir VGLOGS, $valgrind_logdir + or return "Cannot open directory $valgrind_logdir for reading: $!"; + + my @nzlogs; + while ($_ = readdir VGLOGS) { + next if m/^\./; + next if m/\.core\./; + my $log = "$valgrind_logdir/$_"; + next if -z $log; + push(@nzlogs, $_); + + if (open VG, "<$log") { + xlog "Valgrind errors from file $log"; + while () { + chomp; + xlog "$_"; + } + close VG; + } else { + xlog "Cannot open Valgrind log $log for reading: $!"; } -} - -sub _check_valgrind_logs -{ - my ($self) = @_; - return unless Cassandane::Cassini->instance()->bool_val('valgrind', 'enabled'); + } + closedir VGLOGS; - my $valgrind_logdir = $self->{basedir} . '/vglogs'; - - return unless -d $valgrind_logdir; - opendir VGLOGS, $valgrind_logdir - or return "Cannot open directory $valgrind_logdir for reading: $!"; - - my @nzlogs; - while ($_ = readdir VGLOGS) - { - next if m/^\./; - next if m/\.core\./; - my $log = "$valgrind_logdir/$_"; - next if -z $log; - push(@nzlogs, $_); - - if (open VG, "<$log") { - xlog "Valgrind errors from file $log"; - while () { - chomp; - xlog "$_"; - } - close VG; - } - else { - xlog "Cannot open Valgrind log $log for reading: $!"; - } - - } - closedir VGLOGS; + return "Found Valgrind errors, see log for details" + if scalar @nzlogs; - return "Found Valgrind errors, see log for details" - if scalar @nzlogs; - - return; + return; } # The 'file' program seems to consistently misreport cores # so we apply a heuristic that seems to work -sub _detect_core_program -{ - my ($core) = @_; - my $lines = 0; - my $prog; +sub _detect_core_program { + my ($core) = @_; + my $lines = 0; + my $prog; - my $bindir_pattern = qr{ + my $bindir_pattern = qr{ \/ (?:bin|sbin|libexec) \/ }x; - open STRINGS, '-|', ('strings', '-a', $core) - or die "Cannot run strings on $core: $!"; - while () - { - chomp; - if (m/$bindir_pattern/) - { - $prog = $_; - last; - } - $lines++; - last if ($lines > 10); + open STRINGS, '-|', ('strings', '-a', $core) + or die "Cannot run strings on $core: $!"; + while () { + chomp; + if (m/$bindir_pattern/) { + $prog = $_; + last; } - close STRINGS; + $lines++; + last if ($lines > 10); + } + close STRINGS; - return $prog; + return $prog; } -sub find_cores -{ - my ($self) = @_; - my $coredir = $self->{basedir} . '/conf/cores'; +sub find_cores { + my ($self) = @_; + my $coredir = $self->{basedir} . '/conf/cores'; - my $cassini = Cassandane::Cassini->instance(); - my $core_pattern = $cassini->get_core_pattern(); + my $cassini = Cassandane::Cassini->instance(); + my $core_pattern = $cassini->get_core_pattern(); - my @cores; + my @cores; - return unless -d $coredir; - opendir CORES, $coredir - or return "Cannot open directory $coredir for reading: $!"; - while ($_ = readdir CORES) - { - next if m/^\./; - next unless m/$core_pattern/; - my $core = "$coredir/$_"; - next if -z $core; - chmod(0644, $core); - push @cores, $core; - - my $prog = _detect_core_program($core); - - xlog "Found core file $core"; - if (defined $prog) { - xlog " from program $prog"; - my ($bin) = $prog =~ m/^(\S+)/; # binary only - xlog " debug: sudo gdb $bin $core"; - } - } - closedir CORES; + return unless -d $coredir; + opendir CORES, $coredir + or return "Cannot open directory $coredir for reading: $!"; + while ($_ = readdir CORES) { + next if m/^\./; + next unless m/$core_pattern/; + my $core = "$coredir/$_"; + next if -z $core; + chmod(0644, $core); + push @cores, $core; - return @cores; -} + my $prog = _detect_core_program($core); -sub _check_cores -{ - my ($self) = @_; - my $coredir = $self->{basedir} . '/conf/cores'; + xlog "Found core file $core"; + if (defined $prog) { + xlog " from program $prog"; + my ($bin) = $prog =~ m/^(\S+)/; # binary only + xlog " debug: sudo gdb $bin $core"; + } + } + closedir CORES; - return "Core files found in $coredir" if scalar $self->find_cores(); + return @cores; } -sub _check_mupdate -{ - my ($self) = @_; +sub _check_cores { + my ($self) = @_; + my $coredir = $self->{basedir} . '/conf/cores'; - my $mupdate_server = $self->{config}->get('mupdate_server'); - return if not $mupdate_server; # not in a murder - - my $serverlist = $self->{config}->get('serverlist'); - return if $serverlist; # don't sync mboxlist on frontends - - # Run ctl_mboxlist -m to sync backend mailboxes with mupdate. - # - # You typically run this from START, and we do, but at test start - # there's no mailboxes yet, so there's nothing to sync, and if - # something is broken it probably won't be detected. - my $basedir = $self->{basedir}; - eval { - $self->run_command({ - redirects => { stdout => "$basedir/ctl_mboxlist.out", - stderr => "$basedir/ctl_mboxlist.err", - }, - cyrus => 1, - }, 'ctl_mboxlist', '-m'); - }; - if ($@) { - my @err = slurp_file("$basedir/ctl_mboxlist.err"); - chomp for @err; - xlog "ctl_mboxlist -m failed: " . Dumper \@err; - return "unable to sync local mailboxes with mupdate"; - } + return "Core files found in $coredir" if scalar $self->find_cores(); } -sub _check_sanity -{ - my ($self) = @_; +sub _check_mupdate { + my ($self) = @_; - # We added this check during 3.5 development... older versions - # probably fail these checks. If we backport fixes we can decrement - # this version check. - my ($maj, $min) = Cassandane::Instance->get_version($self->{installation}); - if ($maj < 3 || ($maj == 3 && $min < 5)) { - return; - } + my $mupdate_server = $self->{config}->get('mupdate_server'); + return if not $mupdate_server; # not in a murder - my $basedir = $self->{basedir}; - my $found = 0; - eval { - $self->run_command({redirects => {stdout => "$basedir/quota.out", stderr => "$basedir/quota.err"}, cyrus => 1}, 'quota', '-f', '-q'); - }; - if ($@) { - xlog "quota -f failed, $@"; - $found = 1; - } - eval { - $self->run_command({redirects => {stdout => "$basedir/reconstruct.out", stderr => "$basedir/reconstruct.err"}, cyrus => 1}, 'reconstruct', '-q', '-G'); - }; - if ($@) { - xlog "reconstruct failed, $@"; - $found = 1; - } - for my $file ("quota.out", "quota.err", "reconstruct.out", "reconstruct.err") { - next unless open(FH, "<$basedir/$file"); - while () { - next unless $_; - $found = 1; - xlog "INCONSISTENCY FOUND: $file $_"; - } + my $serverlist = $self->{config}->get('serverlist'); + return if $serverlist; # don't sync mboxlist on frontends + + # Run ctl_mboxlist -m to sync backend mailboxes with mupdate. + # + # You typically run this from START, and we do, but at test start + # there's no mailboxes yet, so there's nothing to sync, and if + # something is broken it probably won't be detected. + my $basedir = $self->{basedir}; + eval { + $self->run_command( + { + redirects => { + stdout => "$basedir/ctl_mboxlist.out", + stderr => "$basedir/ctl_mboxlist.err", + }, + cyrus => 1, + }, + 'ctl_mboxlist', + '-m' + ); + }; + if ($@) { + my @err = slurp_file("$basedir/ctl_mboxlist.err"); + chomp for @err; + xlog "ctl_mboxlist -m failed: " . Dumper \@err; + return "unable to sync local mailboxes with mupdate"; + } +} + +sub _check_sanity { + my ($self) = @_; + + # We added this check during 3.5 development... older versions + # probably fail these checks. If we backport fixes we can decrement + # this version check. + my ($maj, $min) = Cassandane::Instance->get_version($self->{installation}); + if ($maj < 3 || ($maj == 3 && $min < 5)) { + return; + } + + my $basedir = $self->{basedir}; + my $found = 0; + eval { + $self->run_command( + { + redirects => + { stdout => "$basedir/quota.out", stderr => "$basedir/quota.err" }, + cyrus => 1 + }, + 'quota', '-f', '-q' + ); + }; + if ($@) { + xlog "quota -f failed, $@"; + $found = 1; + } + eval { + $self->run_command( + { + redirects => { + stdout => "$basedir/reconstruct.out", + stderr => "$basedir/reconstruct.err" + }, + cyrus => 1 + }, + 'reconstruct', + '-q', '-G' + ); + }; + if ($@) { + xlog "reconstruct failed, $@"; + $found = 1; + } + for my $file ("quota.out", "quota.err", "reconstruct.out", "reconstruct.err") + { + next unless open(FH, "<$basedir/$file"); + while () { + next unless $_; + $found = 1; + xlog "INCONSISTENCY FOUND: $file $_"; } + } - return "INCONSISTENCIES FOUND IN SPOOL" if $found; + return "INCONSISTENCIES FOUND IN SPOOL" if $found; - return; + return; } -sub _check_syslog -{ - my ($self, $pattern) = @_; +sub _check_syslog { + my ($self, $pattern) = @_; - if (defined $pattern) { - # pattern is optional but must be a regex if present - die "getsyslog: pattern is not a regular expression" - if lc ref($pattern) ne 'regexp'; - } + if (defined $pattern) { + # pattern is optional but must be a regex if present + die "getsyslog: pattern is not a regular expression" + if lc ref($pattern) ne 'regexp'; + } - my @lines = $self->getsyslog(); - my @errors = grep { - m/ERROR|TRACELOG|Unknown code ____/ || ($pattern && m/$pattern/) - } @lines; + my @lines = $self->getsyslog(); + my @errors + = grep { m/ERROR|TRACELOG|Unknown code ____/ || ($pattern && m/$pattern/) } + @lines; - @errors = grep { not m/DBERROR.*skipstamp/ } @errors; + @errors = grep { not m/DBERROR.*skipstamp/ } @errors; - $self->xlog("syslog error: $_") for @errors; + $self->xlog("syslog error: $_") for @errors; - return "Errors found in syslog" if @errors; + return "Errors found in syslog" if @errors; - return; + return; } # Stop a given PID. Returns 1 if the process died # gracefully (i.e. soon after receiving SIGTERM) # or wasn't even running beforehand. -sub _stop_pid -{ - my ($pid, $reaper) = @_; - - # Try to be nice, but leave open the option of not being nice should - # that be necessary. The signals we send are: - # - # SIGTERM - The standard Cyrus graceful shutdown signal, should - # be handled and propagated by master. - # SIGILL - Not handled by master; kernel's default action is to - # dump a core. We use this to try to get a core when - # something is wrong with master. - # SIGKILL - Hmm, something went wrong with our cunning SIGILL plan, - # let's take off and nuke it from orbit. We just don't - # want to leave processes around cluttering up the place. - # - my @sigs = ( SIGTERM, SIGILL, SIGKILL ); - my %signame = ( - SIGTERM, "TERM", - SIGILL, "ILL", - SIGKILL, "KILL", - ); - my $r = 1; - - foreach my $sig (@sigs) - { - xlog "_stop_pid: sending signal $signame{$sig} to $pid"; - kill($sig, $pid) or xlog "Can't send signal $signame{$sig} to pid $pid: $!"; - eval { - timed_wait(sub { - eval { $reaper->() if (defined $reaper) }; - return (kill(0, $pid) == 0); - }); - }; - last unless $@; - # Timed out -- No More Mr Nice Guy - xlog "_stop_pid: failed to shut down pid $pid with signal $signame{$sig}"; - $r = 0; - } - return $r; +sub _stop_pid { + my ($pid, $reaper) = @_; + + # Try to be nice, but leave open the option of not being nice should + # that be necessary. The signals we send are: + # + # SIGTERM - The standard Cyrus graceful shutdown signal, should + # be handled and propagated by master. + # SIGILL - Not handled by master; kernel's default action is to + # dump a core. We use this to try to get a core when + # something is wrong with master. + # SIGKILL - Hmm, something went wrong with our cunning SIGILL plan, + # let's take off and nuke it from orbit. We just don't + # want to leave processes around cluttering up the place. + # + my @sigs = (SIGTERM, SIGILL, SIGKILL); + my %signame = (SIGTERM, "TERM", SIGILL, "ILL", SIGKILL, "KILL",); + my $r = 1; + + foreach my $sig (@sigs) { + xlog "_stop_pid: sending signal $signame{$sig} to $pid"; + kill($sig, $pid) or xlog "Can't send signal $signame{$sig} to pid $pid: $!"; + eval { + timed_wait(sub { + eval { $reaper->() if (defined $reaper) }; + return (kill(0, $pid) == 0); + }); + }; + last unless $@; + # Timed out -- No More Mr Nice Guy + xlog "_stop_pid: failed to shut down pid $pid with signal $signame{$sig}"; + $r = 0; + } + return $r; } -sub send_sighup -{ - my ($self) = @_; +sub send_sighup { + my ($self) = @_; - return if (!$self->{_started}); - return if ($self->{_stopped}); - xlog "sighup"; + return if (!$self->{_started}); + return if ($self->{_stopped}); + xlog "sighup"; - my $pid = $self->_read_pid_file('master') or return; - kill(SIGHUP, $pid) or die "Can't send signal SIGHUP to pid $pid: $!"; - return 1; + my $pid = $self->_read_pid_file('master') or return; + kill(SIGHUP, $pid) or die "Can't send signal SIGHUP to pid $pid: $!"; + return 1; } # @@ -1641,145 +1528,135 @@ sub send_sighup # will still use the same directory name though, so it won't be obvious # from the logs that this is happening! # -sub stop -{ - my ($self, %params) = @_; +sub stop { + my ($self, %params) = @_; - $self->_init_basedir_and_name(); + $self->_init_basedir_and_name(); - return if ($self->{_stopped} || !$self->{_started}); - $self->{_stopped} = 1; + return if ($self->{_stopped} || !$self->{_started}); + $self->{_stopped} = 1; - my @errors; + my @errors; - push @errors, $self->_check_sanity(); - push @errors, $self->_check_mupdate(); + push @errors, $self->_check_sanity(); + push @errors, $self->_check_mupdate(); - xlog "stop $self->{description}: basedir $self->{basedir}"; + xlog "stop $self->{description}: basedir $self->{basedir}"; - foreach my $name ($self->_list_pid_files()) - { - my $pid = $self->_read_pid_file($name); - next if (!defined $pid); - _stop_pid($pid) - or push @errors, "Cannot shut down $name pid $pid"; - } - # Note: no need to reap this daemon which is not our child anymore - - foreach my $item (@{$self->{_shutdowncallbacks}}) { - eval { - $item->($self); - }; - if ($@) { - push @errors, "some shutdown callback died: $@"; - } + foreach my $name ($self->_list_pid_files()) { + my $pid = $self->_read_pid_file($name); + next if (!defined $pid); + _stop_pid($pid) + or push @errors, "Cannot shut down $name pid $pid"; + } + # Note: no need to reap this daemon which is not our child anymore + + foreach my $item (@{ $self->{_shutdowncallbacks} }) { + eval { $item->($self); }; + if ($@) { + push @errors, "some shutdown callback died: $@"; } - $self->{_shutdowncallbacks} = []; + } + $self->{_shutdowncallbacks} = []; - # n.b. still need this for testing 2.5 - push @errors, $self->_compress_berkeley_crud(); + # n.b. still need this for testing 2.5 + push @errors, $self->_compress_berkeley_crud(); - push @errors, $self->_check_valgrind_logs(); - push @errors, $self->_check_cores(); - push @errors, $self->_check_syslog() unless $params{no_check_syslog}; + push @errors, $self->_check_valgrind_logs(); + push @errors, $self->_check_cores(); + push @errors, $self->_check_syslog() unless $params{no_check_syslog}; - # filter out empty errors (shouldn't be any, but just in case) - @errors = grep { $_ } @errors; + # filter out empty errors (shouldn't be any, but just in case) + @errors = grep { $_ } @errors; - foreach my $e (@errors) { - xlog "$self->{description}: $e"; - } + foreach my $e (@errors) { + xlog "$self->{description}: $e"; + } - return @errors; + return @errors; } -sub DESTROY -{ - my ($self) = @_; +sub DESTROY { + my ($self) = @_; - if ($$ != $self->{_pid}) { - xlog "ignoring DESTROY from bad caller: $$"; - return; - } + if ($$ != $self->{_pid}) { + xlog "ignoring DESTROY from bad caller: $$"; + return; + } - if (defined $self->{basedir} && - !$self->{persistent} && - !$self->{_stopped}) - { - # clean up any dangling master and daemon process - foreach my $name ($self->_list_pid_files()) - { - my $pid = $self->_read_pid_file($name); - next if (!defined $pid); - _stop_pid($pid); - } + if ( defined $self->{basedir} + && !$self->{persistent} + && !$self->{_stopped}) + { + # clean up any dangling master and daemon process + foreach my $name ($self->_list_pid_files()) { + my $pid = $self->_read_pid_file($name); + next if (!defined $pid); + _stop_pid($pid); + } - foreach my $item (@{$self->{_shutdowncallbacks}}) { - $item->($self); - } - $self->{_shutdowncallbacks} = []; + foreach my $item (@{ $self->{_shutdowncallbacks} }) { + $item->($self); } + $self->{_shutdowncallbacks} = []; + } } -sub is_running -{ - my ($self) = @_; +sub is_running { + my ($self) = @_; - my $pid = $self->_read_pid_file(); - return 0 unless defined $pid; - return kill(0, $pid); + my $pid = $self->_read_pid_file(); + return 0 unless defined $pid; + return kill(0, $pid); } -sub _setup_for_deliver -{ - my ($self) = @_; +sub _setup_for_deliver { + my ($self) = @_; - $self->add_service(name => 'lmtp', - argv => ['lmtpd', '-a'], - port => '@basedir@/conf/socket/lmtp'); + $self->add_service( + name => 'lmtp', + argv => [ 'lmtpd', '-a' ], + port => '@basedir@/conf/socket/lmtp' + ); } -sub deliver -{ - my ($self, $msg, %params) = @_; - my $str = $msg->as_string(); - my @cmd = ( 'deliver' ); +sub deliver { + my ($self, $msg, %params) = @_; + my $str = $msg->as_string(); + my @cmd = ('deliver'); - my $folder = $params{folder}; - if (defined $folder) - { - $folder =~ s/^inbox.//i; - push(@cmd, '-m', $folder); - } + my $folder = $params{folder}; + if (defined $folder) { + $folder =~ s/^inbox.//i; + push(@cmd, '-m', $folder); + } - my @users; - if (defined $params{users}) - { - push(@users, @{$params{users}}); - } - elsif (defined $params{user}) - { - push(@users, $params{user}) - } - else - { - push(@users, 'cassandane'); - } - push(@cmd, @users); + my @users; + if (defined $params{users}) { + push(@users, @{ $params{users} }); + } elsif (defined $params{user}) { + push(@users, $params{user}); + } else { + push(@users, 'cassandane'); + } + push(@cmd, @users); - my $ret = 0; + my $ret = 0; - $self->run_command({ - cyrus => 1, - redirects => { - stdin => \$str - }, - handlers => { - exited_abnormally => sub { (undef, $ret) = @_; }, - }, - }, @cmd); + $self->run_command( + { + cyrus => 1, + redirects => { + stdin => \$str + }, + handlers => { + exited_abnormally => sub { (undef, $ret) = @_; }, + }, + }, + @cmd + ); - return $ret; + return $ret; } # Runs a command with the given arguments. The first argument is an @@ -1809,115 +1686,105 @@ sub deliver # # workingdir path to launch the command from # -sub run_command -{ - my ($self, @args) = @_; +sub run_command { + my ($self, @args) = @_; - my $options = {}; - if (ref($args[0]) eq 'HASH') { - $options = shift(@args); - } + my $options = {}; + if (ref($args[0]) eq 'HASH') { + $options = shift(@args); + } - my ($pid, $got_exit) = $self->_fork_command($options, @args); + my ($pid, $got_exit) = $self->_fork_command($options, @args); - return $pid - if ($options->{background}); + return $pid + if ($options->{background}); - if (defined $got_exit) { - # Child already reaped, pass it on - $? = $got_exit; + if (defined $got_exit) { + # Child already reaped, pass it on + $? = $got_exit; - return $self->_handle_wait_status($pid); - } + return $self->_handle_wait_status($pid); + } - return $self->reap_command($pid); + return $self->reap_command($pid); } -sub reap_command -{ - my ($self, $pid) = @_; +sub reap_command { + my ($self, $pid) = @_; - # parent process...wait for child - my $child = waitpid($pid, 0); - # and deal with it's exit status - return $self->_handle_wait_status($pid) - if $child == $pid; - return undef; + # parent process...wait for child + my $child = waitpid($pid, 0); + # and deal with it's exit status + return $self->_handle_wait_status($pid) + if $child == $pid; + return undef; } # returns the command's exit status, or -1 if something went wrong -sub stop_command -{ - my ($self, $pid) = @_; - my $child; - - # it's our child, so we must reap it, otherwise it'll never - # completely exit. but if it ignores the first sigterm, a normal - # waitpid will block forever, so we need to be WNOHANG here - my $r = _stop_pid($pid, sub { $child = waitpid($pid, WNOHANG); }); - return -1 if $r != 1; - - if ($child == $pid) { - return $self->_handle_wait_status($pid) - } - else { - return -1; - } +sub stop_command { + my ($self, $pid) = @_; + my $child; + + # it's our child, so we must reap it, otherwise it'll never + # completely exit. but if it ignores the first sigterm, a normal + # waitpid will block forever, so we need to be WNOHANG here + my $r = _stop_pid($pid, sub { $child = waitpid($pid, WNOHANG); }); + return -1 if $r != 1; + + if ($child == $pid) { + return $self->_handle_wait_status($pid); + } else { + return -1; + } } my %default_command_handlers = ( - signaled => sub - { - my ($child, $sig) = @_; - my $desc = _describe_child($child); - die "child process $desc terminated by signal $sig"; - }, - exited_normally => sub - { - my ($child) = @_; - return 0; - }, - exited_abnormally => sub - { - my ($child, $code) = @_; - my $desc = _describe_child($child); - die "child process $desc exited with code $code"; - }, + signaled => sub { + my ($child, $sig) = @_; + my $desc = _describe_child($child); + die "child process $desc terminated by signal $sig"; + }, + exited_normally => sub { + my ($child) = @_; + return 0; + }, + exited_abnormally => sub { + my ($child, $code) = @_; + my $desc = _describe_child($child); + die "child process $desc exited with code $code"; + }, ); -sub _add_child -{ - my ($self, $binary, $pid, $handlers) = @_; - my $key = $pid; +sub _add_child { + my ($self, $binary, $pid, $handlers) = @_; + my $key = $pid; - $handlers ||= \%default_command_handlers; + $handlers ||= \%default_command_handlers; - my $child = { - binary => $binary, - pid => $pid, - handlers => { %default_command_handlers, %$handlers }, - }; - $self->{_children}->{$key} = $child; - return $child; + my $child = { + binary => $binary, + pid => $pid, + handlers => { %default_command_handlers, %$handlers }, + }; + $self->{_children}->{$key} = $child; + return $child; } -sub _describe_child -{ - my ($child) = @_; - return "unknown" unless $child; - return "(binary $child->{binary} pid $child->{pid})"; -} - -sub _cyrus_perl_search_path -{ - my ($self) = @_; - my @inc = ( - substr($Config{installvendorlib}, length($Config{vendorprefix})), - substr($Config{installvendorarch}, length($Config{vendorprefix})), - substr($Config{installsitelib}, length($Config{siteprefix})), - substr($Config{installsitearch}, length($Config{siteprefix})) - ); - return map { $self->{cyrus_destdir} . $self->{cyrus_prefix} . $_; } @inc; +sub _describe_child { + my ($child) = @_; + return "unknown" unless $child; + return "(binary $child->{binary} pid $child->{pid})"; +} + +sub _cyrus_perl_search_path { + my ($self) = @_; + my @inc = ( + substr($Config{installvendorlib}, length($Config{vendorprefix})), + substr($Config{installvendorarch}, length($Config{vendorprefix})), + substr($Config{installsitelib}, length($Config{siteprefix})), + substr($Config{installsitearch}, length($Config{siteprefix})) + ); + return map { $self->{cyrus_destdir} . $self->{cyrus_prefix} . $_; } @inc; } # @@ -1926,429 +1793,403 @@ sub _cyrus_perl_search_path # Returns launched $pid; you must call _handle_wait_status() to # decode $?. Dies on errors. # -sub _fork_command -{ - my ($self, $options, $binary, @argv) = @_; - - die "No binary specified" - unless defined $binary; - - my %redirects; - if (defined($options->{redirects})) { - %redirects = %{$options->{redirects}}; - } - # stdin is null, stdout is null or unmolested - $redirects{stdin} = '/dev/null' - unless(defined($redirects{stdin})); - $redirects{stdout} = '/dev/null' - unless(get_verbose || defined($redirects{stdout})); - $redirects{stderr} = '/dev/null' - unless(get_verbose || defined($redirects{stderr})); - - my @cmd = (); - if ($options->{cyrus}) - { - push(@cmd, $self->_binary($binary), '-C', $self->_imapd_conf()); - } - elsif ($binary eq 'delve') { - push(@cmd, $self->_binary($binary)); - } - else { - push(@cmd, $binary); - } - push(@cmd, @argv); - - xlog "Running: " . join(' ', map { "\"$_\"" } @cmd); - - if (defined($redirects{stdin}) && (ref($redirects{stdin}) eq 'SCALAR')) - { - my $fh; - my $data = $redirects{stdin}; - $redirects{stdin} = undef; - # Use the fork()ing form of open() - my $pid = open $fh,'|-'; - die "Cannot fork: $!" - if !defined $pid; - if ($pid) - { - xlog "child pid=$pid"; - # parent process - $self->_add_child($binary, $pid, $options->{handlers}); - print $fh ${$data}; - close ($fh); - return ($pid, $?); - } - } - else - { - # No capturing - just plain fork() - my $pid = fork(); - die "Cannot fork: $!" - if !defined $pid; - if ($pid) - { - # parent process - xlog "child pid=$pid"; - $self->_add_child($binary, $pid, $options->{handlers}); - return ($pid, undef); - } - } - - # child process - - my $cassroot = getcwd(); - $ENV{CASSANDANE_CYRUS_DESTDIR} = $self->{cyrus_destdir}; - $ENV{CASSANDANE_CYRUS_PREFIX} = $self->{cyrus_prefix}; - $ENV{CASSANDANE_PREFIX} = $cassroot; - $ENV{CASSANDANE_BASEDIR} = $self->{basedir}; - $ENV{CASSANDANE_VERBOSE} = 1 if get_verbose(); - $ENV{PERL5LIB} = join(':', ($cassroot, $self->_cyrus_perl_search_path())); - if ($self->{have_syslog_replacement}) { - $ENV{CASSANDANE_SYSLOG_FNAME} = abs_path($self->{syslog_fname}); - $ENV{LD_PRELOAD} = abs_path('utils/syslog.so') - } - -# xlog "\$PERL5LIB is"; map { xlog " $_"; } split(/:/, $ENV{PERL5LIB}); - - # Set up the runtime linker path to find the Cyrus shared libraries - # - # TODO: on some platforms we need lib64/ not lib/ but it's not - # entirely clear how to detect that - we could use readelf -d - # on an executable to discover what it thinks it's RPATH ought - # to be, then prepend destdir to that. - if ($self->{cyrus_destdir} ne "") - { - $ENV{LD_LIBRARY_PATH} = join(':', ( - $self->{cyrus_destdir} . $self->{cyrus_prefix} . "/lib", - split(/:/, $ENV{LD_LIBRARY_PATH} || "") - )); - } +sub _fork_command { + my ($self, $options, $binary, @argv) = @_; + + die "No binary specified" + unless defined $binary; + + my %redirects; + if (defined($options->{redirects})) { + %redirects = %{ $options->{redirects} }; + } + # stdin is null, stdout is null or unmolested + $redirects{stdin} = '/dev/null' + unless (defined($redirects{stdin})); + $redirects{stdout} = '/dev/null' + unless (get_verbose || defined($redirects{stdout})); + $redirects{stderr} = '/dev/null' + unless (get_verbose || defined($redirects{stderr})); + + my @cmd = (); + if ($options->{cyrus}) { + push(@cmd, $self->_binary($binary), '-C', $self->_imapd_conf()); + } elsif ($binary eq 'delve') { + push(@cmd, $self->_binary($binary)); + } else { + push(@cmd, $binary); + } + push(@cmd, @argv); + + xlog "Running: " . join(' ', map { "\"$_\"" } @cmd); + + if (defined($redirects{stdin}) && (ref($redirects{stdin}) eq 'SCALAR')) { + my $fh; + my $data = $redirects{stdin}; + $redirects{stdin} = undef; + # Use the fork()ing form of open() + my $pid = open $fh, '|-'; + die "Cannot fork: $!" + if !defined $pid; + if ($pid) { + xlog "child pid=$pid"; + # parent process + $self->_add_child($binary, $pid, $options->{handlers}); + print $fh ${$data}; + close($fh); + return ($pid, $?); + } + } else { + # No capturing - just plain fork() + my $pid = fork(); + die "Cannot fork: $!" + if !defined $pid; + if ($pid) { + # parent process + xlog "child pid=$pid"; + $self->_add_child($binary, $pid, $options->{handlers}); + return ($pid, undef); + } + } + + # child process + + my $cassroot = getcwd(); + $ENV{CASSANDANE_CYRUS_DESTDIR} = $self->{cyrus_destdir}; + $ENV{CASSANDANE_CYRUS_PREFIX} = $self->{cyrus_prefix}; + $ENV{CASSANDANE_PREFIX} = $cassroot; + $ENV{CASSANDANE_BASEDIR} = $self->{basedir}; + $ENV{CASSANDANE_VERBOSE} = 1 if get_verbose(); + $ENV{PERL5LIB} = join(':', ($cassroot, $self->_cyrus_perl_search_path())); + if ($self->{have_syslog_replacement}) { + $ENV{CASSANDANE_SYSLOG_FNAME} = abs_path($self->{syslog_fname}); + $ENV{LD_PRELOAD} = abs_path('utils/syslog.so'); + } + + # xlog "\$PERL5LIB is"; map { xlog " $_"; } split(/:/, $ENV{PERL5LIB}); + + # Set up the runtime linker path to find the Cyrus shared libraries + # + # TODO: on some platforms we need lib64/ not lib/ but it's not + # entirely clear how to detect that - we could use readelf -d + # on an executable to discover what it thinks it's RPATH ought + # to be, then prepend destdir to that. + if ($self->{cyrus_destdir} ne "") { + $ENV{LD_LIBRARY_PATH} = join( + ':', + ( + $self->{cyrus_destdir} . $self->{cyrus_prefix} . "/lib", + split(/:/, $ENV{LD_LIBRARY_PATH} || "") + ) + ); + } # xlog "\$LD_LIBRARY_PATH is"; map { xlog " $_"; } split(/:/, $ENV{LD_LIBRARY_PATH}); - my $cd = $options->{workingdir}; - $cd = $self->{basedir} . '/conf/cores' - unless defined($cd); - chdir($cd) - or die "Cannot cd to $cd: $!"; - - # ulimit -c ... - my $cassini = Cassandane::Cassini->instance(); - my $coresizelimit = 0 + $cassini->val("cyrus $self->{installation}", - 'coresizelimit', '100'); - if ($coresizelimit <= 0) { - $coresizelimit = RLIM_INFINITY; - } - else { - # convert megabytes to bytes - $coresizelimit *= (1024 * 1024); - } - xlog "setting core size limit to $coresizelimit"; - setrlimit(RLIMIT_CORE, $coresizelimit, $coresizelimit); - - # let's log our rlimits, might be useful for diagnosing weirdnesses - if (get_verbose() >= 4) { - my $limits = get_rlimits(); - foreach my $name (keys %{$limits}) { - $limits->{$name} = [ getrlimit($limits->{$name}) ]; - } - xlog "rlimits: " . Dumper $limits; - } - - # TODO: do any setuid, umask, or environment futzing here - - # implement redirects - if (defined $redirects{stdin}) - { - open STDIN,'<',$redirects{stdin} - or die "Cannot redirect STDIN from $redirects{stdin}: $!"; - } - if (defined $redirects{stdout}) - { - open STDOUT,'>',$redirects{stdout} - or die "Cannot redirect STDOUT to $redirects{stdout}: $!"; - } - if (defined $redirects{stderr}) - { - open STDERR,'>',$redirects{stderr} - or die "Cannot redirect STDERR to $redirects{stderr}: $!"; - } - - exec @cmd; - die "Cannot run $binary: $!"; -} - -sub _handle_wait_status -{ - my ($self, $key) = @_; - my $status = $?; - - my $child = delete $self->{_children}->{$key}; - - if (WIFSIGNALED($status)) - { - my $sig = WTERMSIG($status); - return $child->{handlers}->{signaled}->($child, $sig); - } - elsif (WIFEXITED($status)) - { - my $code = WEXITSTATUS($status); - return $child->{handlers}->{exited_abnormally}->($child, $code) - if $code != 0; - } - else - { - die "WTF? Cannot decode wait status $status"; - } - return $child->{handlers}->{exited_normally}->($child); -} - -sub describe -{ - my ($self) = @_; - - print "Cyrus instance\n"; - printf " name: %s\n", $self->{name}; - printf " imapd.conf: %s\n", $self->_imapd_conf(); - printf " services:\n"; - foreach my $srv (values %{$self->{services}}) - { - printf " "; - $srv->describe(); - } - printf " generic listeners:\n"; - foreach my $listener (values %{$self->{generic_listeners}}) - { - printf " "; - $listener->describe(); - } -} - -sub _quota_Z_file -{ - my ($self, $mboxname) = @_; - return $self->{basedir} . '/conf/quota-sync/' . $mboxname; -} - -sub quota_Z_go -{ - my ($self, $mboxname) = @_; - my $filename = $self->_quota_Z_file($mboxname); - - xlog "Allowing quota -Z to proceed for $mboxname"; - - my $dir = dirname($filename); - mkpath $dir - unless ( -d $dir ); - - my $fd = POSIX::creat($filename, 0600); - POSIX::close($fd); -} - -sub quota_Z_wait -{ - my ($self, $mboxname) = @_; - my $filename = $self->_quota_Z_file($mboxname); - - timed_wait(sub { return (! -f $filename); }, - description => "quota -Z to be finished with $mboxname"); + my $cd = $options->{workingdir}; + $cd = $self->{basedir} . '/conf/cores' + unless defined($cd); + chdir($cd) + or die "Cannot cd to $cd: $!"; + + # ulimit -c ... + my $cassini = Cassandane::Cassini->instance(); + my $coresizelimit + = 0 + $cassini->val("cyrus $self->{installation}", 'coresizelimit', '100'); + if ($coresizelimit <= 0) { + $coresizelimit = RLIM_INFINITY; + } else { + # convert megabytes to bytes + $coresizelimit *= (1024 * 1024); + } + xlog "setting core size limit to $coresizelimit"; + setrlimit(RLIMIT_CORE, $coresizelimit, $coresizelimit); + + # let's log our rlimits, might be useful for diagnosing weirdnesses + if (get_verbose() >= 4) { + my $limits = get_rlimits(); + foreach my $name (keys %{$limits}) { + $limits->{$name} = [ getrlimit($limits->{$name}) ]; + } + xlog "rlimits: " . Dumper $limits; + } + + # TODO: do any setuid, umask, or environment futzing here + + # implement redirects + if (defined $redirects{stdin}) { + open STDIN, '<', $redirects{stdin} + or die "Cannot redirect STDIN from $redirects{stdin}: $!"; + } + if (defined $redirects{stdout}) { + open STDOUT, '>', $redirects{stdout} + or die "Cannot redirect STDOUT to $redirects{stdout}: $!"; + } + if (defined $redirects{stderr}) { + open STDERR, '>', $redirects{stderr} + or die "Cannot redirect STDERR to $redirects{stderr}: $!"; + } + + exec @cmd; + die "Cannot run $binary: $!"; +} + +sub _handle_wait_status { + my ($self, $key) = @_; + my $status = $?; + + my $child = delete $self->{_children}->{$key}; + + if (WIFSIGNALED($status)) { + my $sig = WTERMSIG($status); + return $child->{handlers}->{signaled}->($child, $sig); + } elsif (WIFEXITED($status)) { + my $code = WEXITSTATUS($status); + return $child->{handlers}->{exited_abnormally}->($child, $code) + if $code != 0; + } else { + die "WTF? Cannot decode wait status $status"; + } + return $child->{handlers}->{exited_normally}->($child); +} + +sub describe { + my ($self) = @_; + + print "Cyrus instance\n"; + printf " name: %s\n", $self->{name}; + printf " imapd.conf: %s\n", $self->_imapd_conf(); + printf " services:\n"; + foreach my $srv (values %{ $self->{services} }) { + printf " "; + $srv->describe(); + } + printf " generic listeners:\n"; + foreach my $listener (values %{ $self->{generic_listeners} }) { + printf " "; + $listener->describe(); + } +} + +sub _quota_Z_file { + my ($self, $mboxname) = @_; + return $self->{basedir} . '/conf/quota-sync/' . $mboxname; +} + +sub quota_Z_go { + my ($self, $mboxname) = @_; + my $filename = $self->_quota_Z_file($mboxname); + + xlog "Allowing quota -Z to proceed for $mboxname"; + + my $dir = dirname($filename); + mkpath $dir + unless (-d $dir); + + my $fd = POSIX::creat($filename, 0600); + POSIX::close($fd); +} + +sub quota_Z_wait { + my ($self, $mboxname) = @_; + my $filename = $self->_quota_Z_file($mboxname); + + timed_wait(sub { return (!-f $filename); }, + description => "quota -Z to be finished with $mboxname"); } # # Unpacks file. Handles tar, gz, and bz2. # -sub unpackfile -{ - my ($self, $src, $dst) = @_; - - if (!defined($dst)) { - # unpack in base directory - $dst = $self->{basedir}; - } - elsif ($dst !~ /^\//) { - # unpack relatively to base directory - $dst = $self->{basedir} . '/' . $dst; - } - # else: absolute path given - - my $options = {}; - my @cmd = (); - - my $file = [split(/\./, (split(/\//, $src))[-1])]; - if (grep { $_ eq 'tar' } @$file) { - push(@cmd, 'tar', '-x', '-f', $src, '-C', $dst); - } - elsif ($file->[-1] eq 'gz') { - $options->{redirects} = { - stdout => "$dst/" . join('.', splice(@$file, 0, -1)) - }; - push(@cmd, 'gunzip', '-c', $src); - } - elsif ($file->[-1] eq 'bz2') { - $options->{redirects} = { - stdout => "$dst/" . join('.', splice(@$file, 0, -1)) - }; - push(@cmd, 'bunzip2', '-c', $src); - } - else { - # we don't handle this combination - die "Unhandled packed file $src"; +sub unpackfile { + my ($self, $src, $dst) = @_; + + if (!defined($dst)) { + # unpack in base directory + $dst = $self->{basedir}; + } elsif ($dst !~ /^\//) { + # unpack relatively to base directory + $dst = $self->{basedir} . '/' . $dst; + } + # else: absolute path given + + my $options = {}; + my @cmd = (); + + my $file = [ split(/\./, (split(/\//, $src))[-1]) ]; + if (grep { $_ eq 'tar' } @$file) { + push(@cmd, 'tar', '-x', '-f', $src, '-C', $dst); + } elsif ($file->[-1] eq 'gz') { + $options->{redirects} + = { stdout => "$dst/" . join('.', splice(@$file, 0, -1)) }; + push(@cmd, 'gunzip', '-c', $src); + } elsif ($file->[-1] eq 'bz2') { + $options->{redirects} + = { stdout => "$dst/" . join('.', splice(@$file, 0, -1)) }; + push(@cmd, 'bunzip2', '-c', $src); + } else { + # we don't handle this combination + die "Unhandled packed file $src"; + } + + return $self->run_command($options, @cmd); +} + +sub folder_to_directory { + my ($self, $folder) = @_; + + $folder =~ s/^inbox\./user.cassandane./i; + $folder =~ s/^inbox$/user.cassandane/i; + + my $data = eval { $self->run_mbpath($folder) }; + return unless $data; + my $dir = $data->{data}; + return undef unless -d $dir; + return $dir; +} + +sub folder_to_deleted_directories { + my ($self, $folder) = @_; + + $folder =~ s/^inbox\./user.cassandane./i; + $folder =~ s/^inbox$/user.cassandane/i; + + # ideally we'd have a command-line way to do this, but imap works too + my $srv = $self->get_service('imap'); + my $adminstore = $srv->create_store(username => 'admin'); + my $adminclient = $adminstore->get_client(); + my @folders = $adminclient->list('', "DELETED.$folder.%"); + + my @res; + for my $item (@folders) { + next if grep { lc $_ eq '\\noselect' } @{ $item->[0] }; + my $mailbox = $item->[2]; + my $data = eval { $self->run_mbpath($mailbox) }; + my $dir = $data->{data}; + next unless -d $dir; + push @res, $dir; + } + + return @res; +} + +sub notifyd { + my $dir = shift; + + $0 = "cassandane notifyd $dir"; + + my @EVENTS; + tcp_server( + "unix/", + "$dir/notify", + sub { + my $fh = shift; + my $Handle = AnyEvent::Handle->new(fh => $fh,); + $Handle->push_read( + 'Cyrus::DList' => 1, + sub { + my $dlist = $_[1]; + my $event = $dlist->as_perl(); + #xlog "GOT EVENT: " . encode_json($event); + push @EVENTS, $event; + $Handle->push_write( + 'Cyrus::DList' => scalar(Cyrus::DList->new_kvlist("OK")), + 1 + ); + $Handle->push_shutdown(); + } + ); } + ); - return $self->run_command($options, @cmd); -} - -sub folder_to_directory -{ - my ($self, $folder) = @_; - - $folder =~ s/^inbox\./user.cassandane./i; - $folder =~ s/^inbox$/user.cassandane/i; - - my $data = eval { $self->run_mbpath($folder) }; - return unless $data; - my $dir = $data->{data}; - return undef unless -d $dir; - return $dir; -} - -sub folder_to_deleted_directories -{ - my ($self, $folder) = @_; - - $folder =~ s/^inbox\./user.cassandane./i; - $folder =~ s/^inbox$/user.cassandane/i; - - # ideally we'd have a command-line way to do this, but imap works too - my $srv = $self->get_service('imap'); - my $adminstore = $srv->create_store(username => 'admin'); - my $adminclient = $adminstore->get_client(); - my @folders = $adminclient->list('', "DELETED.$folder.%"); - - my @res; - for my $item (@folders) { - next if grep { lc $_ eq '\\noselect' } @{$item->[0]}; - my $mailbox = $item->[2]; - my $data = eval { $self->run_mbpath($mailbox) }; - my $dir = $data->{data}; - next unless -d $dir; - push @res, $dir; + tcp_server( + "unix/", + "$dir/getnotify", + sub { + my $fh = shift; + my $Handle = AnyEvent::Handle->new(fh => $fh,); + #xlog "REPLYING EVENTS: " . scalar(@EVENTS); + $Handle->push_write(json => \@EVENTS); + $Handle->push_shutdown(); + @EVENTS = (); } + ); - return @res; -} - -sub notifyd -{ - my $dir = shift; - - $0 = "cassandane notifyd $dir"; - - my @EVENTS; - tcp_server("unix/", "$dir/notify", sub { - my $fh = shift; - my $Handle = AnyEvent::Handle->new( - fh => $fh, - ); - $Handle->push_read('Cyrus::DList' => 1, sub { - my $dlist = $_[1]; - my $event = $dlist->as_perl(); - #xlog "GOT EVENT: " . encode_json($event); - push @EVENTS, $event; - $Handle->push_write('Cyrus::DList' => scalar(Cyrus::DList->new_kvlist("OK")), 1); - $Handle->push_shutdown(); - }); - }); + my $cv = AnyEvent->condvar(); - tcp_server("unix/", "$dir/getnotify", sub { - my $fh = shift; - my $Handle = AnyEvent::Handle->new( - fh => $fh, - ); - #xlog "REPLYING EVENTS: " . scalar(@EVENTS); - $Handle->push_write(json => \@EVENTS); - $Handle->push_shutdown(); - @EVENTS = (); - }); + $SIG{TERM} = sub { $cv->send() }; - my $cv = AnyEvent->condvar(); - - $SIG{TERM} = sub { $cv->send() }; - - $cv->recv(); + $cv->recv(); } -sub getnotify -{ - my ($self) = @_; +sub getnotify { + my ($self) = @_; - my $basedir = $self->{basedir}; - my $path = "$basedir/run/getnotify"; + my $basedir = $self->{basedir}; + my $path = "$basedir/run/getnotify"; - my $data = eval { - my $sock = IO::Socket::UNIX->new( - Type => SOCK_STREAM(), - Peer => $path, - ) || die "Connection failed $!"; - my $line = $sock->getline(); - my $json = decode_json($line); - if (get_verbose) { - use Data::Dumper; - warn "NOTIFY " . Dumper($json); - } - return $json; - }; - if ($@) { - my $data = `ls -la $basedir/run; whoami; lsof -n | grep notify`; - xlog "Failed $@ ($data)"; + my $data = eval { + my $sock = IO::Socket::UNIX->new( + Type => SOCK_STREAM(), + Peer => $path, + ) || die "Connection failed $!"; + my $line = $sock->getline(); + my $json = decode_json($line); + if (get_verbose) { + use Data::Dumper; + warn "NOTIFY " . Dumper($json); } + return $json; + }; + if ($@) { + my $data = `ls -la $basedir/run; whoami; lsof -n | grep notify`; + xlog "Failed $@ ($data)"; + } - return $data; + return $data; } -sub setup_syslog_replacement -{ - my ($self) = @_; +sub setup_syslog_replacement { + my ($self) = @_; - if (not(-e 'utils/syslog.so') || not(-e 'utils/syslog_probe')) { - xlog "utils/syslog.so not found (do you need to run 'make'?)"; - xlog "tests will not examine syslog output"; - $self->{have_syslog_replacement} = 0; - return; - } + if (not(-e 'utils/syslog.so') || not(-e 'utils/syslog_probe')) { + xlog "utils/syslog.so not found (do you need to run 'make'?)"; + xlog "tests will not examine syslog output"; + $self->{have_syslog_replacement} = 0; + return; + } - $self->{syslog_fname} = "$self->{basedir}/conf/log/syslog"; - $self->{have_syslog_replacement} = 1; + $self->{syslog_fname} = "$self->{basedir}/conf/log/syslog"; + $self->{have_syslog_replacement} = 1; - # if the syslog file already exists, remember how large it is - # so we can seek past existing content without missing the - # startup content! - my $syslog_start = 0; - $syslog_start = -s $self->{syslog_fname} if -e $self->{syslog_fname}; + # if the syslog file already exists, remember how large it is + # so we can seek past existing content without missing the + # startup content! + my $syslog_start = 0; + $syslog_start = -s $self->{syslog_fname} if -e $self->{syslog_fname}; - # check that we can syslog a message and find it again - my $syslog_probe = abs_path('utils/syslog_probe'); - $self->run_command($syslog_probe, $self->{name}); + # check that we can syslog a message and find it again + my $syslog_probe = abs_path('utils/syslog_probe'); + $self->run_command($syslog_probe, $self->{name}); - $self->{_syslogfh} = IO::File->new($self->{syslog_fname}, 'r'); + $self->{_syslogfh} = IO::File->new($self->{syslog_fname}, 'r'); - if ($self->{_syslogfh}) { - $self->{_syslogfh}->seek($syslog_start, 0); - $self->{_syslogfh}->blocking(0); + if ($self->{_syslogfh}) { + $self->{_syslogfh}->seek($syslog_start, 0); + $self->{_syslogfh}->blocking(0); - if (not scalar $self->getsyslog(qr/\bthe magic word\b/)) { - xlog "didn't find the magic word when probing syslog"; - xlog "tests will not examine syslog output"; + if (not scalar $self->getsyslog(qr/\bthe magic word\b/)) { + xlog "didn't find the magic word when probing syslog"; + xlog "tests will not examine syslog output"; - $self->{have_syslog_replacement} = 0; - undef $self->{_syslogfh}; - } + $self->{have_syslog_replacement} = 0; + undef $self->{_syslogfh}; } - else { - xlog "couldn't read $self->{syslog_fname} when probing syslog"; - xlog "tests will not examine syslog output"; + } else { + xlog "couldn't read $self->{syslog_fname} when probing syslog"; + xlog "tests will not examine syslog output"; - $self->{have_syslog_replacement} = 0; - } + $self->{have_syslog_replacement} = 0; + } } # n.b. This only gives you syslog lines if we were able to successfully @@ -2358,473 +2199,471 @@ sub setup_syslog_replacement # But if you need to make sure an error WAS logged, first make sure that # $instance->{have_syslog_replacement} is true, otherwise you will always # fail on systems where the syslog replacement doesn't work. -sub getsyslog -{ - my ($self, $pattern) = @_; +sub getsyslog { + my ($self, $pattern) = @_; - if (defined $pattern) { - # pattern is optional but must be a regex if present - die "getsyslog: pattern is not a regular expression" - if lc ref($pattern) ne 'regexp'; - } - my $logname = $self->{name}; - my @lines; - - if ($self->{have_syslog_replacement} && $self->{_syslogfh}) { - # https://github.com/Perl/perl5/issues/21240 - # eof status is no longer cleared automatically in newer perls - if ($self->{_syslogfh}->eof()) { - $self->{_syslogfh}->clearerr(); - } - if ($self->{_syslogfh}->error()) { - die "error reading $self->{syslog_fname}"; - } + if (defined $pattern) { + # pattern is optional but must be a regex if present + die "getsyslog: pattern is not a regular expression" + if lc ref($pattern) ne 'regexp'; + } + my $logname = $self->{name}; + my @lines; - # hopefully unobtrusively, let busy log finish writing - usleep(100_000); # 100ms (0.1s) as us - @lines = grep { m/$logname/ } $self->{_syslogfh}->getlines(); + if ($self->{have_syslog_replacement} && $self->{_syslogfh}) { + # https://github.com/Perl/perl5/issues/21240 + # eof status is no longer cleared automatically in newer perls + if ($self->{_syslogfh}->eof()) { + $self->{_syslogfh}->clearerr(); + } + if ($self->{_syslogfh}->error()) { + die "error reading $self->{syslog_fname}"; + } - if (defined $pattern) { - @lines = grep { m/$pattern/ } @lines; - } + # hopefully unobtrusively, let busy log finish writing + usleep(100_000); # 100ms (0.1s) as us + @lines = grep { m/$logname/ } $self->{_syslogfh}->getlines(); - chomp for @lines; + if (defined $pattern) { + @lines = grep { m/$pattern/ } @lines; } - return @lines; + chomp for @lines; + } + + return @lines; } -sub _get_sqldb -{ - my $dbfile = shift; - my $dbh = DBI->connect("dbi:SQLite:$dbfile", undef, undef); - my @tables = map { s/"//gs; s/^main\.//; $_ } $dbh->tables(); - my %res; - foreach my $table (@tables) { - $res{$table} = $dbh->selectall_arrayref("SELECT * FROM $table", { Slice => {} }); - } - return \%res; +sub _get_sqldb { + my $dbfile = shift; + my $dbh = DBI->connect("dbi:SQLite:$dbfile", undef, undef); + my @tables = map { s/"//gs; s/^main\.//; $_ } $dbh->tables(); + my %res; + foreach my $table (@tables) { + $res{$table} + = $dbh->selectall_arrayref("SELECT * FROM $table", { Slice => {} }); + } + return \%res; } -sub getalarmdb -{ - my $self = shift; - my $file = "$self->{basedir}/conf/caldav_alarm.sqlite3"; - return [] unless -e $file; - my $data = _get_sqldb($file); - return $data->{events} || die "NO EVENTS IN CALDAV ALARM DB"; +sub getalarmdb { + my $self = shift; + my $file = "$self->{basedir}/conf/caldav_alarm.sqlite3"; + return [] unless -e $file; + my $data = _get_sqldb($file); + return $data->{events} || die "NO EVENTS IN CALDAV ALARM DB"; } -sub getdavdb -{ - my $self = shift; - my $user = shift; - my $file = $self->get_conf_user_file($user, 'dav'); - return unless -e $file; - return _get_sqldb($file); +sub getdavdb { + my $self = shift; + my $user = shift; + my $file = $self->get_conf_user_file($user, 'dav'); + return unless -e $file; + return _get_sqldb($file); } -sub get_sieve_script_dir -{ - my ($self, $cyrusname) = @_; +sub get_sieve_script_dir { + my ($self, $cyrusname) = @_; - if ($cyrusname) { - my $data = eval { $self->run_mbpath('-u', $cyrusname) }; - return $data->{user}{sieve} if $data; - } + if ($cyrusname) { + my $data = eval { $self->run_mbpath('-u', $cyrusname) }; + return $data->{user}{sieve} if $data; + } - $cyrusname //= ''; + $cyrusname //= ''; - my $sieved = "$self->{basedir}/conf/sieve"; + my $sieved = "$self->{basedir}/conf/sieve"; - my ($user, $domain) = split '@', $cyrusname; + my ($user, $domain) = split '@', $cyrusname; - if ($domain) { - my $dhash = substr($domain, 0, 1); - $sieved .= "/domain/$dhash/$domain"; - } + if ($domain) { + my $dhash = substr($domain, 0, 1); + $sieved .= "/domain/$dhash/$domain"; + } - if ($user ne '') - { - my $uhash = substr($user, 0, 1); - $sieved .= "/$uhash/$user/"; - } - else - { - # shared folder - $sieved .= '/global/'; - } + if ($user ne '') { + my $uhash = substr($user, 0, 1); + $sieved .= "/$uhash/$user/"; + } else { + # shared folder + $sieved .= '/global/'; + } - return $sieved; + return $sieved; } -sub get_conf_user_file -{ - my ($self, $cyrusname, $ext) = @_; +sub get_conf_user_file { + my ($self, $cyrusname, $ext) = @_; - my $data = eval { $self->run_mbpath('-u', $cyrusname) }; - return $data->{user}{$ext} if $data; + my $data = eval { $self->run_mbpath('-u', $cyrusname) }; + return $data->{user}{$ext} if $data; } -sub install_sieve_script -{ - my ($self, $script, %params) = @_; +sub install_sieve_script { + my ($self, $script, %params) = @_; - my $user = (exists $params{username} ? $params{username} : 'cassandane'); - my $name = $params{name} || 'test1'; - my $sieved = $self->get_sieve_script_dir($user); + my $user = (exists $params{username} ? $params{username} : 'cassandane'); + my $name = $params{name} || 'test1'; + my $sieved = $self->get_sieve_script_dir($user); - xlog "Installing sieve script $name in $sieved"; + xlog "Installing sieve script $name in $sieved"; - -d $sieved or mkpath $sieved - or die "Cannot make path $sieved: $!"; - die "Path does not exist: $sieved" if not -d $sieved; + -d $sieved + or mkpath $sieved + or die "Cannot make path $sieved: $!"; + die "Path does not exist: $sieved" if not -d $sieved; - open(FH, '>', "$sieved/$name.script") - or die "Cannot open $sieved/$name.script for writing: $!"; - print FH $script; - close(FH); + open(FH, '>', "$sieved/$name.script") + or die "Cannot open $sieved/$name.script for writing: $!"; + print FH $script; + close(FH); - $self->run_command({ cyrus => 1 }, - "sievec", - "$sieved/$name.script", - "$sieved/$name.bc"); - die "File does not exist: $sieved/$name.bc" if not -f "$sieved/$name.bc"; + $self->run_command({ cyrus => 1 }, + "sievec", "$sieved/$name.script", "$sieved/$name.bc"); + die "File does not exist: $sieved/$name.bc" if not -f "$sieved/$name.bc"; - -e "$sieved/defaultbc" || symlink("$name.bc", "$sieved/defaultbc") - or die "Cannot symlink $name.bc to $sieved/defaultbc"; - die "Symlink does not exist: $sieved/defaultbc" if not -l "$sieved/defaultbc"; + -e "$sieved/defaultbc" || symlink("$name.bc", "$sieved/defaultbc") + or die "Cannot symlink $name.bc to $sieved/defaultbc"; + die "Symlink does not exist: $sieved/defaultbc" if not -l "$sieved/defaultbc"; - xlog "Sieve script installed successfully"; + xlog "Sieve script installed successfully"; } -sub install_old_mailbox -{ - my ($self, $user, $version) = @_; +sub install_old_mailbox { + my ($self, $user, $version) = @_; - my $data_file = abs_path("data/old-mailboxes/version$version.tar.gz"); - die "Old mailbox data does not exist: $data_file" if not -f $data_file; + my $data_file = abs_path("data/old-mailboxes/version$version.tar.gz"); + die "Old mailbox data does not exist: $data_file" if not -f $data_file; - xlog "installing version $version mailbox for user $user"; + xlog "installing version $version mailbox for user $user"; - my $dest_dir = "data/user/$user"; + my $dest_dir = "data/user/$user"; - $self->unpackfile($data_file, $dest_dir); - $self->run_command({ cyrus => 1 }, 'reconstruct', '-f', "user.$user"); + $self->unpackfile($data_file, $dest_dir); + $self->run_command({ cyrus => 1 }, 'reconstruct', '-f', "user.$user"); - xlog "installed version $version mailbox for user $user: user.$user.version$version"; + xlog + "installed version $version mailbox for user $user: user.$user.version$version"; - return "user.$user.version$version"; + return "user.$user.version$version"; } -sub install_certificates -{ - my ($self) = @_; +sub install_certificates { + my ($self) = @_; - my $cert_file = abs_path("data/certs/cert.pem"); - my $key_file = abs_path("data/certs/key.pem"); - my $cacert_file = abs_path("data/certs/cacert.pem"); + my $cert_file = abs_path("data/certs/cert.pem"); + my $key_file = abs_path("data/certs/key.pem"); + my $cacert_file = abs_path("data/certs/cacert.pem"); - my $destdir = $self->get_basedir() . "/conf/certs"; - xlog "installing certificate files to $destdir ..."; - foreach my $f ($cert_file, $key_file, $cacert_file) { - copy($f, $destdir) - or die "cannot install $f to $destdir: $!"; - } + my $destdir = $self->get_basedir() . "/conf/certs"; + xlog "installing certificate files to $destdir ..."; + foreach my $f ($cert_file, $key_file, $cacert_file) { + copy($f, $destdir) + or die "cannot install $f to $destdir: $!"; + } - $destdir = $self->get_basedir() . "/conf/certs/http_jwt"; - my $jwt_file = abs_path("data/certs/http_jwt/jwt.pem"); - xlog "installing JSON Web Token key file ..."; - copy($jwt_file, $destdir) - or die "cannot install $jwt_file to $destdir: $!"; + $destdir = $self->get_basedir() . "/conf/certs/http_jwt"; + my $jwt_file = abs_path("data/certs/http_jwt/jwt.pem"); + xlog "installing JSON Web Token key file ..."; + copy($jwt_file, $destdir) + or die "cannot install $jwt_file to $destdir: $!"; } -sub get_servername -{ - my ($self) = @_; +sub get_servername { + my ($self) = @_; - return $self->{config}->get('servername'); + return $self->{config}->get('servername'); } -sub run_mbpath -{ - my ($self, @args) = @_; - my ($maj, $min) = Cassandane::Instance->get_version($self->{installation}); - my $basedir = $self->get_basedir(); - if ($maj < 3 || $maj == 3 && $min <= 4) { - my $folder = pop @args; - my $domain = ''; - if ($folder =~ s/\@([^@]+)$//) { - $domain = $1; - } - - # support -u $user, including users with dots - if (@args and $args[0] eq '-u') { - $folder =~ s/\./\^/g; - $folder = "user.$folder"; - } - - # translate to path - $folder =~ s/\./\//g; - my $user = ''; - if ($folder =~ m{user/([^/]+)}) { - $user = $1; - } - - my $dhash = substr($domain, 0, 1); - my $uhash = substr($user, 0, 1); - - my $dotuser = $user; - $dotuser =~ s/\^/\./g; - - # XXX - hashing smarts? - my $upath = ''; - $upath .= "domain/$dhash/$domain/" if $domain; - $upath .= "user/$uhash/$dotuser"; - my $spath = ''; - # fricking sieve, always different - $spath .= "domain/$dhash/$domain/" if $domain; - $spath .= "$uhash/$dotuser"; - my $xpath = ''; - # et tu xapian - $xpath .= "domain/$dhash/$domain/" if $domain; - $xpath .= "$uhash/user/$dotuser"; - - my $res = { - data => "$basedir/data/$folder", - archive => "$basedir/archive/$folder", - meta => "$basedir/meta/$folder", - # skip mbname, we're not using it - user => { - (map { $_ => "$basedir/conf/$upath.$_" } qw(conversations counters dav seen sub xapianactive)), - sieve => "$basedir/conf/sieve/$spath", - }, - xapian => { - t1 => "$basedir/search/$xpath", - t2 => "$basedir/search2/$xpath", - t3 => "$basedir/search3/$xpath", - }, - }; - return $res; +sub run_mbpath { + my ($self, @args) = @_; + my ($maj, $min) = Cassandane::Instance->get_version($self->{installation}); + my $basedir = $self->get_basedir(); + if ($maj < 3 || $maj == 3 && $min <= 4) { + my $folder = pop @args; + my $domain = ''; + if ($folder =~ s/\@([^@]+)$//) { + $domain = $1; } - my $filename = "$basedir/cyr_info.out"; - $self->run_command({ - cyrus => 1, - redirects => { - stdout => $filename, - }, - }, 'mbpath', '-j', @args); - - return decode_json(slurp_file($filename)); -} - -sub _mkastring -{ - my $string = shift; - return '{' . length($string) . '+}' . "\r\n" . $string; -} - -sub run_dbcommand_cb -{ - my ($self, $linecb, $dbname, $engine, @items) = @_; - - if (@items > 1) { - unshift @items, ['BEGIN']; - push @items, ['COMMIT']; + # support -u $user, including users with dots + if (@args and $args[0] eq '-u') { + $folder =~ s/\./\^/g; + $folder = "user.$folder"; } - my $input = ''; - foreach my $item (@items) { - $input .= $item->[0]; - for (1..2) { - $input .= ' ' . _mkastring($item->[$_]) if defined $item->[$_]; - } - $input .= "\r\n"; + # translate to path + $folder =~ s/\./\//g; + my $user = ''; + if ($folder =~ m{user/([^/]+)}) { + $user = $1; } - my $basedir = $self->{basedir}; - my $res = $self->run_command({ - redirects => { - stdin => \$input, - stdout => "$basedir/run_dbcommand.out", - }, - cyrus => 1, - handlers => { - exited_normally => sub { return 'ok'; }, - exited_abnormally => sub { return 'failure'; }, - }, - }, 'cyr_dbtool', $dbname, $engine, 'batch'); - return $res unless $res eq 'ok'; - - my $needbytes = 0; - my $buf = ''; - - # The output of `cyr_dbtool` is in theory one logical line at a time. - # However each logical line can have IMAP literals in them. In that - # case, you get a real line that ends with "{nbytes+}\r\n" and you then - # have to read that many bytes of data (including possibly \r's and - # \n's as well). This function potentially reads multiple real lines - # in $line to gather up a single logical line in $buf, and then parses - # that. - # It could be made simpler and more efficient by tokenising the line as - # it goes, but it was extracted from an original codebase which processed - # the entire response buffer from `cyr_dbtool` as a single giant string. - open(FH, "<$basedir/run_dbcommand.out"); - LINE: while (defined(my $line = )) { - $buf .= $line; - - # inside a literal, that's all we need - if ($needbytes) { - my $len = length($line); - if ($len <= $needbytes) { - $needbytes -= $len; - next LINE; - } - substr($line, 0, $needbytes, ''); - $needbytes = 0; - } + my $dhash = substr($domain, 0, 1); + my $uhash = substr($user, 0, 1); - # does this line include a literal, process it now - if ($line =~ m/\{(\d+)\+?\}\r?\n$/s) { - $needbytes = $1; - next LINE; - } + my $dotuser = $user; + $dotuser =~ s/\^/\./g; - # we have a line! - - my @array; - my $pos = 0; - my $length = length($buf); - while ($pos < $length) { - my $chr = substr($buf, $pos, 1); - - if ($chr eq ' ') { - $pos++; - next; - } - - if ($chr eq "\n") { - $pos++; - next; - } - - if ($chr eq '{') { - my $end = index($buf, '}', $pos); - die "Missing }" if $end < 0; - my $len = substr($buf, $pos + 1, $end - $pos - 1); - $len =~ s/\+//; - $pos = $end+1; - my $chr = substr($buf, $pos++, 1); - $chr = substr($buf, $pos++, 1) if $chr eq "\r"; - die "BOGUS LITERAL" unless $chr eq "\n"; - push @array, substr($buf, $pos, $len); - $pos += $len; - next; - } - - if ($chr eq '"') { - my $end = index($buf, '"', $pos+1); - die "Missing quote" if $end < 0; - push @array, substr($buf, $pos + 1, $end - $pos - 1); - $pos = $end + 1; - next; - } - - my $space = index($buf, ' ', $pos); - my $endline = index($buf, "\n", $pos); - - if ($space < 0) { - push @array, substr($buf, $pos, $endline - $pos); - $pos = $endline; - next; - } - - if ($endline < 0) { - push @array, substr($buf, $pos, $space - $pos); - $pos = $space; - next; - } - - if ($endline < $space) { - push @array, substr($buf, $pos, $endline - $pos); - $pos = $endline; - next; - } - - if ($space < $endline) { - push @array, substr($buf, $pos, $space - $pos); - $pos = $space; - next; - } - - die "shouldn't get here"; - } + # XXX - hashing smarts? + my $upath = ''; + $upath .= "domain/$dhash/$domain/" if $domain; + $upath .= "user/$uhash/$dotuser"; + my $spath = ''; + # fricking sieve, always different + $spath .= "domain/$dhash/$domain/" if $domain; + $spath .= "$uhash/$dotuser"; + my $xpath = ''; + # et tu xapian + $xpath .= "domain/$dhash/$domain/" if $domain; + $xpath .= "$uhash/user/$dotuser"; - $linecb->(@array); + my $res = { + data => "$basedir/data/$folder", + archive => "$basedir/archive/$folder", + meta => "$basedir/meta/$folder", + # skip mbname, we're not using it + user => { + ( + map { $_ => "$basedir/conf/$upath.$_" } + qw(conversations counters dav seen sub xapianactive) + ), + sieve => "$basedir/conf/sieve/$spath", + }, + xapian => { + t1 => "$basedir/search/$xpath", + t2 => "$basedir/search2/$xpath", + t3 => "$basedir/search3/$xpath", + }, + }; + return $res; + } - $buf = ''; - } - close(FH); + my $filename = "$basedir/cyr_info.out"; + $self->run_command( + { + cyrus => 1, + redirects => { + stdout => $filename, + }, + }, + 'mbpath', '-j', @args + ); - return 'ok'; + return decode_json(slurp_file($filename)); } -sub run_dbcommand -{ - my ($self, $dbname, $engine, @items) = @_; - my @array; - $self->run_dbcommand_cb(sub { push @array, @_ }, $dbname, $engine, @items); - return @array; +sub _mkastring { + my $string = shift; + return '{' . length($string) . '+}' . "\r\n" . $string; } -sub read_mailboxes_db -{ - my ($self, $params) = @_; +sub run_dbcommand_cb { + my ($self, $linecb, $dbname, $engine, @items) = @_; - # run ctl_mboxlist -d to dump mailboxes.db to a file - my $outfile = $params->{outfile} - || $self->get_basedir() . "/$$-ctl_mboxlist.out"; - $self->run_command({ - cyrus => 1, - redirects => { - stdout => $outfile, - }, - }, 'ctl_mboxlist', '-d'); + if (@items > 1) { + unshift @items, ['BEGIN']; + push @items, ['COMMIT']; + } - return JSON::decode_json(slurp_file($outfile)); -} + my $input = ''; + foreach my $item (@items) { + $input .= $item->[0]; + for (1 .. 2) { + $input .= ' ' . _mkastring($item->[$_]) if defined $item->[$_]; + } + $input .= "\r\n"; + } -sub run_cyr_info -{ - my ($self, @args) = @_; + my $basedir = $self->{basedir}; + my $res = $self->run_command( + { + redirects => { + stdin => \$input, + stdout => "$basedir/run_dbcommand.out", + }, + cyrus => 1, + handlers => { + exited_normally => sub { return 'ok'; }, + exited_abnormally => sub { return 'failure'; }, + }, + }, + 'cyr_dbtool', + $dbname, $engine, 'batch' + ); + return $res unless $res eq 'ok'; + + my $needbytes = 0; + my $buf = ''; + + # The output of `cyr_dbtool` is in theory one logical line at a time. + # However each logical line can have IMAP literals in them. In that + # case, you get a real line that ends with "{nbytes+}\r\n" and you then + # have to read that many bytes of data (including possibly \r's and + # \n's as well). This function potentially reads multiple real lines + # in $line to gather up a single logical line in $buf, and then parses + # that. + # It could be made simpler and more efficient by tokenising the line as + # it goes, but it was extracted from an original codebase which processed + # the entire response buffer from `cyr_dbtool` as a single giant string. + open(FH, "<$basedir/run_dbcommand.out"); + LINE: while (defined(my $line = )) { + $buf .= $line; + + # inside a literal, that's all we need + if ($needbytes) { + my $len = length($line); + if ($len <= $needbytes) { + $needbytes -= $len; + next LINE; + } + substr($line, 0, $needbytes, ''); + $needbytes = 0; + } + + # does this line include a literal, process it now + if ($line =~ m/\{(\d+)\+?\}\r?\n$/s) { + $needbytes = $1; + next LINE; + } + + # we have a line! + + my @array; + my $pos = 0; + my $length = length($buf); + while ($pos < $length) { + my $chr = substr($buf, $pos, 1); + + if ($chr eq ' ') { + $pos++; + next; + } + + if ($chr eq "\n") { + $pos++; + next; + } + + if ($chr eq '{') { + my $end = index($buf, '}', $pos); + die "Missing }" if $end < 0; + my $len = substr($buf, $pos + 1, $end - $pos - 1); + $len =~ s/\+//; + $pos = $end + 1; + my $chr = substr($buf, $pos++, 1); + $chr = substr($buf, $pos++, 1) if $chr eq "\r"; + die "BOGUS LITERAL" unless $chr eq "\n"; + push @array, substr($buf, $pos, $len); + $pos += $len; + next; + } + + if ($chr eq '"') { + my $end = index($buf, '"', $pos + 1); + die "Missing quote" if $end < 0; + push @array, substr($buf, $pos + 1, $end - $pos - 1); + $pos = $end + 1; + next; + } + + my $space = index($buf, ' ', $pos); + my $endline = index($buf, "\n", $pos); + + if ($space < 0) { + push @array, substr($buf, $pos, $endline - $pos); + $pos = $endline; + next; + } + + if ($endline < 0) { + push @array, substr($buf, $pos, $space - $pos); + $pos = $space; + next; + } + + if ($endline < $space) { + push @array, substr($buf, $pos, $endline - $pos); + $pos = $endline; + next; + } + + if ($space < $endline) { + push @array, substr($buf, $pos, $space - $pos); + $pos = $space; + next; + } + + die "shouldn't get here"; + } + + $linecb->(@array); + + $buf = ''; + } + close(FH); + + return 'ok'; +} + +sub run_dbcommand { + my ($self, $dbname, $engine, @items) = @_; + my @array; + $self->run_dbcommand_cb(sub { push @array, @_ }, $dbname, $engine, @items); + return @array; +} + +sub read_mailboxes_db { + my ($self, $params) = @_; + + # run ctl_mboxlist -d to dump mailboxes.db to a file + my $outfile = $params->{outfile} + || $self->get_basedir() . "/$$-ctl_mboxlist.out"; + $self->run_command( + { + cyrus => 1, + redirects => { + stdout => $outfile, + }, + }, + 'ctl_mboxlist', + '-d' + ); - my $filename = $self->{basedir} . "/cyr_info.out"; + return JSON::decode_json(slurp_file($outfile)); +} - $self->run_command({ - cyrus => 1, - redirects => { stdout => $filename }, - }, - 'cyr_info', - # we get -C for free - '-M', $self->_master_conf(), - @args - ); +sub run_cyr_info { + my ($self, @args) = @_; - open RESULTS, '<', $filename - or die "Cannot open $filename for reading: $!"; - my @res = readline(RESULTS); - close RESULTS; - - if ($args[0] eq 'proc') { - # if we see any of our fake daemons, no we didn't - my @fakedaemons = qw(fakesaslauthd fakeldapd); - my $pattern = q{\b(?:} . join(q{|}, @fakedaemons) . q{)\b}; - my $re = qr{$pattern}; - @res = grep { $_ !~ m/$re/ } @res; - } + my $filename = $self->{basedir} . "/cyr_info.out"; - return @res; + $self->run_command( + { + cyrus => 1, + redirects => { stdout => $filename }, + }, + 'cyr_info', + # we get -C for free + '-M', + $self->_master_conf(), + @args + ); + + open RESULTS, '<', $filename + or die "Cannot open $filename for reading: $!"; + my @res = readline(RESULTS); + close RESULTS; + + if ($args[0] eq 'proc') { + # if we see any of our fake daemons, no we didn't + my @fakedaemons = qw(fakesaslauthd fakeldapd); + my $pattern = q{\b(?:} . join(q{|}, @fakedaemons) . q{)\b}; + my $re = qr{$pattern}; + @res = grep { $_ !~ m/$re/ } @res; + } + + return @res; } 1; diff --git a/cassandane/Cassandane/MaildirMessageStore.pm b/cassandane/Cassandane/MaildirMessageStore.pm index cf2a7ddcb0..50fa5f4312 100644 --- a/cassandane/Cassandane/MaildirMessageStore.pm +++ b/cassandane/Cassandane/MaildirMessageStore.pm @@ -45,130 +45,117 @@ use File::Path qw(mkpath rmtree); use lib '.'; use base qw(Cassandane::MessageStore); -sub new -{ - my ($class, %params) = @_; - my %bits = ( - directory => delete $params{directory}, - next_uid => 0 + (delete $params{next_uid} || 1), - uids_to_read => [], - ); - my $self = $class->SUPER::new(%params); - map { $self->{$_} = $bits{$_}; } keys %bits; - return $self; +sub new { + my ($class, %params) = @_; + my %bits = ( + directory => delete $params{directory}, + next_uid => 0 + (delete $params{next_uid} || 1), + uids_to_read => [], + ); + my $self = $class->SUPER::new(%params); + map { $self->{$_} = $bits{$_}; } keys %bits; + return $self; } -sub write_begin -{ - my ($self) = @_; +sub write_begin { + my ($self) = @_; - if (defined $self->{directory} && ! -d $self->{directory}) - { - mkpath($self->{directory}) - or die "Couldn't make path $self->{directory}"; - } + if (defined $self->{directory} && !-d $self->{directory}) { + mkpath($self->{directory}) + or die "Couldn't make path $self->{directory}"; + } } -sub write_message -{ - my ($self, $msg) = @_; - - # find a filename which doesn't exist -- we're appending - my $directory = ($self->{directory} || "."); - my $filename; - for (;;) - { - my $uid = $self->{next_uid}; - $self->{next_uid} = $self->{next_uid} + 1; - $filename = "$directory/$uid."; - last unless ( -f $filename ); - } - - my $fh; - open $fh,'>',$filename - or die "Cannot open $filename for writing: $!"; - print $fh $msg; - close $fh; +sub write_message { + my ($self, $msg) = @_; + + # find a filename which doesn't exist -- we're appending + my $directory = ($self->{directory} || "."); + my $filename; + for (;;) { + my $uid = $self->{next_uid}; + $self->{next_uid} = $self->{next_uid} + 1; + $filename = "$directory/$uid."; + last unless (-f $filename); + } + + my $fh; + open $fh, '>', $filename + or die "Cannot open $filename for writing: $!"; + print $fh $msg; + close $fh; } -sub write_end -{ - my ($self) = @_; - # Nothing to do +sub write_end { + my ($self) = @_; + # Nothing to do } -sub read_begin -{ - my ($self) = @_; - - die "No such directory: $self->{directory}" - if (defined $self->{directory} && ! -d $self->{directory}); - - # Scan the directory for filenames. We need to read the - # whole directory and sort the results because the messages - # need to be returned in uid order not directory order. - $self->{uids_to_read} = []; - my @uids; - my $directory = ($self->{directory} || "."); - my $fh; - opendir $fh,$directory - or die "Cannot open directory $directory for reading: $!"; - - while (my $e = readdir $fh) - { - my ($uid) = ($e =~ m/^(\d+)\.$/); - next unless defined $uid; - push(@uids, 0+$uid); - } - - @uids = sort { $a <=> $b } @uids; - $self->{uids_to_read} = \@uids; - closedir $fh; +sub read_begin { + my ($self) = @_; + + die "No such directory: $self->{directory}" + if (defined $self->{directory} && !-d $self->{directory}); + + # Scan the directory for filenames. We need to read the + # whole directory and sort the results because the messages + # need to be returned in uid order not directory order. + $self->{uids_to_read} = []; + my @uids; + my $directory = ($self->{directory} || "."); + my $fh; + opendir $fh, $directory + or die "Cannot open directory $directory for reading: $!"; + + while (my $e = readdir $fh) { + my ($uid) = ($e =~ m/^(\d+)\.$/); + next unless defined $uid; + push(@uids, 0 + $uid); + } + + @uids = sort { $a <=> $b } @uids; + $self->{uids_to_read} = \@uids; + closedir $fh; } -sub read_message -{ - my ($self) = @_; - - my $directory = ($self->{directory} || "."); - my $filename; - - for (;;) - { - my $uid = shift(@{$self->{uids_to_read}}); - return undef - unless defined $uid; - $filename = "$directory/$uid."; - # keep trying if a message disappeared - last if ( -f $filename ); - } - - my $fh; - open $fh,'<',$filename - or die "Cannot open $filename for reading: $!"; - my $msg = Cassandane::Message->new(fh => $fh); - close $fh; - - return $msg; +sub read_message { + my ($self) = @_; + + my $directory = ($self->{directory} || "."); + my $filename; + + for (;;) { + my $uid = shift(@{ $self->{uids_to_read} }); + return undef + unless defined $uid; + $filename = "$directory/$uid."; + # keep trying if a message disappeared + last if (-f $filename); + } + + my $fh; + open $fh, '<', $filename + or die "Cannot open $filename for reading: $!"; + my $msg = Cassandane::Message->new(fh => $fh); + close $fh; + + return $msg; } -sub read_end -{ - my ($self) = @_; +sub read_end { + my ($self) = @_; - $self->{uids_to_read} = []; + $self->{uids_to_read} = []; } -sub remove -{ - my ($self) = @_; +sub remove { + my ($self) = @_; - if (defined $self->{directory}) - { - my $r = rmtree($self->{directory}); - die "rmtree failed: $!" - if (!$r && ! $!{ENOENT} ); - } + if (defined $self->{directory}) { + my $r = rmtree($self->{directory}); + die "rmtree failed: $!" + if (!$r && !$!{ENOENT}); + } } 1; diff --git a/cassandane/Cassandane/MasterDaemon.pm b/cassandane/Cassandane/MasterDaemon.pm index 379cd66a25..85559a52e6 100644 --- a/cassandane/Cassandane/MasterDaemon.pm +++ b/cassandane/Cassandane/MasterDaemon.pm @@ -44,15 +44,13 @@ use warnings; use lib '.'; use base qw(Cassandane::MasterEntry); -sub new -{ - return shift->SUPER::new(@_); +sub new { + return shift->SUPER::new(@_); } -sub _otherparams -{ - my ($self) = @_; - return ( qw(wait) ); +sub _otherparams { + my ($self) = @_; + return (qw(wait)); } 1; diff --git a/cassandane/Cassandane/MasterEntry.pm b/cassandane/Cassandane/MasterEntry.pm index 4e716b93bc..f91ab89ef5 100644 --- a/cassandane/Cassandane/MasterEntry.pm +++ b/cassandane/Cassandane/MasterEntry.pm @@ -46,70 +46,61 @@ use Cassandane::Util::Log; my $next_tag = 1; -sub new -{ - my ($class, %params) = @_; +sub new { + my ($class, %params) = @_; - my $name = delete $params{name}; - if (!defined $name) - { - $name = "xx$next_tag"; - $next_tag++; - } + my $name = delete $params{name}; + if (!defined $name) { + $name = "xx$next_tag"; + $next_tag++; + } - my $argv = delete $params{argv}; - die "No argv= parameter" - unless defined $argv && scalar @$argv; + my $argv = delete $params{argv}; + die "No argv= parameter" + unless defined $argv && scalar @$argv; - my $config = delete $params{config}; + my $config = delete $params{config}; - my $self = bless - { - name => $name, - argv => $argv, - config => $config, - }, $class; + my $self = bless { + name => $name, + argv => $argv, + config => $config, + }, $class; - foreach my $a ($self->_otherparams()) - { - $self->{$a} = delete $params{$a} - if defined $params{$a}; - } - die "Unexpected parameters: " . join(" ", keys %params) - if scalar %params; + foreach my $a ($self->_otherparams()) { + $self->{$a} = delete $params{$a} + if defined $params{$a}; + } + die "Unexpected parameters: " . join(" ", keys %params) + if scalar %params; - return $self; + return $self; } # Return a hash of key,value pairs which need to go into the line in the # cyrus master config file. -sub master_params -{ - my ($self) = @_; - my $params = {}; - foreach my $a ('name', 'argv', 'config', $self->_otherparams()) - { - $params->{$a} = $self->{$a} - if defined $self->{$a}; - } - return $params; +sub master_params { + my ($self) = @_; + my $params = {}; + foreach my $a ('name', 'argv', 'config', $self->_otherparams()) { + $params->{$a} = $self->{$a} + if defined $self->{$a}; + } + return $params; } -sub set_master_param -{ - my ($self, $param, $value) = @_; +sub set_master_param { + my ($self, $param, $value) = @_; - foreach my $a ('name', 'argv', 'config', $self->_otherparams()) - { - $self->{$a} = $value - if ($a eq $param); - } + foreach my $a ('name', 'argv', 'config', $self->_otherparams()) { + $self->{$a} = $value + if ($a eq $param); + } } -sub set_config -{ - my ($self, $config) = @_; - $self->{config} = $config; +sub set_config { + my ($self, $config) = @_; + $self->{config} = $config; } 1; diff --git a/cassandane/Cassandane/MasterEvent.pm b/cassandane/Cassandane/MasterEvent.pm index dfb2d89942..9215144c70 100644 --- a/cassandane/Cassandane/MasterEvent.pm +++ b/cassandane/Cassandane/MasterEvent.pm @@ -44,15 +44,13 @@ use warnings; use lib '.'; use base qw(Cassandane::MasterEntry); -sub new -{ - return shift->SUPER::new(@_); +sub new { + return shift->SUPER::new(@_); } -sub _otherparams -{ - my ($self) = @_; - return ( qw(period at) ); +sub _otherparams { + my ($self) = @_; + return (qw(period at)); } 1; diff --git a/cassandane/Cassandane/MasterStart.pm b/cassandane/Cassandane/MasterStart.pm index 54abe7d3df..fe645b38d8 100644 --- a/cassandane/Cassandane/MasterStart.pm +++ b/cassandane/Cassandane/MasterStart.pm @@ -44,15 +44,13 @@ use warnings; use lib '.'; use base qw(Cassandane::MasterEntry); -sub new -{ - return shift->SUPER::new(@_); +sub new { + return shift->SUPER::new(@_); } -sub _otherparams -{ - my ($self) = @_; - return (); +sub _otherparams { + my ($self) = @_; + return (); } 1; diff --git a/cassandane/Cassandane/MboxMessageStore.pm b/cassandane/Cassandane/MboxMessageStore.pm index 9444e3dd85..c55f813919 100644 --- a/cassandane/Cassandane/MboxMessageStore.pm +++ b/cassandane/Cassandane/MboxMessageStore.pm @@ -43,143 +43,121 @@ use warnings; use POSIX qw(strftime); use lib '.'; -use base qw(Cassandane::MessageStore); +use base qw(Cassandane::MessageStore); use Cassandane::Util::DateTime qw(from_rfc822); use Cassandane::Message; -sub new -{ - my ($class, %params) = @_; - my %bits = ( - filename => delete $params{filename}, - fh => undef, - ourfh => 0, - lineno => undef, - ); - my $self = $class->SUPER::new(%params); - map { $self->{$_} = $bits{$_}; } keys %bits; - return $self; +sub new { + my ($class, %params) = @_; + my %bits = ( + filename => delete $params{filename}, + fh => undef, + ourfh => 0, + lineno => undef, + ); + my $self = $class->SUPER::new(%params); + map { $self->{$_} = $bits{$_}; } keys %bits; + return $self; } -sub write_begin -{ - my ($self) = @_; - if (defined $self->{filename}) - { - my $fh; - open $fh,'>>',$self->{filename} - or die "Cannot open $self->{filename} for appending: $!"; - $self->{fh} = $fh; - $self->{ourfh} = 1; - } - else - { - $self->{fh} = \*STDOUT; - $self->{ourfh} = 0; - } +sub write_begin { + my ($self) = @_; + if (defined $self->{filename}) { + my $fh; + open $fh, '>>', $self->{filename} + or die "Cannot open $self->{filename} for appending: $!"; + $self->{fh} = $fh; + $self->{ourfh} = 1; + } else { + $self->{fh} = \*STDOUT; + $self->{ourfh} = 0; + } } -sub write_message -{ - my ($self, $msg) = @_; - my $fh = $self->{fh}; +sub write_message { + my ($self, $msg) = @_; + my $fh = $self->{fh}; - my $from = $msg->get_header('from'); - $from =~ s/^.*.*$//; + my $from = $msg->get_header('from'); + $from =~ s/^.*.*$//; - my $dt = from_rfc822($msg->get_header('date')); - my $date = 'Mon Dec 1 00:03:08 2008'; - $date = strftime("%a %b %d %T %Y", localtime($dt->epoch)) - if defined $dt; + my $dt = from_rfc822($msg->get_header('date')); + my $date = 'Mon Dec 1 00:03:08 2008'; + $date = strftime("%a %b %d %T %Y", localtime($dt->epoch)) + if defined $dt; - printf $fh "From %s %s\r\n%s", $from, $date, $msg; + printf $fh "From %s %s\r\n%s", $from, $date, $msg; } -sub write_end -{ - my ($self) = @_; - if ($self->{ourfh}) - { - close $self->{fh}; - } - $self->{fh} = undef; +sub write_end { + my ($self) = @_; + if ($self->{ourfh}) { + close $self->{fh}; + } + $self->{fh} = undef; } -sub read_begin -{ - my ($self) = @_; - if (defined $self->{filename}) - { - my $fh; - - if ($self->{filename} =~ m/\.gz$/) - { - open $fh,'-|',('gunzip', '-dc', $self->{filename}) - or die "Cannot gunzip $self->{filename} for reading: $!"; - } - else - { - open $fh,'<',$self->{filename} - or die "Cannot open $self->{filename} for reading: $!"; - } - $self->{fh} = $fh; - $self->{ourfh} = 1; +sub read_begin { + my ($self) = @_; + if (defined $self->{filename}) { + my $fh; + + if ($self->{filename} =~ m/\.gz$/) { + open $fh, '-|', ('gunzip', '-dc', $self->{filename}) + or die "Cannot gunzip $self->{filename} for reading: $!"; + } else { + open $fh, '<', $self->{filename} + or die "Cannot open $self->{filename} for reading: $!"; } - else - { - $self->{fh} = \*STDIN; - $self->{ourfh} = 0; - } - $self->{lineno} = 0; + $self->{fh} = $fh; + $self->{ourfh} = 1; + } else { + $self->{fh} = \*STDIN; + $self->{ourfh} = 0; + } + $self->{lineno} = 0; } -sub read_message -{ - my ($self) = @_; - my @lines; - - my $fh = $self->{fh}; - while (<$fh>) - { - $self->{lineno}++; - - if ($self->{lineno} == 1) - { - die "Bad mbox format - missing From line" - unless m/^From /; - next; - } - return Cassandane::Message->new(lines => \@lines) - if m/^From /; - - push(@lines, $_); +sub read_message { + my ($self) = @_; + my @lines; + + my $fh = $self->{fh}; + while (<$fh>) { + $self->{lineno}++; + + if ($self->{lineno} == 1) { + die "Bad mbox format - missing From line" + unless m/^From /; + next; } + return Cassandane::Message->new(lines => \@lines) + if m/^From /; + + push(@lines, $_); + } - return undef; + return undef; } -sub read_end -{ - my ($self) = @_; - if ($self->{ourfh}) - { - close $self->{fh}; - } - $self->{fh} = undef; - $self->{lineno} = undef; +sub read_end { + my ($self) = @_; + if ($self->{ourfh}) { + close $self->{fh}; + } + $self->{fh} = undef; + $self->{lineno} = undef; } -sub remove -{ - my ($self) = @_; +sub remove { + my ($self) = @_; - if (defined $self->{filename}) - { - my $r = unlink($self->{filename}); - die "unlink failed: $!" - if (!$r && ! $!{ENOENT} ); - } + if (defined $self->{filename}) { + my $r = unlink($self->{filename}); + die "unlink failed: $!" + if (!$r && !$!{ENOENT}); + } } 1; diff --git a/cassandane/Cassandane/Mboxname.pm b/cassandane/Cassandane/Mboxname.pm index fe9f56c65e..2468c984b3 100644 --- a/cassandane/Cassandane/Mboxname.pm +++ b/cassandane/Cassandane/Mboxname.pm @@ -45,242 +45,216 @@ use overload qw("") => \&to_internal; use lib '.'; use Cassandane::Util::Log; -sub new -{ - my ($class, %params) = @_; - - my $self = bless({ - domain => delete $params{domain}, - userid => delete $params{userid}, - box => delete $params{box}, # internal format, i.e. '.' separated - config => delete $params{config} - || Cassandane::Config::default(), - # TODO is_deleted - }, $class); - - my $s; - my $n = 0; - - $s = delete $params{external}; - if (defined $s) - { - $self->from_external($s); - $n++; - } - - $s = delete $params{internal}; - if (defined $s) - { - $self->from_internal($s); - $n++; - } +sub new { + my ($class, %params) = @_; - $s = delete $params{username}; - if (defined $s) + my $self = bless( { - $self->from_username($s); - $n++; - } - - die "Too many contradictory initialisers" - if $n > 1; - die "Unknown extra arguments" - if scalar(%params); - - return $self; + domain => delete $params{domain}, + userid => delete $params{userid}, + box => delete $params{box}, # internal format, i.e. '.' separated + config => delete $params{config} + || Cassandane::Config::default(), + # TODO is_deleted + }, + $class + ); + + my $s; + my $n = 0; + + $s = delete $params{external}; + if (defined $s) { + $self->from_external($s); + $n++; + } + + $s = delete $params{internal}; + if (defined $s) { + $self->from_internal($s); + $n++; + } + + $s = delete $params{username}; + if (defined $s) { + $self->from_username($s); + $n++; + } + + die "Too many contradictory initialisers" + if $n > 1; + die "Unknown extra arguments" + if scalar(%params); + + return $self; } # We don't just use Clone because we actually # want a shallow clone here. We rely on the # c'tor taking parameters which are the same # as the field names. -sub clone -{ - my ($self) = @_; - return Cassandane::Mboxname->new(%$self); +sub clone { + my ($self) = @_; + return Cassandane::Mboxname->new(%$self); } sub domain { return shift->{domain}; } sub userid { return shift->{userid}; } -sub box { return shift->{box}; } +sub box { return shift->{box}; } -sub _set -{ - my ($self, $domain, $userid, $box) = @_; +sub _set { + my ($self, $domain, $userid, $box) = @_; - die "No Config specified" - unless defined $self->{config}; - my $virtdomains = $self->{config}->get('virtdomains') || 'off'; - die "Domain specified but virtdomains not enabled in instance" - if (defined $domain && $virtdomains eq 'off'); + die "No Config specified" + unless defined $self->{config}; + my $virtdomains = $self->{config}->get('virtdomains') || 'off'; + die "Domain specified but virtdomains not enabled in instance" + if (defined $domain && $virtdomains eq 'off'); - $box = undef if defined $box && $box eq ''; + $box = undef if defined $box && $box eq ''; - $self->{domain} = $domain; - $self->{userid} = $userid; - $self->{box} = $box; + $self->{domain} = $domain; + $self->{userid} = $userid; + $self->{box} = $box; } -sub _reset -{ - my ($self) = @_; - $self->_set(undef, undef, undef); +sub _reset { + my ($self) = @_; + $self->_set(undef, undef, undef); } -sub _external_separator -{ - my ($self) = @_; - die "No Config specified" - unless defined $self->{config}; - return $self->{config}->get_bool('unixhierarchysep', 'off') ? '/' : '.'; +sub _external_separator { + my ($self) = @_; + die "No Config specified" + unless defined $self->{config}; + return $self->{config}->get_bool('unixhierarchysep', 'off') ? '/' : '.'; } -sub _external_separator_regexp -{ - my ($self) = @_; - die "No Config specified" - unless defined $self->{config}; - return $self->{config}->get_bool('unixhierarchysep', 'off') ? qr/\// : qr/\./; +sub _external_separator_regexp { + my ($self) = @_; + die "No Config specified" + unless defined $self->{config}; + return $self->{config}->get_bool('unixhierarchysep', 'off') ? qr/\// : qr/\./; } +sub from_external { + my ($self, $s) = @_; -sub from_external -{ - my ($self, $s) = @_; - - if (!defined $s) - { - $self->_reset(); - return; - } + if (!defined $s) { + $self->_reset(); + return; + } - my ($local, $domain) = ($s =~ m/^([^@]+)@([^@]+)$/); - $local ||= $s; - my $sep = $self->_external_separator_regexp; - my ($prefix, $userid, @comps) = split($sep, $local); - die "Bad external name \"$s\"" - if !defined $userid || $prefix ne 'user'; + my ($local, $domain) = ($s =~ m/^([^@]+)@([^@]+)$/); + $local ||= $s; + my $sep = $self->_external_separator_regexp; + my ($prefix, $userid, @comps) = split($sep, $local); + die "Bad external name \"$s\"" + if !defined $userid || $prefix ne 'user'; - $self->_set($domain, $userid, join('.', @comps)); + $self->_set($domain, $userid, join('.', @comps)); } -sub to_external -{ - my ($self) = @_; +sub to_external { + my ($self) = @_; - my @comps; - push(@comps, 'user', $self->{userid}) if defined $self->{userid}; - push(@comps, split(/\./, $self->{box})) if defined $self->{box}; - my $s = join($self->_external_separator, @comps); - $s .= '@' . $self->{domain} if defined $self->{domain}; + my @comps; + push(@comps, 'user', $self->{userid}) if defined $self->{userid}; + push(@comps, split(/\./, $self->{box})) if defined $self->{box}; + my $s = join($self->_external_separator, @comps); + $s .= '@' . $self->{domain} if defined $self->{domain}; - return ($s eq '' ? undef : $s); + return ($s eq '' ? undef : $s); } -sub from_internal -{ - my ($self, $s) = @_; +sub from_internal { + my ($self, $s) = @_; - if (!defined $s) - { - $self->_reset(); - return; - } + if (!defined $s) { + $self->_reset(); + return; + } - my ($domain, $local) = ($s =~ m/^([^!]+)!([^!]+)$/); - $local ||= $s; - my ($userid, $box) = ($local =~ m/^user\.([^.]*)(.*)$/); - $box =~ s/^\.//; + my ($domain, $local) = ($s =~ m/^([^!]+)!([^!]+)$/); + $local ||= $s; + my ($userid, $box) = ($local =~ m/^user\.([^.]*)(.*)$/); + $box =~ s/^\.//; - $self->_set($domain, $userid, $box); + $self->_set($domain, $userid, $box); } -sub to_internal -{ - my ($self) = @_; +sub to_internal { + my ($self) = @_; - my @comps; - push(@comps, 'user', $self->{userid}) if defined $self->{userid}; - push(@comps, $self->{box}) if defined $self->{box}; - my $s = join('.', @comps); - $s = $self->{domain} . '!' . $s if defined $self->{domain}; + my @comps; + push(@comps, 'user', $self->{userid}) if defined $self->{userid}; + push(@comps, $self->{box}) if defined $self->{box}; + my $s = join('.', @comps); + $s = $self->{domain} . '!' . $s if defined $self->{domain}; - return ($s eq '' ? undef : $s); + return ($s eq '' ? undef : $s); } -sub from_username -{ - my ($self, $s) = @_; +sub from_username { + my ($self, $s) = @_; - if (!defined $s) - { - $self->_reset(); - return; - } + if (!defined $s) { + $self->_reset(); + return; + } - my ($userid, $domain) = ($s =~ m/^([^@]+)@([^@]+)$/); - $userid ||= $s; + my ($userid, $domain) = ($s =~ m/^([^@]+)@([^@]+)$/); + $userid ||= $s; - $self->_set($domain, $userid, undef); + $self->_set($domain, $userid, undef); } -sub to_username -{ - my ($self) = @_; - my $s = $self->{userid} || ''; - $s .= '@' . $self->{domain} if defined $self->{domain}; - return ($s eq '' ? undef : $s); +sub to_username { + my ($self) = @_; + my $s = $self->{userid} || ''; + $s .= '@' . $self->{domain} if defined $self->{domain}; + return ($s eq '' ? undef : $s); } -sub make_child -{ - my ($self, @args) = @_; +sub make_child { + my ($self, @args) = @_; - my $sep = $self->_external_separator; + my $sep = $self->_external_separator; - my @comps; - # Flatten out any array refs and stringify - foreach my $c (@args) - { - if (ref $c && ref $c eq 'ARRAY') - { - map { push(@comps, "" . $_); } @$c; - } - elsif (!ref $c) - { - push(@comps, "" . $c); - } + my @comps; + # Flatten out any array refs and stringify + foreach my $c (@args) { + if (ref $c && ref $c eq 'ARRAY') { + map { push(@comps, "" . $_); } @$c; + } elsif (!ref $c) { + push(@comps, "" . $c); } - map { die "Bad mboxname component \"$_\"" if index($_, $sep) >= 0; } @comps; + } + map { die "Bad mboxname component \"$_\"" if index($_, $sep) >= 0; } @comps; - my $child = $self->clone(); - if (scalar @comps) - { - unshift(@comps, $child->{box}) if defined $child->{box}; - $child->{box} = join('.', @comps); - } + my $child = $self->clone(); + if (scalar @comps) { + unshift(@comps, $child->{box}) if defined $child->{box}; + $child->{box} = join('.', @comps); + } - return $child; + return $child; } -sub make_parent -{ - my ($self, @args) = @_; +sub make_parent { + my ($self, @args) = @_; - my @comps = split(/\./, $self->{box} || ''); - pop(@comps); + my @comps = split(/\./, $self->{box} || ''); + pop(@comps); - my $child = $self->clone(); - if (scalar @comps) - { - $child->{box} = join('.', @comps); - } - else - { - $child->{box} = undef; - } + my $child = $self->clone(); + if (scalar @comps) { + $child->{box} = join('.', @comps); + } else { + $child->{box} = undef; + } - return $child; + return $child; } 1; diff --git a/cassandane/Cassandane/Message.pm b/cassandane/Cassandane/Message.pm index a49d36cefd..3ee5aa83ce 100644 --- a/cassandane/Cassandane/Message.pm +++ b/cassandane/Cassandane/Message.pm @@ -40,7 +40,7 @@ package Cassandane::Message; use strict; use warnings; -use base qw(Clone Exporter); +use base qw(Clone Exporter); use overload qw("") => \&as_string; use Math::Int64; @@ -51,474 +51,423 @@ use Cassandane::Util::SHA; our @EXPORT = qw(base_subject); -sub new -{ - my $class = shift; - my %params = @_; - my $self = { - headers => [], - headers_by_name => {}, - body => undef, - # Other message attributes - e.g. IMAP uid & internaldate - attrs => {}, - }; - - bless $self, $class; - - $self->set_lines(@{$params{lines}}) - if (defined $params{lines}); - $self->set_raw($params{raw}) - if (defined $params{raw}); - $self->set_fh($params{fh}) - if (defined $params{fh}); - # do these one by one to normalise the incoming keys - # and any other logic that set_attribute() wants to do. - if (defined $params{attrs}) - { - while (my ($n, $v) = each %{$params{attrs}}) - { - if (lc($n) eq 'annotation') - { - $self->_set_annotations_from_fetch($v); - next; - } - $self->set_attribute($n, $v); - } +sub new { + my $class = shift; + my %params = @_; + my $self = { + headers => [], + headers_by_name => {}, + body => undef, + # Other message attributes - e.g. IMAP uid & internaldate + attrs => {}, + }; + + bless $self, $class; + + $self->set_lines(@{ $params{lines} }) + if (defined $params{lines}); + $self->set_raw($params{raw}) + if (defined $params{raw}); + $self->set_fh($params{fh}) + if (defined $params{fh}); + # do these one by one to normalise the incoming keys + # and any other logic that set_attribute() wants to do. + if (defined $params{attrs}) { + while (my ($n, $v) = each %{ $params{attrs} }) { + if (lc($n) eq 'annotation') { + $self->_set_annotations_from_fetch($v); + next; + } + $self->set_attribute($n, $v); } + } - return $self; + return $self; } -sub _clear() -{ - my ($self) = @_; - $self->{headers} = []; - $self->{headers_by_name} = {}; - $self->{body} = undef; +sub _clear() { + my ($self) = @_; + $self->{headers} = []; + $self->{headers_by_name} = {}; + $self->{body} = undef; } -sub _canon_name($) -{ - my ($name) = @_; +sub _canon_name($) { + my ($name) = @_; - my @cc = split(/([^[:alnum:]])+/, lc($name)); - map - { - $_ = ucfirst($_); - $_ = 'ID' if m/^Id$/; - } @cc; - return join('', @cc); + my @cc = split(/([^[:alnum:]])+/, lc($name)); + map { + $_ = ucfirst($_); + $_ = 'ID' if m/^Id$/; + } @cc; + return join('', @cc); } -sub _canon_value -{ - my ($value) = @_; - - # Lines in RFC2822 are separated by CR+LF. Lone CR or LF - # is not legal, so we replace them with CR+LF. - # Header field continuation lines (the 2nd or subsequent line) - # are marked with a leading linear whitespace, so we insert a TAB - # character if the input didn't have any. - my $res = ""; - foreach my $l (split(/[\r\n]+/, $value)) - { - $res .= ($res ne "" && !($l =~ m/^[ \t]/) ? "\t" : ""); - $res .= $l; - $res .= "\r\n"; - } - $res .= "\r\n" if ($res eq ""); - return $res; +sub _canon_value { + my ($value) = @_; + + # Lines in RFC2822 are separated by CR+LF. Lone CR or LF + # is not legal, so we replace them with CR+LF. + # Header field continuation lines (the 2nd or subsequent line) + # are marked with a leading linear whitespace, so we insert a TAB + # character if the input didn't have any. + my $res = ""; + foreach my $l (split(/[\r\n]+/, $value)) { + $res .= ($res ne "" && !($l =~ m/^[ \t]/) ? "\t" : ""); + $res .= $l; + $res .= "\r\n"; + } + $res .= "\r\n" if ($res eq ""); + return $res; } -sub get_headers -{ - my ($self, $name) = @_; - $name = lc($name); - return $self->{headers_by_name}->{$name}; +sub get_headers { + my ($self, $name) = @_; + $name = lc($name); + return $self->{headers_by_name}->{$name}; } -sub get_header -{ - my ($self, $name) = @_; - $name = lc($name); - my $values = $self->{headers_by_name}->{$name}; - return undef - unless defined $values; - die "Too many values for header \"$name\"" - unless (scalar @$values == 1); - return $values->[0]; +sub get_header { + my ($self, $name) = @_; + $name = lc($name); + my $values = $self->{headers_by_name}->{$name}; + return undef + unless defined $values; + die "Too many values for header \"$name\"" + unless (scalar @$values == 1); + return $values->[0]; } -sub set_headers -{ - my ($self, $name, @values) = @_; - - $name = lc($name); - map { $_ = "" . $_ } @values; - $self->{headers_by_name}->{$name} = \@values; - my @headers = grep { $_->{name} ne $name } @{$self->{headers}}; - foreach my $v (@values) - { - push(@headers, { name => $name, value => "" . $v }); - } - $self->{headers} = \@headers; +sub set_headers { + my ($self, $name, @values) = @_; + + $name = lc($name); + map { $_ = "" . $_ } @values; + $self->{headers_by_name}->{$name} = \@values; + my @headers = grep { $_->{name} ne $name } @{ $self->{headers} }; + foreach my $v (@values) { + push(@headers, { name => $name, value => "" . $v }); + } + $self->{headers} = \@headers; } -sub remove_headers -{ - my ($self, $name) = @_; +sub remove_headers { + my ($self, $name) = @_; - $name = lc($name); - delete $self->{headers_by_name}->{$name}; - my @headers = grep { $_->{name} ne $name } @{$self->{headers}}; - $self->{headers} = \@headers; + $name = lc($name); + delete $self->{headers_by_name}->{$name}; + my @headers = grep { $_->{name} ne $name } @{ $self->{headers} }; + $self->{headers} = \@headers; } -sub add_header -{ - my ($self, $name, $value) = @_; +sub add_header { + my ($self, $name, $value) = @_; - $value = "" . $value; + $value = "" . $value; - $name = lc($name); - my $values = $self->{headers_by_name}->{$name} || []; - push(@$values, $value); - $self->{headers_by_name}->{$name} = $values; + $name = lc($name); + my $values = $self->{headers_by_name}->{$name} || []; + push(@$values, $value); + $self->{headers_by_name}->{$name} = $values; - # XXX This should probably be unshift rather than push, so that headers - # added chronologically later appear at the top rather than the bottom of - # the resulting header block. But changing it also requires changing a - # bunch of tests' expected results, so that's a project for another time. - push(@{$self->{headers}}, { name => $name, value => $value }); + # XXX This should probably be unshift rather than push, so that headers + # added chronologically later appear at the top rather than the bottom of + # the resulting header block. But changing it also requires changing a + # bunch of tests' expected results, so that's a project for another time. + push(@{ $self->{headers} }, { name => $name, value => $value }); } -sub set_body -{ - my ($self, $text) = @_; - $self->{body} = $text; +sub set_body { + my ($self, $text) = @_; + $self->{body} = $text; } -sub get_body -{ - my ($self) = @_; - return $self->{body}; +sub get_body { + my ($self) = @_; + return $self->{body}; } -sub set_attribute -{ - my ($self, $name, $value) = @_; - $self->{attrs}->{lc($name)} = $value; +sub set_attribute { + my ($self, $name, $value) = @_; + $self->{attrs}->{ lc($name) } = $value; } -sub set_attributes -{ - my ($self, @args) = @_; +sub set_attributes { + my ($self, @args) = @_; - while (my $name = shift @args) - { - my $value = shift @args; - $self->set_attribute($name, $value); - } + while (my $name = shift @args) { + my $value = shift @args; + $self->set_attribute($name, $value); + } } -sub has_attribute -{ - my ($self, $name) = @_; - return exists $self->{attrs}->{lc($name)}; +sub has_attribute { + my ($self, $name) = @_; + return exists $self->{attrs}->{ lc($name) }; } -sub get_attribute -{ - my ($self, $name) = @_; - return $self->{attrs}->{lc($name)}; +sub get_attribute { + my ($self, $name) = @_; + return $self->{attrs}->{ lc($name) }; } -sub _annotation_key -{ - my ($self, $ea) = @_; - return "annotation $ea->{entry} $ea->{attrib}"; +sub _annotation_key { + my ($self, $ea) = @_; + return "annotation $ea->{entry} $ea->{attrib}"; } -sub _validate_ea -{ - my ($self, $ea) = @_; +sub _validate_ea { + my ($self, $ea) = @_; - die "Bad entry \"$ea->{entry}\"" - unless $ea->{entry} =~ m/^(\/[a-z0-9.]+)*$/i; - die "Bad attrib \"$ea->{attrib}\"" - unless $ea->{attrib} =~ m/^value.(shared|priv)$/i; + die "Bad entry \"$ea->{entry}\"" + unless $ea->{entry} =~ m/^(\/[a-z0-9.]+)*$/i; + die "Bad attrib \"$ea->{attrib}\"" + unless $ea->{attrib} =~ m/^value.(shared|priv)$/i; } -sub has_annotation -{ - my $self = shift; - my $ea = shift; - if (ref $ea ne 'HASH') - { - $ea = { entry => $ea, attrib => shift }; - } +sub has_annotation { + my $self = shift; + my $ea = shift; + if (ref $ea ne 'HASH') { + $ea = { entry => $ea, attrib => shift }; + } - $self->_validate_ea($ea); - return $self->has_attribute($self->_annotation_key($ea)); + $self->_validate_ea($ea); + return $self->has_attribute($self->_annotation_key($ea)); } -sub get_annotation -{ - my $self = shift; - my $ea = shift; - if (ref $ea ne 'HASH') - { - $ea = { entry => $ea, attrib => shift }; - } +sub get_annotation { + my $self = shift; + my $ea = shift; + if (ref $ea ne 'HASH') { + $ea = { entry => $ea, attrib => shift }; + } - $self->_validate_ea($ea); - return $self->get_attribute($self->_annotation_key($ea)); + $self->_validate_ea($ea); + return $self->get_attribute($self->_annotation_key($ea)); } -sub list_annotations -{ - my ($self) = @_; - my @res; +sub list_annotations { + my ($self) = @_; + my @res; - foreach my $key (keys %{$self->{attrs}}) - { - my ($dummy, $entry, $attrib) = split / /,$key; - next unless defined $attrib && $dummy eq 'annotation'; - push (@res, { entry => $entry, attrib => $attrib }); - } - return @res; + foreach my $key (keys %{ $self->{attrs} }) { + my ($dummy, $entry, $attrib) = split / /, $key; + next unless defined $attrib && $dummy eq 'annotation'; + push(@res, { entry => $entry, attrib => $attrib }); + } + return @res; } -sub set_annotation -{ - my $self = shift; - my $ea = shift; - if (ref $ea ne 'HASH') - { - $ea = { entry => $ea, attrib => shift }; - } - my $value = shift; +sub set_annotation { + my $self = shift; + my $ea = shift; + if (ref $ea ne 'HASH') { + $ea = { entry => $ea, attrib => shift }; + } + my $value = shift; - $self->_validate_ea($ea); - $self->set_attribute($self->_annotation_key($ea), $value); + $self->_validate_ea($ea); + $self->set_attribute($self->_annotation_key($ea), $value); } -sub _set_annotations_from_fetch -{ - my ($self, $fetchitem) = @_; - my $ea = {}; +sub _set_annotations_from_fetch { + my ($self, $fetchitem) = @_; + my $ea = {}; - foreach my $entry (keys %$fetchitem) - { - $ea->{entry} = $entry; - my $av = $fetchitem->{$entry}; - foreach my $attrib (keys %$av) - { - $ea->{attrib} = $attrib; - $self->set_annotation($ea, $av->{$attrib}); - } + foreach my $entry (keys %$fetchitem) { + $ea->{entry} = $entry; + my $av = $fetchitem->{$entry}; + foreach my $attrib (keys %$av) { + $ea->{attrib} = $attrib; + $self->set_annotation($ea, $av->{$attrib}); } + } } -sub as_string -{ - my ($self) = @_; - my $s = ''; +sub as_string { + my ($self) = @_; + my $s = ''; - foreach my $h (@{$self->{headers}}) - { - $s .= _canon_name($h->{name}) . ": " . _canon_value($h->{value}); - } - $s .= "\r\n"; - $s .= $self->{body} - if defined $self->{body}; + foreach my $h (@{ $self->{headers} }) { + $s .= _canon_name($h->{name}) . ": " . _canon_value($h->{value}); + } + $s .= "\r\n"; + $s .= $self->{body} + if defined $self->{body}; - return $s; + return $s; } -sub set_lines -{ - my ($self, @lines) = @_; - my @pending; +sub set_lines { + my ($self, @lines) = @_; + my @pending; -# xlog "set_lines"; - $self->_clear(); + # xlog "set_lines"; + $self->_clear(); - # First parse the headers - while (scalar @lines) - { - my $line = shift @lines; - # remove trailing end of line chars - $line =~ s/[\r\n]*$//; - -# xlog " raw line \"$line\""; - - if ($line =~ m/^\s/) - { - # continuation line -- gather the line - push(@pending, $line); -# xlog " gathering continuation line"; - next; - } -# xlog " pending \"" . join("CRLF", @pending) . "\""; - - # Not a continuation line; handle the previous pending line - if (@pending) - { -# xlog " finished joined line \"$pending\""; - my $first = shift @pending; - my ($name, $value) = ($first =~ m/^([!-9;-~]+):(.*)$/); - - die "Malformed RFC822 header at or near \"$first\"" - unless defined $value; - - $value = join("\r\n", ($value, @pending)); - - # Lose a single SP after the : which we will be putting - # back when we canonicalise on output. This is technically - # wrong but does make for prettier output *and* circular - # consistency with most messages in the wild. - $value =~ s/^ //; - -# xlog " saving header $name=$value"; - $self->add_header($name, $value); - } - - last if ($line eq ''); - @pending = ( $line ); - } -# xlog " finished with headers, next line is \"" . $lines[0] . "\""; + # First parse the headers + while (scalar @lines) { + my $line = shift @lines; + # remove trailing end of line chars + $line =~ s/[\r\n]*$//; - # Now collect the body...assuming any remains. - my $body = ''; - foreach my $line (@lines) - { - $line =~ s/[\r\n]*$//; - $body .= $line . "\r\n"; + # xlog " raw line \"$line\""; + + if ($line =~ m/^\s/) { + # continuation line -- gather the line + push(@pending, $line); + # xlog " gathering continuation line"; + next; } - $self->set_body($body); -} + # xlog " pending \"" . join("CRLF", @pending) . "\""; -sub set_fh -{ - my ($self, $fh) = @_; - my @lines; - while (<$fh>) - { - push(@lines, $_); + # Not a continuation line; handle the previous pending line + if (@pending) { + # xlog " finished joined line \"$pending\""; + my $first = shift @pending; + my ($name, $value) = ($first =~ m/^([!-9;-~]+):(.*)$/); + + die "Malformed RFC822 header at or near \"$first\"" + unless defined $value; + + $value = join("\r\n", ($value, @pending)); + + # Lose a single SP after the : which we will be putting + # back when we canonicalise on output. This is technically + # wrong but does make for prettier output *and* circular + # consistency with most messages in the wild. + $value =~ s/^ //; + + # xlog " saving header $name=$value"; + $self->add_header($name, $value); } - $self->set_lines(@lines); + + last if ($line eq ''); + @pending = ($line); + } + # xlog " finished with headers, next line is \"" . $lines[0] . "\""; + + # Now collect the body...assuming any remains. + my $body = ''; + foreach my $line (@lines) { + $line =~ s/[\r\n]*$//; + $body .= $line . "\r\n"; + } + $self->set_body($body); } -sub set_raw -{ - my ($self, $raw) = @_; - my $fh; - open $fh,'<',\$raw - or die "Cannot open in-memory file for reading: $!"; - $self->set_fh($fh); - close $fh; +sub set_fh { + my ($self, $fh) = @_; + my @lines; + while (<$fh>) { + push(@lines, $_); + } + $self->set_lines(@lines); } +sub set_raw { + my ($self, $raw) = @_; + my $fh; + open $fh, '<', \$raw + or die "Cannot open in-memory file for reading: $!"; + $self->set_fh($fh); + close $fh; +} -sub set_internaldate -{ - my ($self, $id) = @_; +sub set_internaldate { + my ($self, $id) = @_; - if (ref $id eq 'DateTime') - { - $id = to_rfc3501($id); - } - $self->set_attribute(internaldate => $id); + if (ref $id eq 'DateTime') { + $id = to_rfc3501($id); + } + $self->set_attribute(internaldate => $id); } # Calculate and return the GUID of the message -sub get_guid -{ - my ($self) = @_; +sub get_guid { + my ($self) = @_; - return sha1_hex($self->as_string()); + return sha1_hex($self->as_string()); } # Calculate a CID from a message - this is the CID that the # first message in a new conversation will be assigned. -sub make_cid -{ - my ($self) = @_; - - my $sha1 = sha1($self->as_string()); - my $cid = Math::Int64::uint64(0); - for (0..7) { - $cid <<= 8; - $cid |= ord(substr($sha1, $_, 1)); - } - $cid ^= Math::Int64::string_to_uint64("0x91f3d9e10b690b12", 16); # chosen by fair dice roll - my $res = lc Math::Int64::uint64_to_string($cid, 16); - return sprintf("%016s", $res); +sub make_cid { + my ($self) = @_; + + my $sha1 = sha1($self->as_string()); + my $cid = Math::Int64::uint64(0); + for (0 .. 7) { + $cid <<= 8; + $cid |= ord(substr($sha1, $_, 1)); + } + $cid ^= Math::Int64::string_to_uint64("0x91f3d9e10b690b12", 16) + ; # chosen by fair dice roll + my $res = lc Math::Int64::uint64_to_string($cid, 16); + return sprintf("%016s", $res); } # Handy accessors -sub uid { return shift->get_attribute('uid'); } -sub cid { return shift->get_attribute('cid'); } -sub guid { return shift->get_guid(); } -sub from { return shift->get_header('from'); } -sub to { return shift->get_header('to'); } -sub subject { return shift->get_header('subject'); } +sub uid { return shift->get_attribute('uid'); } +sub cid { return shift->get_attribute('cid'); } +sub guid { return shift->get_guid(); } +sub from { return shift->get_header('from'); } +sub to { return shift->get_header('to'); } +sub subject { return shift->get_header('subject'); } sub messageid { return shift->get_header('message-id'); } -sub date { return shift->get_header('date'); } -sub size { return length(shift->as_string); } +sub date { return shift->get_header('date'); } +sub size { return length(shift->as_string); } # Utility functions # Given a subject string, return the "base subject" # as defined by RFC5256. Used for SORT & THREAD. -sub base_subject -{ - my ($s) = @_; - - # Lexical $_ is a 5.10ism dammit - my $saved_ = $_; - $_ = $s; - - # (1) [ ignoring the RFC2047 decoding ] - # Convert all tabs and continuations to space. - # Convert all multiple spaces to a single space. - s/\s+/ /g; - - # (2) Remove all trailing text of the subject that - # matches the subj-trailer ABNF; repeat until no - # more matches are possible. - while (s/(\s|\(fwd\))$//i) { } - - for (;;) +sub base_subject { + my ($s) = @_; + + # Lexical $_ is a 5.10ism dammit + my $saved_ = $_; + $_ = $s; + + # (1) [ ignoring the RFC2047 decoding ] + # Convert all tabs and continuations to space. + # Convert all multiple spaces to a single space. + s/\s+/ /g; + + # (2) Remove all trailing text of the subject that + # matches the subj-trailer ABNF; repeat until no + # more matches are possible. + while (s/(\s|\(fwd\))$//i) { } + + for (;;) { + # (3) Remove all prefix text of the subject that + # matches the subj-leader ABNF. + my $n = 0; + while (s/^\s+// + || s/^\[[^][]*\]\s*// + || s/^re\s*(\[[^][]*\])?://i + || s/^fw\s*(\[[^][]*\])?://i + || s/^fwd\s*(\[[^][]*\])?://i) { - # (3) Remove all prefix text of the subject that - # matches the subj-leader ABNF. - my $n = 0; - while (s/^\s+// || - s/^\[[^][]*\]\s*// || - s/^re\s*(\[[^][]*\])?://i || - s/^fw\s*(\[[^][]*\])?://i || - s/^fwd\s*(\[[^][]*\])?://i) - { - $n++; - } - last if !$n; - - # (4) If there is prefix text of the subject that - # matches the subj-blob ABNF, and removing that - # prefix leaves a non-empty subj-base, then remove - # the prefix text. - my ($prefix, $base) = m/^\[[^][]*\]\s*(.*)$/; - last if !defined $prefix; - $_ = $base if ($base ne ''); + $n++; } - # (5) Repeat (3) and (4) until no matches remain. - - $s = $_; - $_ = $saved_; - return $s; + last if !$n; + + # (4) If there is prefix text of the subject that + # matches the subj-blob ABNF, and removing that + # prefix leaves a non-empty subj-base, then remove + # the prefix text. + my ($prefix, $base) = m/^\[[^][]*\]\s*(.*)$/; + last if !defined $prefix; + $_ = $base if ($base ne ''); + } + # (5) Repeat (3) and (4) until no matches remain. + + $s = $_; + $_ = $saved_; + return $s; } 1; diff --git a/cassandane/Cassandane/MessageStore.pm b/cassandane/Cassandane/MessageStore.pm index 327b43d674..71be02fcdd 100644 --- a/cassandane/Cassandane/MessageStore.pm +++ b/cassandane/Cassandane/MessageStore.pm @@ -45,75 +45,62 @@ use overload qw("") => \&as_string; use lib '.'; use Cassandane::Util::Log; -sub new -{ - my ($class, %params) = @_; - my $self = { - verbose => delete $params{verbose} || 0, - }; - die "Unknown parameters: " . join(' ', keys %params) - if scalar %params; - return bless $self, $class; +sub new { + my ($class, %params) = @_; + my $self = { verbose => delete $params{verbose} || 0, }; + die "Unknown parameters: " . join(' ', keys %params) + if scalar %params; + return bless $self, $class; } -sub connect -{ - my ($self) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub connect { + my ($self) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub disconnect -{ - my ($self) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub disconnect { + my ($self) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub write_begin -{ - my ($self) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub write_begin { + my ($self) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub write_message -{ - my ($self, $msg, %opts) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub write_message { + my ($self, $msg, %opts) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub write_end -{ - my ($self) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub write_end { + my ($self) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub read_begin -{ - my ($self) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub read_begin { + my ($self) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub read_message -{ - my ($self) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub read_message { + my ($self) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub read_end -{ - my ($self) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub read_end { + my ($self) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub get_client -{ - my ($self) = @_; - die "Unimplemented in base class " . __PACKAGE__; +sub get_client { + my ($self) = @_; + die "Unimplemented in base class " . __PACKAGE__; } -sub as_string -{ - my ($self) = @_; - return "unknown"; +sub as_string { + my ($self) = @_; + return "unknown"; } 1; diff --git a/cassandane/Cassandane/MessageStoreFactory.pm b/cassandane/Cassandane/MessageStoreFactory.pm index 6a194a95df..a65ab423cd 100644 --- a/cassandane/Cassandane/MessageStoreFactory.pm +++ b/cassandane/Cassandane/MessageStoreFactory.pm @@ -43,7 +43,7 @@ use warnings; use Mail::IMAPTalk; use URI; use URI::Escape qw(uri_unescape); -use Exporter (); +use Exporter (); use lib '.'; use Cassandane::MboxMessageStore; @@ -51,149 +51,129 @@ use Cassandane::MaildirMessageStore; use Cassandane::IMAPMessageStore; use Cassandane::POP3MessageStore; -our @ISA = qw(Exporter); +our @ISA = qw(Exporter); our @EXPORT = qw(create); -our %fmethods = -( - mbox => sub { return Cassandane::MboxMessageStore->new(@_); }, - maildir => sub { return Cassandane::MaildirMessageStore->new(@_); }, - imap => sub { return Cassandane::IMAPMessageStore->new(@_); }, - imaps => sub { return Cassandane::IMAPMessageStore->new(@_, ssl => 1); }, - pop3 => sub { return Cassandane::POP3MessageStore->new(@_); }, +our %fmethods = ( + mbox => sub { return Cassandane::MboxMessageStore->new(@_); }, + maildir => sub { return Cassandane::MaildirMessageStore->new(@_); }, + imap => sub { return Cassandane::IMAPMessageStore->new(@_); }, + imaps => sub { return Cassandane::IMAPMessageStore->new(@_, ssl => 1); }, + pop3 => sub { return Cassandane::POP3MessageStore->new(@_); }, ); -our %cleanups = -( - mbox => sub - { - my ($params) = @_; - if (defined $params->{path}) - { - $params->{filename} = $params->{path}; - delete $params->{path}; - } - }, - maildir => sub - { - my ($params) = @_; - if (defined $params->{path}) - { - $params->{directory} = $params->{path}; - delete $params->{path}; - } +our %cleanups = ( + mbox => sub { + my ($params) = @_; + if (defined $params->{path}) { + $params->{filename} = $params->{path}; + delete $params->{path}; } -); - -our %uriparsers = -( - file => sub - { - my ($uri, $params) = @_; - $params->{filename} = $uri->file(); - return 'mbox'; - }, - mbox => sub - { - my ($uri, $params) = @_; - $params->{filename} = $uri->path(); - return 'mbox'; - }, - maildir => sub - { - my ($uri, $params) = @_; - $params->{directory} = $uri->path(); - return 'maildir'; - }, - imap => sub - { - my ($uri, $params) = @_; - - # The URI module doesn't know how to parse imap: URIs. - # But it does know how to parse pop: URIs, and those - # are sufficiently close to work for us (as we ignore - # the special UIDVALIDITY and TYPE stuff anyway). So - # hackily recreate the URI object. - my $u = "" . $uri; - $u =~ s/^imap:/pop:/; - $uri = URI->new($u); - - $params->{host} = $uri->host(); - $uri->_port() and $params->{port} = 0 + $uri->_port(); - if ($uri->userinfo()) - { - my ($u, $p) = split(/:/, $uri->userinfo()); - $params->{username} = uri_unescape($u) - if defined $u; - $params->{password} = uri_unescape($p) - if defined $p; - } - $params->{folder} = substr($uri->path(),1) - if (defined $uri->path() && $uri->path() ne "/"); - return 'imap'; - }, - # XXX need to add a uriparser for imaps urls - 'pop' => sub - { - my ($uri, $params) = @_; - - $params->{host} = $uri->host(); - $uri->_port() and $params->{port} = 0 + $uri->_port(); - if ($uri->userinfo()) - { - my ($u, $p) = split(/:/, $uri->userinfo()); - $params->{username} = uri_unescape($u) - if defined $u; - $params->{password} = uri_unescape($p) - if defined $p; - } - $params->{folder} = substr($uri->path(),1) - if (defined $uri->path() && $uri->path() ne "/"); - return 'pop3'; - }, -); - -sub create -{ - my $class = shift; - my %params = @_; - my $type; - - if (defined $params{uri}) - { - my $uri = URI->new($params{uri}); - delete $params{uri}; - - die "Unsupported URI scheme \"$uri->scheme\"" - unless defined $uriparsers{$uri->scheme()}; - $type = $uriparsers{$uri->scheme()}->($uri, \%params); - } - - if (!defined $type && defined $params{type}) - { - $type = $params{type}; - delete $params{type}; + }, + maildir => sub { + my ($params) = @_; + if (defined $params->{path}) { + $params->{directory} = $params->{path}; + delete $params->{path}; } + } +); - # some heuristics - if (defined $params{directory}) - { - $type = 'maildir'; +our %uriparsers = ( + file => sub { + my ($uri, $params) = @_; + $params->{filename} = $uri->file(); + return 'mbox'; + }, + mbox => sub { + my ($uri, $params) = @_; + $params->{filename} = $uri->path(); + return 'mbox'; + }, + maildir => sub { + my ($uri, $params) = @_; + $params->{directory} = $uri->path(); + return 'maildir'; + }, + imap => sub { + my ($uri, $params) = @_; + + # The URI module doesn't know how to parse imap: URIs. + # But it does know how to parse pop: URIs, and those + # are sufficiently close to work for us (as we ignore + # the special UIDVALIDITY and TYPE stuff anyway). So + # hackily recreate the URI object. + my $u = "" . $uri; + $u =~ s/^imap:/pop:/; + $uri = URI->new($u); + + $params->{host} = $uri->host(); + $uri->_port() and $params->{port} = 0 + $uri->_port(); + if ($uri->userinfo()) { + my ($u, $p) = split(/:/, $uri->userinfo()); + $params->{username} = uri_unescape($u) + if defined $u; + $params->{password} = uri_unescape($p) + if defined $p; } - elsif (defined $params{filename}) - { - $type = 'mbox'; + $params->{folder} = substr($uri->path(), 1) + if (defined $uri->path() && $uri->path() ne "/"); + return 'imap'; + }, + # XXX need to add a uriparser for imaps urls + 'pop' => sub { + my ($uri, $params) = @_; + + $params->{host} = $uri->host(); + $uri->_port() and $params->{port} = 0 + $uri->_port(); + if ($uri->userinfo()) { + my ($u, $p) = split(/:/, $uri->userinfo()); + $params->{username} = uri_unescape($u) + if defined $u; + $params->{password} = uri_unescape($p) + if defined $p; } + $params->{folder} = substr($uri->path(), 1) + if (defined $uri->path() && $uri->path() ne "/"); + return 'pop3'; + }, +); - $type = 'mbox' - unless defined $type; - - $cleanups{$type}->(\%params) - if defined $cleanups{$type}; - - die "No such type \"$type\"" - unless defined $fmethods{$type}; - return $fmethods{$type}->(%params); +sub create { + my $class = shift; + my %params = @_; + my $type; + + if (defined $params{uri}) { + my $uri = URI->new($params{uri}); + delete $params{uri}; + + die "Unsupported URI scheme \"$uri->scheme\"" + unless defined $uriparsers{ $uri->scheme() }; + $type = $uriparsers{ $uri->scheme() }->($uri, \%params); + } + + if (!defined $type && defined $params{type}) { + $type = $params{type}; + delete $params{type}; + } + + # some heuristics + if (defined $params{directory}) { + $type = 'maildir'; + } elsif (defined $params{filename}) { + $type = 'mbox'; + } + + $type = 'mbox' + unless defined $type; + + $cleanups{$type}->(\%params) + if defined $cleanups{$type}; + + die "No such type \"$type\"" + unless defined $fmethods{$type}; + return $fmethods{$type}->(%params); } 1; diff --git a/cassandane/Cassandane/Net/SMTPServer.pm b/cassandane/Cassandane/Net/SMTPServer.pm index 3f1df74157..55c40bb9e1 100644 --- a/cassandane/Cassandane/Net/SMTPServer.pm +++ b/cassandane/Cassandane/Net/SMTPServer.pm @@ -14,101 +14,101 @@ use JSON; use base qw(Net::XmtpServer Net::Server::PreForkSimple); sub new { - my $class = shift; - return $class->SUPER::new(@_); + my $class = shift; + return $class->SUPER::new(@_); } sub override { - my $Self = shift; - my $stage = shift; - if ($Self->{server}{control_file} and -e $Self->{server}{control_file}) { - my $data = decode_json(slurp_file($Self->{server}{control_file})); - if ($data->{$stage}) { - $Self->send_client_resp(@{$data->{$stage}}); - return 1; - } + my $Self = shift; + my $stage = shift; + if ($Self->{server}{control_file} and -e $Self->{server}{control_file}) { + my $data = decode_json(slurp_file($Self->{server}{control_file})); + if ($data->{$stage}) { + $Self->send_client_resp(@{ $data->{$stage} }); + return 1; } - return 0; + } + return 0; } sub mylog { - my $Self = shift; - if ($Self->{server}->{cass_verbose}) { - xlog @_; - } + my $Self = shift; + if ($Self->{server}->{cass_verbose}) { + xlog @_; + } } sub new_connection { - my ($Self) = @_; - $Self->mylog("SMTP: new connection"); - return if $Self->override('new'); - $Self->send_client_resp(220, "localhost ESMTP"); + my ($Self) = @_; + $Self->mylog("SMTP: new connection"); + return if $Self->override('new'); + $Self->send_client_resp(220, "localhost ESMTP"); } sub helo { - my ($Self) = @_; - $Self->mylog("SMTP: HELO"); - return if $Self->override('helo'); - $Self->send_client_resp(250, "localhost", - "AUTH", "DSN", "SIZE 10000", "ENHANCEDSTATUSCODES"); + my ($Self) = @_; + $Self->mylog("SMTP: HELO"); + return if $Self->override('helo'); + $Self->send_client_resp(250, "localhost", + "AUTH", "DSN", "SIZE 10000", "ENHANCEDSTATUSCODES"); } sub mail_from { - my ($Self, $From, @FromExtra) = @_; - $Self->mylog("SMTP: MAIL FROM $From @FromExtra"); - return if $Self->override('from'); - # don't just quietly accept garbage! - if ($From =~ m/[<>]/ || grep { m/[<>]/ } @FromExtra) { - $Self->send_client_resp(501, "Junk in parameters"); - } - $Self->send_client_resp(250, "ok"); + my ($Self, $From, @FromExtra) = @_; + $Self->mylog("SMTP: MAIL FROM $From @FromExtra"); + return if $Self->override('from'); + # don't just quietly accept garbage! + if ($From =~ m/[<>]/ || grep { m/[<>]/ } @FromExtra) { + $Self->send_client_resp(501, "Junk in parameters"); + } + $Self->send_client_resp(250, "ok"); } sub rcpt_to { - my ($Self, $To, @ToExtra) = @_; - $Self->mylog("SMTP: RCPT TO $To @ToExtra"); - return if $Self->override('to'); - - $Self->{_rcpt_to_count}++; - if ($Self->{_rcpt_to_count} > 10) { - $Self->send_client_resp(550, "5.5.3 Too many recipients"); - } elsif ($To =~ /[<>]/ || $To =~ /\@fail\.to\.deliver$/i) { - $Self->send_client_resp(553, "5.1.1 Bad destination mailbox address"); - $Self->mylog("SMTP: 553 5.1.1"); - } else { - $Self->send_client_resp(250, "ok"); - } + my ($Self, $To, @ToExtra) = @_; + $Self->mylog("SMTP: RCPT TO $To @ToExtra"); + return if $Self->override('to'); + + $Self->{_rcpt_to_count}++; + if ($Self->{_rcpt_to_count} > 10) { + $Self->send_client_resp(550, "5.5.3 Too many recipients"); + } elsif ($To =~ /[<>]/ || $To =~ /\@fail\.to\.deliver$/i) { + $Self->send_client_resp(553, "5.1.1 Bad destination mailbox address"); + $Self->mylog("SMTP: 553 5.1.1"); + } else { + $Self->send_client_resp(250, "ok"); + } } sub begin_data { - my ($Self) = @_; - $Self->mylog("SMTP: BEGIN DATA"); - return if $Self->override('begin_data'); - $Self->send_client_resp(354, "ok"); - return 1; + my ($Self) = @_; + $Self->mylog("SMTP: BEGIN DATA"); + return if $Self->override('begin_data'); + $Self->send_client_resp(354, "ok"); + return 1; } sub end_data { - my ($Self) = @_; - $Self->mylog("SMTP: END DATA"); - return if $Self->override('end_data'); - $Self->send_client_resp(250, "ok"); - return 0; + my ($Self) = @_; + $Self->mylog("SMTP: END DATA"); + return if $Self->override('end_data'); + $Self->send_client_resp(250, "ok"); + return 0; } sub rset { - my ($Self) = @_; - $Self->mylog("SMTP: RSET"); - return if $Self->override('rset'); - $Self->send_client_resp(250, "ok"); - return 0; + my ($Self) = @_; + $Self->mylog("SMTP: RSET"); + return if $Self->override('rset'); + $Self->send_client_resp(250, "ok"); + return 0; } sub quit { - my ($Self) = @_; - $Self->mylog("SMTP: QUIT"); - return if $Self->override('quit'); - $Self->send_client_resp(221, "bye!"); + my ($Self) = @_; + $Self->mylog("SMTP: QUIT"); + return if $Self->override('quit'); + $Self->send_client_resp(221, "bye!"); } 1; diff --git a/cassandane/Cassandane/POP3MessageStore.pm b/cassandane/Cassandane/POP3MessageStore.pm index cec493ec60..488612a524 100644 --- a/cassandane/Cassandane/POP3MessageStore.pm +++ b/cassandane/Cassandane/POP3MessageStore.pm @@ -46,126 +46,113 @@ use lib '.'; use base qw(Cassandane::MessageStore); use Cassandane::Util::Log; -sub new -{ - my ($class, %params) = @_; - my %bits = ( - host => delete $params{host} || 'localhost', - port => 0 + (delete $params{port} || 110), - folder => delete $params{folder} || 'INBOX', - username => delete $params{username}, - password => delete $params{password}, - client => undef, - # state for streaming read - next_id => undef, - last_id => undef, - ); - - # Sadly, it seems the version of Net::POP3 I'm using has - # neither support for specifying inet6 nor a way of passing - # an already connected socket. So, no IPv6 for us. - my $af = delete $params{address_family}; - die "Sorry, only INET supported for POP3" - if (defined $af && $af ne 'inet'); - - my $self = $class->SUPER::new(%params); - map { $self->{$_} = $bits{$_}; } keys %bits; - return $self; +sub new { + my ($class, %params) = @_; + my %bits = ( + host => delete $params{host} || 'localhost', + port => 0 + (delete $params{port} || 110), + folder => delete $params{folder} || 'INBOX', + username => delete $params{username}, + password => delete $params{password}, + client => undef, + # state for streaming read + next_id => undef, + last_id => undef, + ); + + # Sadly, it seems the version of Net::POP3 I'm using has + # neither support for specifying inet6 nor a way of passing + # an already connected socket. So, no IPv6 for us. + my $af = delete $params{address_family}; + die "Sorry, only INET supported for POP3" + if (defined $af && $af ne 'inet'); + + my $self = $class->SUPER::new(%params); + map { $self->{$_} = $bits{$_}; } keys %bits; + return $self; } -sub connect -{ - my ($self) = @_; - - # if already successfully connected, do nothing - return - if (defined $self->{client}); - - # xlog "connect: creating POP3 object"; - my %opts; - $opts{Debug} = $self->{verbose} - if $self->{verbose}; - my $client = Net::POP3->new("$self->{host}:$self->{port}", %opts) - or die "Cannot create Net::POP3 object"; - - my ($uu, $ud) = split(/@/, $self->{username}); - - $ud = (defined $ud ? "\@$ud" : ""); - - my $ff = $self->{folder}; - if ($ff =~ m/^inbox$/i) - { - $ff = ''; - } - elsif ($ff =~ m/^inbox\./i) - { - $ff =~ s/^inbox\./+/i; - } - else - { - $ff = "+$ff"; - } - - my $pop3_username = "$uu$ff$ud"; - # xlog "connect: pop3_username=\"$pop3_username\"", ; - # xlog "connect: password=\"" . $self->{password} . "\""; - - my $res = $client->login($pop3_username, $self->{password}) - or die "Cannot login via POP3"; - $res = 0 if ($res eq '0E0'); - $res = 0 + $res; - - xlog "connect: found $res messages"; - - $self->{last_id} = $res; - $self->{client} = $client; +sub connect { + my ($self) = @_; + + # if already successfully connected, do nothing + return + if (defined $self->{client}); + + # xlog "connect: creating POP3 object"; + my %opts; + $opts{Debug} = $self->{verbose} + if $self->{verbose}; + my $client = Net::POP3->new("$self->{host}:$self->{port}", %opts) + or die "Cannot create Net::POP3 object"; + + my ($uu, $ud) = split(/@/, $self->{username}); + + $ud = (defined $ud ? "\@$ud" : ""); + + my $ff = $self->{folder}; + if ($ff =~ m/^inbox$/i) { + $ff = ''; + } elsif ($ff =~ m/^inbox\./i) { + $ff =~ s/^inbox\./+/i; + } else { + $ff = "+$ff"; + } + + my $pop3_username = "$uu$ff$ud"; + # xlog "connect: pop3_username=\"$pop3_username\"", ; + # xlog "connect: password=\"" . $self->{password} . "\""; + + my $res = $client->login($pop3_username, $self->{password}) + or die "Cannot login via POP3"; + $res = 0 if ($res eq '0E0'); + $res = 0 + $res; + + xlog "connect: found $res messages"; + + $self->{last_id} = $res; + $self->{client} = $client; } -sub disconnect -{ - my ($self) = @_; +sub disconnect { + my ($self) = @_; - if (defined $self->{client}) - { - $self->{client}->quit(); - $self->{client} = undef; - } + if (defined $self->{client}) { + $self->{client}->quit(); + $self->{client} = undef; + } } -sub read_begin -{ - my ($self) = @_; +sub read_begin { + my ($self) = @_; - $self->connect(); - $self->{next_id} = 1; + $self->connect(); + $self->{next_id} = 1; } -sub read_message -{ - my ($self) = @_; +sub read_message { + my ($self) = @_; - my $id = $self->{next_id}; - return undef - if ($id > $self->{last_id}); - $self->{next_id}++; + my $id = $self->{next_id}; + return undef + if ($id > $self->{last_id}); + $self->{next_id}++; - return Cassandane::Message->new(fh => $self->{client}->getfh($id)); + return Cassandane::Message->new(fh => $self->{client}->getfh($id)); } -sub read_end -{ - my ($self) = @_; +sub read_end { + my ($self) = @_; - $self->disconnect(); - $self->{next_id} = undef; + $self->disconnect(); + $self->{next_id} = undef; } -sub get_client -{ - my ($self) = @_; +sub get_client { + my ($self) = @_; - $self->connect(); - return $self->{client}; + $self->connect(); + return $self->{client}; } 1; diff --git a/cassandane/Cassandane/PortManager.pm b/cassandane/Cassandane/PortManager.pm index c664499a12..f23a6e6281 100644 --- a/cassandane/Cassandane/PortManager.pm +++ b/cassandane/Cassandane/PortManager.pm @@ -51,81 +51,72 @@ my $max_ports = 20; my $next_port = 0; my %allocated; -sub alloc -{ - my $host = shift; +sub alloc { + my $host = shift; - if (!defined $base_port) - { - my $workerid = $ENV{TEST_UNIT_WORKER_ID} || '1'; - die "Invalid TEST_UNIT_WORKER_ID - code not run in Worker context" - if (defined($workerid) && $workerid eq 'invalid'); - my $cassini = Cassandane::Cassini->instance(); - my $cassini_base_port = $cassini->val('cassandane', 'base_port') // 0; - $base_port = 0 + $cassini_base_port || 9100; - $base_port += $max_ports * ($workerid-1); + if (!defined $base_port) { + my $workerid = $ENV{TEST_UNIT_WORKER_ID} || '1'; + die "Invalid TEST_UNIT_WORKER_ID - code not run in Worker context" + if (defined($workerid) && $workerid eq 'invalid'); + my $cassini = Cassandane::Cassini->instance(); + my $cassini_base_port = $cassini->val('cassandane', 'base_port') // 0; + $base_port = 0 + $cassini_base_port || 9100; + $base_port += $max_ports * ($workerid - 1); + } + for (my $i = 0; $i < $max_ports; $i++) { + my $port = $base_port + (($next_port + $i) % $max_ports); + if (!$allocated{$port} && port_is_free($host, $port)) { + $allocated{$port} = 1; + $next_port++; + return $port; } - for (my $i = 0 ; $i < $max_ports ; $i++) - { - my $port = $base_port + (($next_port + $i) % $max_ports); - if (!$allocated{$port} && port_is_free($host, $port)) - { - $allocated{$port} = 1; - $next_port++; - return $port; - } - } - die "No ports remaining"; + } + die "No ports remaining"; } -sub port_is_free -{ - my $host = shift; - my $port = shift; - - # If we can bind to the port no one else is currently using it - my $socket = IO::Socket::IP->new( - LocalAddr => $host, - LocalPort => $port, - Proto => 'tcp', - ReuseAddr => 1, - ); +sub port_is_free { + my $host = shift; + my $port = shift; - unless ($socket) { - if ($! == EADDRINUSE) { - return 0; - } + # If we can bind to the port no one else is currently using it + my $socket = IO::Socket::IP->new( + LocalAddr => $host, + LocalPort => $port, + Proto => 'tcp', + ReuseAddr => 1, + ); - warn "Unknown error binding $host:$port: $!\n"; - return 0; + unless ($socket) { + if ($! == EADDRINUSE) { + return 0; } - return 1; + warn "Unknown error binding $host:$port: $!\n"; + return 0; + } + + return 1; } -sub free -{ - my ($port) = @_; +sub free { + my ($port) = @_; - return unless defined $base_port; + return unless defined $base_port; - $allocated{$port} = 0; + $allocated{$port} = 0; } -sub free_all -{ - return unless defined $base_port; - my @freed; - for (my $i = 0 ; $i < $max_ports ; $i++) - { - my $port = $base_port + $i; - if ($allocated{$port}) - { - $allocated{$port} = 0; - push(@freed, $port); - } +sub free_all { + return unless defined $base_port; + my @freed; + for (my $i = 0; $i < $max_ports; $i++) { + my $port = $base_port + $i; + if ($allocated{$port}) { + $allocated{$port} = 0; + push(@freed, $port); } - return @freed; + } + return @freed; } 1; diff --git a/cassandane/Cassandane/SequenceGenerator.pm b/cassandane/Cassandane/SequenceGenerator.pm index 22aafdbd62..d1e00a88e6 100644 --- a/cassandane/Cassandane/SequenceGenerator.pm +++ b/cassandane/Cassandane/SequenceGenerator.pm @@ -42,7 +42,7 @@ use strict; use warnings; use lib '.'; -use base qw(Cassandane::Generator); +use base qw(Cassandane::Generator); use Cassandane::Util::DateTime qw(to_iso8601); use Cassandane::Address; use Cassandane::Message; @@ -50,19 +50,18 @@ use Cassandane::Util::Log; use Cassandane::Util::Words; my $NMESSAGES = 240; -my $DELTAT = 3600; # seconds +my $DELTAT = 3600; # seconds -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); - $self->{nmessages} = $NMESSAGES; - $self->{deltat} = $DELTAT; - $self->{next_date} = DateTime->now->epoch - - $self->{deltat} * ($self->{nmessages}+1); + $self->{nmessages} = $NMESSAGES; + $self->{deltat} = $DELTAT; + $self->{next_date} + = DateTime->now->epoch - $self->{deltat} * ($self->{nmessages} + 1); - return $self; + return $self; } # @@ -70,22 +69,21 @@ sub new # Args: Generator, (param-key => param-value ... ) # Returns: Message ref # -sub generate -{ - my ($self, %params) = @_; +sub generate { + my ($self, %params) = @_; - return undef - if (!$self->{nmessages}); + return undef + if (!$self->{nmessages}); - my $dt = DateTime->from_epoch( epoch => $self->{next_date} ); - $params{subject} = "message at " . to_iso8601($dt); - $params{date} = $dt; - $self->{next_date} += $self->{deltat}; + my $dt = DateTime->from_epoch(epoch => $self->{next_date}); + $params{subject} = "message at " . to_iso8601($dt); + $params{date} = $dt; + $self->{next_date} += $self->{deltat}; - my $msg = $self->SUPER::generate(%params); - $self->{nmessages}--; + my $msg = $self->SUPER::generate(%params); + $self->{nmessages}--; - return $msg; + return $msg; } 1; diff --git a/cassandane/Cassandane/Service.pm b/cassandane/Cassandane/Service.pm index 010ec123a9..7fe3db3a4b 100644 --- a/cassandane/Cassandane/Service.pm +++ b/cassandane/Cassandane/Service.pm @@ -48,128 +48,112 @@ use Cassandane::Util::Log; use Cassandane::MessageStoreFactory; use Cassandane::Util::Socket; -sub new -{ - my ($class, %params) = @_; - - my $host = '127.0.0.1'; - $host = delete $params{host} - if (exists $params{host}); - my $port = delete $params{port}; - my $type = delete $params{type} || 'unknown'; - my $instance = delete $params{instance}; - - my $self = $class->SUPER::new(%params); - - # GenericListener is a bit different from MasterEntry et al, and must - # always have a config specified, so pass through the default explicitly - my $listener_config = $params{config} || $instance->{config}; - - $self->{_listener} = Cassandane::GenericListener->new( - name => $params{name}, - host => $host, - port => $port, - config => $listener_config); - $self->{type} = $type; - - return $self; +sub new { + my ($class, %params) = @_; + + my $host = '127.0.0.1'; + $host = delete $params{host} + if (exists $params{host}); + my $port = delete $params{port}; + my $type = delete $params{type} || 'unknown'; + my $instance = delete $params{instance}; + + my $self = $class->SUPER::new(%params); + + # GenericListener is a bit different from MasterEntry et al, and must + # always have a config specified, so pass through the default explicitly + my $listener_config = $params{config} || $instance->{config}; + + $self->{_listener} = Cassandane::GenericListener->new( + name => $params{name}, + host => $host, + port => $port, + config => $listener_config + ); + $self->{type} = $type; + + return $self; } -sub _otherparams -{ - my ($self) = @_; - return ( qw(prefork maxchild maxforkrate maxfds proto babysit) ); +sub _otherparams { + my ($self) = @_; + return (qw(prefork maxchild maxforkrate maxfds proto babysit)); } -sub set_config -{ - my ($self, $config) = @_; - $self->SUPER::set_config($config); - $self->{_listener}->set_config($config); +sub set_config { + my ($self, $config) = @_; + $self->SUPER::set_config($config); + $self->{_listener}->set_config($config); } # Return the host -sub host -{ - my ($self) = @_; - return $self->{_listener}->host(); +sub host { + my ($self) = @_; + return $self->{_listener}->host(); } # Return the port -sub port -{ - my ($self) = @_; - return $self->{_listener}->port(); +sub port { + my ($self) = @_; + return $self->{_listener}->port(); } -sub set_port -{ - my ($self, $port) = @_; - return $self->{_listener}->set_port($port); +sub set_port { + my ($self, $port) = @_; + return $self->{_listener}->set_port($port); } # Return a hash of parameters suitable for passing # to MessageStoreFactory::create. -sub store_params -{ - my ($self, %params) = @_; - - my $pp = $self->{_listener}->connection_params(%params); - $pp->{type} ||= $self->{type}; - $pp->{username} ||= 'cassandane'; - $pp->{password} ||= 'testpw'; - return $pp; +sub store_params { + my ($self, %params) = @_; + + my $pp = $self->{_listener}->connection_params(%params); + $pp->{type} ||= $self->{type}; + $pp->{username} ||= 'cassandane'; + $pp->{password} ||= 'testpw'; + return $pp; } -sub create_store -{ - my ($self, @args) = @_; - my $params = $self->store_params(@args); - return Cassandane::MessageStoreFactory->create(%$params); +sub create_store { + my ($self, @args) = @_; + my $params = $self->store_params(@args); + return Cassandane::MessageStoreFactory->create(%$params); } sub get_socket { - my ($self) = @_; - return create_client_socket( - $self->address_family(), - $self->host(), - $self->port() - ); + my ($self) = @_; + return create_client_socket($self->address_family(), $self->host(), + $self->port()); } # Return a hash of key,value pairs which need to go into the line in the # cyrus master config file. -sub master_params -{ - my ($self) = @_; - my $params = $self->SUPER::master_params(); - $params->{listen} = $self->address(); - return $params; +sub master_params { + my ($self) = @_; + my $params = $self->SUPER::master_params(); + $params->{listen} = $self->address(); + return $params; } -sub address -{ - my ($self) = @_; - return $self->{_listener}->address(); +sub address { + my ($self) = @_; + return $self->{_listener}->address(); } -sub address_family -{ - my ($self) = @_; - return $self->{_listener}->address_family(); +sub address_family { + my ($self) = @_; + return $self->{_listener}->address_family(); } -sub is_listening -{ - my ($self) = @_; - return $self->{_listener}->is_listening(); +sub is_listening { + my ($self) = @_; + return $self->{_listener}->is_listening(); } -sub describe -{ - my ($self) = @_; - $self->{_listener}->describe(); +sub describe { + my ($self) = @_; + $self->{_listener}->describe(); } - 1; diff --git a/cassandane/Cassandane/ServiceFactory.pm b/cassandane/Cassandane/ServiceFactory.pm index 5507431ee6..195b50e303 100644 --- a/cassandane/Cassandane/ServiceFactory.pm +++ b/cassandane/Cassandane/ServiceFactory.pm @@ -46,87 +46,75 @@ use Cassandane::Util::Log; use Cassandane::Service; use Cassandane::IMAPService; -sub create -{ - my ($class, %params) = @_; +sub create { + my ($class, %params) = @_; - my $name = $params{name}; - die "No name specified" - unless defined $name; + my $name = $params{name}; + die "No name specified" + unless defined $name; - # if caller knows what they're asking for, don't try to guess - if (defined $params{argv}) { - return Cassandane::Service->new(%params); - } + # if caller knows what they're asking for, don't try to guess + if (defined $params{argv}) { + return Cassandane::Service->new(%params); + } - # try and guess some service-specific defaults - if ($name =~ m/imaps/) - { - return Cassandane::IMAPService->new( - argv => ['imapd', '-s'], - %params); - } - elsif ($name =~ m/imap/) - { - return Cassandane::IMAPService->new( - argv => ['imapd'], - %params); - } - elsif ($name =~ m/sync/) - { - return Cassandane::Service->new( - argv => ['imapd'], - %params); - } - elsif ($name =~ m/http/) - { - return Cassandane::Service->new( - argv => ['httpd'], - %params); - } - elsif ($name =~ m/lmtp/) - { - return Cassandane::Service->new( - argv => ['lmtpd'], - %params); - } - elsif ($name =~ m/sieve/) - { - return Cassandane::Service->new( - argv => ['timsieved'], - %params); - } - elsif ($name =~ m/nntp/) - { - return Cassandane::Service->new( - argv => ['nntpd'], - %params); - } - elsif ($name =~ m/smmap/) - { - return Cassandane::Service->new( - argv => ['smmapd'], - %params); - } - elsif ($name =~ m/pop/) - { - return Cassandane::Service->new( - type => 'pop3', - argv => ['pop3d'], - %params); - } - elsif ($name =~ m/ptloader/) - { - return Cassandane::Service->new( - type => 'ptloader', - argv => ['ptloader', '-d', '99'], - port => '@basedir@/conf/ptsock', - %params); - } - else - { - die "$name: No command specified and cannot guess a default"; - } + # try and guess some service-specific defaults + if ($name =~ m/imaps/) { + return Cassandane::IMAPService->new( + argv => [ 'imapd', '-s' ], + %params + ); + } elsif ($name =~ m/imap/) { + return Cassandane::IMAPService->new( + argv => ['imapd'], + %params + ); + } elsif ($name =~ m/sync/) { + return Cassandane::Service->new( + argv => ['imapd'], + %params + ); + } elsif ($name =~ m/http/) { + return Cassandane::Service->new( + argv => ['httpd'], + %params + ); + } elsif ($name =~ m/lmtp/) { + return Cassandane::Service->new( + argv => ['lmtpd'], + %params + ); + } elsif ($name =~ m/sieve/) { + return Cassandane::Service->new( + argv => ['timsieved'], + %params + ); + } elsif ($name =~ m/nntp/) { + return Cassandane::Service->new( + argv => ['nntpd'], + %params + ); + } elsif ($name =~ m/smmap/) { + return Cassandane::Service->new( + argv => ['smmapd'], + %params + ); + } elsif ($name =~ m/pop/) { + return Cassandane::Service->new( + type => 'pop3', + argv => ['pop3d'], + %params + ); + } elsif ($name =~ m/ptloader/) { + return Cassandane::Service->new( + type => 'ptloader', + argv => [ 'ptloader', '-d', '99' ], + port => '@basedir@/conf/ptsock', + %params + ); + } else { + die "$name: No command specified and cannot guess a default"; + } } 1; diff --git a/cassandane/Cassandane/Test/Address.pm b/cassandane/Cassandane/Test/Address.pm index ceeb7cac41..f0dff82d37 100644 --- a/cassandane/Cassandane/Test/Address.pm +++ b/cassandane/Cassandane/Test/Address.pm @@ -45,39 +45,36 @@ use lib '.'; use base qw(Cassandane::Unit::TestCase); use Cassandane::Address; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } -sub test_default_ctor -{ - my ($self) = @_; - my $a = Cassandane::Address->new(); - $self->assert(!defined $a->name); - $self->assert($a->localpart eq 'unknown-user'); - $self->assert($a->domain eq 'unspecified-domain'); - $self->assert($a->address eq 'unknown-user@unspecified-domain'); - $self->assert($a->as_string eq ''); - $self->assert("" . $a eq ''); +sub test_default_ctor { + my ($self) = @_; + my $a = Cassandane::Address->new(); + $self->assert(!defined $a->name); + $self->assert($a->localpart eq 'unknown-user'); + $self->assert($a->domain eq 'unspecified-domain'); + $self->assert($a->address eq 'unknown-user@unspecified-domain'); + $self->assert($a->as_string eq ''); + $self->assert("" . $a eq ''); } -sub test_full_ctor -{ - my ($self) = @_; - my $a = Cassandane::Address->new( - name => 'Fred J. Bloggs', - localpart => 'fbloggs', - domain => 'fastmail.fm', - ); - $self->assert($a->name eq 'Fred J. Bloggs'); - $self->assert($a->localpart eq 'fbloggs'); - $self->assert($a->domain eq 'fastmail.fm'); - $self->assert($a->address eq 'fbloggs@fastmail.fm'); - $self->assert($a->as_string eq 'Fred J. Bloggs '); - $self->assert("" . $a eq 'Fred J. Bloggs '); +sub test_full_ctor { + my ($self) = @_; + my $a = Cassandane::Address->new( + name => 'Fred J. Bloggs', + localpart => 'fbloggs', + domain => 'fastmail.fm', + ); + $self->assert($a->name eq 'Fred J. Bloggs'); + $self->assert($a->localpart eq 'fbloggs'); + $self->assert($a->domain eq 'fastmail.fm'); + $self->assert($a->address eq 'fbloggs@fastmail.fm'); + $self->assert($a->as_string eq 'Fred J. Bloggs '); + $self->assert("" . $a eq 'Fred J. Bloggs '); } 1; diff --git a/cassandane/Cassandane/Test/Cassini.pm b/cassandane/Cassandane/Test/Cassini.pm index 673c71ef8b..4672baf2ce 100644 --- a/cassandane/Cassandane/Test/Cassini.pm +++ b/cassandane/Cassandane/Test/Cassini.pm @@ -48,332 +48,295 @@ use base qw(Cassandane::Unit::TestCase); use Cassandane::Cassini; use Cassandane::Util::Log; -sub new -{ - my $class = shift; - return $class->SUPER::new(@_); +sub new { + my $class = shift; + return $class->SUPER::new(@_); } -sub write_inifile -{ - my ($options, %contents) = @_; +sub write_inifile { + my ($options, %contents) = @_; - my $filename = $options->{filename} || 'cassandane.ini'; + my $filename = $options->{filename} || 'cassandane.ini'; - my %sections; - foreach my $k (keys %contents) - { - my ($sec, $param) = split(/\./, $k); - $sections{$sec} ||= {}; - $sections{$sec}->{$param} = $contents{$k}; - } + my %sections; + foreach my $k (keys %contents) { + my ($sec, $param) = split(/\./, $k); + $sections{$sec} ||= {}; + $sections{$sec}->{$param} = $contents{$k}; + } - open INIFILE, '>', $filename - or die "Cannot open file $filename for writing: $!"; - foreach my $sec (keys %sections) - { - printf INIFILE "[%s]\n", $sec; - foreach my $param (keys %{$sections{$sec}}) - { - printf INIFILE "%s=%s\n", $param, $sections{$sec}->{$param}; - } + open INIFILE, '>', $filename + or die "Cannot open file $filename for writing: $!"; + foreach my $sec (keys %sections) { + printf INIFILE "[%s]\n", $sec; + foreach my $param (keys %{ $sections{$sec} }) { + printf INIFILE "%s=%s\n", $param, $sections{$sec}->{$param}; } - close INIFILE; + } + close INIFILE; +} + +sub test_basic { + my ($self) = @_; + + local $CWD = tempdir(CLEANUP => 1); + + xlog "Working in temporary directory $CWD"; + # data thanks to hipsteripsum.me + write_inifile({}, 'helvetica.blog' => 'ethical',); + + my $cassini = new Cassandane::Cassini; + + # Don't find non-existant param in non-existant section + $self->assert_null($cassini->val('swag', 'quinoa')); + # or return the default + $self->assert_str_equals('whatever', + $cassini->val('swag', 'quinoa', 'whatever')); + + # Don't find non-existant param in existant section + $self->assert_null($cassini->val('helvetica', 'quinoa')); + # or return the default + $self->assert_str_equals('whatever', + $cassini->val('helvetica', 'quinoa', 'whatever')); + + # Don't find param in non-existant section where the + # param does exist in another section + $self->assert_null($cassini->val('swag', 'blog')); + # or return the default + $self->assert_str_equals('whatever', + $cassini->val('swag', 'blog', 'whatever')); + + # Don't find case aliases for existant param + $self->assert_null($cassini->val('Helvetica', 'blog')); + $self->assert_null($cassini->val('helvetica', 'Blog')); + $self->assert_null($cassini->val('HELvEtIca', 'blOG')); + + # Do find exact match for existant param + $self->assert_str_equals('ethical', $cassini->val('helvetica', 'blog')); } -sub test_basic -{ - my ($self) = @_; - - local $CWD = tempdir(CLEANUP => 1); - - xlog "Working in temporary directory $CWD"; - # data thanks to hipsteripsum.me - write_inifile({}, - 'helvetica.blog' => 'ethical', - ); - - my $cassini = new Cassandane::Cassini; - - # Don't find non-existant param in non-existant section - $self->assert_null($cassini->val('swag', 'quinoa')); - # or return the default - $self->assert_str_equals('whatever', - $cassini->val('swag', 'quinoa', 'whatever')); - - # Don't find non-existant param in existant section - $self->assert_null($cassini->val('helvetica', 'quinoa')); - # or return the default - $self->assert_str_equals('whatever', - $cassini->val('helvetica', 'quinoa', 'whatever')); - - # Don't find param in non-existant section where the - # param does exist in another section - $self->assert_null($cassini->val('swag', 'blog')); - # or return the default - $self->assert_str_equals('whatever', - $cassini->val('swag', 'blog', 'whatever')); - - # Don't find case aliases for existant param - $self->assert_null($cassini->val('Helvetica', 'blog')); - $self->assert_null($cassini->val('helvetica', 'Blog')); - $self->assert_null($cassini->val('HELvEtIca', 'blOG')); - - # Do find exact match for existant param - $self->assert_str_equals('ethical', $cassini->val('helvetica', 'blog')); +sub test_boolval { + my ($self) = @_; + + local $CWD = tempdir(CLEANUP => 1); + + xlog "Working in temporary directory $CWD"; + # data thanks to hipsteripsum.me + write_inifile( + {}, + 'narwhal.cardigan' => 'no', + 'narwhal.banksy' => 'yes', + 'narwhal.occupy' => 'NO', + 'narwhal.mustache' => 'YES', + 'narwhal.gentrify' => 'false', + 'narwhal.thundercats' => 'true', + 'narwhal.scenester' => 'FALSE', + 'narwhal.squid' => 'TRUE', + 'narwhal.selvage' => '0', + 'narwhal.portland' => '1', + 'narwhal.bunch' => 'off', + 'narwhal.bicycle' => 'on', + 'narwhal.organic' => 'OFF', + 'narwhal.leggings' => 'ON', + 'narwhal.mixtape' => '', + 'narwhal.vegan' => 'invalid', + ); + + my $cassini = new Cassandane::Cassini; + + $self->assert_equals(0, $cassini->bool_val('narwhal', 'cardigan')); + $self->assert_equals(1, $cassini->bool_val('narwhal', 'banksy')); + $self->assert_equals(0, $cassini->bool_val('narwhal', 'occupy')); + $self->assert_equals(1, $cassini->bool_val('narwhal', 'mustache')); + $self->assert_equals(0, $cassini->bool_val('narwhal', 'gentrify')); + $self->assert_equals(1, $cassini->bool_val('narwhal', 'thundercats')); + $self->assert_equals(0, $cassini->bool_val('narwhal', 'scenester')); + $self->assert_equals(1, $cassini->bool_val('narwhal', 'squid')); + $self->assert_equals(0, $cassini->bool_val('narwhal', 'selvage')); + $self->assert_equals(1, $cassini->bool_val('narwhal', 'portland')); + $self->assert_equals(0, $cassini->bool_val('narwhal', 'brunch')); + $self->assert_equals(1, $cassini->bool_val('narwhal', 'bicycle')); + $self->assert_equals(0, $cassini->bool_val('narwhal', 'organic')); + $self->assert_equals(1, $cassini->bool_val('narwhal', 'leggings')); + + eval { $cassini->bool_val('narwhal', 'mixtape'); }; + my $exception = $@; + $self->assert_matches(qr/Bad boolean/, $exception); + + eval { $cassini->bool_val('narwhal', 'vegan'); }; + $exception = $@; + $self->assert_matches(qr/Bad boolean/, $exception); } -sub test_boolval -{ - my ($self) = @_; - - local $CWD = tempdir(CLEANUP => 1); - - xlog "Working in temporary directory $CWD"; - # data thanks to hipsteripsum.me - write_inifile({}, - 'narwhal.cardigan' => 'no', - 'narwhal.banksy' => 'yes', - 'narwhal.occupy' => 'NO', - 'narwhal.mustache' => 'YES', - 'narwhal.gentrify' => 'false', - 'narwhal.thundercats' => 'true', - 'narwhal.scenester' => 'FALSE', - 'narwhal.squid' => 'TRUE', - 'narwhal.selvage' => '0', - 'narwhal.portland' => '1', - 'narwhal.bunch' => 'off', - 'narwhal.bicycle' => 'on', - 'narwhal.organic' => 'OFF', - 'narwhal.leggings' => 'ON', - 'narwhal.mixtape' => '', - 'narwhal.vegan' => 'invalid', - ); - - my $cassini = new Cassandane::Cassini; - - $self->assert_equals(0, $cassini->bool_val('narwhal', 'cardigan')); - $self->assert_equals(1, $cassini->bool_val('narwhal', 'banksy')); - $self->assert_equals(0, $cassini->bool_val('narwhal', 'occupy')); - $self->assert_equals(1, $cassini->bool_val('narwhal', 'mustache')); - $self->assert_equals(0, $cassini->bool_val('narwhal', 'gentrify')); - $self->assert_equals(1, $cassini->bool_val('narwhal', 'thundercats')); - $self->assert_equals(0, $cassini->bool_val('narwhal', 'scenester')); - $self->assert_equals(1, $cassini->bool_val('narwhal', 'squid')); - $self->assert_equals(0, $cassini->bool_val('narwhal', 'selvage')); - $self->assert_equals(1, $cassini->bool_val('narwhal', 'portland')); - $self->assert_equals(0, $cassini->bool_val('narwhal', 'brunch')); - $self->assert_equals(1, $cassini->bool_val('narwhal', 'bicycle')); - $self->assert_equals(0, $cassini->bool_val('narwhal', 'organic')); - $self->assert_equals(1, $cassini->bool_val('narwhal', 'leggings')); - - eval { $cassini->bool_val('narwhal', 'mixtape'); }; +sub test_environment_override { + my ($self) = @_; + + local $CWD = tempdir(CLEANUP => 1); + local %ENV = (); # stop real environment from interfering! + + xlog "Working in temporary directory $CWD"; + # artisinal handcrafted data + # okay that's a lie... these are the real options and their defaults + # as documented by cassandane.ini.example + write_inifile( + {}, + 'cassandane.rootdir' => '/var/tmp/cass', + 'cassandane.pwcheck' => 'alwaystrue', + 'cassandane.cleanup' => 'no', + 'cassandane.maxworkers' => '1', + 'cassandane.base_port' => '9100', + 'cassandane.suppress' => '', + 'valgrind.enabled' => 'no', + 'valgrind.binary' => '/usr/bin/valgrind', + 'valgrind.suppressions' => 'vg.supp', + 'valgrind.arguments' => + '-q --tool=memcheck --leak-check=full --run-libc-freeres=no', + 'cyrus default.prefix' => '/usr/cyrus', + 'cyrus default.destdir' => '', + 'cyrus default.quota' => 'cyr_quota', + 'cyrus default.coresizelimit' => '100', + 'cyrus replica.prefix' => '/usr/cyrus', + 'cyrus replica.destdir' => '', + 'cyrus murder.prefix' => '/usr/cyrus', + 'cyrus murder.destdir' => '', + 'gdb.imapd' => 'yes', + 'gdb.sync_server' => 'yes', + 'gdb.lmtpd' => 'yes', + 'gdb.timsieved' => 'yes', + 'gdb.backupd' => 'yes', + 'config.sasl_mech_list' => 'PLAIN LOGIN', + 'config.debug_command' => '@prefix@/utils/gdbtramp %s %d', + 'caldavtalk.basedir' => '', + 'imaptest.basedir' => '', + 'imaptest.suppress' => 'listext subscribe', + 'caldavtester.basedir' => '', + 'caldavtester.suppress-caldav' => '', + 'caldavtester.suppress-carddav' => '', + 'jmaptestsuite.basedir' => '', + 'jmaptestsuite.suppress' => '', + ); + + my $cassini = new Cassandane::Cassini; + + # let's test the things we provide examples of + $self->assert_str_equals('/var/tmp/cass', + $cassini->val('cassandane', 'rootdir', 'ignored')); + + $ENV{CASSINI_CASSANDANE_ROOTDIR} = 'overridden!'; + $self->assert_str_equals('overridden!', + $cassini->val('cassandane', 'rootdir', 'ignored')); + + $ENV{CASSINI_CASSANDANE_ROOTDIR} = ''; + $self->assert_str_equals('', + $cassini->val('cassandane', 'rootdir', 'ignored')); + + delete $ENV{CASSINI_CASSANDANE_ROOTDIR}; + $self->assert_str_equals('/var/tmp/cass', + $cassini->val('cassandane', 'rootdir', 'ignored')); + + # [cyrus default] is a section with a space in its name + $self->assert_str_equals('/usr/cyrus', + $cassini->val('cyrus default', 'prefix', 'ignored')); + + $ENV{CASSINI_CYRUS_DEFAULT_PREFIX} = 'overridden!'; + $self->assert_str_equals('overridden!', + $cassini->val('cyrus default', 'prefix', 'ignored')); + + $ENV{CASSINI_CYRUS_DEFAULT_PREFIX} = ''; + $self->assert_str_equals('', + $cassini->val('cyrus default', 'prefix', 'ignored')); + + delete $ENV{CASSINI_CYRUS_DEFAULT_PREFIX}; + $self->assert_str_equals('/usr/cyrus', + $cassini->val('cyrus default', 'prefix', 'ignored')); + + # booleans should work too + $self->assert_str_equals('no', + $cassini->val('cassandane', 'cleanup', 'ignored')); + + foreach my $x (qw( no NO false FALSE 0 off OFF )) { + $ENV{CASSINI_CASSANDANE_CLEANUP} = $x; + $self->assert_equals(0, $cassini->bool_val('cassandane', 'cleanup')); + } + + foreach my $x (qw( yes YES true TRUE 1 on ON )) { + $ENV{CASSINI_CASSANDANE_CLEANUP} = $x; + $self->assert_equals(1, $cassini->bool_val('cassandane', 'cleanup')); + } + + foreach my $x (q{}, 'invalid') { + $ENV{CASSINI_CASSANDANE_CLEANUP} = $x; + eval { $cassini->bool_val('cassandane', 'cleanup'); }; my $exception = $@; $self->assert_matches(qr/Bad boolean/, $exception); + } - eval { $cassini->bool_val('narwhal', 'vegan'); }; - $exception = $@; - $self->assert_matches(qr/Bad boolean/, $exception); + delete $ENV{CASSINI_CASSANDANE_CLEANUP}; + $self->assert_str_equals('no', + $cassini->val('cassandane', 'cleanup', 'ignored')); } -sub test_environment_override -{ - my ($self) = @_; - - local $CWD = tempdir(CLEANUP => 1); - local %ENV = (); # stop real environment from interfering! - - xlog "Working in temporary directory $CWD"; - # artisinal handcrafted data - # okay that's a lie... these are the real options and their defaults - # as documented by cassandane.ini.example - write_inifile({}, - 'cassandane.rootdir' => '/var/tmp/cass', - 'cassandane.pwcheck' => 'alwaystrue', - 'cassandane.cleanup' => 'no', - 'cassandane.maxworkers' => '1', - 'cassandane.base_port' => '9100', - 'cassandane.suppress' => '', - 'valgrind.enabled' => 'no', - 'valgrind.binary' => '/usr/bin/valgrind', - 'valgrind.suppressions' => 'vg.supp', - 'valgrind.arguments' => '-q --tool=memcheck --leak-check=full --run-libc-freeres=no', - 'cyrus default.prefix' => '/usr/cyrus', - 'cyrus default.destdir' => '', - 'cyrus default.quota' => 'cyr_quota', - 'cyrus default.coresizelimit' => '100', - 'cyrus replica.prefix' => '/usr/cyrus', - 'cyrus replica.destdir' => '', - 'cyrus murder.prefix' => '/usr/cyrus', - 'cyrus murder.destdir' => '', - 'gdb.imapd' => 'yes', - 'gdb.sync_server' => 'yes', - 'gdb.lmtpd' => 'yes', - 'gdb.timsieved' => 'yes', - 'gdb.backupd' => 'yes', - 'config.sasl_mech_list' => 'PLAIN LOGIN', - 'config.debug_command' => '@prefix@/utils/gdbtramp %s %d', - 'caldavtalk.basedir' => '', - 'imaptest.basedir' => '', - 'imaptest.suppress' => 'listext subscribe', - 'caldavtester.basedir' => '', - 'caldavtester.suppress-caldav' => '', - 'caldavtester.suppress-carddav' => '', - 'jmaptestsuite.basedir' => '', - 'jmaptestsuite.suppress' => '', - ); - - my $cassini = new Cassandane::Cassini; - - # let's test the things we provide examples of - $self->assert_str_equals( - '/var/tmp/cass', - $cassini->val('cassandane', 'rootdir', 'ignored') - ); - - $ENV{CASSINI_CASSANDANE_ROOTDIR} = 'overridden!'; - $self->assert_str_equals( - 'overridden!', - $cassini->val('cassandane', 'rootdir', 'ignored') - ); - - $ENV{CASSINI_CASSANDANE_ROOTDIR} = ''; - $self->assert_str_equals( - '', - $cassini->val('cassandane', 'rootdir', 'ignored') - ); - - delete $ENV{CASSINI_CASSANDANE_ROOTDIR}; - $self->assert_str_equals( - '/var/tmp/cass', - $cassini->val('cassandane', 'rootdir', 'ignored') - ); - - # [cyrus default] is a section with a space in its name - $self->assert_str_equals( - '/usr/cyrus', - $cassini->val('cyrus default', 'prefix', 'ignored') - ); - - $ENV{CASSINI_CYRUS_DEFAULT_PREFIX} = 'overridden!'; - $self->assert_str_equals( - 'overridden!', - $cassini->val('cyrus default', 'prefix', 'ignored') - ); - - $ENV{CASSINI_CYRUS_DEFAULT_PREFIX} = ''; - $self->assert_str_equals( - '', - $cassini->val('cyrus default', 'prefix', 'ignored') - ); - - delete $ENV{CASSINI_CYRUS_DEFAULT_PREFIX}; - $self->assert_str_equals( - '/usr/cyrus', - $cassini->val('cyrus default', 'prefix', 'ignored') - ); - - # booleans should work too - $self->assert_str_equals( - 'no', - $cassini->val('cassandane', 'cleanup', 'ignored') - ); - - foreach my $x (qw( no NO false FALSE 0 off OFF )) { - $ENV{CASSINI_CASSANDANE_CLEANUP} = $x; - $self->assert_equals(0, $cassini->bool_val('cassandane', 'cleanup')); - } +sub test_environment_only { + my ($self) = @_; - foreach my $x (qw( yes YES true TRUE 1 on ON )) { - $ENV{CASSINI_CASSANDANE_CLEANUP} = $x; - $self->assert_equals(1, $cassini->bool_val('cassandane', 'cleanup')); - } + local $CWD = tempdir(CLEANUP => 1); + local %ENV = (); # stop real environment from interfering! - foreach my $x (q{}, 'invalid') { - $ENV{CASSINI_CASSANDANE_CLEANUP} = $x; - eval { $cassini->bool_val('cassandane', 'cleanup'); }; - my $exception = $@; - $self->assert_matches(qr/Bad boolean/, $exception); - } + my $cassini; - delete $ENV{CASSINI_CASSANDANE_CLEANUP}; - $self->assert_str_equals( - 'no', - $cassini->val('cassandane', 'cleanup', 'ignored') - ); -} + # n.b. did not create an ini file! -sub test_environment_only -{ - my ($self) = @_; + # allow_noinifile has not been set, so it should barf + eval { $cassini = new Cassandane::Cassini; }; + my $e = $@; + $self->assert_matches(qr/couldn't find a cassandane\.ini file/, $e); - local $CWD = tempdir(CLEANUP => 1); - local %ENV = (); # stop real environment from interfering! + # set allow_noinifile and other config via environment + $ENV{CASSINI_CASSANDANE_ALLOW_NOINIFILE} = 'yes'; + $ENV{CASSINI_CASSANDANE_ROOTDIR} = 'overridden!'; - my $cassini; + # should work this time + $cassini = new Cassandane::Cassini; - # n.b. did not create an ini file! + $self->assert_equals(1, $cassini->bool_val('cassandane', 'allow_noinifile')); - # allow_noinifile has not been set, so it should barf - eval { $cassini = new Cassandane::Cassini; }; - my $e = $@; - $self->assert_matches(qr/couldn't find a cassandane\.ini file/, $e); + $self->assert_str_equals('overridden!', + $cassini->val('cassandane', 'rootdir', 'ignored')); - # set allow_noinifile and other config via environment - $ENV{CASSINI_CASSANDANE_ALLOW_NOINIFILE} = 'yes'; - $ENV{CASSINI_CASSANDANE_ROOTDIR} = 'overridden!'; + # we didn't change this one at all, and its default is 'no' + $self->assert_equals(0, $cassini->bool_val('cassandane', 'cleanup')); +} - # should work this time - $cassini = new Cassandane::Cassini; +sub test_override { + my ($self) = @_; - $self->assert_equals(1, - $cassini->bool_val('cassandane', 'allow_noinifile')); + local $CWD = tempdir(CLEANUP => 1); - $self->assert_str_equals( - 'overridden!', - $cassini->val('cassandane', 'rootdir', 'ignored') - ); + xlog "Working in temporary directory $CWD"; + # data thanks to hipsteripsum.me + write_inifile({}, 'semiotics.skateboard' => 'flexitarian',); - # we didn't change this one at all, and its default is 'no' - $self->assert_equals(0, - $cassini->bool_val('cassandane', 'cleanup')); -} + my $cassini = new Cassandane::Cassini; -sub test_override -{ - my ($self) = @_; - - local $CWD = tempdir(CLEANUP => 1); - - xlog "Working in temporary directory $CWD"; - # data thanks to hipsteripsum.me - write_inifile({}, - 'semiotics.skateboard' => 'flexitarian', - ); - - my $cassini = new Cassandane::Cassini; - - $self->assert_null($cassini->val('semiotics', 'typewriter')); - $self->assert_str_equals('whatever', - $cassini->val('semiotics', 'typewriter', 'whatever')); - $self->assert_str_equals('flexitarian', - $cassini->val('semiotics', 'skateboard', 'whatever')); - $self->assert_str_equals('flexitarian', - $cassini->val('semiotics', 'skateboard')); - $self->assert_null($cassini->val('twee', 'cliche')); - - $cassini->override('semiotics', 'typewriter', 'vegan'); - - $self->assert_str_equals('vegan', - $cassini->val('semiotics', 'typewriter')); - $self->assert_str_equals('vegan', - $cassini->val('semiotics', 'typewriter', 'whatever')); - $self->assert_str_equals('flexitarian', - $cassini->val('semiotics', 'skateboard', 'whatever')); - $self->assert_str_equals('flexitarian', - $cassini->val('semiotics', 'skateboard')); - $self->assert_null($cassini->val('twee', 'cliche')); -} + $self->assert_null($cassini->val('semiotics', 'typewriter')); + $self->assert_str_equals('whatever', + $cassini->val('semiotics', 'typewriter', 'whatever')); + $self->assert_str_equals('flexitarian', + $cassini->val('semiotics', 'skateboard', 'whatever')); + $self->assert_str_equals('flexitarian', + $cassini->val('semiotics', 'skateboard')); + $self->assert_null($cassini->val('twee', 'cliche')); + $cassini->override('semiotics', 'typewriter', 'vegan'); + + $self->assert_str_equals('vegan', $cassini->val('semiotics', 'typewriter')); + $self->assert_str_equals('vegan', + $cassini->val('semiotics', 'typewriter', 'whatever')); + $self->assert_str_equals('flexitarian', + $cassini->val('semiotics', 'skateboard', 'whatever')); + $self->assert_str_equals('flexitarian', + $cassini->val('semiotics', 'skateboard')); + $self->assert_null($cassini->val('twee', 'cliche')); +} 1; diff --git a/cassandane/Cassandane/Test/Clone.pm b/cassandane/Cassandane/Test/Clone.pm index 02ba310da2..05596332bf 100644 --- a/cassandane/Cassandane/Test/Clone.pm +++ b/cassandane/Cassandane/Test/Clone.pm @@ -45,71 +45,67 @@ use Clone qw(clone); use lib '.'; use base qw(Cassandane::Unit::TestCase); -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } -sub test_undef -{ - my ($self) = @_; - my $a = undef; - my $b = clone($a); - $self->assert_null($a); - $self->assert_null($b); +sub test_undef { + my ($self) = @_; + my $a = undef; + my $b = clone($a); + $self->assert_null($a); + $self->assert_null($b); } -sub test_string -{ - my ($self) = @_; - my $a = "Hello World"; - my $b = clone($a); - $self->assert_str_equals("Hello World", $a); - $self->assert_str_equals("Hello World", $b); - $b = "Jeepers"; - $self->assert_str_equals("Hello World", $a); - $self->assert_str_equals("Jeepers", $b); +sub test_string { + my ($self) = @_; + my $a = "Hello World"; + my $b = clone($a); + $self->assert_str_equals("Hello World", $a); + $self->assert_str_equals("Hello World", $b); + $b = "Jeepers"; + $self->assert_str_equals("Hello World", $a); + $self->assert_str_equals("Jeepers", $b); } -sub test_hash -{ - my ($self) = @_; - my $a = { foo => 42 }; - my $b = clone($a); - $self->assert_deep_equals({ foo => 42 }, $a); - $self->assert_deep_equals({ foo => 42 }, $b); - $b->{bar} = 123; - $self->assert_deep_equals({ foo => 42 }, $a); - $self->assert_deep_equals({ foo => 42, bar => 123 }, $b); - delete $b->{foo}; - $self->assert_deep_equals({ foo => 42 }, $a); - $self->assert_deep_equals({ bar => 123 }, $b); +sub test_hash { + my ($self) = @_; + my $a = { foo => 42 }; + my $b = clone($a); + $self->assert_deep_equals({ foo => 42 }, $a); + $self->assert_deep_equals({ foo => 42 }, $b); + $b->{bar} = 123; + $self->assert_deep_equals({ foo => 42 }, $a); + $self->assert_deep_equals({ foo => 42, bar => 123 }, $b); + delete $b->{foo}; + $self->assert_deep_equals({ foo => 42 }, $a); + $self->assert_deep_equals({ bar => 123 }, $b); } -sub test_array -{ - my ($self) = @_; - my $a = [ 42 ]; - my $b = clone($a); - $self->assert_deep_equals([ 42 ], $a); - $self->assert_deep_equals([ 42 ], $b); - push(@$b, 123); - $self->assert_deep_equals([ 42 ], $a); - $self->assert_deep_equals([ 42, 123 ], $b); - shift @$b; - $self->assert_deep_equals([ 42 ], $a); - $self->assert_deep_equals([ 123 ], $b); +sub test_array { + my ($self) = @_; + my $a = [42]; + my $b = clone($a); + $self->assert_deep_equals([42], $a); + $self->assert_deep_equals([42], $b); + push(@$b, 123); + $self->assert_deep_equals([42], $a); + $self->assert_deep_equals([ 42, 123 ], $b); + shift @$b; + $self->assert_deep_equals([42], $a); + $self->assert_deep_equals([123], $b); } -sub test_complex -{ - my ($self) = @_; - my $a = { foo => [ { x => 42, y => 123 } ], - bar => { quux => 37, foonly => 475 } }; - my $b = clone($a); - $self->assert_deep_equals($a, $b); +sub test_complex { + my ($self) = @_; + my $a = { + foo => [ { x => 42, y => 123 } ], + bar => { quux => 37, foonly => 475 } + }; + my $b = clone($a); + $self->assert_deep_equals($a, $b); } 1; diff --git a/cassandane/Cassandane/Test/Config.pm b/cassandane/Cassandane/Test/Config.pm index fc24d4001e..45a3dc5dfa 100644 --- a/cassandane/Cassandane/Test/Config.pm +++ b/cassandane/Cassandane/Test/Config.pm @@ -48,386 +48,369 @@ use base qw(Cassandane::Unit::TestCase); use Cassandane::Config; use Cassandane::Util::Log; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } -sub test_default -{ - my ($self) = @_; +sub test_default { + my ($self) = @_; - my $c = Cassandane::Config->default(); - $self->assert(defined $c); - $self->assert(!defined $c->get('hello')); + my $c = Cassandane::Config->default(); + $self->assert(defined $c); + $self->assert(!defined $c->get('hello')); - my $c2 = Cassandane::Config->default(); - $self->assert(defined $c2); - $self->assert($c2 eq $c); - $self->assert(!defined $c->get('hello')); - $self->assert(!defined $c2->get('hello')); + my $c2 = Cassandane::Config->default(); + $self->assert(defined $c2); + $self->assert($c2 eq $c); + $self->assert(!defined $c->get('hello')); + $self->assert(!defined $c2->get('hello')); - $c->set(hello => 'world'); - $self->assert($c->get('hello') eq 'world'); - $self->assert($c2->get('hello') eq 'world'); + $c->set(hello => 'world'); + $self->assert($c->get('hello') eq 'world'); + $self->assert($c2->get('hello') eq 'world'); - $c->set(hello => undef); - $self->assert(!defined $c->get('hello')); - $self->assert(!defined $c2->get('hello')); + $c->set(hello => undef); + $self->assert(!defined $c->get('hello')); + $self->assert(!defined $c2->get('hello')); } -sub test_clone -{ - my ($self) = @_; - - my $c = Cassandane::Config->new(); - $self->assert(!defined $c->get('hello')); - $self->assert(!defined $c->get('foo')); - - my $c2 = $c->clone(); - $self->assert($c2 ne $c); - $self->assert(!defined $c->get('hello')); - $self->assert(!defined $c2->get('hello')); - $self->assert(!defined $c->get('foo')); - $self->assert(!defined $c2->get('foo')); - - $c2->set(hello => 'world'); - $self->assert(!defined $c->get('hello')); - $self->assert($c2->get('hello') eq 'world'); - $self->assert(!defined $c->get('foo')); - $self->assert(!defined $c2->get('foo')); - - $c->set(foo => 'bar'); - $self->assert(!defined $c->get('hello')); - $self->assert($c2->get('hello') eq 'world'); - $self->assert($c->get('foo') eq 'bar'); - $self->assert($c2->get('foo') eq 'bar'); - - $c2->set(foo => 'baz'); - $self->assert(!defined $c->get('hello')); - $self->assert($c2->get('hello') eq 'world'); - $self->assert($c->get('foo') eq 'bar'); - $self->assert($c2->get('foo') eq 'baz'); - - $c2->set(foo => undef); - $self->assert(!defined $c->get('hello')); - $self->assert($c2->get('hello') eq 'world'); - $self->assert($c->get('foo') eq 'bar'); - $self->assert(!defined $c2->get('foo')); - - $c->set(foo => undef); - $self->assert(!defined $c->get('hello')); - $self->assert($c2->get('hello') eq 'world'); - $self->assert(!defined $c->get('foo')); - $self->assert(!defined $c2->get('foo')); - - $c2->set(hello => undef); - $self->assert(!defined $c->get('hello')); - $self->assert(!defined $c2->get('hello')); - $self->assert(!defined $c->get('foo')); - $self->assert(!defined $c2->get('foo')); +sub test_clone { + my ($self) = @_; + + my $c = Cassandane::Config->new(); + $self->assert(!defined $c->get('hello')); + $self->assert(!defined $c->get('foo')); + + my $c2 = $c->clone(); + $self->assert($c2 ne $c); + $self->assert(!defined $c->get('hello')); + $self->assert(!defined $c2->get('hello')); + $self->assert(!defined $c->get('foo')); + $self->assert(!defined $c2->get('foo')); + + $c2->set(hello => 'world'); + $self->assert(!defined $c->get('hello')); + $self->assert($c2->get('hello') eq 'world'); + $self->assert(!defined $c->get('foo')); + $self->assert(!defined $c2->get('foo')); + + $c->set(foo => 'bar'); + $self->assert(!defined $c->get('hello')); + $self->assert($c2->get('hello') eq 'world'); + $self->assert($c->get('foo') eq 'bar'); + $self->assert($c2->get('foo') eq 'bar'); + + $c2->set(foo => 'baz'); + $self->assert(!defined $c->get('hello')); + $self->assert($c2->get('hello') eq 'world'); + $self->assert($c->get('foo') eq 'bar'); + $self->assert($c2->get('foo') eq 'baz'); + + $c2->set(foo => undef); + $self->assert(!defined $c->get('hello')); + $self->assert($c2->get('hello') eq 'world'); + $self->assert($c->get('foo') eq 'bar'); + $self->assert(!defined $c2->get('foo')); + + $c->set(foo => undef); + $self->assert(!defined $c->get('hello')); + $self->assert($c2->get('hello') eq 'world'); + $self->assert(!defined $c->get('foo')); + $self->assert(!defined $c2->get('foo')); + + $c2->set(hello => undef); + $self->assert(!defined $c->get('hello')); + $self->assert(!defined $c2->get('hello')); + $self->assert(!defined $c->get('foo')); + $self->assert(!defined $c2->get('foo')); } -sub _generate_and_read -{ - my ($self, $c) = @_; - - # Write the file - my ($fh, $filename) = tempfile() - or die "Cannot open temporary file: $!"; - $c->generate($filename); - - # read it back again to check - my %nv; - while (<$fh>) - { - chomp; - my ($n, $v) = m/^([^:\s]+):\s*(.+)*$/; - $self->assert(defined $v); - $nv{$n} = $v; - } - - close $fh; - unlink $filename; - - return \%nv; +sub _generate_and_read { + my ($self, $c) = @_; + + # Write the file + my ($fh, $filename) = tempfile() + or die "Cannot open temporary file: $!"; + $c->generate($filename); + + # read it back again to check + my %nv; + while (<$fh>) { + chomp; + my ($n, $v) = m/^([^:\s]+):\s*(.+)*$/; + $self->assert(defined $v); + $nv{$n} = $v; + } + + close $fh; + unlink $filename; + + return \%nv; } -sub test_generate -{ - my ($self) = @_; - - my $c = Cassandane::Config->new(); - $c->set(foo => 'bar'); - $c->set(quux => 'foonly'); - $c->set('httpmodules', 'caldav jmap'); - $c->set('event_groups', [qw(quota)]); - - my $c2 = $c->clone(); - $c2->set(hello => 'world'); - $c2->set(foo => 'baz'); - $c2->set('event_groups', [qw(flags quota)]); - - my $nv = $self->_generate_and_read($c2); - - $self->assert_num_equals(5, scalar(keys(%$nv))); - $self->assert_str_equals('baz', $nv->{foo}); - $self->assert_str_equals('world', $nv->{hello}); - $self->assert_str_equals('foonly', $nv->{quux}); - $self->assert_str_equals('caldav jmap', $nv->{httpmodules}); - $self->assert_str_equals('flags quota', $nv->{event_groups}); +sub test_generate { + my ($self) = @_; + + my $c = Cassandane::Config->new(); + $c->set(foo => 'bar'); + $c->set(quux => 'foonly'); + $c->set('httpmodules', 'caldav jmap'); + $c->set('event_groups', [qw(quota)]); + + my $c2 = $c->clone(); + $c2->set(hello => 'world'); + $c2->set(foo => 'baz'); + $c2->set('event_groups', [qw(flags quota)]); + + my $nv = $self->_generate_and_read($c2); + + $self->assert_num_equals(5, scalar(keys(%$nv))); + $self->assert_str_equals('baz', $nv->{foo}); + $self->assert_str_equals('world', $nv->{hello}); + $self->assert_str_equals('foonly', $nv->{quux}); + $self->assert_str_equals('caldav jmap', $nv->{httpmodules}); + $self->assert_str_equals('flags quota', $nv->{event_groups}); } -sub test_variables -{ - my ($self) = @_; - - my $c = Cassandane::Config->new(); - $c->set(foo => 'b@grade@r'); - $c->set(quux => 'fo@grade@nly'); - my $c2 = $c->clone(); - $c2->set(hello => 'w@grade@rld'); - $c2->set(foo => 'baz'); - - # missing @grade@ variable throws an exception - my $nv; - eval - { - $nv = $self->_generate_and_read($c2); - }; - $self->assert(defined $@ && $@ =~ m/Variable grade not defined/i); - - # @grade@ on the parent affects all variable expansions - $c->set_variables('grade' => 'B'); - $nv = $self->_generate_and_read($c2); - $self->assert_num_equals(3, scalar(keys(%$nv))); - $self->assert_str_equals('baz', $nv->{foo}); - $self->assert_str_equals('wBrld', $nv->{hello}); - $self->assert_str_equals('foBnly', $nv->{quux}); - - # @grade@ on the child overrides @grade@ on the parent - $c2->set_variables('grade' => 'A'); - $nv = $self->_generate_and_read($c2); - $self->assert_num_equals(scalar(keys(%$nv)), 3); - $self->assert_str_equals('baz', $nv->{foo}); - $self->assert_str_equals('wArld', $nv->{hello}); - $self->assert_str_equals('foAnly', $nv->{quux}); +sub test_variables { + my ($self) = @_; + + my $c = Cassandane::Config->new(); + $c->set(foo => 'b@grade@r'); + $c->set(quux => 'fo@grade@nly'); + my $c2 = $c->clone(); + $c2->set(hello => 'w@grade@rld'); + $c2->set(foo => 'baz'); + + # missing @grade@ variable throws an exception + my $nv; + eval { $nv = $self->_generate_and_read($c2); }; + $self->assert(defined $@ && $@ =~ m/Variable grade not defined/i); + + # @grade@ on the parent affects all variable expansions + $c->set_variables('grade' => 'B'); + $nv = $self->_generate_and_read($c2); + $self->assert_num_equals(3, scalar(keys(%$nv))); + $self->assert_str_equals('baz', $nv->{foo}); + $self->assert_str_equals('wBrld', $nv->{hello}); + $self->assert_str_equals('foBnly', $nv->{quux}); + + # @grade@ on the child overrides @grade@ on the parent + $c2->set_variables('grade' => 'A'); + $nv = $self->_generate_and_read($c2); + $self->assert_num_equals(scalar(keys(%$nv)), 3); + $self->assert_str_equals('baz', $nv->{foo}); + $self->assert_str_equals('wArld', $nv->{hello}); + $self->assert_str_equals('foAnly', $nv->{quux}); } -sub test_bitfields -{ - my ($self) = @_; - - my $c = Cassandane::Config->new(); - - # can set bitfields as space separated strings - $c->set('httpmodules' => 'caldav jmap'); - # get in scalar context returns space separated string - $self->assert_str_equals('caldav jmap', scalar $c->get('httpmodules')); - # get in list context returns list - $self->assert_deep_equals([qw(caldav jmap)], [$c->get('httpmodules')]); - - # can clear a whole bitfield - $c->clear_all_bits('httpmodules'); - $self->assert_null($c->get('httpmodules')); - - # can set bitfields as array reference - $c->set('httpmodules' => [qw(caldav jmap)]); - # get in scalar context returns space separated string - $self->assert_str_equals('caldav jmap', scalar $c->get('httpmodules')); - # get in list context returns list - $self->assert_deep_equals([qw(caldav jmap)], [$c->get('httpmodules')]); - - # can clear one bit - $c->clear_bits('httpmodules', 'caldav'); - $self->assert_str_equals('jmap', $c->get('httpmodules')); - - # can set one bit - $c->set_bits('httpmodules', 'prometheus'); - $self->assert_str_equals('jmap prometheus', scalar $c->get('httpmodules')); - - # can get one bit - $self->assert($c->get_bit('httpmodules', 'prometheus')); - $self->assert($c->get_bit('httpmodules', 'jmap')); - # valid bits that aren't set are false - $self->assert(not $c->get_bit('httpmodules', 'caldav')); - $self->assert(not $c->get_bit('httpmodules', 'freebusy')); - - # can set a few bits - $c->set_bits('httpmodules', 'caldav', 'carddav'); - $self->assert_str_equals('caldav carddav jmap prometheus', - scalar $c->get('httpmodules')); - $c->set_bits('httpmodules', 'ischedule rss'); - $self->assert_str_equals('caldav carddav ischedule jmap prometheus rss', - scalar $c->get('httpmodules')); - - # can clear a few bits - $c->clear_bits('httpmodules', 'caldav', 'carddav'); - $self->assert_str_equals('ischedule jmap prometheus rss', - scalar $c->get('httpmodules')); - $c->clear_bits('httpmodules', 'ischedule rss'); - $self->assert_str_equals('jmap prometheus', - scalar $c->get('httpmodules')); - - # setting with set() should replace previous bit set - $c->set('httpmodules' => [qw(admin tzdist)]); - $self->assert(not $c->get_bit('httpmodules', 'prometheus')); - $self->assert(not $c->get_bit('httpmodules', 'jmap')); - $self->assert($c->get_bit('httpmodules', 'admin')); - $self->assert($c->get_bit('httpmodules', 'tzdist')); - $self->assert_str_equals('admin tzdist', scalar $c->get('httpmodules')); - - # cannot set bits on non-bitfield options - eval { - $c->set_bits('conversations', 'irrelevant'); - }; - my $e = $@; - $self->assert_matches(qr{conversations is not a bitfield option}, $e); - - # cannot set invalid bits on bitfield options - eval { - $c->set_bits('httpmodules', 'bogus'); - }; - $e = $@; - $self->assert_matches(qr{bogus is not a httpmodules value}, $e); - - # cannot mix and match bits from other bitfields - eval { - $c->set_bits('httpmodules', 'VEVENT'); - }; - $e = $@; - $self->assert_matches(qr{VEVENT is not a httpmodules value}, $e); - - # should be able to set valid bitfields in constructor - my $c2 = Cassandane::Config->new('foo' => 'bar', - 'httpmodules' => 'caldav jmap', - 'event_groups' => [qw(message quota)]); - $self->assert_not_null($c2); - - # expectations should still hold for bitfields set via constructor - $self->assert_str_equals('bar', $c2->get('foo')); - $self->assert_str_equals('caldav jmap', scalar $c2->get('httpmodules')); - $self->assert_deep_equals([qw(caldav jmap)], [$c2->get('httpmodules')]); - $self->assert_str_equals('message quota', scalar $c2->get('event_groups')); - $self->assert_deep_equals([qw(message quota)], [$c2->get('event_groups')]); - - # should be able to set bitfield values containing underscores - $c->set_bits('sieve_extensions', 'vnd.cyrus.implicit_keep_target'); - $self->assert_str_equals('vnd.cyrus.implicit_keep_target', - scalar $c->get('sieve_extensions')); - $self->assert_deep_equals([qw(vnd.cyrus.implicit_keep_target)], - [$c->get('sieve_extensions')]); +sub test_bitfields { + my ($self) = @_; + + my $c = Cassandane::Config->new(); + + # can set bitfields as space separated strings + $c->set('httpmodules' => 'caldav jmap'); + # get in scalar context returns space separated string + $self->assert_str_equals('caldav jmap', scalar $c->get('httpmodules')); + # get in list context returns list + $self->assert_deep_equals([qw(caldav jmap)], [ $c->get('httpmodules') ]); + + # can clear a whole bitfield + $c->clear_all_bits('httpmodules'); + $self->assert_null($c->get('httpmodules')); + + # can set bitfields as array reference + $c->set('httpmodules' => [qw(caldav jmap)]); + # get in scalar context returns space separated string + $self->assert_str_equals('caldav jmap', scalar $c->get('httpmodules')); + # get in list context returns list + $self->assert_deep_equals([qw(caldav jmap)], [ $c->get('httpmodules') ]); + + # can clear one bit + $c->clear_bits('httpmodules', 'caldav'); + $self->assert_str_equals('jmap', $c->get('httpmodules')); + + # can set one bit + $c->set_bits('httpmodules', 'prometheus'); + $self->assert_str_equals('jmap prometheus', scalar $c->get('httpmodules')); + + # can get one bit + $self->assert($c->get_bit('httpmodules', 'prometheus')); + $self->assert($c->get_bit('httpmodules', 'jmap')); + # valid bits that aren't set are false + $self->assert(not $c->get_bit('httpmodules', 'caldav')); + $self->assert(not $c->get_bit('httpmodules', 'freebusy')); + + # can set a few bits + $c->set_bits('httpmodules', 'caldav', 'carddav'); + $self->assert_str_equals('caldav carddav jmap prometheus', + scalar $c->get('httpmodules')); + $c->set_bits('httpmodules', 'ischedule rss'); + $self->assert_str_equals('caldav carddav ischedule jmap prometheus rss', + scalar $c->get('httpmodules')); + + # can clear a few bits + $c->clear_bits('httpmodules', 'caldav', 'carddav'); + $self->assert_str_equals('ischedule jmap prometheus rss', + scalar $c->get('httpmodules')); + $c->clear_bits('httpmodules', 'ischedule rss'); + $self->assert_str_equals('jmap prometheus', scalar $c->get('httpmodules')); + + # setting with set() should replace previous bit set + $c->set('httpmodules' => [qw(admin tzdist)]); + $self->assert(not $c->get_bit('httpmodules', 'prometheus')); + $self->assert(not $c->get_bit('httpmodules', 'jmap')); + $self->assert($c->get_bit('httpmodules', 'admin')); + $self->assert($c->get_bit('httpmodules', 'tzdist')); + $self->assert_str_equals('admin tzdist', scalar $c->get('httpmodules')); + + # cannot set bits on non-bitfield options + eval { $c->set_bits('conversations', 'irrelevant'); }; + my $e = $@; + $self->assert_matches(qr{conversations is not a bitfield option}, $e); + + # cannot set invalid bits on bitfield options + eval { $c->set_bits('httpmodules', 'bogus'); }; + $e = $@; + $self->assert_matches(qr{bogus is not a httpmodules value}, $e); + + # cannot mix and match bits from other bitfields + eval { $c->set_bits('httpmodules', 'VEVENT'); }; + $e = $@; + $self->assert_matches(qr{VEVENT is not a httpmodules value}, $e); + + # should be able to set valid bitfields in constructor + my $c2 = Cassandane::Config->new( + 'foo' => 'bar', + 'httpmodules' => 'caldav jmap', + 'event_groups' => [qw(message quota)] + ); + $self->assert_not_null($c2); + + # expectations should still hold for bitfields set via constructor + $self->assert_str_equals('bar', $c2->get('foo')); + $self->assert_str_equals('caldav jmap', scalar $c2->get('httpmodules')); + $self->assert_deep_equals([qw(caldav jmap)], [ $c2->get('httpmodules') ]); + $self->assert_str_equals('message quota', scalar $c2->get('event_groups')); + $self->assert_deep_equals([qw(message quota)], [ $c2->get('event_groups') ]); + + # should be able to set bitfield values containing underscores + $c->set_bits('sieve_extensions', 'vnd.cyrus.implicit_keep_target'); + $self->assert_str_equals('vnd.cyrus.implicit_keep_target', + scalar $c->get('sieve_extensions')); + $self->assert_deep_equals([qw(vnd.cyrus.implicit_keep_target)], + [ $c->get('sieve_extensions') ]); } -sub test_clone_bitfields -{ - my ($self) = @_; - - my $c = Cassandane::Config->new(); - $self->assert_null($c->get('httpmodules')); - $self->assert_null($c->get('event_groups')); - - my $c2 = $c->clone(); - $self->assert($c2 ne $c); - $self->assert_null($c->get('httpmodules')); - $self->assert_null($c2->get('httpmodules')); - $self->assert_null($c->get('event_groups')); - $self->assert_null($c2->get('event_groups')); - - # set bit in clone doesn't affect parent - $c2->set_bits('httpmodules', 'caldav'); - $self->assert_null($c->get('httpmodules')); - $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); - $self->assert_null($c->get('event_groups')); - $self->assert_null($c2->get('event_groups')); - - # set bit in parent is inherited by child - $c->set_bits('event_groups', 'access', 'mailbox'); - $self->assert_null($c->get('httpmodules')); - $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); - $self->assert_str_equals('access mailbox', scalar $c->get('event_groups')); - $self->assert_str_equals('access mailbox', scalar $c2->get('event_groups')); - - # set bit in child supplements parent - $c2->set_bits('event_groups', 'quota'); - $self->assert_null($c->get('httpmodules')); - $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); - $self->assert_str_equals('access mailbox', scalar $c->get('event_groups')); - $self->assert_str_equals('access mailbox quota', scalar $c2->get('event_groups')); - - # clear bit in child overrides parent - $c2->clear_bits('event_groups', 'mailbox'); - $self->assert_null($c->get('httpmodules')); - $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); - $self->assert_str_equals('access mailbox', scalar $c->get('event_groups')); - $self->assert_str_equals('access quota', scalar $c2->get('event_groups')); - - # clear bit in parent updates inheriting child - $c->clear_bits('event_groups', 'access'); - $self->assert_null($c->get('httpmodules')); - $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); - $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); - $self->assert_str_equals('quota', scalar $c2->get('event_groups')); - - # clear bit in child updates child - $c2->clear_bits('event_groups', 'quota'); - $self->assert_null($c->get('httpmodules')); - $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); - $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); - $self->assert_null($c2->get('event_groups')); - - # set explicit list in parent updates child - $c->set('httpmodules', 'jmap prometheus carddav'); - $self->assert_str_equals('carddav jmap prometheus', - scalar $c->get('httpmodules')); - $self->assert_str_equals('caldav carddav jmap prometheus', - scalar $c2->get('httpmodules')); - $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); - $self->assert_null($c2->get('event_groups')); - - # clear all in child overrides parent - $c2->clear_all_bits('httpmodules'); - $self->assert_str_equals('carddav jmap prometheus', - scalar $c->get('httpmodules')); - $self->assert_null($c2->get('httpmodules')); - $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); - $self->assert_null($c2->get('event_groups')); - - # discard clone and recreate, clone should be the same as parent again - undef $c2; - $c2 = $c->clone(); - $self->assert_not_equals($c, $c2); - $self->assert_equals(scalar $c->get('httpmodules'), - scalar $c2->get('httpmodules')); - $self->assert_equals(scalar $c->get('event_groups'), - scalar $c2->get('event_groups')); - - # bit set in both parent and child is only listed once - $c2->set_bits('httpmodules', 'jmap'); - $self->assert_str_equals('carddav jmap prometheus', - scalar $c->get('httpmodules')); - $self->assert_str_equals('carddav jmap prometheus', - scalar $c2->get('httpmodules')); - $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); - $self->assert_str_equals('mailbox', scalar $c2->get('event_groups')); - - # clearing bit in parent doesn't affect child who has it explicitly set - $c->clear_bits('httpmodules', 'jmap'); - $self->assert_str_equals('carddav prometheus', - scalar $c->get('httpmodules')); - $self->assert_str_equals('carddav jmap prometheus', - scalar $c2->get('httpmodules')); - $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); - $self->assert_str_equals('mailbox', scalar $c2->get('event_groups')); - - # clearing all in parent doesn't affect child's explicit bits - $c->clear_all_bits('httpmodules'); - $self->assert_null($c->get('httpmodules')); - $self->assert_str_equals('jmap', scalar $c2->get('httpmodules')); - $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); - $self->assert_str_equals('mailbox', scalar $c2->get('event_groups')); +sub test_clone_bitfields { + my ($self) = @_; + + my $c = Cassandane::Config->new(); + $self->assert_null($c->get('httpmodules')); + $self->assert_null($c->get('event_groups')); + + my $c2 = $c->clone(); + $self->assert($c2 ne $c); + $self->assert_null($c->get('httpmodules')); + $self->assert_null($c2->get('httpmodules')); + $self->assert_null($c->get('event_groups')); + $self->assert_null($c2->get('event_groups')); + + # set bit in clone doesn't affect parent + $c2->set_bits('httpmodules', 'caldav'); + $self->assert_null($c->get('httpmodules')); + $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); + $self->assert_null($c->get('event_groups')); + $self->assert_null($c2->get('event_groups')); + + # set bit in parent is inherited by child + $c->set_bits('event_groups', 'access', 'mailbox'); + $self->assert_null($c->get('httpmodules')); + $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); + $self->assert_str_equals('access mailbox', scalar $c->get('event_groups')); + $self->assert_str_equals('access mailbox', scalar $c2->get('event_groups')); + + # set bit in child supplements parent + $c2->set_bits('event_groups', 'quota'); + $self->assert_null($c->get('httpmodules')); + $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); + $self->assert_str_equals('access mailbox', scalar $c->get('event_groups')); + $self->assert_str_equals('access mailbox quota', + scalar $c2->get('event_groups')); + + # clear bit in child overrides parent + $c2->clear_bits('event_groups', 'mailbox'); + $self->assert_null($c->get('httpmodules')); + $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); + $self->assert_str_equals('access mailbox', scalar $c->get('event_groups')); + $self->assert_str_equals('access quota', scalar $c2->get('event_groups')); + + # clear bit in parent updates inheriting child + $c->clear_bits('event_groups', 'access'); + $self->assert_null($c->get('httpmodules')); + $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); + $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); + $self->assert_str_equals('quota', scalar $c2->get('event_groups')); + + # clear bit in child updates child + $c2->clear_bits('event_groups', 'quota'); + $self->assert_null($c->get('httpmodules')); + $self->assert_str_equals('caldav', scalar $c2->get('httpmodules')); + $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); + $self->assert_null($c2->get('event_groups')); + + # set explicit list in parent updates child + $c->set('httpmodules', 'jmap prometheus carddav'); + $self->assert_str_equals('carddav jmap prometheus', + scalar $c->get('httpmodules')); + $self->assert_str_equals('caldav carddav jmap prometheus', + scalar $c2->get('httpmodules')); + $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); + $self->assert_null($c2->get('event_groups')); + + # clear all in child overrides parent + $c2->clear_all_bits('httpmodules'); + $self->assert_str_equals('carddav jmap prometheus', + scalar $c->get('httpmodules')); + $self->assert_null($c2->get('httpmodules')); + $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); + $self->assert_null($c2->get('event_groups')); + + # discard clone and recreate, clone should be the same as parent again + undef $c2; + $c2 = $c->clone(); + $self->assert_not_equals($c, $c2); + $self->assert_equals(scalar $c->get('httpmodules'), + scalar $c2->get('httpmodules')); + $self->assert_equals(scalar $c->get('event_groups'), + scalar $c2->get('event_groups')); + + # bit set in both parent and child is only listed once + $c2->set_bits('httpmodules', 'jmap'); + $self->assert_str_equals('carddav jmap prometheus', + scalar $c->get('httpmodules')); + $self->assert_str_equals('carddav jmap prometheus', + scalar $c2->get('httpmodules')); + $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); + $self->assert_str_equals('mailbox', scalar $c2->get('event_groups')); + + # clearing bit in parent doesn't affect child who has it explicitly set + $c->clear_bits('httpmodules', 'jmap'); + $self->assert_str_equals('carddav prometheus', scalar $c->get('httpmodules')); + $self->assert_str_equals('carddav jmap prometheus', + scalar $c2->get('httpmodules')); + $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); + $self->assert_str_equals('mailbox', scalar $c2->get('event_groups')); + + # clearing all in parent doesn't affect child's explicit bits + $c->clear_all_bits('httpmodules'); + $self->assert_null($c->get('httpmodules')); + $self->assert_str_equals('jmap', scalar $c2->get('httpmodules')); + $self->assert_str_equals('mailbox', scalar $c->get('event_groups')); + $self->assert_str_equals('mailbox', scalar $c2->get('event_groups')); } 1; diff --git a/cassandane/Cassandane/Test/Core.pm b/cassandane/Cassandane/Test/Core.pm index 757c551374..018078b345 100644 --- a/cassandane/Cassandane/Test/Core.pm +++ b/cassandane/Cassandane/Test/Core.pm @@ -49,93 +49,89 @@ use Cassandane::Util::Log; my $crash_bin = getcwd() . '/utils/crash'; -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - die "No crash binary $crash_bin. Did you run \"make\" in the Cassandane directory?" - unless (-f $crash_bin); - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + die + "No crash binary $crash_bin. Did you run \"make\" in the Cassandane directory?" + unless (-f $crash_bin); + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } -sub _test_core_files_with_size -{ - my ($self, $alloc) = @_; - - my $instance = $self->{instance}; - my $signaled = 0; #n.b. spelling - my $pid; - - $instance->run_command( - { cyrus => 0, - handlers => { - signaled => sub { - my ($child, $sig) = @_; - $pid = $child->{pid}; - $signaled++; - } }, - }, - $crash_bin, $alloc); - - $self->assert_equals(1, $signaled); - $self->assert_not_null($pid); - - my @cores = $instance->find_cores(); - - # expect there's exactly one core - $self->assert_num_equals(1, scalar @cores); - - my $cassini = Cassandane::Cassini->instance(); - my $core_pattern = $cassini->get_core_pattern(); - - my $core = shift @cores; - if ($core =~ m/$core_pattern/ && $1) { - # if there's a pid in the filename, check it - $self->assert_num_equals($pid, $1); - } - my $size = -s $core; - - # clean up the core we expected, so we don't barf on it existing! - unlink $core or die "unlink $core: $!"; - # but don't clean up any other unexpected cores! - - $self->assert($size > $alloc); +sub _test_core_files_with_size { + my ($self, $alloc) = @_; + + my $instance = $self->{instance}; + my $signaled = 0; #n.b. spelling + my $pid; + + $instance->run_command( + { + cyrus => 0, + handlers => { + signaled => sub { + my ($child, $sig) = @_; + $pid = $child->{pid}; + $signaled++; + } + }, + }, + $crash_bin, + $alloc + ); + + $self->assert_equals(1, $signaled); + $self->assert_not_null($pid); + + my @cores = $instance->find_cores(); + + # expect there's exactly one core + $self->assert_num_equals(1, scalar @cores); + + my $cassini = Cassandane::Cassini->instance(); + my $core_pattern = $cassini->get_core_pattern(); + + my $core = shift @cores; + if ($core =~ m/$core_pattern/ && $1) { + # if there's a pid in the filename, check it + $self->assert_num_equals($pid, $1); + } + my $size = -s $core; + + # clean up the core we expected, so we don't barf on it existing! + unlink $core or die "unlink $core: $!"; + # but don't clean up any other unexpected cores! + + $self->assert($size > $alloc); } -sub test_core_files_1KB -{ - shift->_test_core_files_with_size(1 * 1024); +sub test_core_files_1KB { + shift->_test_core_files_with_size(1 * 1024); } -sub test_core_files_1MB -{ - shift->_test_core_files_with_size(1 * 1024 * 1024); +sub test_core_files_1MB { + shift->_test_core_files_with_size(1 * 1024 * 1024); } -sub test_core_files_5MB -{ - shift->_test_core_files_with_size(5 * 1024 * 1024); +sub test_core_files_5MB { + shift->_test_core_files_with_size(5 * 1024 * 1024); } -sub test_core_files_10MB -{ - shift->_test_core_files_with_size(10 * 1024 * 1024); +sub test_core_files_10MB { + shift->_test_core_files_with_size(10 * 1024 * 1024); } -sub test_core_files_50MB -{ - shift->_test_core_files_with_size(50 * 1024 * 1024); +sub test_core_files_50MB { + shift->_test_core_files_with_size(50 * 1024 * 1024); } # Cassandane::Instance::_fork_command limits core sizes to 100MB diff --git a/cassandane/Cassandane/Test/DateTime.pm b/cassandane/Cassandane/Test/DateTime.pm index 9932fe6b0d..132d960d2f 100644 --- a/cassandane/Cassandane/Test/DateTime.pm +++ b/cassandane/Cassandane/Test/DateTime.pm @@ -45,71 +45,68 @@ use lib '.'; use base qw(Cassandane::Unit::TestCase); use Cassandane::Util::DateTime; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } -sub test_basic -{ - my ($self) = @_; - $self->assert_num_equals(1287073192, from_iso8601('20101014T161952Z')->epoch); - $self->assert_num_equals(1287073192, from_rfc822('Fri, 15 Oct 2010 03:19:52 +1100')->epoch); - $self->assert_num_equals(1287073192, from_rfc3501('15-Oct-2010 03:19:52 +1100')->epoch); - $self->assert_str_equals('20101014T161952Z', to_iso8601(DateTime->from_epoch(epoch => 1287073192))); - local $ENV{TZ} = "Australia/Melbourne"; - $self->assert_str_equals('Fri, 15 Oct 2010 03:19:52 +1100', to_rfc822(DateTime->from_epoch(epoch => 1287073192))); - $self->assert_str_equals('15-Oct-2010 03:19:52 +1100', to_rfc3501(DateTime->from_epoch(epoch => 1287073192))); +sub test_basic { + my ($self) = @_; + $self->assert_num_equals(1287073192, from_iso8601('20101014T161952Z')->epoch); + $self->assert_num_equals(1287073192, + from_rfc822('Fri, 15 Oct 2010 03:19:52 +1100')->epoch); + $self->assert_num_equals(1287073192, + from_rfc3501('15-Oct-2010 03:19:52 +1100')->epoch); + $self->assert_str_equals('20101014T161952Z', + to_iso8601(DateTime->from_epoch(epoch => 1287073192))); + local $ENV{TZ} = "Australia/Melbourne"; + $self->assert_str_equals('Fri, 15 Oct 2010 03:19:52 +1100', + to_rfc822(DateTime->from_epoch(epoch => 1287073192))); + $self->assert_str_equals('15-Oct-2010 03:19:52 +1100', + to_rfc3501(DateTime->from_epoch(epoch => 1287073192))); } -sub test_localtime_month_ahead_of_utc -{ - my ($self) = @_; +sub test_localtime_month_ahead_of_utc { + my ($self) = @_; - # early morning 2023-09-01 UTC+10 - # late evening 2023-08-31 UTC - my $dt = DateTime->new( - year => 2023, - month => 9, - day => 1, - hour => 9, - minute => 30, - second => 0, - time_zone => 'Australia/Sydney', - ); - $dt->set_time_zone('Etc/UTC'); + # early morning 2023-09-01 UTC+10 + # late evening 2023-08-31 UTC + my $dt = DateTime->new( + year => 2023, + month => 9, + day => 1, + hour => 9, + minute => 30, + second => 0, + time_zone => 'Australia/Sydney', + ); + $dt->set_time_zone('Etc/UTC'); - local $ENV{TZ} = 'Australia/Sydney'; - $self->assert_str_equals('Fri, 01 Sep 2023 09:30:00 +1000', - to_rfc822($dt)); - $self->assert_str_equals(' 1-Sep-2023 09:30:00 +1000', - to_rfc3501($dt)); + local $ENV{TZ} = 'Australia/Sydney'; + $self->assert_str_equals('Fri, 01 Sep 2023 09:30:00 +1000', to_rfc822($dt)); + $self->assert_str_equals(' 1-Sep-2023 09:30:00 +1000', to_rfc3501($dt)); } -sub test_localtime_month_behind_utc -{ - my ($self) = @_; +sub test_localtime_month_behind_utc { + my ($self) = @_; - # late evening 2023-08-31 UTC-4 - # early morning 2023-09-01 UTC - my $dt = DateTime->new( - year => 2023, - month => 8, - day => 31, - hour => 22, - minute => 30, - second => 0, - time_zone => 'America/New_York', - ); - $dt->set_time_zone('Etc/UTC'); + # late evening 2023-08-31 UTC-4 + # early morning 2023-09-01 UTC + my $dt = DateTime->new( + year => 2023, + month => 8, + day => 31, + hour => 22, + minute => 30, + second => 0, + time_zone => 'America/New_York', + ); + $dt->set_time_zone('Etc/UTC'); - local $ENV{TZ} = 'America/New_York'; - $self->assert_str_equals('Thu, 31 Aug 2023 22:30:00 -0400', - to_rfc822($dt)); - $self->assert_str_equals('31-Aug-2023 22:30:00 -0400', - to_rfc3501($dt)); + local $ENV{TZ} = 'America/New_York'; + $self->assert_str_equals('Thu, 31 Aug 2023 22:30:00 -0400', to_rfc822($dt)); + $self->assert_str_equals('31-Aug-2023 22:30:00 -0400', to_rfc3501($dt)); } 1; diff --git a/cassandane/Cassandane/Test/Mboxname.pm b/cassandane/Cassandane/Test/Mboxname.pm index a831ce8669..0bf7aab8e0 100644 --- a/cassandane/Cassandane/Test/Mboxname.pm +++ b/cassandane/Cassandane/Test/Mboxname.pm @@ -46,255 +46,226 @@ use base qw(Cassandane::Unit::TestCase); use Cassandane::Mboxname; use Cassandane::Config; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } -sub myconfig -{ - my $conf = Cassandane::Config::default(); - $conf->set(virtdomains => 'userid'); - return $conf; +sub myconfig { + my $conf = Cassandane::Config::default(); + $conf->set(virtdomains => 'userid'); + return $conf; } -sub test_default_ctor -{ - my ($self) = @_; - my $mb = Cassandane::Mboxname->new(); - $self->assert_null($mb->domain); - $self->assert_null($mb->userid); - $self->assert_null($mb->box); - $self->assert_null($mb->to_internal); - $self->assert_null($mb->to_external); - $self->assert_null($mb->to_username); +sub test_default_ctor { + my ($self) = @_; + my $mb = Cassandane::Mboxname->new(); + $self->assert_null($mb->domain); + $self->assert_null($mb->userid); + $self->assert_null($mb->box); + $self->assert_null($mb->to_internal); + $self->assert_null($mb->to_external); + $self->assert_null($mb->to_username); } -sub test_parts_ctor -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new( - domain => 'quinoa.com', - userid => 'pickled', - box => 'fanny.pack'); - $self->assert_str_equals($mb->domain, 'quinoa.com'); - $self->assert_str_equals($mb->userid, 'pickled'); - $self->assert_str_equals($mb->box, 'fanny.pack'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb->to_external, - 'user.pickled.fanny.pack@quinoa.com'); - $self->assert_str_equals($mb->to_username, - 'pickled@quinoa.com'); +sub test_parts_ctor { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new( + domain => 'quinoa.com', + userid => 'pickled', + box => 'fanny.pack' + ); + $self->assert_str_equals($mb->domain, 'quinoa.com'); + $self->assert_str_equals($mb->userid, 'pickled'); + $self->assert_str_equals($mb->box, 'fanny.pack'); + $self->assert_str_equals($mb->to_internal, + 'quinoa.com!user.pickled.fanny.pack'); + $self->assert_str_equals($mb->to_external, + 'user.pickled.fanny.pack@quinoa.com'); + $self->assert_str_equals($mb->to_username, 'pickled@quinoa.com'); } -sub test_internal_ctor -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new( - config => myconfig(), - internal => 'quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb->domain, 'quinoa.com'); - $self->assert_str_equals($mb->userid, 'pickled'); - $self->assert_str_equals($mb->box, 'fanny.pack'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb->to_external, - 'user.pickled.fanny.pack@quinoa.com'); - $self->assert_str_equals($mb->to_username, - 'pickled@quinoa.com'); +sub test_internal_ctor { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new( + config => myconfig(), + internal => 'quinoa.com!user.pickled.fanny.pack' + ); + $self->assert_str_equals($mb->domain, 'quinoa.com'); + $self->assert_str_equals($mb->userid, 'pickled'); + $self->assert_str_equals($mb->box, 'fanny.pack'); + $self->assert_str_equals($mb->to_internal, + 'quinoa.com!user.pickled.fanny.pack'); + $self->assert_str_equals($mb->to_external, + 'user.pickled.fanny.pack@quinoa.com'); + $self->assert_str_equals($mb->to_username, 'pickled@quinoa.com'); } -sub test_external_ctor -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new( - config => myconfig(), - external => 'user.pickled.fanny.pack@quinoa.com'); - $self->assert_str_equals($mb->domain, 'quinoa.com'); - $self->assert_str_equals($mb->userid, 'pickled'); - $self->assert_str_equals($mb->box, 'fanny.pack'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb->to_external, - 'user.pickled.fanny.pack@quinoa.com'); - $self->assert_str_equals($mb->to_username, - 'pickled@quinoa.com'); +sub test_external_ctor { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new( + config => myconfig(), + external => 'user.pickled.fanny.pack@quinoa.com' + ); + $self->assert_str_equals($mb->domain, 'quinoa.com'); + $self->assert_str_equals($mb->userid, 'pickled'); + $self->assert_str_equals($mb->box, 'fanny.pack'); + $self->assert_str_equals($mb->to_internal, + 'quinoa.com!user.pickled.fanny.pack'); + $self->assert_str_equals($mb->to_external, + 'user.pickled.fanny.pack@quinoa.com'); + $self->assert_str_equals($mb->to_username, 'pickled@quinoa.com'); } -sub test_username_ctor -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new( - config => myconfig(), - username => 'pickled@quinoa.com'); - $self->assert_str_equals($mb->domain, 'quinoa.com'); - $self->assert_str_equals($mb->userid, 'pickled'); - $self->assert_null($mb->box); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled'); - $self->assert_str_equals($mb->to_external, - 'user.pickled@quinoa.com'); - $self->assert_str_equals($mb->to_username, - 'pickled@quinoa.com'); +sub test_username_ctor { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new( + config => myconfig(), + username => 'pickled@quinoa.com' + ); + $self->assert_str_equals($mb->domain, 'quinoa.com'); + $self->assert_str_equals($mb->userid, 'pickled'); + $self->assert_null($mb->box); + $self->assert_str_equals($mb->to_internal, 'quinoa.com!user.pickled'); + $self->assert_str_equals($mb->to_external, 'user.pickled@quinoa.com'); + $self->assert_str_equals($mb->to_username, 'pickled@quinoa.com'); } -sub test_broken_ctor -{ - my ($self) = @_; - my $mb; - my $ex; - - eval - { - $mb = Cassandane::Mboxname->new( - config => myconfig(), - internal => 'quinoa.com!user.pickled.fanny.pack', - username => 'pickled@quinoa.com'); - }; - $ex = $@; - $self->assert_matches(qr/contradictory initialisers/, $ex); - - eval - { - $mb = Cassandane::Mboxname->new( - config => myconfig(), - external => 'user.pickled.fanny.pack@quinoa.com', - username => 'pickled@quinoa.com'); - }; - $ex = $@; - $self->assert_matches(qr/contradictory initialisers/, $ex); - - eval - { - $mb = Cassandane::Mboxname->new( - config => myconfig(), - internal => 'quinoa.com!user.pickled.fanny.pack', - external => 'user.pickled.fanny.pack@quinoa.com'); - }; - $ex = $@; - $self->assert_matches(qr/contradictory initialisers/, $ex); - - eval - { - $mb = Cassandane::Mboxname->new( - config => myconfig(), - internal => 'quinoa.com!user.pickled.fanny.pack', - selvage => 'sustainble'); - }; - $ex = $@; - $self->assert_matches(qr/extra arguments/, $ex); +sub test_broken_ctor { + my ($self) = @_; + my $mb; + my $ex; + + eval { + $mb = Cassandane::Mboxname->new( + config => myconfig(), + internal => 'quinoa.com!user.pickled.fanny.pack', + username => 'pickled@quinoa.com' + ); + }; + $ex = $@; + $self->assert_matches(qr/contradictory initialisers/, $ex); + + eval { + $mb = Cassandane::Mboxname->new( + config => myconfig(), + external => 'user.pickled.fanny.pack@quinoa.com', + username => 'pickled@quinoa.com' + ); + }; + $ex = $@; + $self->assert_matches(qr/contradictory initialisers/, $ex); + + eval { + $mb = Cassandane::Mboxname->new( + config => myconfig(), + internal => 'quinoa.com!user.pickled.fanny.pack', + external => 'user.pickled.fanny.pack@quinoa.com' + ); + }; + $ex = $@; + $self->assert_matches(qr/contradictory initialisers/, $ex); + + eval { + $mb = Cassandane::Mboxname->new( + config => myconfig(), + internal => 'quinoa.com!user.pickled.fanny.pack', + selvage => 'sustainble' + ); + }; + $ex = $@; + $self->assert_matches(qr/extra arguments/, $ex); } -sub test_from_internal -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new(config => myconfig()); - $mb->from_internal('quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb->domain, 'quinoa.com'); - $self->assert_str_equals($mb->userid, 'pickled'); - $self->assert_str_equals($mb->box, 'fanny.pack'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb->to_external, - 'user.pickled.fanny.pack@quinoa.com'); - $self->assert_str_equals($mb->to_username, - 'pickled@quinoa.com'); +sub test_from_internal { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new(config => myconfig()); + $mb->from_internal('quinoa.com!user.pickled.fanny.pack'); + $self->assert_str_equals($mb->domain, 'quinoa.com'); + $self->assert_str_equals($mb->userid, 'pickled'); + $self->assert_str_equals($mb->box, 'fanny.pack'); + $self->assert_str_equals($mb->to_internal, + 'quinoa.com!user.pickled.fanny.pack'); + $self->assert_str_equals($mb->to_external, + 'user.pickled.fanny.pack@quinoa.com'); + $self->assert_str_equals($mb->to_username, 'pickled@quinoa.com'); } -sub test_from_external -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new(config => myconfig()); - $mb->from_external('user.pickled.fanny.pack@quinoa.com'); - $self->assert_str_equals($mb->domain, 'quinoa.com'); - $self->assert_str_equals($mb->userid, 'pickled'); - $self->assert_str_equals($mb->box, 'fanny.pack'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb->to_external, - 'user.pickled.fanny.pack@quinoa.com'); - $self->assert_str_equals($mb->to_username, - 'pickled@quinoa.com'); +sub test_from_external { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new(config => myconfig()); + $mb->from_external('user.pickled.fanny.pack@quinoa.com'); + $self->assert_str_equals($mb->domain, 'quinoa.com'); + $self->assert_str_equals($mb->userid, 'pickled'); + $self->assert_str_equals($mb->box, 'fanny.pack'); + $self->assert_str_equals($mb->to_internal, + 'quinoa.com!user.pickled.fanny.pack'); + $self->assert_str_equals($mb->to_external, + 'user.pickled.fanny.pack@quinoa.com'); + $self->assert_str_equals($mb->to_username, 'pickled@quinoa.com'); } -sub test_from_username -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new(config => myconfig()); - $mb->from_username('pickled@quinoa.com'); - $self->assert_str_equals($mb->domain, 'quinoa.com'); - $self->assert_str_equals($mb->userid, 'pickled'); - $self->assert_null($mb->box); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled'); - $self->assert_str_equals($mb->to_external, - 'user.pickled@quinoa.com'); - $self->assert_str_equals($mb->to_username, - 'pickled@quinoa.com'); +sub test_from_username { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new(config => myconfig()); + $mb->from_username('pickled@quinoa.com'); + $self->assert_str_equals($mb->domain, 'quinoa.com'); + $self->assert_str_equals($mb->userid, 'pickled'); + $self->assert_null($mb->box); + $self->assert_str_equals($mb->to_internal, 'quinoa.com!user.pickled'); + $self->assert_str_equals($mb->to_external, 'user.pickled@quinoa.com'); + $self->assert_str_equals($mb->to_username, 'pickled@quinoa.com'); } -sub test_make_child -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new( - config => myconfig(), - internal => 'quinoa.com!user.pickled'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled'); - - my $mb2 = $mb->make_child('fanny'); - $self->assert_str_equals($mb2->to_internal, - 'quinoa.com!user.pickled.fanny'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled'); - - my $mb3 = $mb2->make_child('pack'); - $self->assert_str_equals($mb3->to_internal, - 'quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb2->to_internal, - 'quinoa.com!user.pickled.fanny'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled'); +sub test_make_child { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new( + config => myconfig(), + internal => 'quinoa.com!user.pickled' + ); + $self->assert_str_equals($mb->to_internal, 'quinoa.com!user.pickled'); + + my $mb2 = $mb->make_child('fanny'); + $self->assert_str_equals($mb2->to_internal, 'quinoa.com!user.pickled.fanny'); + $self->assert_str_equals($mb->to_internal, 'quinoa.com!user.pickled'); + + my $mb3 = $mb2->make_child('pack'); + $self->assert_str_equals($mb3->to_internal, + 'quinoa.com!user.pickled.fanny.pack'); + $self->assert_str_equals($mb2->to_internal, 'quinoa.com!user.pickled.fanny'); + $self->assert_str_equals($mb->to_internal, 'quinoa.com!user.pickled'); } -sub test_make_parent -{ - my ($self) = @_; - - my $mb = Cassandane::Mboxname->new( - config => myconfig(), - internal => 'quinoa.com!user.pickled.fanny.pack'); - $self->assert_str_equals($mb->to_internal, - 'quinoa.com!user.pickled.fanny.pack'); - - my $mb2 = $mb->make_parent(); - $self->assert_str_equals($mb2->to_internal, - 'quinoa.com!user.pickled.fanny'); - - my $mb3 = $mb2->make_parent(); - $self->assert_str_equals($mb3->to_internal, - 'quinoa.com!user.pickled'); - $self->assert_str_equals($mb2->to_internal, - 'quinoa.com!user.pickled.fanny'); - - my $mb4 = $mb3->make_parent(); - $self->assert_str_equals($mb4->to_internal, - 'quinoa.com!user.pickled'); - $self->assert_str_equals($mb3->to_internal, - 'quinoa.com!user.pickled'); - $self->assert_str_equals($mb2->to_internal, - 'quinoa.com!user.pickled.fanny'); +sub test_make_parent { + my ($self) = @_; + + my $mb = Cassandane::Mboxname->new( + config => myconfig(), + internal => 'quinoa.com!user.pickled.fanny.pack' + ); + $self->assert_str_equals($mb->to_internal, + 'quinoa.com!user.pickled.fanny.pack'); + + my $mb2 = $mb->make_parent(); + $self->assert_str_equals($mb2->to_internal, 'quinoa.com!user.pickled.fanny'); + + my $mb3 = $mb2->make_parent(); + $self->assert_str_equals($mb3->to_internal, 'quinoa.com!user.pickled'); + $self->assert_str_equals($mb2->to_internal, 'quinoa.com!user.pickled.fanny'); + + my $mb4 = $mb3->make_parent(); + $self->assert_str_equals($mb4->to_internal, 'quinoa.com!user.pickled'); + $self->assert_str_equals($mb3->to_internal, 'quinoa.com!user.pickled'); + $self->assert_str_equals($mb2->to_internal, 'quinoa.com!user.pickled.fanny'); } 1; diff --git a/cassandane/Cassandane/Test/Message.pm b/cassandane/Cassandane/Test/Message.pm index 2e335d00b5..cd7581e1d8 100755 --- a/cassandane/Cassandane/Test/Message.pm +++ b/cassandane/Cassandane/Test/Message.pm @@ -48,208 +48,215 @@ use Cassandane::Address; use Cassandane::Util::Log; use Cassandane::Util::DateTime qw(to_rfc3501); -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } # Test default ctor -sub test_empty -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $self->assert_null($m->get_headers('from')); - $self->assert_null($m->get_headers('to')); - $self->assert_null($m->get_body()); +sub test_empty { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $self->assert_null($m->get_headers('from')); + $self->assert_null($m->get_headers('to')); + $self->assert_null($m->get_body()); - my $exp = <<'EOF'; + my $exp = <<'EOF'; EOF - $exp =~ s/\n/\r\n/g; + $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test case sensitivity of header names -sub test_header_case -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('SUBJECT', 'Hello World'); - $self->assert_null($m->get_headers('from')); - $self->assert_null($m->get_headers('to')); - $self->assert_null($m->get_body); - $self->assert_str_equals('Hello World', $m->get_headers('SUBJECT')->[0]); - $self->assert_str_equals('Hello World', $m->get_headers('Subject')->[0]); - $self->assert_str_equals('Hello World', $m->get_headers('subject')->[0]); - $self->assert_str_equals('Hello World', $m->get_headers('sUbJeCt')->[0]); - - my $exp = <<'EOF'; +sub test_header_case { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('SUBJECT', 'Hello World'); + $self->assert_null($m->get_headers('from')); + $self->assert_null($m->get_headers('to')); + $self->assert_null($m->get_body); + $self->assert_str_equals('Hello World', $m->get_headers('SUBJECT')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('Subject')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('subject')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('sUbJeCt')->[0]); + + my $exp = <<'EOF'; Subject: Hello World EOF - $exp =~ s/\n/\r\n/g; + $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test implicit stringification of Addresses when passing to headers -sub test_address_stringification -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); - $m->add_header('From', Cassandane::Address->new( - name => 'Fred J. Bloggs', - localpart => 'fbloggs', - domain => 'fastmail.fm')); - $self->assert_null($m->get_headers('to')); - $self->assert_null($m->get_body); - $self->assert_str_equals('Fred J. Bloggs ', - $m->get_headers('from')->[0]); - my $exp = <<'EOF'; +sub test_address_stringification { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); + $m->add_header( + 'From', + Cassandane::Address->new( + name => 'Fred J. Bloggs', + localpart => 'fbloggs', + domain => 'fastmail.fm' + ) + ); + $self->assert_null($m->get_headers('to')); + $self->assert_null($m->get_body); + $self->assert_str_equals('Fred J. Bloggs ', + $m->get_headers('from')->[0]); + my $exp = <<'EOF'; Subject: Hello World From: Fred J. Bloggs EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test stringification of a list of Addresses when passing to headers -sub test_address_list_stringification -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); - my @tos = ( - Cassandane::Address->new( - name => 'Sarah Jane Smith', - localpart => 'sjsmith', - domain => 'tard.is'), - Cassandane::Address->new( - name => 'Genghis Khan', - localpart => 'gkhan', - domain => 'horde.mo'), - ); - $m->add_header('To', join(', ', @tos)); - $self->assert_null($m->get_body()); - $self->assert_null($m->get_headers('from')); - $self->assert_str_equals( - 'Sarah Jane Smith , Genghis Khan ', - $m->get_headers('to')->[0]); - my $exp = <<'EOF'; +sub test_address_list_stringification { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); + my @tos = ( + Cassandane::Address->new( + name => 'Sarah Jane Smith', + localpart => 'sjsmith', + domain => 'tard.is' + ), + Cassandane::Address->new( + name => 'Genghis Khan', + localpart => 'gkhan', + domain => 'horde.mo' + ), + ); + $m->add_header('To', join(', ', @tos)); + $self->assert_null($m->get_body()); + $self->assert_null($m->get_headers('from')); + $self->assert_str_equals( + 'Sarah Jane Smith , Genghis Khan ', + $m->get_headers('to')->[0]); + my $exp = <<'EOF'; Subject: Hello World To: Sarah Jane Smith , Genghis Khan EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test multiple headers with the same name -sub test_multiple_headers -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); - $m->add_header("received", "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software); Fri, 29 Oct 2010 13:05:01 +1100"); - $m->add_header("received", "from mail.bar.com (mail.bar.com [10.0.0.1]) by mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100"); - $m->add_header("received", "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by mail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100"); - $self->assert_deep_equals([ - "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software); Fri, 29 Oct 2010 13:05:01 +1100", - "from mail.bar.com (mail.bar.com [10.0.0.1]) by mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", - "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by mail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", - ], $m->get_headers("received")); - my $exp = <<'EOF'; +sub test_multiple_headers { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); + $m->add_header("received", + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software); Fri, 29 Oct 2010 13:05:01 +1100" + ); + $m->add_header("received", + "from mail.bar.com (mail.bar.com [10.0.0.1]) by mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100" + ); + $m->add_header("received", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by mail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100" + ); + $self->assert_deep_equals( + [ + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software); Fri, 29 Oct 2010 13:05:01 +1100", + "from mail.bar.com (mail.bar.com [10.0.0.1]) by mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by mail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", + ], + $m->get_headers("received") + ); + my $exp = <<'EOF'; Subject: Hello World Received: from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software); Fri, 29 Oct 2010 13:05:01 +1100 Received: from mail.bar.com (mail.bar.com [10.0.0.1]) by mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100 Received: from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by mail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100 EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } - # Test replacing headers -sub test_replacing_headers -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); - $self->assert_str_equals( - 'Hello World', - $m->get_header('subject')); - $m->set_headers('subject', 'No, scratch that'); - $self->assert_str_equals( - 'No, scratch that', - $m->get_header('subject')); - my $exp = <<'EOF'; +sub test_replacing_headers { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); + $self->assert_str_equals('Hello World', $m->get_header('subject')); + $m->set_headers('subject', 'No, scratch that'); + $self->assert_str_equals('No, scratch that', $m->get_header('subject')); + my $exp = <<'EOF'; Subject: No, scratch that EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test deleting headers -sub test_deleting_headers -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); - $m->add_header("received", "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software); Fri, 29 Oct 2010 13:05:01 +1100"); - $m->add_header("received", "from mail.bar.com (mail.bar.com [10.0.0.1]) by mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100"); - $m->add_header("received", "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by mail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100"); - $self->assert_str_equals('Hello World', $m->get_header('subject')); - $m->remove_headers('subject'); - $self->assert_null($m->get_header('subject')); - my $exp = <<'EOF'; +sub test_deleting_headers { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); + $m->add_header("received", + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software); Fri, 29 Oct 2010 13:05:01 +1100" + ); + $m->add_header("received", + "from mail.bar.com (mail.bar.com [10.0.0.1]) by mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100" + ); + $m->add_header("received", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by mail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100" + ); + $self->assert_str_equals('Hello World', $m->get_header('subject')); + $m->remove_headers('subject'); + $self->assert_null($m->get_header('subject')); + my $exp = <<'EOF'; Received: from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software); Fri, 29 Oct 2010 13:05:01 +1100 Received: from mail.bar.com (mail.bar.com [10.0.0.1]) by mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100 Received: from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by mail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100 EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test adding a body -- only plain text for now, no MIME -sub test_add_body -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); - $m->set_body("This is a message to let you know\r\nthat I'm alive and well\r\n"); - my $exp = <<'EOF'; +sub test_add_body { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); + $m->set_body( + "This is a message to let you know\r\nthat I'm alive and well\r\n"); + my $exp = <<'EOF'; Subject: Hello World This is a message to let you know that I'm alive and well EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test setting lines. -sub test_setting_lines -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); +sub test_setting_lines { + my ($self) = @_; + my $m = Cassandane::Message->new(); - my $txt = <<'EOF'; + my $txt = <<'EOF'; From: Fred J. Bloggs To: Sarah Jane Smith , Genghis Khan Subject: Hello World @@ -263,56 +270,55 @@ Received: from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by This is a message to let you know that I'm alive and well EOF - my @lines = split(/\n/, $txt); - map { $_ .= "\r\n" } @lines; - - my $exp = $txt; - $exp =~ s/\n/\r\n/g; - - $m->set_lines(@lines); - $self->assert_str_equals( - 'Fred J. Bloggs ', - $m->get_headers('from')->[0]); - $self->assert_str_equals( - 'Sarah Jane Smith , Genghis Khan ', - $m->get_headers('to')->[0]); - $self->assert_str_equals( - 'Hello World', - $m->get_headers('Subject')->[0]); - $self->assert_deep_equals([ - "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", - "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", - "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", - ], $m->get_headers('received')); - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); - - $m = Cassandane::Message->new(lines => \@lines); - $self->assert_str_equals( - 'Fred J. Bloggs ', - $m->get_headers('from')->[0]); - $self->assert_str_equals( - 'Sarah Jane Smith , Genghis Khan ', - $m->get_headers('to')->[0]); - $self->assert_str_equals( - 'Hello World', - $m->get_headers('Subject')->[0]); - $self->assert_deep_equals([ - "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", - "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", - "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", - ], $m->get_headers('received')); - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + my @lines = split(/\n/, $txt); + map { $_ .= "\r\n" } @lines; + + my $exp = $txt; + $exp =~ s/\n/\r\n/g; + + $m->set_lines(@lines); + $self->assert_str_equals('Fred J. Bloggs ', + $m->get_headers('from')->[0]); + $self->assert_str_equals( + 'Sarah Jane Smith , Genghis Khan ', + $m->get_headers('to')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('Subject')->[0]); + $self->assert_deep_equals( + [ + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", + "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", + ], + $m->get_headers('received') + ); + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); + + $m = Cassandane::Message->new(lines => \@lines); + $self->assert_str_equals('Fred J. Bloggs ', + $m->get_headers('from')->[0]); + $self->assert_str_equals( + 'Sarah Jane Smith , Genghis Khan ', + $m->get_headers('to')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('Subject')->[0]); + $self->assert_deep_equals( + [ + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", + "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", + ], + $m->get_headers('received') + ); + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test setting raw text -sub test_setting_raw -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); +sub test_setting_raw { + my ($self) = @_; + my $m = Cassandane::Message->new(); - my $txt = <<'EOF'; + my $txt = <<'EOF'; From: Fred J. Bloggs To: Sarah Jane Smith , Genghis Khan Subject: Hello World @@ -326,112 +332,110 @@ Received: from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by This is a message to let you know that I'm alive and well EOF - $txt =~ s/\n/\r\n/g; - - my $exp = $txt; - - $m->set_raw($txt); - $self->assert_str_equals( - 'Fred J. Bloggs ', - $m->get_headers('from')->[0]); - $self->assert_str_equals( - 'Sarah Jane Smith , Genghis Khan ', - $m->get_headers('to')->[0]); - $self->assert_str_equals( - 'Hello World', - $m->get_headers('Subject')->[0]); - $self->assert_deep_equals([ - "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", - "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", - "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", - ], $m->get_headers('received')); - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); - - $m = Cassandane::Message->new(raw => $txt); - $self->assert_str_equals( - 'Fred J. Bloggs ', - $m->get_headers('from')->[0]); - $self->assert_str_equals( - 'Sarah Jane Smith , Genghis Khan ', - $m->get_headers('to')->[0]); - $self->assert_str_equals( - 'Hello World', - $m->get_headers('Subject')->[0]); - $self->assert_deep_equals([ - "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", - "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", - "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", - ], $m->get_headers('received')); - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $txt =~ s/\n/\r\n/g; + + my $exp = $txt; + + $m->set_raw($txt); + $self->assert_str_equals('Fred J. Bloggs ', + $m->get_headers('from')->[0]); + $self->assert_str_equals( + 'Sarah Jane Smith , Genghis Khan ', + $m->get_headers('to')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('Subject')->[0]); + $self->assert_deep_equals( + [ + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", + "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", + ], + $m->get_headers('received') + ); + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); + + $m = Cassandane::Message->new(raw => $txt); + $self->assert_str_equals('Fred J. Bloggs ', + $m->get_headers('from')->[0]); + $self->assert_str_equals( + 'Sarah Jane Smith , Genghis Khan ', + $m->get_headers('to')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('Subject')->[0]); + $self->assert_deep_equals( + [ + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", + "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", + ], + $m->get_headers('received') + ); + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test message attributes -sub test_attributes -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - - $self->assert(!$m->has_attribute('uid')); - $self->assert_null($m->get_attribute('uid')); - $self->assert_null($m->get_attribute('UID')); - $self->assert_null($m->get_attribute('uId')); - $self->assert(!$m->has_attribute('internaldate')); - $self->assert_null($m->get_attribute('internaldate')); - - $m->set_attribute('uid', 123); - $self->assert($m->has_attribute('uid')); - $self->assert($m->get_attribute('uid') == 123); - $self->assert($m->get_attribute('UID') == 123); - $self->assert($m->get_attribute('uId') == 123); - $self->assert(!$m->has_attribute('internaldate')); - $self->assert_null($m->get_attribute('internaldate')); - - $m->set_attribute('uid'); - $self->assert($m->has_attribute('uid')); - $self->assert_null($m->get_attribute('uid')); - $self->assert_null($m->get_attribute('UID')); - $self->assert_null($m->get_attribute('uId')); - $self->assert(!$m->has_attribute('internaldate')); - $self->assert_null($m->get_attribute('internaldate')); - - $m->set_internaldate('15-Oct-2010 03:19:52 +1100'); - $self->assert($m->has_attribute('internaldate')); - $self->assert_str_equals('15-Oct-2010 03:19:52 +1100', - $m->get_attribute('internaldate')); - $m->set_internaldate(undef); - $self->assert($m->has_attribute('internaldate')); - $self->assert_null($m->get_attribute('internaldate')); - my $dt = DateTime->new( - year => 2010, - month => 10, - day => 15, - hour => 3, - minute => 19, - second => 47, - time_zone => 'Australia/Melbourne'); - $m->set_internaldate($dt); - $self->assert($m->has_attribute('internaldate')); - $self->assert_str_equals(to_rfc3501($dt), - $m->get_attribute('internaldate')); - - $m = Cassandane::Message->new(attrs => { UID => 456 }); - $self->assert($m->has_attribute('uid')); - $self->assert($m->get_attribute('uid') == 456); - $self->assert($m->get_attribute('UID') == 456); - $self->assert($m->get_attribute('uId') == 456); - $self->assert(!$m->has_attribute('internaldate')); - $self->assert_null($m->get_attribute('internaldate')); +sub test_attributes { + my ($self) = @_; + my $m = Cassandane::Message->new(); + + $self->assert(!$m->has_attribute('uid')); + $self->assert_null($m->get_attribute('uid')); + $self->assert_null($m->get_attribute('UID')); + $self->assert_null($m->get_attribute('uId')); + $self->assert(!$m->has_attribute('internaldate')); + $self->assert_null($m->get_attribute('internaldate')); + + $m->set_attribute('uid', 123); + $self->assert($m->has_attribute('uid')); + $self->assert($m->get_attribute('uid') == 123); + $self->assert($m->get_attribute('UID') == 123); + $self->assert($m->get_attribute('uId') == 123); + $self->assert(!$m->has_attribute('internaldate')); + $self->assert_null($m->get_attribute('internaldate')); + + $m->set_attribute('uid'); + $self->assert($m->has_attribute('uid')); + $self->assert_null($m->get_attribute('uid')); + $self->assert_null($m->get_attribute('UID')); + $self->assert_null($m->get_attribute('uId')); + $self->assert(!$m->has_attribute('internaldate')); + $self->assert_null($m->get_attribute('internaldate')); + + $m->set_internaldate('15-Oct-2010 03:19:52 +1100'); + $self->assert($m->has_attribute('internaldate')); + $self->assert_str_equals('15-Oct-2010 03:19:52 +1100', + $m->get_attribute('internaldate')); + $m->set_internaldate(undef); + $self->assert($m->has_attribute('internaldate')); + $self->assert_null($m->get_attribute('internaldate')); + my $dt = DateTime->new( + year => 2010, + month => 10, + day => 15, + hour => 3, + minute => 19, + second => 47, + time_zone => 'Australia/Melbourne' + ); + $m->set_internaldate($dt); + $self->assert($m->has_attribute('internaldate')); + $self->assert_str_equals(to_rfc3501($dt), $m->get_attribute('internaldate')); + + $m = Cassandane::Message->new(attrs => { UID => 456 }); + $self->assert($m->has_attribute('uid')); + $self->assert($m->get_attribute('uid') == 456); + $self->assert($m->get_attribute('UID') == 456); + $self->assert($m->get_attribute('uId') == 456); + $self->assert(!$m->has_attribute('internaldate')); + $self->assert_null($m->get_attribute('internaldate')); } # Test parsing lines with unusually but validly named headers -sub test_strange_headers -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); +sub test_strange_headers { + my ($self) = @_; + my $m = Cassandane::Message->new(); - my $txt = <<'EOF'; + my $txt = <<'EOF'; From: Fred J. Bloggs To: Sarah Jane Smith , Genghis Khan X-Foo_Bar.Baz&Quux: Foonly @@ -446,331 +450,331 @@ Received: from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by This is a message to let you know that I'm alive and well EOF - my @lines = split(/\n/, $txt); - map { $_ .= "\r\n" } @lines; - - my $exp = $txt; - $exp =~ s/\n/\r\n/g; - - $m->set_lines(@lines); - $self->assert_str_equals( - 'Fred J. Bloggs ', - $m->get_headers('from')->[0]); - $self->assert_str_equals( - 'Sarah Jane Smith , Genghis Khan ', - $m->get_headers('to')->[0]); - $self->assert_str_equals( - 'Foonly', - $m->get_headers('X-Foo_Bar.Baz&Quux')->[0]); - $self->assert_str_equals( - 'Hello World', - $m->get_headers('Subject')->[0]); - $self->assert_deep_equals([ - "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", - "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", - "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", - ], $m->get_headers('received')); - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); - - $m = Cassandane::Message->new(lines => \@lines); - $self->assert_str_equals( - 'Fred J. Bloggs ', - $m->get_headers('from')->[0]); - $self->assert_str_equals( - 'Sarah Jane Smith , Genghis Khan ', - $m->get_headers('to')->[0]); - $self->assert_str_equals( - 'Foonly', - $m->get_headers('X-Foo_Bar.Baz&Quux')->[0]); - $self->assert_str_equals( - 'Hello World', - $m->get_headers('Subject')->[0]); - $self->assert_deep_equals([ - "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", - "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", - "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", - ], $m->get_headers('received')); - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + my @lines = split(/\n/, $txt); + map { $_ .= "\r\n" } @lines; + + my $exp = $txt; + $exp =~ s/\n/\r\n/g; + + $m->set_lines(@lines); + $self->assert_str_equals('Fred J. Bloggs ', + $m->get_headers('from')->[0]); + $self->assert_str_equals( + 'Sarah Jane Smith , Genghis Khan ', + $m->get_headers('to')->[0]); + $self->assert_str_equals('Foonly', + $m->get_headers('X-Foo_Bar.Baz&Quux')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('Subject')->[0]); + $self->assert_deep_equals( + [ + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", + "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", + ], + $m->get_headers('received') + ); + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); + + $m = Cassandane::Message->new(lines => \@lines); + $self->assert_str_equals('Fred J. Bloggs ', + $m->get_headers('from')->[0]); + $self->assert_str_equals( + 'Sarah Jane Smith , Genghis Khan ', + $m->get_headers('to')->[0]); + $self->assert_str_equals('Foonly', + $m->get_headers('X-Foo_Bar.Baz&Quux')->[0]); + $self->assert_str_equals('Hello World', $m->get_headers('Subject')->[0]); + $self->assert_deep_equals( + [ + "from mail.quux.com (mail.quux.com [10.0.0.1]) by mail.gmail.com (Software);\r\n\tFri, 29 Oct 2010 13:05:01 +1100", + "from mail.bar.com (mail.bar.com [10.0.0.1])\r\n\tby mail.quux.com (Software); Fri, 29 Oct 2010 13:03:03 +1100", + "from mail.fastmail.fm (mail.fastmail.fm [10.0.0.1]) by\r\n\tmail.bar.com (Software); Fri, 29 Oct 2010 13:01:01 +1100", + ], + $m->get_headers('received') + ); + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test parsing lines with unusually but validly named headers -sub test_clone -{ - my ($self) = @_; +sub test_clone { + my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); - $m->set_body("This is a message to let you know\r\nthat I'm alive and well\r\n"); - $m->set_attribute('uid', 42); + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); + $m->set_body( + "This is a message to let you know\r\nthat I'm alive and well\r\n"); + $m->set_attribute('uid', 42); - my $exp = <<'EOF'; + my $exp = <<'EOF'; Subject: Hello World This is a message to let you know that I'm alive and well EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_num_equals(42, $m->get_attribute('uid')); - $self->assert_str_equals('Hello World', $m->get_header('subject')); - - my $m2 = $m->clone(); - $self->assert_str_equals($exp, $m2->as_string); - $self->assert_num_equals(42, $m2->get_attribute('uid')); - $self->assert_str_equals('Hello World', $m2->get_header('subject')); - - my $addr = Cassandane::Address->new( - name => 'Fred J. Bloggs', - localpart => 'fbloggs', - domain => 'fastmail.fm'); - $m->add_header('From', $addr); - $self->assert_str_equals($addr->as_string, $m->get_header('from')); - $self->assert_null($m2->get_header('from')); - my $exp2 = <<'EOF'; + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_num_equals(42, $m->get_attribute('uid')); + $self->assert_str_equals('Hello World', $m->get_header('subject')); + + my $m2 = $m->clone(); + $self->assert_str_equals($exp, $m2->as_string); + $self->assert_num_equals(42, $m2->get_attribute('uid')); + $self->assert_str_equals('Hello World', $m2->get_header('subject')); + + my $addr = Cassandane::Address->new( + name => 'Fred J. Bloggs', + localpart => 'fbloggs', + domain => 'fastmail.fm' + ); + $m->add_header('From', $addr); + $self->assert_str_equals($addr->as_string, $m->get_header('from')); + $self->assert_null($m2->get_header('from')); + my $exp2 = <<'EOF'; Subject: Hello World From: Fred J. Bloggs This is a message to let you know that I'm alive and well EOF - $exp2 =~ s/\n/\r\n/g; - $self->assert_str_equals($exp2, $m->as_string); - $self->assert_str_equals($exp, $m2->as_string); + $exp2 =~ s/\n/\r\n/g; + $self->assert_str_equals($exp2, $m->as_string); + $self->assert_str_equals($exp, $m2->as_string); } # Test base_subject() -sub test_base_subject -{ - my ($self) = @_; - - my @testcases = ( - 'Hello World' => 'Hello World', - ' Hello World' => 'Hello World', - 'Hello World ' => 'Hello World', - ' Hello World ' => 'Hello World', - ' Hello World ' => 'Hello World', - " \t\t Hello \t\t World \t\t " => 'Hello World', - 're: Hello World' => 'Hello World', - 'Re: Hello World' => 'Hello World', - 'RE: Hello World' => 'Hello World', - 're : Hello World' => 'Hello World', - 'Re : Hello World' => 'Hello World', - 'RE : Hello World' => 'Hello World', - "re \t : Hello World" => 'Hello World', - "Re \t : Hello World" => 'Hello World', - "RE \t : Hello World" => 'Hello World', - 'fw: Hello World' => 'Hello World', - 'Fw: Hello World' => 'Hello World', - 'FW: Hello World' => 'Hello World', - 'fw : Hello World' => 'Hello World', - 'Fw : Hello World' => 'Hello World', - 'FW : Hello World' => 'Hello World', - "fw \t : Hello World" => 'Hello World', - "Fw \t : Hello World" => 'Hello World', - "FW \t : Hello World" => 'Hello World', - 'fwd: Hello World' => 'Hello World', - 'Fwd: Hello World' => 'Hello World', - 'FWD: Hello World' => 'Hello World', - 'fwd : Hello World' => 'Hello World', - 'Fwd : Hello World' => 'Hello World', - 'FWD : Hello World' => 'Hello World', - "fwd \t : Hello World" => 'Hello World', - "Fwd \t : Hello World" => 'Hello World', - "FWD \t : Hello World" => 'Hello World', - "Hello World (fwd)" => 'Hello World', - "Hello World (fwd) \t " => 'Hello World', - "Hello World \t \t (fwd)" => 'Hello World', - "Hello World (FWD) \t" => 'Hello World', - "Hello World \t\t (FWD)" => 'Hello World', - "Hello World (FWD) " => 'Hello World', - " \t\t Hello World" => "Hello World", - "[PATCH]Hello World" => "Hello World", - "[PATCH] Hello World" => "Hello World", - "\t [PATCH] \t Hello World" => "Hello World", - "[RFC][PATCH][WTF]Hello World" => "Hello World", - " [RFC] [PATCH] [WTF] Hello World" => "Hello World", - ); - - while (@testcases) - { - my $in = shift @testcases; - my $exp = shift @testcases; - my $out = base_subject($in); - xlog "base_subject(\"$in\") = \"$out\""; - $self->assert_str_equals($exp, $out); - } +sub test_base_subject { + my ($self) = @_; + + my @testcases = ( + 'Hello World' => 'Hello World', + ' Hello World' => 'Hello World', + 'Hello World ' => 'Hello World', + ' Hello World ' => 'Hello World', + ' Hello World ' => 'Hello World', + " \t\t Hello \t\t World \t\t " => 'Hello World', + 're: Hello World' => 'Hello World', + 'Re: Hello World' => 'Hello World', + 'RE: Hello World' => 'Hello World', + 're : Hello World' => 'Hello World', + 'Re : Hello World' => 'Hello World', + 'RE : Hello World' => 'Hello World', + "re \t : Hello World" => 'Hello World', + "Re \t : Hello World" => 'Hello World', + "RE \t : Hello World" => 'Hello World', + 'fw: Hello World' => 'Hello World', + 'Fw: Hello World' => 'Hello World', + 'FW: Hello World' => 'Hello World', + 'fw : Hello World' => 'Hello World', + 'Fw : Hello World' => 'Hello World', + 'FW : Hello World' => 'Hello World', + "fw \t : Hello World" => 'Hello World', + "Fw \t : Hello World" => 'Hello World', + "FW \t : Hello World" => 'Hello World', + 'fwd: Hello World' => 'Hello World', + 'Fwd: Hello World' => 'Hello World', + 'FWD: Hello World' => 'Hello World', + 'fwd : Hello World' => 'Hello World', + 'Fwd : Hello World' => 'Hello World', + 'FWD : Hello World' => 'Hello World', + "fwd \t : Hello World" => 'Hello World', + "Fwd \t : Hello World" => 'Hello World', + "FWD \t : Hello World" => 'Hello World', + "Hello World (fwd)" => 'Hello World', + "Hello World (fwd) \t " => 'Hello World', + "Hello World \t \t (fwd)" => 'Hello World', + "Hello World (FWD) \t" => 'Hello World', + "Hello World \t\t (FWD)" => 'Hello World', + "Hello World (FWD) " => 'Hello World', + " \t\t Hello World" => "Hello World", + "[PATCH]Hello World" => "Hello World", + "[PATCH] Hello World" => "Hello World", + "\t [PATCH] \t Hello World" => "Hello World", + "[RFC][PATCH][WTF]Hello World" => "Hello World", + " [RFC] [PATCH] [WTF] Hello World" => "Hello World", + ); + + while (@testcases) { + my $in = shift @testcases; + my $exp = shift @testcases; + my $out = base_subject($in); + xlog "base_subject(\"$in\") = \"$out\""; + $self->assert_str_equals($exp, $out); + } } -sub test_attributes2 -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - - $self->assert(!$m->has_attribute('foo')); - $self->assert(!$m->has_attribute('bar')); - $self->assert(!$m->has_attribute('baz')); - - # set_attribute() sets an attribute to the given value - $m->set_attribute(foo => 'cosby'); - $self->assert($m->has_attribute('foo')); - $self->assert($m->has_attribute('Foo')); - $self->assert($m->has_attribute('FOO')); - # attribute names are case-insensitive - $self->assert_str_equals('cosby', $m->get_attribute('foo')); - $self->assert_str_equals('cosby', $m->get_attribute('Foo')); - $self->assert_str_equals('cosby', $m->get_attribute('FOO')); - # other attributes unchanged - $self->assert(!$m->has_attribute('bar')); - $self->assert(!$m->has_attribute('baz')); - - # set_attributes() sets a list of attributes from the - # given list of attribute,value pairs - $m->set_attributes(bar => 'sweater', foo => 'etsy'); - $self->assert($m->has_attribute('foo')); - $self->assert_str_equals('etsy', $m->get_attribute('foo')); - $self->assert($m->has_attribute('bar')); - $self->assert_str_equals('sweater', $m->get_attribute('bar')); - $self->assert(!$m->has_attribute('baz')); - - # set_attribute to an undef value doesn't remove the attribute - # but remembers the undef - this is necessary for strict checking - # of IMAP server responses in a number of cases. - $m->set_attribute(foo => undef); - $self->assert($m->has_attribute('foo')); - $self->assert_null($m->get_attribute('foo')); - $self->assert($m->has_attribute('bar')); - $self->assert_str_equals('sweater', $m->get_attribute('bar')); - $self->assert(!$m->has_attribute('baz')); +sub test_attributes2 { + my ($self) = @_; + my $m = Cassandane::Message->new(); + + $self->assert(!$m->has_attribute('foo')); + $self->assert(!$m->has_attribute('bar')); + $self->assert(!$m->has_attribute('baz')); + + # set_attribute() sets an attribute to the given value + $m->set_attribute(foo => 'cosby'); + $self->assert($m->has_attribute('foo')); + $self->assert($m->has_attribute('Foo')); + $self->assert($m->has_attribute('FOO')); + # attribute names are case-insensitive + $self->assert_str_equals('cosby', $m->get_attribute('foo')); + $self->assert_str_equals('cosby', $m->get_attribute('Foo')); + $self->assert_str_equals('cosby', $m->get_attribute('FOO')); + # other attributes unchanged + $self->assert(!$m->has_attribute('bar')); + $self->assert(!$m->has_attribute('baz')); + + # set_attributes() sets a list of attributes from the + # given list of attribute,value pairs + $m->set_attributes(bar => 'sweater', foo => 'etsy'); + $self->assert($m->has_attribute('foo')); + $self->assert_str_equals('etsy', $m->get_attribute('foo')); + $self->assert($m->has_attribute('bar')); + $self->assert_str_equals('sweater', $m->get_attribute('bar')); + $self->assert(!$m->has_attribute('baz')); + + # set_attribute to an undef value doesn't remove the attribute + # but remembers the undef - this is necessary for strict checking + # of IMAP server responses in a number of cases. + $m->set_attribute(foo => undef); + $self->assert($m->has_attribute('foo')); + $self->assert_null($m->get_attribute('foo')); + $self->assert($m->has_attribute('bar')); + $self->assert_str_equals('sweater', $m->get_attribute('bar')); + $self->assert(!$m->has_attribute('baz')); } -sub test_attributes_from_fetch -{ - my ($self) = @_; - my $m = Cassandane::Message->new(attrs => { - foo => 'ethical', - bar => 'pitchfork', - }); - - $self->assert($m->has_attribute('foo')); - $self->assert_str_equals('ethical', $m->get_attribute('foo')); - $self->assert($m->has_attribute('bar')); - $self->assert_str_equals('pitchfork', $m->get_attribute('bar')); - $self->assert(!$m->has_attribute('baz')); +sub test_attributes_from_fetch { + my ($self) = @_; + my $m = Cassandane::Message->new( + attrs => { + foo => 'ethical', + bar => 'pitchfork', + } + ); + + $self->assert($m->has_attribute('foo')); + $self->assert_str_equals('ethical', $m->get_attribute('foo')); + $self->assert($m->has_attribute('bar')); + $self->assert_str_equals('pitchfork', $m->get_attribute('bar')); + $self->assert(!$m->has_attribute('baz')); } -sub test_annotations -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - - my $e1 = '/comment'; - my $a1 = 'value.shared'; - my $e2 = '/vendor/hipsteripsum.me/buzzword'; - my $a2 = 'value.priv'; - - # no annotations on empty message - $self->assert(!$m->has_annotation($e1, $a1)); - $self->assert(!$m->has_annotation($e2, $a2)); - # alternate syntax for has_annotation - $self->assert(!$m->has_annotation({ entry => $e1, attrib => $a1 })); - $self->assert(!$m->has_annotation({ entry => $e2, attrib => $a2 })); - # get_annotation returns no annotations - $self->assert_null($m->get_annotation($e1, $a1)); - $self->assert_null($m->get_annotation($e2, $a2)); - # alternate syntax for get_annotation - $self->assert_null($m->get_annotation({ entry => $e1, attrib => $a1 })); - $self->assert_null($m->get_annotation({ entry => $e2, attrib => $a2 })); - # list_annotations returns no annotations - my @aa = $m->list_annotations(); - $self->assert_deep_equals([], \@aa); - - # set_annotation() sets an annotation to the given value - $m->set_annotation($e1, $a1, 'wayfarers'); - $self->assert($m->has_annotation($e1, $a1)); - $self->assert(!$m->has_annotation($e2, $a2)); - $self->assert($m->has_annotation({ entry => $e1, attrib => $a1 })); - $self->assert(!$m->has_annotation({ entry => $e2, attrib => $a2 })); - $self->assert_str_equals('wayfarers', $m->get_annotation($e1, $a1)); - $self->assert_null($m->get_annotation($e2, $a2)); - $self->assert_str_equals('wayfarers', $m->get_annotation({ entry => $e1, attrib => $a1 })); - $self->assert_null($m->get_annotation({ entry => $e2, attrib => $a2 })); - @aa = $m->list_annotations(); - $self->assert_deep_equals([{entry => $e1, attrib => $a1}], \@aa); - - # set_annotation to an undef value doesn't remove the annotation - # but remembers the undef - this is necessary for strict checking - # of IMAP server responses in a number of cases. - $m->set_annotation($e1, $a1, undef); - $self->assert($m->has_annotation($e1, $a1)); - $self->assert(!$m->has_annotation($e2, $a2)); - $self->assert($m->has_annotation({ entry => $e1, attrib => $a1 })); - $self->assert(!$m->has_annotation({ entry => $e2, attrib => $a2 })); - $self->assert_null($m->get_annotation($e1, $a1)); - $self->assert_null($m->get_annotation($e2, $a2)); - $self->assert_null($m->get_annotation({ entry => $e1, attrib => $a1 })); - $self->assert_null($m->get_annotation({ entry => $e2, attrib => $a2 })); - @aa = $m->list_annotations(); - $self->assert_deep_equals([{entry => $e1, attrib => $a1}], \@aa); - - # Can set two annotations - $m->set_annotation($e1, $a1, 'brooklyn'); - $m->set_annotation($e2, $a2, 'sustainable'); - $self->assert($m->has_annotation($e1, $a1)); - $self->assert($m->has_annotation($e2, $a2)); - $self->assert($m->has_annotation({ entry => $e1, attrib => $a1 })); - $self->assert($m->has_annotation({ entry => $e2, attrib => $a2 })); - $self->assert_str_equals('brooklyn', $m->get_annotation($e1, $a1)); - $self->assert_str_equals('sustainable', $m->get_annotation($e2, $a2)); - $self->assert_str_equals('brooklyn', $m->get_annotation({ entry => $e1, attrib => $a1 })); - $self->assert_str_equals('sustainable', $m->get_annotation({ entry => $e2, attrib => $a2 })); - @aa = $m->list_annotations(); - @aa = sort { $a->{entry} cmp $b->{entry} } @aa; - $self->assert_deep_equals([ - {entry => $e1, attrib => $a1}, - {entry => $e2, attrib => $a2}, - ], \@aa); +sub test_annotations { + my ($self) = @_; + my $m = Cassandane::Message->new(); + + my $e1 = '/comment'; + my $a1 = 'value.shared'; + my $e2 = '/vendor/hipsteripsum.me/buzzword'; + my $a2 = 'value.priv'; + + # no annotations on empty message + $self->assert(!$m->has_annotation($e1, $a1)); + $self->assert(!$m->has_annotation($e2, $a2)); + # alternate syntax for has_annotation + $self->assert(!$m->has_annotation({ entry => $e1, attrib => $a1 })); + $self->assert(!$m->has_annotation({ entry => $e2, attrib => $a2 })); + # get_annotation returns no annotations + $self->assert_null($m->get_annotation($e1, $a1)); + $self->assert_null($m->get_annotation($e2, $a2)); + # alternate syntax for get_annotation + $self->assert_null($m->get_annotation({ entry => $e1, attrib => $a1 })); + $self->assert_null($m->get_annotation({ entry => $e2, attrib => $a2 })); + # list_annotations returns no annotations + my @aa = $m->list_annotations(); + $self->assert_deep_equals([], \@aa); + + # set_annotation() sets an annotation to the given value + $m->set_annotation($e1, $a1, 'wayfarers'); + $self->assert($m->has_annotation($e1, $a1)); + $self->assert(!$m->has_annotation($e2, $a2)); + $self->assert($m->has_annotation({ entry => $e1, attrib => $a1 })); + $self->assert(!$m->has_annotation({ entry => $e2, attrib => $a2 })); + $self->assert_str_equals('wayfarers', $m->get_annotation($e1, $a1)); + $self->assert_null($m->get_annotation($e2, $a2)); + $self->assert_str_equals('wayfarers', + $m->get_annotation({ entry => $e1, attrib => $a1 })); + $self->assert_null($m->get_annotation({ entry => $e2, attrib => $a2 })); + @aa = $m->list_annotations(); + $self->assert_deep_equals([ { entry => $e1, attrib => $a1 } ], \@aa); + + # set_annotation to an undef value doesn't remove the annotation + # but remembers the undef - this is necessary for strict checking + # of IMAP server responses in a number of cases. + $m->set_annotation($e1, $a1, undef); + $self->assert($m->has_annotation($e1, $a1)); + $self->assert(!$m->has_annotation($e2, $a2)); + $self->assert($m->has_annotation({ entry => $e1, attrib => $a1 })); + $self->assert(!$m->has_annotation({ entry => $e2, attrib => $a2 })); + $self->assert_null($m->get_annotation($e1, $a1)); + $self->assert_null($m->get_annotation($e2, $a2)); + $self->assert_null($m->get_annotation({ entry => $e1, attrib => $a1 })); + $self->assert_null($m->get_annotation({ entry => $e2, attrib => $a2 })); + @aa = $m->list_annotations(); + $self->assert_deep_equals([ { entry => $e1, attrib => $a1 } ], \@aa); + + # Can set two annotations + $m->set_annotation($e1, $a1, 'brooklyn'); + $m->set_annotation($e2, $a2, 'sustainable'); + $self->assert($m->has_annotation($e1, $a1)); + $self->assert($m->has_annotation($e2, $a2)); + $self->assert($m->has_annotation({ entry => $e1, attrib => $a1 })); + $self->assert($m->has_annotation({ entry => $e2, attrib => $a2 })); + $self->assert_str_equals('brooklyn', $m->get_annotation($e1, $a1)); + $self->assert_str_equals('sustainable', $m->get_annotation($e2, $a2)); + $self->assert_str_equals('brooklyn', + $m->get_annotation({ entry => $e1, attrib => $a1 })); + $self->assert_str_equals('sustainable', + $m->get_annotation({ entry => $e2, attrib => $a2 })); + @aa = $m->list_annotations(); + @aa = sort { $a->{entry} cmp $b->{entry} } @aa; + $self->assert_deep_equals( + [ { entry => $e1, attrib => $a1 }, { entry => $e2, attrib => $a2 }, ], + \@aa); } -sub test_annotations_from_fetch -{ - my ($self) = @_; - - my $e1 = '/comment'; - my $a1 = 'value.shared'; - my $e2 = '/vendor/hipsteripsum.me/buzzword'; - my $a2 = 'value.priv'; - - my $m = Cassandane::Message->new(attrs => { - annotation => { - $e1 => { $a1 => 'whatever' }, - $e2 => { $a2 => 'sartorial' } - }}); - - $self->assert($m->has_annotation($e1, $a1)); - $self->assert($m->has_annotation($e2, $a2)); - $self->assert($m->has_annotation({ entry => $e1, attrib => $a1 })); - $self->assert($m->has_annotation({ entry => $e2, attrib => $a2 })); - $self->assert_str_equals('whatever', $m->get_annotation($e1, $a1)); - $self->assert_str_equals('sartorial', $m->get_annotation($e2, $a2)); - $self->assert_str_equals('whatever', $m->get_annotation({ entry => $e1, attrib => $a1 })); - $self->assert_str_equals('sartorial', $m->get_annotation({ entry => $e2, attrib => $a2 })); - my @aa = $m->list_annotations(); - @aa = sort { $a->{entry} cmp $b->{entry} } @aa; - $self->assert_deep_equals([ - {entry => $e1, attrib => $a1}, - {entry => $e2, attrib => $a2}, - ], \@aa); +sub test_annotations_from_fetch { + my ($self) = @_; + + my $e1 = '/comment'; + my $a1 = 'value.shared'; + my $e2 = '/vendor/hipsteripsum.me/buzzword'; + my $a2 = 'value.priv'; + + my $m = Cassandane::Message->new( + attrs => { + annotation => { + $e1 => { $a1 => 'whatever' }, + $e2 => { $a2 => 'sartorial' } + } + } + ); + + $self->assert($m->has_annotation($e1, $a1)); + $self->assert($m->has_annotation($e2, $a2)); + $self->assert($m->has_annotation({ entry => $e1, attrib => $a1 })); + $self->assert($m->has_annotation({ entry => $e2, attrib => $a2 })); + $self->assert_str_equals('whatever', $m->get_annotation($e1, $a1)); + $self->assert_str_equals('sartorial', $m->get_annotation($e2, $a2)); + $self->assert_str_equals('whatever', + $m->get_annotation({ entry => $e1, attrib => $a1 })); + $self->assert_str_equals('sartorial', + $m->get_annotation({ entry => $e2, attrib => $a2 })); + my @aa = $m->list_annotations(); + @aa = sort { $a->{entry} cmp $b->{entry} } @aa; + $self->assert_deep_equals( + [ { entry => $e1, attrib => $a1 }, { entry => $e2, attrib => $a2 }, ], + \@aa); } -sub test_accessors -{ - my ($self) = @_; +sub test_accessors { + my ($self) = @_; - my $txt = <<'EOF'; + my $txt = <<'EOF'; From: Fred J. Bloggs To: Sarah Jane Smith , Genghis Khan Subject: Hello World @@ -786,49 +790,54 @@ Message-ID: This is a message to let you know that I'm alive and well EOF - my @lines = split(/\n/, $txt); - map { $_ .= "\r\n" } @lines; - - my $m = Cassandane::Message->new( - lines => \@lines, - attrs => { - uid => 42 - }); - - $self->assert_str_equals('Fred J. Bloggs ', $m->from()); - $self->assert_str_equals('Sarah Jane Smith , Genghis Khan ', $m->to()); - $self->assert_str_equals('Hello World', $m->subject()); - $self->assert_str_equals('Tue, 06 Dec 2011 13:57:57 +1100', $m->date()); - $self->assert_str_equals('', $m->messageid()); - $self->assert_num_equals(42, $m->uid()); - $self->assert_num_equals(651, $m->size()); - $self->assert_str_equals('e2f2c19a8097587d54745801621d4bde4fa664b3', $m->guid()); - $self->assert_null($m->cid()); - - # make_cid() returns a new CID but doesn't set the attribute - $self->assert_str_equals('7301187b8bfe536f', $m->make_cid()); - $self->assert_null($m->cid()); - $m->set_attribute(cid => $m->make_cid()); - $self->assert_str_equals('7301187b8bfe536f', $m->cid()); + my @lines = split(/\n/, $txt); + map { $_ .= "\r\n" } @lines; + + my $m = Cassandane::Message->new( + lines => \@lines, + attrs => { + uid => 42 + } + ); + + $self->assert_str_equals('Fred J. Bloggs ', $m->from()); + $self->assert_str_equals( + 'Sarah Jane Smith , Genghis Khan ', + $m->to()); + $self->assert_str_equals('Hello World', $m->subject()); + $self->assert_str_equals('Tue, 06 Dec 2011 13:57:57 +1100', $m->date()); + $self->assert_str_equals('', + $m->messageid()); + $self->assert_num_equals(42, $m->uid()); + $self->assert_num_equals(651, $m->size()); + $self->assert_str_equals('e2f2c19a8097587d54745801621d4bde4fa664b3', + $m->guid()); + $self->assert_null($m->cid()); + + # make_cid() returns a new CID but doesn't set the attribute + $self->assert_str_equals('7301187b8bfe536f', $m->make_cid()); + $self->assert_null($m->cid()); + $m->set_attribute(cid => $m->make_cid()); + $self->assert_str_equals('7301187b8bfe536f', $m->cid()); } -sub test_header_normalisation -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); +sub test_header_normalisation { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); - # data thanks to hipsteripsum.me - $m->add_header('x-cliche', "sartorial"); - $m->add_header('x-cliche', "mixtape\nfreegan"); - $m->add_header('x-cliche', "leggings\r\nreadymade quinoa"); - $m->add_header('x-cliche', "chambray\rdenim"); + # data thanks to hipsteripsum.me + $m->add_header('x-cliche', "sartorial"); + $m->add_header('x-cliche', "mixtape\nfreegan"); + $m->add_header('x-cliche', "leggings\r\nreadymade quinoa"); + $m->add_header('x-cliche', "chambray\rdenim"); - $m->set_headers('x-vegan', - "helvetica\rwayfarers keytar\nshoreditch\r\n \t portland"); + $m->set_headers('x-vegan', + "helvetica\rwayfarers keytar\nshoreditch\r\n \t portland"); - $m->set_body("This is a message to let you know\r\nthat I'm alive and well\r\n"); - my $exp = <<'EOF'; + $m->set_body( + "This is a message to let you know\r\nthat I'm alive and well\r\n"); + my $exp = <<'EOF'; Subject: Hello World X-Cliche: sartorial X-Cliche: mixtape @@ -846,24 +855,28 @@ This is a message to let you know that I'm alive and well EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } # Test a header field which is present but with an empty value -sub test_add_empty -{ - my ($self) = @_; - my $m = Cassandane::Message->new(); - $m->add_header('subject', 'Hello World'); - $m->add_header('X-Justin-Beiber', ""); - $m->add_header('From', Cassandane::Address->new( - name => 'Fred J. Bloggs', - localpart => 'fbloggs', - domain => 'fastmail.fm')); - $m->set_body("This is a message to let you know\r\nthat I'm alive and well\r\n"); - my $exp = <<'EOF'; +sub test_add_empty { + my ($self) = @_; + my $m = Cassandane::Message->new(); + $m->add_header('subject', 'Hello World'); + $m->add_header('X-Justin-Beiber', ""); + $m->add_header( + 'From', + Cassandane::Address->new( + name => 'Fred J. Bloggs', + localpart => 'fbloggs', + domain => 'fastmail.fm' + ) + ); + $m->set_body( + "This is a message to let you know\r\nthat I'm alive and well\r\n"); + my $exp = <<'EOF'; Subject: Hello World X-Justin-Beiber: From: Fred J. Bloggs @@ -871,10 +884,9 @@ From: Fred J. Bloggs This is a message to let you know that I'm alive and well EOF - $exp =~ s/\n/\r\n/g; - $self->assert_str_equals($exp, $m->as_string); - $self->assert_str_equals($exp, "" . $m); + $exp =~ s/\n/\r\n/g; + $self->assert_str_equals($exp, $m->as_string); + $self->assert_str_equals($exp, "" . $m); } - 1; diff --git a/cassandane/Cassandane/Test/MessageStoreFactory.pm b/cassandane/Cassandane/Test/MessageStoreFactory.pm index 2d1b235c7b..6401b2464a 100644 --- a/cassandane/Cassandane/Test/MessageStoreFactory.pm +++ b/cassandane/Cassandane/Test/MessageStoreFactory.pm @@ -45,110 +45,110 @@ use lib '.'; use base qw(Cassandane::Unit::TestCase); use Cassandane::MessageStoreFactory; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } # Test no args at all - default is mbox to stdout/stdin -sub test_no_args -{ - my ($self) = @_; - my $ms = Cassandane::MessageStoreFactory->create(); - $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); - $self->assert( !defined $ms->{filename}); +sub test_no_args { + my ($self) = @_; + my $ms = Cassandane::MessageStoreFactory->create(); + $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); + $self->assert(!defined $ms->{filename}); } # Test guessing type from single attribute, one of 'filename' # 'directory' or 'host'. -sub test_single_attr -{ - my ($self) = @_; - my $ms = Cassandane::MessageStoreFactory->create(filename => 'foo'); - $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); - $self->assert($ms->{filename} eq 'foo'); - - $ms = Cassandane::MessageStoreFactory->create(directory => 'foo'); - $self->assert(ref $ms eq 'Cassandane::MaildirMessageStore'); - $self->assert($ms->{directory} eq 'foo'); +sub test_single_attr { + my ($self) = @_; + my $ms = Cassandane::MessageStoreFactory->create(filename => 'foo'); + $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); + $self->assert($ms->{filename} eq 'foo'); + + $ms = Cassandane::MessageStoreFactory->create(directory => 'foo'); + $self->assert(ref $ms eq 'Cassandane::MaildirMessageStore'); + $self->assert($ms->{directory} eq 'foo'); } # Test creating from a URI -sub test_uri -{ - my ($self) = @_; - my $ms = Cassandane::MessageStoreFactory->create(uri => 'mbox:///foo/bar'); - $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); - $self->assert($ms->{filename} eq '/foo/bar'); - - $ms = Cassandane::MessageStoreFactory->create(uri => 'file:///foo/bar'); - $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); - $self->assert($ms->{filename} eq '/foo/bar'); - - $ms = Cassandane::MessageStoreFactory->create(uri => 'maildir:///foo/bar'); - $self->assert(ref $ms eq 'Cassandane::MaildirMessageStore'); - $self->assert($ms->{directory} eq '/foo/bar'); - - $ms = Cassandane::MessageStoreFactory->create(uri => 'imap://victoria:secret@foo.com:9143/inbox.foo'); - $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); - $self->assert($ms->{username} eq 'victoria'); - $self->assert($ms->{password} eq 'secret'); - $self->assert($ms->{host} eq 'foo.com'); - $self->assert($ms->{port} == 9143); - $self->assert($ms->{folder} eq 'inbox.foo'); - - $ms = Cassandane::MessageStoreFactory->create(uri => 'imap://victoria@foo.com:9143/inbox.foo'); - $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); - $self->assert($ms->{username} eq 'victoria'); - $self->assert(!defined $ms->{password}); - $self->assert($ms->{host} eq 'foo.com'); - $self->assert($ms->{port} == 9143); - $self->assert($ms->{folder} eq 'inbox.foo'); - - $ms = Cassandane::MessageStoreFactory->create(uri => 'imap://foo.com:9143/inbox.foo'); - $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); - $self->assert(!defined $ms->{username}); - $self->assert(!defined $ms->{password}); - $self->assert($ms->{host} eq 'foo.com'); - $self->assert($ms->{port} == 9143); - $self->assert($ms->{folder} eq 'inbox.foo'); - - $ms = Cassandane::MessageStoreFactory->create(uri => 'imap://foo.com/inbox.foo'); - $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); - $self->assert(!defined $ms->{username}); - $self->assert(!defined $ms->{password}); - $self->assert($ms->{host} eq 'foo.com'); - $self->assert($ms->{port} == 143); - $self->assert($ms->{folder} eq 'inbox.foo'); - - $ms = Cassandane::MessageStoreFactory->create(uri => 'imap://foo.com/'); - $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); - $self->assert(!defined $ms->{username}); - $self->assert(!defined $ms->{password}); - $self->assert($ms->{host} eq 'foo.com'); - $self->assert($ms->{port} == 143); - $self->assert($ms->{folder} eq 'INBOX'); +sub test_uri { + my ($self) = @_; + my $ms = Cassandane::MessageStoreFactory->create(uri => 'mbox:///foo/bar'); + $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); + $self->assert($ms->{filename} eq '/foo/bar'); + + $ms = Cassandane::MessageStoreFactory->create(uri => 'file:///foo/bar'); + $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); + $self->assert($ms->{filename} eq '/foo/bar'); + + $ms = Cassandane::MessageStoreFactory->create(uri => 'maildir:///foo/bar'); + $self->assert(ref $ms eq 'Cassandane::MaildirMessageStore'); + $self->assert($ms->{directory} eq '/foo/bar'); + + $ms = Cassandane::MessageStoreFactory->create( + uri => 'imap://victoria:secret@foo.com:9143/inbox.foo'); + $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); + $self->assert($ms->{username} eq 'victoria'); + $self->assert($ms->{password} eq 'secret'); + $self->assert($ms->{host} eq 'foo.com'); + $self->assert($ms->{port} == 9143); + $self->assert($ms->{folder} eq 'inbox.foo'); + + $ms = Cassandane::MessageStoreFactory->create( + uri => 'imap://victoria@foo.com:9143/inbox.foo'); + $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); + $self->assert($ms->{username} eq 'victoria'); + $self->assert(!defined $ms->{password}); + $self->assert($ms->{host} eq 'foo.com'); + $self->assert($ms->{port} == 9143); + $self->assert($ms->{folder} eq 'inbox.foo'); + + $ms = Cassandane::MessageStoreFactory->create( + uri => 'imap://foo.com:9143/inbox.foo'); + $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); + $self->assert(!defined $ms->{username}); + $self->assert(!defined $ms->{password}); + $self->assert($ms->{host} eq 'foo.com'); + $self->assert($ms->{port} == 9143); + $self->assert($ms->{folder} eq 'inbox.foo'); + + $ms = Cassandane::MessageStoreFactory->create( + uri => 'imap://foo.com/inbox.foo'); + $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); + $self->assert(!defined $ms->{username}); + $self->assert(!defined $ms->{password}); + $self->assert($ms->{host} eq 'foo.com'); + $self->assert($ms->{port} == 143); + $self->assert($ms->{folder} eq 'inbox.foo'); + + $ms = Cassandane::MessageStoreFactory->create(uri => 'imap://foo.com/'); + $self->assert(ref $ms eq 'Cassandane::IMAPMessageStore'); + $self->assert(!defined $ms->{username}); + $self->assert(!defined $ms->{password}); + $self->assert($ms->{host} eq 'foo.com'); + $self->assert($ms->{port} == 143); + $self->assert($ms->{folder} eq 'INBOX'); } # Test creation with the 'path' and 'type' attribute - default # arguments for genmail3.pl -sub test_path -{ - my ($self) = @_; +sub test_path { + my ($self) = @_; - my $ms = Cassandane::MessageStoreFactory->create(path => 'foo'); - $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); - $self->assert($ms->{filename} eq 'foo'); + my $ms = Cassandane::MessageStoreFactory->create(path => 'foo'); + $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); + $self->assert($ms->{filename} eq 'foo'); - $ms = Cassandane::MessageStoreFactory->create(type => 'mbox', path => 'foo'); - $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); - $self->assert($ms->{filename} eq 'foo'); + $ms = Cassandane::MessageStoreFactory->create(type => 'mbox', path => 'foo'); + $self->assert(ref $ms eq 'Cassandane::MboxMessageStore'); + $self->assert($ms->{filename} eq 'foo'); - $ms = Cassandane::MessageStoreFactory->create(type => 'maildir', path => 'foo'); - $self->assert(ref $ms eq 'Cassandane::MaildirMessageStore'); - $self->assert($ms->{directory} eq 'foo'); + $ms + = Cassandane::MessageStoreFactory->create(type => 'maildir', path => 'foo'); + $self->assert(ref $ms eq 'Cassandane::MaildirMessageStore'); + $self->assert($ms->{directory} eq 'foo'); } 1; diff --git a/cassandane/Cassandane/Test/Metronome.pm b/cassandane/Cassandane/Test/Metronome.pm index be5b93e432..bd68b53535 100644 --- a/cassandane/Cassandane/Test/Metronome.pm +++ b/cassandane/Cassandane/Test/Metronome.pm @@ -47,42 +47,39 @@ use Cassandane::Util::Metronome; use Cassandane::Util::Sample; use Cassandane::Util::Log; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } -sub test_basic -{ - my ($self) = @_; +sub test_basic { + my ($self) = @_; - return unless $ENV{METRONOME_ENABLED}; + return unless $ENV{METRONOME_ENABLED}; - my $rate = 100.0; - my $epsilon = 0.05; - my $m = Cassandane::Util::Metronome->new(rate => $rate); + my $rate = 100.0; + my $epsilon = 0.05; + my $m = Cassandane::Util::Metronome->new(rate => $rate); - my $ss = new Cassandane::Util::Sample; + my $ss = new Cassandane::Util::Sample; - for (1..$rate) - { - $m->tick(); - my $r = $m->actual_rate(); - xlog "Actual rate $r"; - # Be forgiving of early samples to let the - # metronome stabilise. - $ss->add($r) if ($_ >= 20) - } + for (1 .. $rate) { + $m->tick(); + my $r = $m->actual_rate(); + xlog "Actual rate $r"; + # Be forgiving of early samples to let the + # metronome stabilise. + $ss->add($r) if ($_ >= 20); + } - xlog "Rates: $ss"; - my $avg = $ss->average(); - my $std = $ss->sample_deviation(); - $self->assert($avg >= (1.0-$epsilon)*$rate && $avg <= (1.0+$epsilon)*$rate, - "Average $avg is outside expected range"); - $self->assert($std/$rate < $epsilon, - "Standard deviation $std is too high"); + xlog "Rates: $ss"; + my $avg = $ss->average(); + my $std = $ss->sample_deviation(); + $self->assert($avg >= (1.0 - $epsilon) * $rate + && $avg <= (1.0 + $epsilon) * $rate, + "Average $avg is outside expected range"); + $self->assert($std / $rate < $epsilon, "Standard deviation $std is too high"); } 1; diff --git a/cassandane/Cassandane/Test/NewTestUrl.pm b/cassandane/Cassandane/Test/NewTestUrl.pm index f3dcb260cc..5f37fd80c8 100644 --- a/cassandane/Cassandane/Test/NewTestUrl.pm +++ b/cassandane/Cassandane/Test/NewTestUrl.pm @@ -10,124 +10,104 @@ use LWP::UserAgent; use lib '.'; use base qw(Cassandane::Unit::TestCase); -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } -sub test_basic -{ - my ($self) = @_; - - my $string_based_url = $self->new_test_url("string based"); - my $code_based_url = $self->new_test_url(sub { - return [ - 201, - [], - [ "code based" ], - ], - }); - - my $lwp = LWP::UserAgent->new; - - { - my $res = $lwp->get($string_based_url->url); - $self->assert_str_equals('200', $res->code); - $self->assert_str_equals('string based', $res->decoded_content); +sub test_basic { + my ($self) = @_; + + my $string_based_url = $self->new_test_url("string based"); + my $code_based_url = $self->new_test_url(sub { + return [ 201, [], ["code based"], ],; + }); + + my $lwp = LWP::UserAgent->new; + + { + my $res = $lwp->get($string_based_url->url); + $self->assert_str_equals('200', $res->code); + $self->assert_str_equals('string based', $res->decoded_content); + } + + { + my $res = $lwp->get($code_based_url->url); + $self->assert_str_equals('201', $res->code); + $self->assert_str_equals('code based', $res->decoded_content); + } + + $string_based_url->unregister; + + { + # Unregistering one shouldn't affect the other + my $res = $lwp->get($code_based_url->url); + $self->assert_str_equals('201', $res->code); + $self->assert_str_equals('code based', $res->decoded_content); + } + + $code_based_url->update("newval"); + + { + my $res = $lwp->get($code_based_url->url); + $self->assert_str_equals('200', $res->code); + $self->assert_str_equals('newval', $res->decoded_content); + } + + { + eval { $string_based_url->url }; + my $err = $@; + $self->assert_matches( + qr/\QCannot call ->url after ->unregister has been called\E/, $err,); + } + + { + eval { $string_based_url->update("foo") }; + my $err = $@; + $self->assert_matches( + qr/\QCannot call ->update after ->unregister has been called\E/, $err,); + } + + # Plack example + my $plack_based_url = $self->new_test_url(sub { + my $env = shift; + my $req = Plack::Request->new($env); + + my $payload = decode_json($req->raw_body); + + my $res; + + if ($payload->{good}) { + $res = Plack::Response->new(200); + $res->content_type('application/json'); + $res->body(encode_json({ good => "job" })); + } else { + $res = Plack::Response->new(400); + $res->content_type('application/json'); + $res->body(encode_json({ tough => "luck" })); } - { - my $res = $lwp->get($code_based_url->url); - $self->assert_str_equals('201', $res->code); - $self->assert_str_equals('code based', $res->decoded_content); - } - - $string_based_url->unregister; - - { - # Unregistering one shouldn't affect the other - my $res = $lwp->get($code_based_url->url); - $self->assert_str_equals('201', $res->code); - $self->assert_str_equals('code based', $res->decoded_content); - } + return $res->finalize; + }); - $code_based_url->update("newval"); + { + my $res = $lwp->post($plack_based_url->url, + Content => encode_json({ good => 1 }),); + $self->assert_str_equals('200', $res->code); - { - my $res = $lwp->get($code_based_url->url); - $self->assert_str_equals('200', $res->code); - $self->assert_str_equals('newval', $res->decoded_content); - } + my $json = decode_json($res->decoded_content); + $self->assert_deep_equals({ good => "job" }, $json,); + } - { - eval { $string_based_url->url }; - my $err = $@; - $self->assert_matches( - qr/\QCannot call ->url after ->unregister has been called\E/, - $err, - ); - } + { + my $res = $lwp->post($plack_based_url->url, + Content => encode_json({ good => 0 }),); + $self->assert_str_equals('400', $res->code); - { - eval { $string_based_url->update("foo") }; - my $err = $@; - $self->assert_matches( - qr/\QCannot call ->update after ->unregister has been called\E/, - $err, - ); - } - - # Plack example - my $plack_based_url = $self->new_test_url(sub { - my $env = shift; - my $req = Plack::Request->new($env); - - my $payload = decode_json($req->raw_body); - - my $res; - - if ($payload->{good}) { - $res = Plack::Response->new(200); - $res->content_type('application/json'); - $res->body(encode_json({ good => "job" })); - } else { - $res = Plack::Response->new(400); - $res->content_type('application/json'); - $res->body(encode_json({ tough => "luck" })); - } - - return $res->finalize; - }); - - { - my $res = $lwp->post( - $plack_based_url->url, - Content => encode_json({ good => 1 }), - ); - $self->assert_str_equals('200', $res->code); - - my $json = decode_json($res->decoded_content); - $self->assert_deep_equals( - { good => "job" }, - $json, - ); - } - - { - my $res = $lwp->post( - $plack_based_url->url, - Content => encode_json({ good => 0 }), - ); - $self->assert_str_equals('400', $res->code); - - my $json = decode_json($res->decoded_content); - $self->assert_deep_equals( - { tough => "luck" }, - $json, - ); - } + my $json = decode_json($res->decoded_content); + $self->assert_deep_equals({ tough => "luck" }, $json,); + } } 1; diff --git a/cassandane/Cassandane/Test/Parameter.pm b/cassandane/Cassandane/Test/Parameter.pm index 7b17a68705..c936ddf9b3 100644 --- a/cassandane/Cassandane/Test/Parameter.pm +++ b/cassandane/Cassandane/Test/Parameter.pm @@ -45,15 +45,15 @@ use lib '.'; use base qw(Cassandane::Unit::TestCase); use Cassandane::Util::Log; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - return $self; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; } my $mustache; -Cassandane::Unit::TestCase::parameter(\$mustache, 'walrus', 'toothbrush', 'waxed'); +Cassandane::Unit::TestCase::parameter(\$mustache, 'walrus', 'toothbrush', + 'waxed'); my $nose; Cassandane::Unit::TestCase::parameter(\$nose, 'roman'); @@ -61,9 +61,8 @@ Cassandane::Unit::TestCase::parameter(\$nose, 'roman'); my $eyes; Cassandane::Unit::TestCase::parameter(\$eyes, 'brown', 'cat'); -sub test_face -{ - xlog "XXX face: mustache=$mustache eyes=$eyes nose=$nose"; +sub test_face { + xlog "XXX face: mustache=$mustache eyes=$eyes nose=$nose"; } 1; diff --git a/cassandane/Cassandane/Test/Sample.pm b/cassandane/Cassandane/Test/Sample.pm index 31f99f86cf..5f1a72ab50 100644 --- a/cassandane/Cassandane/Test/Sample.pm +++ b/cassandane/Cassandane/Test/Sample.pm @@ -46,64 +46,60 @@ use base qw(Cassandane::Unit::TestCase); use Cassandane::Util::Sample; use Cassandane::Util::Log; -sub new -{ - my $class = shift; - return $class->SUPER::new(@_); +sub new { + my $class = shift; + return $class->SUPER::new(@_); } -sub check_expected -{ - my ($self, $ss, $ecount, $eavg, $emin, $emax, $estd) = @_; +sub check_expected { + my ($self, $ss, $ecount, $eavg, $emin, $emax, $estd) = @_; - my $epsilon = 0.01; + my $epsilon = 0.01; - $self->assert_equals($ss->nsamples(), $ecount); + $self->assert_equals($ss->nsamples(), $ecount); - my $avg = $ss->average(); - $self->assert(abs($eavg - $avg) < $epsilon, - "Average: expecting $eavg got $avg"); + my $avg = $ss->average(); + $self->assert(abs($eavg - $avg) < $epsilon, + "Average: expecting $eavg got $avg"); - my $min = $ss->minimum(); - $self->assert(abs($emin - $min) < $epsilon, - "Minimum: expecting $emin got $min"); + my $min = $ss->minimum(); + $self->assert(abs($emin - $min) < $epsilon, + "Minimum: expecting $emin got $min"); - my $max = $ss->maximum(); - $self->assert(abs($emax - $max) < $epsilon, - "Maximum: expecting $emax got $max"); + my $max = $ss->maximum(); + $self->assert(abs($emax - $max) < $epsilon, + "Maximum: expecting $emax got $max"); - my $std = $ss->sample_deviation(); - $self->assert(abs($estd - $std) < $epsilon, - "Sample Deviation: expecting $estd got $std"); + my $std = $ss->sample_deviation(); + $self->assert(abs($estd - $std) < $epsilon, + "Sample Deviation: expecting $estd got $std"); } -sub test_uniform -{ - my ($self) = @_; +sub test_uniform { + my ($self) = @_; - xlog "Sample with 4 x 10.0"; - my $ss = new Cassandane::Util::Sample; - $ss->add(10.0); - $ss->add(10.0); - $ss->add(10.0); - $ss->add(10.0); - xlog "Sample: $ss"; - $self->check_expected($ss, 4, 10.0, 10.0, 10.0, 0.0); + xlog "Sample with 4 x 10.0"; + my $ss = new Cassandane::Util::Sample; + $ss->add(10.0); + $ss->add(10.0); + $ss->add(10.0); + $ss->add(10.0); + xlog "Sample: $ss"; + $self->check_expected($ss, 4, 10.0, 10.0, 10.0, 0.0); } -sub test_ramp -{ - my ($self) = @_; +sub test_ramp { + my ($self) = @_; - xlog "Sample with ramp from 1 to 5"; - my $ss = new Cassandane::Util::Sample; - $ss->add(1.0); - $ss->add(2.0); - $ss->add(3.0); - $ss->add(4.0); - $ss->add(5.0); - xlog "Sample: $ss"; - $self->check_expected($ss, 5, 3.0, 1.0, 5.0, 1.5811); + xlog "Sample with ramp from 1 to 5"; + my $ss = new Cassandane::Util::Sample; + $ss->add(1.0); + $ss->add(2.0); + $ss->add(3.0); + $ss->add(4.0); + $ss->add(5.0); + xlog "Sample: $ss"; + $self->check_expected($ss, 5, 3.0, 1.0, 5.0, 1.5811); } 1; diff --git a/cassandane/Cassandane/Test/Skip.pm b/cassandane/Cassandane/Test/Skip.pm index 1b0db99aae..1be51fd6dd 100644 --- a/cassandane/Cassandane/Test/Skip.pm +++ b/cassandane/Cassandane/Test/Skip.pm @@ -44,70 +44,63 @@ use warnings; use lib '.'; use base qw(Cassandane::Cyrus::TestCase); -sub new -{ - my $class = shift; - return $class->SUPER::new({}, @_); +sub new { + my $class = shift; + return $class->SUPER::new({}, @_); } -sub set_up -{ - my ($self) = @_; - $self->SUPER::set_up(); +sub set_up { + my ($self) = @_; + $self->SUPER::set_up(); } -sub tear_down -{ - my ($self) = @_; - $self->SUPER::tear_down(); +sub tear_down { + my ($self) = @_; + $self->SUPER::tear_down(); } sub test_skip_old_version - :min_version_3_0 -{ - my ($self) = @_; + : min_version_3_0 { + my ($self) = @_; - my ($maj, $min) = Cassandane::Instance->get_version(); + my ($maj, $min) = Cassandane::Instance->get_version(); - $self->assert($maj >= 3); - $self->assert($min >= 0); + $self->assert($maj >= 3); + $self->assert($min >= 0); } sub test_skip_new_version - :max_version_2_5 -{ - my ($self) = @_; + : max_version_2_5 { + my ($self) = @_; - my ($maj, $min) = Cassandane::Instance->get_version(); + my ($maj, $min) = Cassandane::Instance->get_version(); - $self->assert($maj <= 2); - $self->assert($min <= 5); + $self->assert($maj <= 2); + $self->assert($min <= 5); } sub test_skip_outside_range - :min_version_2_5_0 :max_version_2_5_9 -{ - my ($self) = @_; + : min_version_2_5_0 : max_version_2_5_9 { + my ($self) = @_; - my ($maj, $min, $rev) = Cassandane::Instance->get_version(); + my ($maj, $min, $rev) = Cassandane::Instance->get_version(); - $self->assert_equals($maj, 2); - $self->assert_equals($min, 5); - $self->assert($rev >= 0); - $self->assert($rev <= 9); + $self->assert_equals($maj, 2); + $self->assert_equals($min, 5); + $self->assert($rev >= 0); + $self->assert($rev <= 9); } # Don't actually use this device in real tests. This is meant to exercise the # skip mechanism, not as an example of its proper use :) sub test_skip_everything - :min_version_3_0 :max_version_2_5 -{ - my ($self) = @_; + : min_version_3_0 : max_version_2_5 { + my ($self) = @_; - my ($maj, $min, $rev) = Cassandane::Instance->get_version(); + my ($maj, $min, $rev) = Cassandane::Instance->get_version(); - # should never get here -- if we do, we've failed - $self->assert(0); + # should never get here -- if we do, we've failed + $self->assert(0); } 1; diff --git a/cassandane/Cassandane/ThreadedGenerator.pm b/cassandane/Cassandane/ThreadedGenerator.pm index f633872f2a..5549af8ef5 100644 --- a/cassandane/Cassandane/ThreadedGenerator.pm +++ b/cassandane/Cassandane/ThreadedGenerator.pm @@ -48,83 +48,74 @@ use Cassandane::Message; use Cassandane::Util::Log; use Cassandane::Util::Words; -my $NTHREADS = 5; -my $NMESSAGES = 20 * $NTHREADS; -my $DELTAT = 300; # seconds +my $NTHREADS = 5; +my $NMESSAGES = 20 * $NTHREADS; +my $DELTAT = 300; # seconds my $FINISH_CHANCE = 0.08; my $FOLLOW_CHANCE = 0.30; -sub new -{ - my ($class, %params) = @_; - - my $nmessages = $NMESSAGES; - $nmessages = delete $params{nmessages} - if defined $params{nmessages}; - my $deltat = $DELTAT; - $deltat = delete $params{deltat} - if defined $params{deltat}; - my $nthreads = $NTHREADS; - $nthreads = delete $params{nthreads} - if defined $params{nthreads}; - - my $self = $class->SUPER::new(%params); - - $self->{nmessages} = $nmessages; - $self->{deltat} = $deltat; - - $self->{threads} = []; - for (my $i = 1 ; $i <= $nthreads ; $i++) - { - my $thread = - { - id => $i, - subject => ucfirst(random_word()) . " " . random_word(), - cid => undef, - last_message => undef, - }; - push(@{$self->{threads}}, $thread); - } - - $self->{next_date} = DateTime->now->epoch - - $self->{deltat} * ($self->{nmessages}+1); - $self->{last_thread} = undef; - - return $self; +sub new { + my ($class, %params) = @_; + + my $nmessages = $NMESSAGES; + $nmessages = delete $params{nmessages} + if defined $params{nmessages}; + my $deltat = $DELTAT; + $deltat = delete $params{deltat} + if defined $params{deltat}; + my $nthreads = $NTHREADS; + $nthreads = delete $params{nthreads} + if defined $params{nthreads}; + + my $self = $class->SUPER::new(%params); + + $self->{nmessages} = $nmessages; + $self->{deltat} = $deltat; + + $self->{threads} = []; + for (my $i = 1; $i <= $nthreads; $i++) { + my $thread = { + id => $i, + subject => ucfirst(random_word()) . " " . random_word(), + cid => undef, + last_message => undef, + }; + push(@{ $self->{threads} }, $thread); + } + + $self->{next_date} + = DateTime->now->epoch - $self->{deltat} * ($self->{nmessages} + 1); + $self->{last_thread} = undef; + + return $self; } -sub _choose_thread -{ - my ($self) = @_; - - my $dice = rand; - my $thread; - if ($dice <= $FINISH_CHANCE) - { - # follow-up on the last thread - $thread = $self->{last_thread}; - } - if (!defined $thread) - { - my $i = int(rand(scalar(@{$self->{threads}}))); - $thread = $self->{threads}->[$i]; - } - - $dice = rand; - if ($dice <= $FINISH_CHANCE) - { - # detach from the generator...we won't find it again - my @tt = grep { $thread != $_ } @{$self->{threads}}; - $self->{threads} = \@tt; - $self->{last_thread} = undef - if defined $self->{last_thread} && $thread == $self->{last_thread}; - } - else - { - $self->{last_thread} = $thread; - } - - return $thread; +sub _choose_thread { + my ($self) = @_; + + my $dice = rand; + my $thread; + if ($dice <= $FINISH_CHANCE) { + # follow-up on the last thread + $thread = $self->{last_thread}; + } + if (!defined $thread) { + my $i = int(rand(scalar(@{ $self->{threads} }))); + $thread = $self->{threads}->[$i]; + } + + $dice = rand; + if ($dice <= $FINISH_CHANCE) { + # detach from the generator...we won't find it again + my @tt = grep { $thread != $_ } @{ $self->{threads} }; + $self->{threads} = \@tt; + $self->{last_thread} = undef + if defined $self->{last_thread} && $thread == $self->{last_thread}; + } else { + $self->{last_thread} = $thread; + } + + return $thread; } # @@ -132,42 +123,38 @@ sub _choose_thread # Args: Generator, (param-key => param-value ... ) # Returns: Message ref # -sub generate -{ - my ($self, %params) = @_; - - return undef - if (!$self->{nmessages}); - - my $thread = $self->_choose_thread(); - return undef - if (!defined $thread); - - my $last = $thread->{last_message}; - if (defined $last) - { - $params{subject} = "Re: " . $thread->{subject}; - $params{references} = [ $last ]; - } - else - { - $params{subject} = $thread->{subject}; - } - $params{date} = DateTime->from_epoch( epoch => $self->{next_date} ); - $self->{next_date} += $self->{deltat}; - - my $msg = $self->SUPER::generate(%params); - $msg->add_header('X-Cassandane-Thread', $thread->{id}); - - my $cid = $thread->{cid}; - $cid = $thread->{cid} = $msg->make_cid() - unless defined $cid; - $msg->set_attributes(cid => $cid); - - $thread->{last_message} = $msg; - $self->{nmessages}--; - - return $msg; +sub generate { + my ($self, %params) = @_; + + return undef + if (!$self->{nmessages}); + + my $thread = $self->_choose_thread(); + return undef + if (!defined $thread); + + my $last = $thread->{last_message}; + if (defined $last) { + $params{subject} = "Re: " . $thread->{subject}; + $params{references} = [$last]; + } else { + $params{subject} = $thread->{subject}; + } + $params{date} = DateTime->from_epoch(epoch => $self->{next_date}); + $self->{next_date} += $self->{deltat}; + + my $msg = $self->SUPER::generate(%params); + $msg->add_header('X-Cassandane-Thread', $thread->{id}); + + my $cid = $thread->{cid}; + $cid = $thread->{cid} = $msg->make_cid() + unless defined $cid; + $msg->set_attributes(cid => $cid); + + $thread->{last_message} = $msg; + $self->{nmessages}--; + + return $msg; } # TODO: test that both References: and In-Reply-To: are tracked in the server diff --git a/cassandane/Cassandane/Tiny/Loader.pm b/cassandane/Cassandane/Tiny/Loader.pm index 30218b383f..d81ce0d91a 100644 --- a/cassandane/Cassandane/Tiny/Loader.pm +++ b/cassandane/Cassandane/Tiny/Loader.pm @@ -12,7 +12,9 @@ sub import { my $into = caller; unless (-d $path) { - Carp::confess(qq{can't find path "$path" for loading tests; Cassandane expects to be run from the ./cyrus-imapd/cassandane directory"}); + Carp::confess( + qq{can't find path "$path" for loading tests; Cassandane expects to be run from the ./cyrus-imapd/cassandane directory"} + ); } my @tests = `find $path -type f \! -name "*~" \! -name ".*"`; @@ -31,7 +33,8 @@ sub import { } unless ($RELOADED) { - Carp::confess("tried to load $test but it did not 'use Cassandane::Tiny'"); + Carp::confess( + "tried to load $test but it did not 'use Cassandane::Tiny'"); } } diff --git a/cassandane/Cassandane/Unit/Runner.pm b/cassandane/Cassandane/Unit/Runner.pm index 0ae79d11c0..484cb67bc7 100644 --- a/cassandane/Cassandane/Unit/Runner.pm +++ b/cassandane/Cassandane/Unit/Runner.pm @@ -47,54 +47,49 @@ use IO::File; use lib '.'; use Cassandane::Cassini; -sub new -{ - my $class = shift; - my $self = $class->SUPER::new(@_); - $self->{remove_me_in_cassandane_child} = 1; +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + $self->{remove_me_in_cassandane_child} = 1; - my $cassini = Cassandane::Cassini->instance(); - my $rootdir = $cassini->val('cassandane', 'rootdir', '/var/tmp/cass'); - my $failed_file = "$rootdir/failed"; - $self->{failed_fh} = IO::File->new($failed_file, 'w'); - # if we can't write there, we just won't record failed tests! + my $cassini = Cassandane::Cassini->instance(); + my $rootdir = $cassini->val('cassandane', 'rootdir', '/var/tmp/cass'); + my $failed_file = "$rootdir/failed"; + $self->{failed_fh} = IO::File->new($failed_file, 'w'); + # if we can't write there, we just won't record failed tests! - return $self; + return $self; } -sub create_test_result -{ - my ($self) = @_; - $self->{_result} = Test::Unit::Result->new(); - return $self->{_result}; +sub create_test_result { + my ($self) = @_; + $self->{_result} = Test::Unit::Result->new(); + return $self->{_result}; } -sub record_failed -{ - my ($self, $test) = @_; - return if not $self->{failed_fh}; +sub record_failed { + my ($self, $test) = @_; + return if not $self->{failed_fh}; - my $suite = ref($test); - $suite =~ s/^Cassandane:://; + my $suite = ref($test); + $suite =~ s/^Cassandane:://; - my $testname = $test->{"Test::Unit::TestCase_name"}; - $testname =~ s/^test_//; + my $testname = $test->{"Test::Unit::TestCase_name"}; + $testname =~ s/^test_//; - $self->{failed_fh}->print("$suite.$testname\n"); + $self->{failed_fh}->print("$suite.$testname\n"); } -sub add_error -{ - my ($self, $test) = @_; - $self->record_failed($test); - $self->SUPER::add_error($test); +sub add_error { + my ($self, $test) = @_; + $self->record_failed($test); + $self->SUPER::add_error($test); } -sub add_failure -{ - my ($self, $test) = @_; - $self->record_failed($test); - $self->SUPER::add_failure($test); +sub add_failure { + my ($self, $test) = @_; + $self->record_failed($test); + $self->SUPER::add_failure($test); } 1; diff --git a/cassandane/Cassandane/Unit/RunnerPretty.pm b/cassandane/Cassandane/Unit/RunnerPretty.pm index 94a20f4590..402106b337 100644 --- a/cassandane/Cassandane/Unit/RunnerPretty.pm +++ b/cassandane/Cassandane/Unit/RunnerPretty.pm @@ -44,206 +44,196 @@ use warnings; use lib '.'; use base qw(Cassandane::Unit::Runner); -sub new -{ - my ($class, $params, @args) = @_; - my $self = $class->SUPER::new(@args); - if ($params->{quiet}) { - # if we're in quiet ("prettier") mode, write detailed error/failure - # reports to $rootdir/reports (if we can) rather than terminal - $self->{_quiet} = 1; - - my $cassini = Cassandane::Cassini->instance(); - my $rootdir = $cassini->val('cassandane', 'rootdir', '/var/tmp/cass'); - my $quiet_report_file = "$rootdir/reports"; - - $self->{_quiet_report_fh} = IO::File->new($quiet_report_file, 'w'); - # if we can't write there, just don't do it - } - return $self; +sub new { + my ($class, $params, @args) = @_; + my $self = $class->SUPER::new(@args); + if ($params->{quiet}) { + # if we're in quiet ("prettier") mode, write detailed error/failure + # reports to $rootdir/reports (if we can) rather than terminal + $self->{_quiet} = 1; + + my $cassini = Cassandane::Cassini->instance(); + my $rootdir = $cassini->val('cassandane', 'rootdir', '/var/tmp/cass'); + my $quiet_report_file = "$rootdir/reports"; + + $self->{_quiet_report_fh} = IO::File->new($quiet_report_file, 'w'); + # if we can't write there, just don't do it + } + return $self; } -sub ansi -{ - my ($self, $codes, @args) = @_; - my $isatty = -t $self->print_stream; +sub ansi { + my ($self, $codes, @args) = @_; + my $isatty = -t $self->print_stream; - my $ansi; + my $ansi; - $ansi .= "\e[" . join(',', @{$codes}) . 'm' if $isatty; - $ansi .= join ('', @args); - $ansi .= "\e[0m" if $isatty; + $ansi .= "\e[" . join(',', @{$codes}) . 'm' if $isatty; + $ansi .= join('', @args); + $ansi .= "\e[0m" if $isatty; - return $ansi; + return $ansi; } -sub start_test -{ - my $self = shift; - my $test = shift; - # prevent the default action which is to print "." +sub start_test { + my $self = shift; + my $test = shift; + # prevent the default action which is to print "." } -sub add_pass -{ - my $self = shift; - my $test = shift; +sub add_pass { + my $self = shift; + my $test = shift; - my $line = sprintf "%s %s\n", - $self->ansi([32], '[ OK ]'), - _getname($test); - $self->_print($line); + my $line = sprintf "%s %s\n", $self->ansi([32], '[ OK ]'), _getname($test); + $self->_print($line); } -sub add_error -{ - my $self = shift; - my $test = shift; +sub add_error { + my $self = shift; + my $test = shift; - $self->record_failed($test); + $self->record_failed($test); - my $line = sprintf "%s %s\n", - $self->ansi([31], '[ERROR ]'), - _getname($test); - $self->_print($line); + my $line = sprintf "%s %s\n", $self->ansi([31], '[ERROR ]'), _getname($test); + $self->_print($line); } -sub add_failure -{ - my $self = shift; - my $test = shift; +sub add_failure { + my $self = shift; + my $test = shift; - $self->record_failed($test); - my $line = sprintf "%s %s\n", - $self->ansi([33], '[FAILED]'), - _getname($test); - $self->_print($line); + $self->record_failed($test); + my $line = sprintf "%s %s\n", $self->ansi([33], '[FAILED]'), _getname($test); + $self->_print($line); } -sub _getname -{ - my $test = shift; - my $suite = ref($test); - $suite =~ s/^Cassandane:://; +sub _getname { + my $test = shift; + my $suite = ref($test); + $suite =~ s/^Cassandane:://; - my $testname = $test->{"Test::Unit::TestCase_name"}; - $testname =~ s/^test_//; + my $testname = $test->{"Test::Unit::TestCase_name"}; + $testname =~ s/^test_//; - return "$suite.$testname"; + return "$suite.$testname"; } -sub _prettytest -{ - my $test = shift; - die "WEIRD TEST $test" unless $test =~ m/^test_(.*)\((.*)\)$/; - my $item = $1; - my $suite = $2; - $suite =~ s/^Cassandane::Cyrus:://; - return "$suite.$item"; +sub _prettytest { + my $test = shift; + die "WEIRD TEST $test" unless $test =~ m/^test_(.*)\((.*)\)$/; + my $item = $1; + my $suite = $2; + $suite =~ s/^Cassandane::Cyrus:://; + return "$suite.$item"; } -sub print_errors -{ - my $self = shift; - - my $saved_output_stream; - if ($self->{_quiet}) { - if ($self->{_quiet_report_fh}) { - $saved_output_stream = $self->{_Print_stream}; - $self->{_Print_stream} = $self->{_quiet_report_fh}; - } - else { - return; - } - } - - my ($result) = @_; - return unless my $error_count = $result->error_count(); - my $msg = "\nThere " . - ($error_count == 1 ? - "was 1 error" - : "were $error_count errors") . - ":\n"; - $self->_print($msg); - - my $i = 0; - for my $e (@{$result->errors()}) { - my ($test, $errors) = split(/\n/, $e->to_string(), 2); - chomp $errors; - my $prettytest = _prettytest($test); - $self->_print("\n") if $i++; - $self->_print($self->ansi([31], "$i) $prettytest") . "\n$errors\n"); - $self->_print("\nAnnotations:\n", $e->object->annotations()) - if $e->object->annotations(); - } +sub print_errors { + my $self = shift; - if ($saved_output_stream) { - $self->{_Print_stream} = $saved_output_stream; + my $saved_output_stream; + if ($self->{_quiet}) { + if ($self->{_quiet_report_fh}) { + $saved_output_stream = $self->{_Print_stream}; + $self->{_Print_stream} = $self->{_quiet_report_fh}; + } else { + return; } + } + + my ($result) = @_; + return unless my $error_count = $result->error_count(); + my $msg = "\nThere " + . ( + $error_count == 1 + ? "was 1 error" + : "were $error_count errors" + ) . ":\n"; + $self->_print($msg); + + my $i = 0; + for my $e (@{ $result->errors() }) { + my ($test, $errors) = split(/\n/, $e->to_string(), 2); + chomp $errors; + my $prettytest = _prettytest($test); + $self->_print("\n") if $i++; + $self->_print($self->ansi([31], "$i) $prettytest") . "\n$errors\n"); + $self->_print("\nAnnotations:\n", $e->object->annotations()) + if $e->object->annotations(); + } + + if ($saved_output_stream) { + $self->{_Print_stream} = $saved_output_stream; + } } -sub print_failures -{ - my $self = shift; - - my $saved_output_stream; - if ($self->{_quiet}) { - if ($self->{_quiet_report_fh}) { - $saved_output_stream = $self->{_Print_stream}; - $self->{_Print_stream} = $self->{_quiet_report_fh}; - } - else { - return; - } - } +sub print_failures { + my $self = shift; - my ($result) = @_; - return unless my $failure_count = $result->failure_count; - my $msg = "\nThere " . - ($failure_count == 1 ? - "was 1 failure" - : "were $failure_count failures") . - ":\n"; - $self->_print($msg); - - my $i = 0; - for my $f (@{$result->failures()}) { - my ($test, $failures) = split(/\n/, $f->to_string(), 2); - chomp $failures; - my $prettytest = _prettytest($test); - $self->_print("\n") if $i++; - $self->_print($self->ansi([33], "$i) $prettytest") . "\n$failures\n"); - $self->_print("\nAnnotations:\n", $f->object->annotations()) - if $f->object->annotations(); - } - - if ($saved_output_stream) { - $self->{_Print_stream} = $saved_output_stream; + my $saved_output_stream; + if ($self->{_quiet}) { + if ($self->{_quiet_report_fh}) { + $saved_output_stream = $self->{_Print_stream}; + $self->{_Print_stream} = $self->{_quiet_report_fh}; + } else { + return; } + } + + my ($result) = @_; + return unless my $failure_count = $result->failure_count; + my $msg = "\nThere " + . ( + $failure_count == 1 + ? "was 1 failure" + : "were $failure_count failures" + ) . ":\n"; + $self->_print($msg); + + my $i = 0; + for my $f (@{ $result->failures() }) { + my ($test, $failures) = split(/\n/, $f->to_string(), 2); + chomp $failures; + my $prettytest = _prettytest($test); + $self->_print("\n") if $i++; + $self->_print($self->ansi([33], "$i) $prettytest") . "\n$failures\n"); + $self->_print("\nAnnotations:\n", $f->object->annotations()) + if $f->object->annotations(); + } + + if ($saved_output_stream) { + $self->{_Print_stream} = $saved_output_stream; + } } sub print_header { - my $self = shift; - my ($result) = @_; - if ($result->was_successful()) { - $self->_print("\n", - $self->ansi([32], "OK"), - " (", $result->run_count(), " tests)\n"); - } else { - my $failure_count = $result->failure_count() - ? $self->ansi([33], $result->failure_count) - : "0"; - my $error_count = $result->error_count() - ? $self->ansi([31], $result->error_count) - : "0"; - - $self->_print("\n", $self->ansi([31], "!!!FAILURES!!!"), "\n", - "Test Results:\n", - "Run: ", $result->run_count(), - ", Failures: $failure_count", - ", Errors: $error_count", - "\n"); - } + my $self = shift; + my ($result) = @_; + if ($result->was_successful()) { + $self->_print("\n", $self->ansi([32], "OK"), + " (", $result->run_count(), " tests)\n"); + } else { + my $failure_count + = $result->failure_count() + ? $self->ansi([33], $result->failure_count) + : "0"; + my $error_count + = $result->error_count() + ? $self->ansi([31], $result->error_count) + : "0"; + + $self->_print( + "\n", + $self->ansi([31], "!!!FAILURES!!!"), + "\n", + "Test Results:\n", + "Run: ", + $result->run_count(), + ", Failures: $failure_count", + ", Errors: $error_count", + "\n" + ); + } } 1; diff --git a/cassandane/Cassandane/Unit/RunnerXML.pm b/cassandane/Cassandane/Unit/RunnerXML.pm index c2b54eb496..139378f0e1 100644 --- a/cassandane/Cassandane/Unit/RunnerXML.pm +++ b/cassandane/Cassandane/Unit/RunnerXML.pm @@ -54,154 +54,180 @@ use base qw(Test::Unit::Runner); $VERSION = '0.1'; sub new { - my ($class, $directory, $generator) = @_; - - $generator ||= XML::Generator->new(escape => 'always', pretty => 2); - - return bless({directory => $directory, gen => $generator, - all_tests_passed => 1, - classrecs => {}}, - $class); + my ($class, $directory, $generator) = @_; + + $generator ||= XML::Generator->new(escape => 'always', pretty => 2); + + return bless( + { + directory => $directory, + gen => $generator, + all_tests_passed => 1, + classrecs => {} + }, + $class + ); } sub all_tests_passed { - my ($self) = @_; + my ($self) = @_; - return $self->{all_tests_passed}; + return $self->{all_tests_passed}; } sub start { - my ($self, $suite) = @_; + my ($self, $suite) = @_; - my $result = $self->create_test_result(); - $result->add_listener($self); - my $start_time = time(); - $suite->run($result, $self); - $self->_emit_xml(); + my $result = $self->create_test_result(); + $result->add_listener($self); + my $start_time = time(); + $suite->run($result, $self); + $self->_emit_xml(); } sub _classrec { - my ($self, $test) = @_; - - return $self->{classrecs}->{ref($test)} ||= { - testrecs => {}, tests => 0, - errors => 0, failures => 0, - timestamp => strftime("%Y-%m-%dT%H:%M:%S", gmtime(time())), - }; + my ($self, $test) = @_; + + return $self->{classrecs}->{ ref($test) } ||= { + testrecs => {}, + tests => 0, + errors => 0, + failures => 0, + timestamp => strftime("%Y-%m-%dT%H:%M:%S", gmtime(time())), + }; } sub _testrec { - my ($self, $test) = @_; + my ($self, $test) = @_; - my $cr = $self->_classrec($test); - return $cr->{testrecs}->{$test->name()} ||= - { start_time => 0, node => undef, child_nodes => [] }; + my $cr = $self->_classrec($test); + return $cr->{testrecs}->{ $test->name() } + ||= { start_time => 0, node => undef, child_nodes => [] }; } -sub add_pass {} +sub add_pass { } -sub _extype -{ - my ($exception) = @_; - my $o = $exception->object(); - return $o->to_string() - if (defined $o && $o->can('to_string')); - return "unknown"; +sub _extype { + my ($exception) = @_; + my $o = $exception->object(); + return $o->to_string() + if (defined $o && $o->can('to_string')); + return "unknown"; } sub add_failure { - my ($self, $test, $exception) = @_; - - my $cr = $self->_classrec($test); - my $tr = $self->_testrec($test); - $cr->{failures}++; - $self->{all_tests_passed} = 0; - push(@{$tr->{child_nodes}}, - $self->{gen}->failure({type => _extype($exception), - message => $exception->get_message()}, - $exception->stringify())); + my ($self, $test, $exception) = @_; + + my $cr = $self->_classrec($test); + my $tr = $self->_testrec($test); + $cr->{failures}++; + $self->{all_tests_passed} = 0; + push( + @{ $tr->{child_nodes} }, + $self->{gen}->failure( + { + type => _extype($exception), + message => $exception->get_message() + }, + $exception->stringify() + ) + ); } sub add_error { - my ($self, $test, $exception) = @_; - - my $cr = $self->_classrec($test); - my $tr = $self->_testrec($test); - $cr->{errors}++; - $self->{all_tests_passed} = 0; - push(@{$tr->{child_nodes}}, - $self->{gen}->error({type => _extype($exception), - message => $exception->get_message()}, - $exception->stringify())); + my ($self, $test, $exception) = @_; + + my $cr = $self->_classrec($test); + my $tr = $self->_testrec($test); + $cr->{errors}++; + $self->{all_tests_passed} = 0; + push( + @{ $tr->{child_nodes} }, + $self->{gen}->error( + { + type => _extype($exception), + message => $exception->get_message() + }, + $exception->stringify() + ) + ); } sub start_test { - my ($self, $test) = @_; + my ($self, $test) = @_; - my $cr = $self->_classrec($test); - my $tr = $self->_testrec($test); - $tr->{start_time} = time(); - $cr->{tests}++; + my $cr = $self->_classrec($test); + my $tr = $self->_testrec($test); + $tr->{start_time} = time(); + $cr->{tests}++; } sub fake_start_time { - my ($self, $test, $time) = @_; + my ($self, $test, $time) = @_; - my $tr = $self->_testrec($test); - $tr->{start_time} = $time; + my $tr = $self->_testrec($test); + $tr->{start_time} = $time; } sub end_test { - my ($self, $test) = @_; - - my $cr = $self->_classrec($test); - my $tr = $self->_testrec($test); - my $time = time() - $tr->{start_time}; - $tr->{node} = $self->{gen}->testcase({name => $test->name(), - classname => ref($test), - time => sprintf('%.4f', $time)}, - @{$tr->{child_nodes}}); - $cr->{time} += $time; + my ($self, $test) = @_; + + my $cr = $self->_classrec($test); + my $tr = $self->_testrec($test); + my $time = time() - $tr->{start_time}; + $tr->{node} = $self->{gen}->testcase( + { + name => $test->name(), + classname => ref($test), + time => sprintf('%.4f', $time) + }, + @{ $tr->{child_nodes} } + ); + $cr->{time} += $time; } sub _emit_xml { - my ($self) = @_; - - my $hostname = hostname(); - - foreach my $class (keys %{$self->{classrecs}}) { - my $cr = $self->{classrecs}->{$class}; - - my $output = IO::File->new(">" . $self->_xml_filename($class)); - unless(defined($output)) { - die("Can't open " . $self->_xml_filename($class) . ": $!"); - } - - my $time = sprintf('%.4f', $cr->{time}); - my @child_nodes = map { $_->{node}; } (values %{$cr->{testrecs}}); - unshift(@child_nodes, $self->{gen}->properties()); - my $system_out = 'system-out'; - push(@child_nodes, $self->{gen}->$system_out()); - my $system_err = 'system-err'; - push(@child_nodes, $self->{gen}->$system_err()); - my $xml = $self->{gen}->testsuite({tests => $cr->{tests}, - failures => $cr->{failures}, - errors => $cr->{errors}, - time => $time, - name => $class, - hostname => $hostname, - timestamp => $cr->{timestamp}}, - @child_nodes); - $output->print($xml); - $output->close(); + my ($self) = @_; + + my $hostname = hostname(); + + foreach my $class (keys %{ $self->{classrecs} }) { + my $cr = $self->{classrecs}->{$class}; + + my $output = IO::File->new(">" . $self->_xml_filename($class)); + unless (defined($output)) { + die("Can't open " . $self->_xml_filename($class) . ": $!"); } + + my $time = sprintf('%.4f', $cr->{time}); + my @child_nodes = map { $_->{node}; } (values %{ $cr->{testrecs} }); + unshift(@child_nodes, $self->{gen}->properties()); + my $system_out = 'system-out'; + push(@child_nodes, $self->{gen}->$system_out()); + my $system_err = 'system-err'; + push(@child_nodes, $self->{gen}->$system_err()); + my $xml = $self->{gen}->testsuite( + { + tests => $cr->{tests}, + failures => $cr->{failures}, + errors => $cr->{errors}, + time => $time, + name => $class, + hostname => $hostname, + timestamp => $cr->{timestamp} + }, + @child_nodes + ); + $output->print($xml); + $output->close(); + } } sub _xml_filename { - my ($self, $class) = @_; + my ($self, $class) = @_; - $class =~ s/::/./g; - return File::Spec->catfile($self->{directory}, "TEST-${class}.xml"); + $class =~ s/::/./g; + return File::Spec->catfile($self->{directory}, "TEST-${class}.xml"); } 1; diff --git a/cassandane/Cassandane/Unit/TestCase.pm b/cassandane/Cassandane/Unit/TestCase.pm index 0332feddb4..cdf840a7a5 100644 --- a/cassandane/Cassandane/Unit/TestCase.pm +++ b/cassandane/Cassandane/Unit/TestCase.pm @@ -53,252 +53,225 @@ use Cassandane::Util::TestUrl; my $enabled; my $buildinfo; -sub new -{ - my $class = shift; - if (not $buildinfo) { - $buildinfo = Cassandane::BuildInfo->new(); - } - return $class->SUPER::new(@_); +sub new { + my $class = shift; + if (not $buildinfo) { + $buildinfo = Cassandane::BuildInfo->new(); + } + return $class->SUPER::new(@_); } -sub enable_test -{ - my ($class, $test) = @_; - $enabled = $test; +sub enable_test { + my ($class, $test) = @_; + $enabled = $test; } -sub _skip_version -{ - my ($str) = @_; +sub _skip_version { + my ($str) = @_; - return if not $str =~ m/^(min|max)_version_([\d_]+)$/; - my $minmax = $1; - my ($lim_major, $lim_minor, $lim_revision, $lim_commits) - = map { 0 + $_ } split /_/, $2; - return if not defined $lim_major; + return if not $str =~ m/^(min|max)_version_([\d_]+)$/; + my $minmax = $1; + my ($lim_major, $lim_minor, $lim_revision, $lim_commits) + = map { 0 + $_ } split /_/, $2; + return if not defined $lim_major; - my ($major, $minor, $revision, $commits) = Cassandane::Instance->get_version(); + my ($major, $minor, $revision, $commits) + = Cassandane::Instance->get_version(); - if ($minmax eq 'min') { - return 1 if $major < $lim_major; # too old, skip! - return if $major > $lim_major; # definitely new enough + if ($minmax eq 'min') { + return 1 if $major < $lim_major; # too old, skip! + return if $major > $lim_major; # definitely new enough - return if not defined $lim_minor; # don't check deeper if caller doesn't care - return 1 if $minor < $lim_minor; - return if $minor > $lim_minor; + return + if not defined $lim_minor; # don't check deeper if caller doesn't care + return 1 if $minor < $lim_minor; + return if $minor > $lim_minor; - return if not defined $lim_revision; - return 1 if $revision < $lim_revision; + return if not defined $lim_revision; + return 1 if $revision < $lim_revision; - return if not defined $lim_commits; - return 1 if $commits < $lim_commits; - } - else { - return 1 if $major > $lim_major; # too new, skip! - return if $major < $lim_major; # definitely old enough + return if not defined $lim_commits; + return 1 if $commits < $lim_commits; + } else { + return 1 if $major > $lim_major; # too new, skip! + return if $major < $lim_major; # definitely old enough - return if not defined $lim_minor; # don't check deeper if caller doesn't care - return 1 if $minor > $lim_minor; - return if $minor < $lim_minor; + return + if not defined $lim_minor; # don't check deeper if caller doesn't care + return 1 if $minor > $lim_minor; + return if $minor < $lim_minor; - return if not defined $lim_revision; - return 1 if $revision > $lim_revision; + return if not defined $lim_revision; + return 1 if $revision > $lim_revision; - return if not defined $lim_commits; - return 1 if $commits > $lim_commits; - } + return if not defined $lim_commits; + return 1 if $commits > $lim_commits; + } - return; + return; } -sub filter -{ - my ($self) = @_; - return - { - # filters return 1 if the test should be skipped, or undef otherwise - x => sub - { - my $method = shift; - $method =~ s/^test_//; - # Only the explicitly enabled test runs - return ($enabled eq $method ? undef : 1); - }, - skip_version => sub - { - return if not exists $self->{_name}; - my $sub = $self->can($self->{_name}); - return if not defined $sub; - foreach my $attr (attributes::get($sub)) { - next if $attr !~ m/^(?:min|max)_version_[\d_]+$/; - return 1 if _skip_version($attr); - } - return; - }, - skip_missing_features => sub - { - return if not exists $self->{_name}; - my $sub = $self->can($self->{_name}); - return if not defined $sub; - foreach my $attr (attributes::get($sub)) { - next if $attr !~ - m/^needs_([A-Za-z0-9]+)_(\w+)(?:\(([^\)]*)\))?$/; - - if (defined $3) { - my $actual = $buildinfo->get($1, $2); - if ($actual ne $3) { - xlog "$1.$2 not '$3' (is '$actual'),", - "$self->{_name} will be skipped"; - return 1; - } - } - elsif (not $buildinfo->get($1, $2)) { - xlog "$1.$2 not enabled, $self->{_name} will be skipped"; - return 1; - } - } - return; - }, - skip_slow => sub - { - my ($method) = @_; - return 1 if $method =~ m/_slow$/; - return; - }, - slow_only => sub - { - my ($method) = @_; - return 1 if $method !~ m/_slow$/; - return; - }, - skip_runtime_check => sub - { - # To use: add a skip_check method to your test suite that - # implements logic to determine whether some test should run or - # not (perhaps by examining $self->{_name}). Return undef if - # the test should run, or a message explaining why the test is - # being skipped - return if not $self->can('skip_check'); - my $reason = $self->skip_check(); - if ($reason) { - xlog "$self->{_name} will be skipped:", - "skip_check said '$reason'"; - return 1; - } - return; - }, - }; +sub filter { + my ($self) = @_; + return { + # filters return 1 if the test should be skipped, or undef otherwise + x => sub { + my $method = shift; + $method =~ s/^test_//; + # Only the explicitly enabled test runs + return ($enabled eq $method ? undef : 1); + }, + skip_version => sub { + return if not exists $self->{_name}; + my $sub = $self->can($self->{_name}); + return if not defined $sub; + foreach my $attr (attributes::get($sub)) { + next if $attr !~ m/^(?:min|max)_version_[\d_]+$/; + return 1 if _skip_version($attr); + } + return; + }, + skip_missing_features => sub { + return if not exists $self->{_name}; + my $sub = $self->can($self->{_name}); + return if not defined $sub; + foreach my $attr (attributes::get($sub)) { + next if $attr !~ m/^needs_([A-Za-z0-9]+)_(\w+)(?:\(([^\)]*)\))?$/; + + if (defined $3) { + my $actual = $buildinfo->get($1, $2); + if ($actual ne $3) { + xlog "$1.$2 not '$3' (is '$actual'),", + "$self->{_name} will be skipped"; + return 1; + } + } elsif (not $buildinfo->get($1, $2)) { + xlog "$1.$2 not enabled, $self->{_name} will be skipped"; + return 1; + } + } + return; + }, + skip_slow => sub { + my ($method) = @_; + return 1 if $method =~ m/_slow$/; + return; + }, + slow_only => sub { + my ($method) = @_; + return 1 if $method !~ m/_slow$/; + return; + }, + skip_runtime_check => sub { + # To use: add a skip_check method to your test suite that + # implements logic to determine whether some test should run or + # not (perhaps by examining $self->{_name}). Return undef if + # the test should run, or a message explaining why the test is + # being skipped + return if not $self->can('skip_check'); + my $reason = $self->skip_check(); + if ($reason) { + xlog "$self->{_name} will be skipped:", "skip_check said '$reason'"; + return 1; + } + return; + }, + }; } -sub annotate_from_file -{ - my ($self, $filename) = @_; - return if !defined $filename; +sub annotate_from_file { + my ($self, $filename) = @_; + return if !defined $filename; - open LOG, '<', $filename - or die "Cannot open $filename for reading: $!"; - while () - { - $self->annotate($_); - } - close LOG; + open LOG, '<', $filename + or die "Cannot open $filename for reading: $!"; + while () { + $self->annotate($_); + } + close LOG; } my @params; -sub parameter -{ - my ($ref, @values) = @_; +sub parameter { + my ($ref, @values) = @_; - return if (!scalar(@values)); + return if (!scalar(@values)); - my $param = { - id => scalar(@params), - package => caller, - values => \@values, - maxvidx => scalar(@values)-1, - reference => $ref, - }; - push(@params, $param); + my $param = { + id => scalar(@params), + package => caller, + values => \@values, + maxvidx => scalar(@values) - 1, + reference => $ref, + }; + push(@params, $param); # xlog "XXX registering parameter id $param->{id} in package $param->{package}"; } -sub _describe_setting -{ - my ($setting) = @_; - $setting ||= []; - - my @parts; - my @ss = ( @$setting ); - while (scalar @ss) - { - my $id = shift @ss; - my $value = $params[$id]->{values}->[shift @ss]; - push(@parts, "$id:\"$value\""); - } - return '[' . join(' ', @parts) . ']'; +sub _describe_setting { + my ($setting) = @_; + $setting ||= []; + + my @parts; + my @ss = (@$setting); + while (scalar @ss) { + my $id = shift @ss; + my $value = $params[$id]->{values}->[ shift @ss ]; + push(@parts, "$id:\"$value\""); + } + return '[' . join(' ', @parts) . ']'; } -sub make_parameter_settings -{ - my ($class, $package) = @_; - -# xlog "XXX making parameter settings for package $package"; - - my @settings; - my @stack; - foreach my $param (grep { $_->{package} eq $package } @params) - { - push(@stack, { param => $param, vidx => 0 }); - } - return [] if !scalar(@stack); - - SETTING: while (1) - { - # save a setting - my $setting = [ map { $_->{param}->{id}, $_->{vidx} } @stack ]; -# xlog "XXX making setting " . _describe_setting($setting); - push(@settings, $setting); - # increment indexes, wrapping and overflowing - foreach my $s (@stack) - { - $s->{vidx}++; - if ($s->{vidx} > $s->{param}->{maxvidx}) - { - $s->{vidx} = 0; - } - else - { - next SETTING; - } - } - last; +sub make_parameter_settings { + my ($class, $package) = @_; + + # xlog "XXX making parameter settings for package $package"; + + my @settings; + my @stack; + foreach my $param (grep { $_->{package} eq $package } @params) { + push(@stack, { param => $param, vidx => 0 }); + } + return [] if !scalar(@stack); + + SETTING: while (1) { + # save a setting + my $setting = [ map { $_->{param}->{id}, $_->{vidx} } @stack ]; + # xlog "XXX making setting " . _describe_setting($setting); + push(@settings, $setting); + # increment indexes, wrapping and overflowing + foreach my $s (@stack) { + $s->{vidx}++; + if ($s->{vidx} > $s->{param}->{maxvidx}) { + $s->{vidx} = 0; + } else { + next SETTING; + } } + last; + } - return @settings; + return @settings; } -sub apply_parameter_setting -{ - my ($class, $setting) = @_; +sub apply_parameter_setting { + my ($class, $setting) = @_; -# xlog "XXX applying setting " . _describe_setting($setting); + # xlog "XXX applying setting " . _describe_setting($setting); - foreach my $param (@params) - { - ${$param->{reference}} = undef; - } + foreach my $param (@params) { + ${ $param->{reference} } = undef; + } - my @ss = ( @$setting ); - while (scalar @ss) - { - my $param = $params[shift @ss]; - my $value = $param->{values}->[shift @ss]; -# xlog "XXX setting parameter id $param->{id} to value \"$value\""; - ${$param->{reference}} = $value; - } + my @ss = (@$setting); + while (scalar @ss) { + my $param = $params[ shift @ss ]; + my $value = $param->{values}->[ shift @ss ]; + # xlog "XXX setting parameter id $param->{id} to value \"$value\""; + ${ $param->{reference} } = $value; + } } # n.b. it's okay for unexpected bits to also be set! @@ -307,155 +280,138 @@ sub apply_parameter_setting # assert_bits_set($want, $got); # assert_bits_not_set(~$want, $got); # -sub assert_bits_set -{ - my ($self, $expected_bits, $actual_bitfield) = @_; +sub assert_bits_set { + my ($self, $expected_bits, $actual_bitfield) = @_; - # force args to be numeric - # XXX use feature 'bitwise'; - $expected_bits += 0; - $actual_bitfield += 0; + # force args to be numeric + # XXX use feature 'bitwise'; + $expected_bits += 0; + $actual_bitfield += 0; - my $fail_msg = sprintf("%#.8b does not have all of %#.8b bits set", - $actual_bitfield, $expected_bits); + my $fail_msg = sprintf("%#.8b does not have all of %#.8b bits set", + $actual_bitfield, $expected_bits); - $self->assert((($actual_bitfield & $expected_bits) == $expected_bits), - $fail_msg); + $self->assert((($actual_bitfield & $expected_bits) == $expected_bits), + $fail_msg); } -sub assert_bits_not_set -{ - my ($self, $expected_bits, $actual_bitfield) = @_; +sub assert_bits_not_set { + my ($self, $expected_bits, $actual_bitfield) = @_; - # force args to be numeric - # XXX use feature 'bitwise'; - $expected_bits += 0; - $actual_bitfield += 0; + # force args to be numeric + # XXX use feature 'bitwise'; + $expected_bits += 0; + $actual_bitfield += 0; - my $fail_msg = sprintf("%#.8b has some of %#.8b bits set", - $actual_bitfield, $expected_bits); + my $fail_msg = sprintf("%#.8b has some of %#.8b bits set", + $actual_bitfield, $expected_bits); - $self->assert((($actual_bitfield & $expected_bits) == 0), $fail_msg); + $self->assert((($actual_bitfield & $expected_bits) == 0), $fail_msg); } -sub assert_num_gte -{ - my ($self, $expected, $actual) = @_; +sub assert_num_gte { + my ($self, $expected, $actual) = @_; - $self->assert(($actual >= $expected), - "$actual is not greater-than-or-equal-to $expected"); + $self->assert(($actual >= $expected), + "$actual is not greater-than-or-equal-to $expected"); } -sub assert_num_lte -{ - my ($self, $expected, $actual) = @_; +sub assert_num_lte { + my ($self, $expected, $actual) = @_; - $self->assert(($actual <= $expected), - "$actual is not less-than-or-equal-to $expected"); + $self->assert(($actual <= $expected), + "$actual is not less-than-or-equal-to $expected"); } -sub assert_num_gt -{ - my ($self, $expected, $actual) = @_; +sub assert_num_gt { + my ($self, $expected, $actual) = @_; - $self->assert(($actual > $expected), - "$actual is not greater-than $expected"); + $self->assert(($actual > $expected), "$actual is not greater-than $expected"); } -sub assert_num_lt -{ - my ($self, $expected, $actual) = @_; +sub assert_num_lt { + my ($self, $expected, $actual) = @_; - $self->assert(($actual < $expected), - "$actual is not less-than $expected"); + $self->assert(($actual < $expected), "$actual is not less-than $expected"); } -sub assert_date_matches -{ - my ($self, $expected, $actual, $tolerance) = @_; - - my ($expected_dt, $expected_str, $actual_dt, $actual_str); - - # $expected may be a DateTime object or an ISO8601 string - my $reftype = ref $expected; - if (not $reftype) { - $expected_str = $expected; - $expected_dt = DateTime::Format::ISO8601->parse_datetime($expected); - } - elsif ($reftype ne 'DateTime') { - die "wanted string or 'DateTime' for expected, got '$reftype'"; - } - else { - $expected_dt = $expected; - $expected_str = $expected_dt->stringify(); - } - - # $actual may be a DateTime object or an ISO8601 string - $reftype = ref $actual; - if (not $reftype) { - $actual_str = $actual; - $actual_dt = DateTime::Format::ISO8601->parse_datetime($actual); - } - elsif ($reftype ne 'DateTime') { - die "wanted string or 'DateTime' for actual, got '$reftype'"; - } - else { - $actual_dt = $actual; - $actual_str = $actual_dt->stringify(); - } - - # $tolerance is in seconds, default 0 - $tolerance //= 0; - - # XXX here is where to check that timezones match: - # XXX * if one has a timezone and the other doesn't, fail - # XXX * if both have timezones but they're different, fail - # XXX otherwise, carry on... - - my $diff = $expected_dt->epoch() - $actual_dt->epoch(); +sub assert_date_matches { + my ($self, $expected, $actual, $tolerance) = @_; + + my ($expected_dt, $expected_str, $actual_dt, $actual_str); + + # $expected may be a DateTime object or an ISO8601 string + my $reftype = ref $expected; + if (not $reftype) { + $expected_str = $expected; + $expected_dt = DateTime::Format::ISO8601->parse_datetime($expected); + } elsif ($reftype ne 'DateTime') { + die "wanted string or 'DateTime' for expected, got '$reftype'"; + } else { + $expected_dt = $expected; + $expected_str = $expected_dt->stringify(); + } + + # $actual may be a DateTime object or an ISO8601 string + $reftype = ref $actual; + if (not $reftype) { + $actual_str = $actual; + $actual_dt = DateTime::Format::ISO8601->parse_datetime($actual); + } elsif ($reftype ne 'DateTime') { + die "wanted string or 'DateTime' for actual, got '$reftype'"; + } else { + $actual_dt = $actual; + $actual_str = $actual_dt->stringify(); + } + + # $tolerance is in seconds, default 0 + $tolerance //= 0; + + # XXX here is where to check that timezones match: + # XXX * if one has a timezone and the other doesn't, fail + # XXX * if both have timezones but they're different, fail + # XXX otherwise, carry on... + + my $diff = $expected_dt->epoch() - $actual_dt->epoch(); + + my $msg = "expected '$expected_str', got '$actual_str'"; + if ($tolerance) { + $msg .= " (difference $diff is greater than $tolerance)"; + } + + $self->assert((abs($diff) <= $tolerance), $msg); +} - my $msg = "expected '$expected_str', got '$actual_str'"; - if ($tolerance) { - $msg .= " (difference $diff is greater than $tolerance)"; - } +sub assert_file_test { + my ($self, $path, $test_type) = @_; - $self->assert((abs($diff) <= $tolerance), $msg); + # see `perldoc -f -X` for valid test types + $test_type ||= '-e'; + my $test = "$test_type \$path"; + xlog "XXX test=<$test> path=<$path>"; + my $result = eval $test; + die $@ if $@; + $self->assert($result, "'$path' failed '$test_type' test"); } -sub assert_file_test -{ - my ($self, $path, $test_type) = @_; - - # see `perldoc -f -X` for valid test types - $test_type ||= '-e'; - my $test = "$test_type \$path"; - xlog "XXX test=<$test> path=<$path>"; - my $result = eval $test; - die $@ if $@; - $self->assert($result, "'$path' failed '$test_type' test"); -} +sub assert_not_file_test { + my ($self, $path, $test_type) = @_; -sub assert_not_file_test -{ - my ($self, $path, $test_type) = @_; - - # see `perldoc -f -X` for valid test types - $test_type ||= '-e'; - my $test = "$test_type \$path"; - xlog "XXX test=<$test> path=<$path>"; - my $result = eval $test; - die $@ if $@; - $self->assert(!$result, - "'$path' unexpectedly passed '$test_type' test"); + # see `perldoc -f -X` for valid test types + $test_type ||= '-e'; + my $test = "$test_type \$path"; + xlog "XXX test=<$test> path=<$path>"; + my $result = eval $test; + die $@ if $@; + $self->assert(!$result, "'$path' unexpectedly passed '$test_type' test"); } -sub new_test_url -{ - my ($self, $content_or_app) = @_; +sub new_test_url { + my ($self, $content_or_app) = @_; - return Cassandane::Util::TestURL->new({ - app => $content_or_app, - }); + return Cassandane::Util::TestURL->new({ + app => $content_or_app, + }); } 1; diff --git a/cassandane/Cassandane/Unit/TestPlan.pm b/cassandane/Cassandane/Unit/TestPlan.pm index 2837769b86..e17b0f21c0 100644 --- a/cassandane/Cassandane/Unit/TestPlan.pm +++ b/cassandane/Cassandane/Unit/TestPlan.pm @@ -47,69 +47,64 @@ use Time::HiRes qw(time); use lib '.'; use Cassandane::Unit::TestCase; -sub new -{ - my ($class, $suite) = @_; - my $self = { - suite => $suite, - loaded_suite => undef, - denied => {}, - allowed => {}, - }; - return bless $self, $class; +sub new { + my ($class, $suite) = @_; + my $self = { + suite => $suite, + loaded_suite => undef, + denied => {}, + allowed => {}, + }; + return bless $self, $class; } -sub _get_loaded_suite -{ - my ($self) = @_; - return $self->{loaded_suite} ||= Test::Unit::Loader::load($self->{suite}); +sub _get_loaded_suite { + my ($self) = @_; + return $self->{loaded_suite} ||= Test::Unit::Loader::load($self->{suite}); } -sub _is_allowed -{ - my ($self, $name) = @_; +sub _is_allowed { + my ($self, $name) = @_; - # Rules are: - # deny if method has been explicitly denied - return 0 if $self->{denied}->{$name}; + # Rules are: + # deny if method has been explicitly denied + return 0 if $self->{denied}->{$name}; - # deny if test name matches any denied pattern - $name =~ $_ && return 0 for @{ $self->{denied_patterns} // [] }; + # deny if test name matches any denied pattern + $name =~ $_ && return 0 for @{ $self->{denied_patterns} // [] }; - # allow if method has been explicitly allowed - return 1 if $self->{allowed}->{$name}; + # allow if method has been explicitly allowed + return 1 if $self->{allowed}->{$name}; - # allow if test name matches any allowed patterns - my @allow_patterns = @{ $self->{allowed_patterns} // []}; - $name =~ $_ && return 1 for @allow_patterns; + # allow if test name matches any allowed patterns + my @allow_patterns = @{ $self->{allowed_patterns} // [] }; + $name =~ $_ && return 1 for @allow_patterns; - # deny if anything is explicitly allowed - return 0 if %{$self->{allowed}} || @allow_patterns; + # deny if anything is explicitly allowed + return 0 if %{ $self->{allowed} } || @allow_patterns; - # finally, allow - return 1; + # finally, allow + return 1; } -sub _deny -{ - my ($self, $test) = @_; - if (ref $test) { - push @{ $self->{denied_patterns} }, $test; - } else { - $self->{denied}->{$test} = 1; - } - return; +sub _deny { + my ($self, $test) = @_; + if (ref $test) { + push @{ $self->{denied_patterns} }, $test; + } else { + $self->{denied}->{$test} = 1; + } + return; } -sub _allow -{ - my ($self, $test) = @_; - if (ref $test) { - push @{ $self->{allowed_patterns} }, $test; - } else { - $self->{allowed}->{$test} = 1; - } - return; +sub _allow { + my ($self, $test) = @_; + if (ref $test) { + push @{ $self->{allowed_patterns} }, $test; + } else { + $self->{allowed}->{$test} = 1; + } + return; } package Cassandane::Unit::Worker; @@ -118,366 +113,315 @@ use MIME::Base64; my $nextid = 1; -sub new -{ - my ($class) = @_; - my $self = { - id => $nextid++, - pid => undef, - downpipe => undef, - uppipe => undef, - busy => 0, - handler => undef, - }; - return bless $self, $class; -} - -sub _pipe_read_fh -{ - my ($r, $w) = @_; - - POSIX::close($w); - my $fh = IO::Handle->new_from_fd($r, "r"); - $fh->autoflush(1); - return $fh; -} - -sub _pipe_write_fh -{ - my ($r, $w) = @_; - - POSIX::close($r); - my $fh = IO::Handle->new_from_fd($w, "w"); - $fh->autoflush(1); - return $fh; -} - -sub start -{ - my ($self) = @_; - - my ($dr, $dw) = POSIX::pipe(); - die "Cannot create down pipe: $!" - unless defined $dw; - - my ($ur, $uw) = POSIX::pipe(); - die "Cannot create up pipe: $!" - unless defined $uw; - - my $pid = fork(); - die "Cannot fork: $!" unless defined $pid; +sub new { + my ($class) = @_; + my $self = { + id => $nextid++, + pid => undef, + downpipe => undef, + uppipe => undef, + busy => 0, + handler => undef, + }; + return bless $self, $class; +} + +sub _pipe_read_fh { + my ($r, $w) = @_; + + POSIX::close($w); + my $fh = IO::Handle->new_from_fd($r, "r"); + $fh->autoflush(1); + return $fh; +} + +sub _pipe_write_fh { + my ($r, $w) = @_; + + POSIX::close($r); + my $fh = IO::Handle->new_from_fd($w, "w"); + $fh->autoflush(1); + return $fh; +} + +sub start { + my ($self) = @_; + + my ($dr, $dw) = POSIX::pipe(); + die "Cannot create down pipe: $!" + unless defined $dw; + + my ($ur, $uw) = POSIX::pipe(); + die "Cannot create up pipe: $!" + unless defined $uw; + + my $pid = fork(); + die "Cannot fork: $!" unless defined $pid; + + if ($pid) { + # parent + $self->{downpipe} = _pipe_write_fh($dr, $dw); + $self->{uppipe} = _pipe_read_fh($ur, $uw); + $self->{pid} = $pid; + } else { + # child + $self->{downpipe} = _pipe_read_fh($dr, $dw); + $self->{uppipe} = _pipe_write_fh($ur, $uw); + $ENV{TEST_UNIT_WORKER_ID} = $self->{id}; # 1, 2, 3... + $ENV{TEST_UNIT_BASENAME} = $0; + $0 = "$ENV{TEST_UNIT_BASENAME} ($ENV{TEST_UNIT_WORKER_ID})"; + $self->_mainloop(); + exit(0); + } +} + +sub _send { + my ($fh, $fmt, @args) = @_; + my $msg = sprintf($fmt, @args); + # print STDERR "--> \"$msg\"\n"; + syswrite($fh, $msg) + or die "Cannot write to pipe: $!"; +} + +sub _receive { + my ($fh) = @_; + my $msg = $fh->gets() + or return; + # print STDERR "<-- \"$msg\"\n"; + chomp $msg; + return $msg; +} + +sub _mainloop { + my ($self) = @_; + + while (my $msg = _receive($self->{downpipe})) { + my ($command, @args) = split(/\s+/, $msg); - if ($pid) - { - # parent - $self->{downpipe} = _pipe_write_fh($dr, $dw); - $self->{uppipe} = _pipe_read_fh($ur, $uw); - $self->{pid} = $pid; - } - else - { - # child - $self->{downpipe} = _pipe_read_fh($dr, $dw); - $self->{uppipe} = _pipe_write_fh($ur, $uw); - $ENV{TEST_UNIT_WORKER_ID} = $self->{id}; # 1, 2, 3... - $ENV{TEST_UNIT_BASENAME} = $0; - $0 = "$ENV{TEST_UNIT_BASENAME} ($ENV{TEST_UNIT_WORKER_ID})"; - $self->_mainloop(); - exit(0); + if ($command eq 'stop') { + return; + } elsif ($command eq 'run') { + my ($witem) = thaw(decode_base64($args[0])); + $0 + = "$ENV{TEST_UNIT_BASENAME} ($ENV{TEST_UNIT_WORKER_ID}) $witem->{suite}.$witem->{testname}"; + $self->{handler}->($witem); + $0 = "$ENV{TEST_UNIT_BASENAME} ($ENV{TEST_UNIT_WORKER_ID})"; + _send($self->{uppipe}, "done %s\n", encode_base64(freeze($witem), '')); + } else { + print STDERR "_mainloop: unknown command '$command'\n"; } + } } -sub _send -{ - my ($fh, $fmt, @args) = @_; - my $msg = sprintf($fmt, @args); -# print STDERR "--> \"$msg\"\n"; - syswrite($fh, $msg) - or die "Cannot write to pipe: $!"; +sub get_reply { + my ($self) = @_; + return if !$self->{busy}; + my $msg = _receive($self->{uppipe}); + return if !defined $msg; + my ($command, @args) = split(/\s+/, $msg); + die "Unknown message \"$msg\"" + if ($command ne 'done'); + $self->{busy} = 0; + my ($witem) = thaw(decode_base64($args[0])); + return $witem; } -sub _receive -{ - my ($fh) = @_; - my $msg = $fh->gets() - or return; -# print STDERR "<-- \"$msg\"\n"; - chomp $msg; - return $msg; +sub assign { + my ($self, $witem) = @_; + $witem->{start_time} = time(); + _send($self->{downpipe}, "run %s\n", encode_base64(freeze($witem), '')); + $self->{busy} = 1; } -sub _mainloop -{ - my ($self) = @_; - - while (my $msg = _receive($self->{downpipe})) - { - my ($command, @args) = split(/\s+/, $msg); - - if ($command eq 'stop') - { - return; - } - elsif ($command eq 'run') - { - my ($witem) = thaw(decode_base64($args[0])); - $0 = "$ENV{TEST_UNIT_BASENAME} ($ENV{TEST_UNIT_WORKER_ID}) $witem->{suite}.$witem->{testname}"; - $self->{handler}->($witem); - $0 = "$ENV{TEST_UNIT_BASENAME} ($ENV{TEST_UNIT_WORKER_ID})"; - _send($self->{uppipe}, - "done %s\n", encode_base64(freeze($witem), '')); - } - else - { - print STDERR "_mainloop: unknown command '$command'\n"; - } - } +sub stop { + my ($self) = @_; + eval { + # We don't care if this dies, it just + # means the Worker has died prematurely. + _send($self->{downpipe}, "stop\n"); + }; + while (1) { + my $res = waitpid($self->{pid}, 0); + last if ($res < 0 || $res == $self->{pid}); + } + $self->_cleanup(); } -sub get_reply -{ - my ($self) = @_; - return if !$self->{busy}; - my $msg = _receive($self->{uppipe}); - return if !defined $msg; - my ($command, @args) = split(/\s+/, $msg); - die "Unknown message \"$msg\"" - if ($command ne 'done'); - $self->{busy} = 0; - my ($witem) = thaw(decode_base64($args[0])); - return $witem; -} - -sub assign -{ - my ($self, $witem) = @_; - $witem->{start_time} = time(); - _send($self->{downpipe}, - "run %s\n", encode_base64(freeze($witem), '')); - $self->{busy} = 1; -} - -sub stop -{ - my ($self) = @_; - eval - { - # We don't care if this dies, it just - # means the Worker has died prematurely. - _send($self->{downpipe}, "stop\n"); - }; - while (1) - { - my $res = waitpid($self->{pid}, 0); - last if ($res < 0 || $res == $self->{pid}); - } - $self->_cleanup(); -} +sub _cleanup { + my ($self) = @_; -sub _cleanup -{ - my ($self) = @_; + if ($self->{downpipe}) { + close $self->{downpipe}; + $self->{downpipe} = undef; + } - if ($self->{downpipe}) - { - close $self->{downpipe}; - $self->{downpipe} = undef; - } + if ($self->{uppipe}) { + close $self->{uppipe}; + $self->{uppipe} = undef; + } - if ($self->{uppipe}) - { - close $self->{uppipe}; - $self->{uppipe} = undef; - } - - $self->{pid} = undef; + $self->{pid} = undef; } -sub DESTROY -{ - my ($self) = @_; - $self->_cleanup(); +sub DESTROY { + my ($self) = @_; + $self->_cleanup(); } package Cassandane::Unit::WorkerPool; use Errno qw(EINTR); -sub new -{ - my ($class, %params) = @_; - my $self = { - workers => [], - maxworkers => 2, - pending => [], - handler => sub { die "This should not happen"; }, - }; - foreach my $p (qw(maxworkers handler)) - { - $self->{$p} = $params{$p} if $params{$p}; - } - return bless $self, $class; +sub new { + my ($class, %params) = @_; + my $self = { + workers => [], + maxworkers => 2, + pending => [], + handler => sub { die "This should not happen"; }, + }; + foreach my $p (qw(maxworkers handler)) { + $self->{$p} = $params{$p} if $params{$p}; + } + return bless $self, $class; } -sub start -{ - my ($self) = @_; +sub start { + my ($self) = @_; - while (scalar @{$self->{workers}} < $self->{maxworkers}) - { - my $w = Cassandane::Unit::Worker->new(); - $w->{handler} = $self->{handler}; - $w->start(); - push(@{$self->{workers}}, $w); - } + while (scalar @{ $self->{workers} } < $self->{maxworkers}) { + my $w = Cassandane::Unit::Worker->new(); + $w->{handler} = $self->{handler}; + $w->start(); + push(@{ $self->{workers} }, $w); + } } # Assign an work item to an idle worker if necessary # block until a worker is idle. -sub assign -{ - my ($self, $witem) = @_; +sub assign { + my ($self, $witem) = @_; - my @idle = grep { !$_->{busy}; } @{$self->{workers}}; - my $w = shift @idle || $self->_wait(); - $w->assign($witem); + my @idle = grep { !$_->{busy}; } @{ $self->{workers} }; + my $w = shift @idle || $self->_wait(); + $w->assign($witem); } # Wait for a Worker to send back a completed work item. # Mark the Worker idle, remember its work item where # retrieve() will find it, and returns the Worker. -sub _wait -{ - my ($self) = @_; +sub _wait { + my ($self) = @_; + # Build the bit mask for select() + my $rbits = ''; + foreach my $w (@{ $self->{workers} }) { + next if (!$w->{busy}); + vec($rbits, fileno($w->{uppipe}), 1) = 1; + } - # Build the bit mask for select() - my $rbits = ''; - foreach my $w (@{$self->{workers}}) - { - next if (!$w->{busy}); - vec($rbits, fileno($w->{uppipe}), 1) = 1; - } + # select() with no timeout + my $res; + do { + $res = select($rbits, undef, undef, undef); + } while ($res < 0 && $! == EINTR); + die "select failed: $!" if ($res < 0); - # select() with no timeout - my $res; - do { - $res = select($rbits, undef, undef, undef); - } while ($res < 0 && $! == EINTR); - die "select failed: $!" if ($res < 0); - - # discover which of our workers has responded - foreach my $w (@{$self->{workers}}) - { - if (vec($rbits, fileno($w->{uppipe}), 1)) - { - push(@{$self->{pending}}, $w->get_reply()); - return $w; - } + # discover which of our workers has responded + foreach my $w (@{ $self->{workers} }) { + if (vec($rbits, fileno($w->{uppipe}), 1)) { + push(@{ $self->{pending} }, $w->get_reply()); + return $w; } - die "Unexpected result from select: $res"; + } + die "Unexpected result from select: $res"; } # Retrieve a completed work item. If $blocking is true, # wait if necessary (used when draining i.e. no more work # items will be made available). -sub retrieve -{ - my ($self, $blocking) = @_; +sub retrieve { + my ($self, $blocking) = @_; - if ($blocking && !scalar @{$self->{pending}}) - { - my @busy = grep { $_->{busy}; } @{$self->{workers}}; - $self->_wait() if (scalar @busy); - } - return shift @{$self->{pending}}; + if ($blocking && !scalar @{ $self->{pending} }) { + my @busy = grep { $_->{busy}; } @{ $self->{workers} }; + $self->_wait() if (scalar @busy); + } + return shift @{ $self->{pending} }; } # reap all workers -sub stop -{ - my ($self) = @_; +sub stop { + my ($self) = @_; - while (my $w = pop @{$self->{workers}}) - { - $w->stop(); - } + while (my $w = pop @{ $self->{workers} }) { + $w->stop(); + } } -sub DESTROY -{ - my ($self) = @_; - $self->stop(); +sub DESTROY { + my ($self) = @_; + $self->stop(); } package Cassandane::Unit::WorkerListener; use base qw(Test::Unit::Listener); use Cassandane::Util::Log; -sub new -{ - my ($class) = shift; - my $self = { - witem => undef, - }; - return bless $self, $class; +sub new { + my ($class) = shift; + my $self = { witem => undef, }; + return bless $self, $class; } -sub start_suite -{ - my ($self, $suite) = @_; - # nothing to see here +sub start_suite { + my ($self, $suite) = @_; + # nothing to see here } -sub start_test -{ - my ($self, $test) = @_; - # nothing to see here +sub start_test { + my ($self, $test) = @_; + # nothing to see here } -sub end_test -{ - my ($self, $test) = @_; - # nothing to see here +sub end_test { + my ($self, $test) = @_; + # nothing to see here } -sub end_suite -{ - my ($self, $suite) = @_; - # nothing to see here +sub end_suite { + my ($self, $suite) = @_; + # nothing to see here } -sub add_error -{ - my ($self, $test, $exception) = @_; - my $witem = $self->{witem}; - $witem->{result} = 'error'; +sub add_error { + my ($self, $test, $exception) = @_; + my $witem = $self->{witem}; + $witem->{result} = 'error'; - # Remove '-object' which points at the TestCase, which will have all - # sorts of stuff we can't thaw. We have enough information to - # discover the right TestCase in the parent process. - $exception->{'-object'} = undef; - $witem->{exception} = $exception; + # Remove '-object' which points at the TestCase, which will have all + # sorts of stuff we can't thaw. We have enough information to + # discover the right TestCase in the parent process. + $exception->{'-object'} = undef; + $witem->{exception} = $exception; } -sub add_failure -{ - my ($self, $test, $exception) = @_; - my $witem = $self->{witem}; - $witem->{result} = 'fail'; +sub add_failure { + my ($self, $test, $exception) = @_; + my $witem = $self->{witem}; + $witem->{result} = 'fail'; - # Remove '-object' which points at the TestCase, which will have all - # sorts of stuff we can't thaw. We have enough information to - # discover the right TestCase in the parent process. - $exception->{'-object'} = undef; - $witem->{exception} = $exception; + # Remove '-object' which points at the TestCase, which will have all + # sorts of stuff we can't thaw. We have enough information to + # discover the right TestCase in the parent process. + $exception->{'-object'} = undef; + $witem->{exception} = $exception; } -sub add_pass -{ - my ($self, $test) = @_; - my $witem = $self->{witem}; - $witem->{result} = 'pass'; +sub add_pass { + my ($self, $test) = @_; + my $witem = $self->{witem}; + $witem->{result} = 'pass'; } package Cassandane::Unit::TestPlan; @@ -486,510 +430,456 @@ use File::Path qw(mkpath); use Data::Dumper; use Cassandane::Util::Log; -my @test_roots = ( - 'Cassandane/Test', - 'Cassandane/Cyrus', -); - -sub new -{ - my ($class, %opts) = @_; - my $self = { - schedule => {}, - keep_going => delete $opts{keep_going} || 0, - log_directory => delete $opts{log_directory}, - maxworkers => delete $opts{maxworkers} || 1, - skip_slow => delete $opts{skip_slow} // 1, - slow_only => delete $opts{slow_only} // 0, - }; - die "Unknown options: " . join(' ', keys %opts) - if scalar %opts; - return bless $self, $class; -} - -sub _get_item -{ - my ($self, $suite) = @_; - return $self->{schedule}->{$suite} ||= - Cassandane::Unit::TestPlanItem->new($suite); -} - -sub _schedule -{ - my ($self, $neg, $path, $testname) = @_; - return if ($path =~ m/\/TestCase\.pm$/); - - my $suite = $path; - $suite =~ s/\.pm$//; - $suite =~ s/\//::/g; - - if ($neg eq '!') - { - if (defined $testname) - { - # disable a specific test - $self->_get_item($suite)->_deny($testname); - } - else - { - # remove entire suite - delete $self->{schedule}->{$suite}; - } +my @test_roots = ('Cassandane/Test', 'Cassandane/Cyrus',); + +sub new { + my ($class, %opts) = @_; + my $self = { + schedule => {}, + keep_going => delete $opts{keep_going} || 0, + log_directory => delete $opts{log_directory}, + maxworkers => delete $opts{maxworkers} || 1, + skip_slow => delete $opts{skip_slow} // 1, + slow_only => delete $opts{slow_only} // 0, + }; + die "Unknown options: " . join(' ', keys %opts) + if scalar %opts; + return bless $self, $class; +} + +sub _get_item { + my ($self, $suite) = @_; + return $self->{schedule}->{$suite} + ||= Cassandane::Unit::TestPlanItem->new($suite); +} + +sub _schedule { + my ($self, $neg, $path, $testname) = @_; + return if ($path =~ m/\/TestCase\.pm$/); + + my $suite = $path; + $suite =~ s/\.pm$//; + $suite =~ s/\//::/g; + + if ($neg eq '!') { + if (defined $testname) { + # disable a specific test + $self->_get_item($suite)->_deny($testname); + } else { + # remove entire suite + delete $self->{schedule}->{$suite}; } - else - { - # add to the schedule - my $item = $self->_get_item($suite); - if (defined $testname) - { - $item->_allow($testname) if $testname; - } + } else { + # add to the schedule + my $item = $self->_get_item($suite); + if (defined $testname) { + $item->_allow($testname) if $testname; } + } } # Returns ($neg, $ostype, $ospath, $testname) -sub _parse_test_spec -{ - my ($name) = @_; - - my ($neg, $path) = ($name =~ m/^([~!]?)(.*)$/); - $path =~ s/\.pm$//g; - $path =~ s/::/\//g; - $path =~ s/\./\//g; - $path =~ s/\/+/\//g; - $path =~ s/^\/*//; - $path =~ s/\/*$//; - - $neg = '!' if $neg eq '~'; - - # Allow Cyrus::TesterJMAP and TesterJMAP to work - my @paths; - - my @dirs = split('/', $path); - - while (@dirs) { - push @paths, join('/', @dirs); - shift @dirs; - } - - foreach my $candidate (@paths) { - foreach my $root (@test_roots) - { - return ($neg, 'd', $candidate, undef) - if ($root eq $candidate); - - my $fpath = $candidate; - $fpath = "$root/$candidate" - if ("$root/" ne substr($candidate, 0, length($root)+1)); - - return ($neg, 'd', $fpath, undef) - if ( -d $fpath ); - return ($neg, 'f', "$fpath.pm", undef) - if ( -f "$fpath.pm" ); - - my $test; - ($fpath, $test) = ($fpath =~ m/^(.*)\/([^\/]+)$/); - next unless defined $test; - - if ( -f "$fpath.pm" ) { - if ($test =~ /\*/) { - my @hunks = split /\*+/, $test, -1; - my $regex = join q{.*}, map {; quotemeta } @hunks; - return ($neg, 'f', "$fpath.pm", qr{\A$regex\z}); - } else { - return ($neg, 'f', "$fpath.pm", $test) - } - } +sub _parse_test_spec { + my ($name) = @_; + + my ($neg, $path) = ($name =~ m/^([~!]?)(.*)$/); + $path =~ s/\.pm$//g; + $path =~ s/::/\//g; + $path =~ s/\./\//g; + $path =~ s/\/+/\//g; + $path =~ s/^\/*//; + $path =~ s/\/*$//; + + $neg = '!' if $neg eq '~'; + + # Allow Cyrus::TesterJMAP and TesterJMAP to work + my @paths; + + my @dirs = split('/', $path); + + while (@dirs) { + push @paths, join('/', @dirs); + shift @dirs; + } + + foreach my $candidate (@paths) { + foreach my $root (@test_roots) { + return ($neg, 'd', $candidate, undef) + if ($root eq $candidate); + + my $fpath = $candidate; + $fpath = "$root/$candidate" + if ("$root/" ne substr($candidate, 0, length($root) + 1)); + + return ($neg, 'd', $fpath, undef) + if (-d $fpath); + return ($neg, 'f', "$fpath.pm", undef) + if (-f "$fpath.pm"); + + my $test; + ($fpath, $test) = ($fpath =~ m/^(.*)\/([^\/]+)$/); + next unless defined $test; + + if (-f "$fpath.pm") { + if ($test =~ /\*/) { + my @hunks = split /\*+/, $test, -1; + my $regex = join q{.*}, map { ; quotemeta } @hunks; + return ($neg, 'f', "$fpath.pm", qr{\A$regex\z}); + } else { + return ($neg, 'f', "$fpath.pm", $test); } + } } + } - die "Unrecognised test specification: $name"; + die "Unrecognised test specification: $name"; } -sub _default_test_list -{ - my ($self) = @_; +sub _default_test_list { + my ($self) = @_; - my $cassini = Cassandane::Cassini->instance(); - my @tosuppress = split /\s+/, $cassini->val('cassandane', 'suppress', ''); + my $cassini = Cassandane::Cassini->instance(); + my @tosuppress = split /\s+/, $cassini->val('cassandane', 'suppress', ''); - my %default; - my %suppressed; - @default{@test_roots} = (); + my %default; + my %suppressed; + @default{@test_roots} = (); - # skip suppressions - foreach my $s (@tosuppress) { - if (exists $default{$s}) { - # if it's named explicitly in the default list, un-name it - delete $default{$s}; - } - else { - # otherwise, add a negation for it - $suppressed{"!$s"} = undef; - } + # skip suppressions + foreach my $s (@tosuppress) { + if (exists $default{$s}) { + # if it's named explicitly in the default list, un-name it + delete $default{$s}; + } else { + # otherwise, add a negation for it + $suppressed{"!$s"} = undef; } + } - die "no default tests" if not scalar keys %default; - return (sort(keys %default), sort(keys %suppressed)); + die "no default tests" if not scalar keys %default; + return (sort(keys %default), sort(keys %suppressed)); } -sub schedule -{ - my ($self, @names) = @_; +sub schedule { + my ($self, @names) = @_; - if (not scalar @names) { - # if no names provided, use default list - @names = $self->_default_test_list(); - } - elsif (not scalar grep { m/^[^!~]/ } @names) { - # if only negations provided, start with default list - @names = ($self->_default_test_list(), @names); - } + if (not scalar @names) { + # if no names provided, use default list + @names = $self->_default_test_list(); + } elsif (not scalar grep { m/^[^!~]/ } @names) { + # if only negations provided, start with default list + @names = ($self->_default_test_list(), @names); + } - foreach my $name (@names) - { - my ($neg, $type, $path, $test) = _parse_test_spec($name); - - # slow test explicitly requested by name, so turn off the filter - if (defined $test - and ! ref $test - and $test =~ m/_slow$/ - and ! $neg - and $self->{skip_slow}) - { - xlog "$name was explicitly requested. Enabling slow tests!"; - $self->{skip_slow} = 0; - } + foreach my $name (@names) { + my ($neg, $type, $path, $test) = _parse_test_spec($name); - # non-slow test explicitly requested by name, so turn off slow-only - if (defined $test - and ! ref $test - and $test !~ m/_slow$/ - and ! $neg - and $self->{slow_only}) - { - xlog "$name was explicitly requested. Enabling regular tests!"; - $self->{slow_only} = 0; - } + # slow test explicitly requested by name, so turn off the filter + if ( defined $test + and !ref $test + and $test =~ m/_slow$/ + and !$neg + and $self->{skip_slow}) + { + xlog "$name was explicitly requested. Enabling slow tests!"; + $self->{skip_slow} = 0; + } - if ($type eq 'd') - { - opendir DIR, $path - or die "Cannot open directory $path for reading: $!"; - while ($_ = readdir DIR) - { - next unless m/\.pm$/; - $self->_schedule($neg, "$path/$_", undef); - } - closedir DIR; - } - else - { - $self->_schedule($neg, $path, $test); - } + # non-slow test explicitly requested by name, so turn off slow-only + if ( defined $test + and !ref $test + and $test !~ m/_slow$/ + and !$neg + and $self->{slow_only}) + { + xlog "$name was explicitly requested. Enabling regular tests!"; + $self->{slow_only} = 0; + } + + if ($type eq 'd') { + opendir DIR, $path + or die "Cannot open directory $path for reading: $!"; + while ($_ = readdir DIR) { + next unless m/\.pm$/; + $self->_schedule($neg, "$path/$_", undef); + } + closedir DIR; + } else { + $self->_schedule($neg, $path, $test); } + } } - # # Get the entire expanded schedule as specific {suite,testname} tuples, # sorted in alphabetic order on suite name then testname. # -sub _get_schedule -{ - my ($self) = @_; - - my @items = sort { $a->{suite} cmp $b->{suite} } values %{$self->{schedule}}; - my @res; - foreach my $item (@items) - { - my $loaded = $item->_get_loaded_suite(); - foreach my $name (sort @{$loaded->names()}) - { - $name =~ s/^test_//; - next unless $item->_is_allowed($name); - - my (@settings) = Cassandane::Unit::TestCase->make_parameter_settings($item->{suite}); - foreach my $setting (@settings) - { - push(@res, { - suite => $item->{suite}, - testname => $name, - result => 'unknown', - exception => undef, - logfile => undef, - parameter_setting => $setting, - }); - } - } +sub _get_schedule { + my ($self) = @_; + + my @items + = sort { $a->{suite} cmp $b->{suite} } values %{ $self->{schedule} }; + my @res; + foreach my $item (@items) { + my $loaded = $item->_get_loaded_suite(); + foreach my $name (sort @{ $loaded->names() }) { + $name =~ s/^test_//; + next unless $item->_is_allowed($name); + + my (@settings) + = Cassandane::Unit::TestCase->make_parameter_settings($item->{suite}); + foreach my $setting (@settings) { + push( + @res, + { + suite => $item->{suite}, + testname => $name, + result => 'unknown', + exception => undef, + logfile => undef, + parameter_setting => $setting, + } + ); + } } - return @res; + } + return @res; } # Sort and return the schedule as a list of "suite.test" strings # e.g. "Cassandane::Cyrus::Quota.using_storage". -sub list -{ - my ($self) = @_; - - my @res; - foreach my $witem ($self->_get_schedule()) - { - push(@res, "$witem->{suite}.$witem->{testname}"); - } - - return @res; -} - -sub _setup_logfile -{ - my ($self, $witem) = @_; - - # Flush the old stdout/stderr - ${\*STDOUT}->flush; - ${\*STDERR}->flush; - - # Save the old stdout/stderr - this is important - # for the single threaded case where inter-test - # messages go to the original stdout. - open my $oldout, '>&', \*STDOUT - or die "Cannot save STDOUT"; - open my $olderr, '>&', \*STDERR - or die "Cannot save STDERR"; - - my $logfh; - my $logfile; - srand; # XXX does this fix the tempfile() issue (#42)? - if (defined $self->{log_directory}) - { - # Log directory specified so create the log file - # there with a semi-obvious name - if (! -d $self->{log_directory}) - { - mkpath($self->{log_directory}) - or die "Cannot create directory $self->{log_directory}: $!"; - } - my $template = $witem->{suite} . '.' . $witem->{testname} . '.XXXXXX'; - $template =~ s/::/./g; - ($logfh, $logfile) = tempfile($template, - DIR => $self->{log_directory}, - SUFFIX => '.log', - UNLINK => 0); - chmod(0644, $logfile); - } - else - { - # Create a per-test temporary logfile - ($logfh, $logfile) = tempfile(UNLINK => 0); - } - - # Redirect both STDOUT and STDERR to the log file - open STDOUT, '>&', $logfh - or die "Cannot redirect STDOUT"; - open STDERR, '>&', $logfh - or die "Cannot redirect STDERR"; - close $logfh; - - $witem->{logfile} = $logfile; - $self->{oldout} = $oldout; - $self->{olderr} = $olderr; +sub list { + my ($self) = @_; + + my @res; + foreach my $witem ($self->_get_schedule()) { + push(@res, "$witem->{suite}.$witem->{testname}"); + } + + return @res; +} + +sub _setup_logfile { + my ($self, $witem) = @_; + + # Flush the old stdout/stderr + ${ \*STDOUT }->flush; + ${ \*STDERR }->flush; + + # Save the old stdout/stderr - this is important + # for the single threaded case where inter-test + # messages go to the original stdout. + open my $oldout, '>&', \*STDOUT + or die "Cannot save STDOUT"; + open my $olderr, '>&', \*STDERR + or die "Cannot save STDERR"; + + my $logfh; + my $logfile; + srand; # XXX does this fix the tempfile() issue (#42)? + if (defined $self->{log_directory}) { + # Log directory specified so create the log file + # there with a semi-obvious name + if (!-d $self->{log_directory}) { + mkpath($self->{log_directory}) + or die "Cannot create directory $self->{log_directory}: $!"; + } + my $template = $witem->{suite} . '.' . $witem->{testname} . '.XXXXXX'; + $template =~ s/::/./g; + ($logfh, $logfile) = tempfile( + $template, + DIR => $self->{log_directory}, + SUFFIX => '.log', + UNLINK => 0 + ); + chmod(0644, $logfile); + } else { + # Create a per-test temporary logfile + ($logfh, $logfile) = tempfile(UNLINK => 0); + } + + # Redirect both STDOUT and STDERR to the log file + open STDOUT, '>&', $logfh + or die "Cannot redirect STDOUT"; + open STDERR, '>&', $logfh + or die "Cannot redirect STDERR"; + close $logfh; + + $witem->{logfile} = $logfile; + $self->{oldout} = $oldout; + $self->{olderr} = $olderr; } # Redirect STDOUT and STDERR back to their original fds -sub _restore_stdout -{ - my ($self) = @_; - - ${\*STDOUT}->flush; - open STDOUT, '>&', $self->{oldout} - or die "Cannot restore STDOUT"; - close $self->{oldout}; - $self->{oldout} = undef; - - ${\*STDERR}->flush; - open STDERR, '>&', $self->{olderr} - or die "Cannot restore STDERR"; - close $self->{olderr}; - $self->{olderr} = undef; -} - -sub _dump_logfile -{ - my ($logfile) = @_; - - open LOGFILE, '<', $logfile - or die "Cannot open $logfile for reading: $!"; - while () - { - print STDERR $_; - } - close LOGFILE; -} - -sub _get_suite_and_test -{ - my ($self, $witem) = @_; - my $suite = $self->_get_item($witem->{suite})->_get_loaded_suite(); - my ($test) = grep { $_->name() eq 'test_' . $witem->{testname}; } @{$suite->tests()}; - return ($suite, $test); -} - -sub _run_workitem -{ - my ($self, $witem, $result, $runner, $annotate_flag) = @_; - my ($suite, $test) = $self->_get_suite_and_test($witem); - Cassandane::Unit::TestCase->enable_test($witem->{testname}); - $self->_setup_logfile($witem); - Cassandane::Unit::TestCase->apply_parameter_setting($witem->{parameter_setting}); - $suite->run($result, $runner); - - if ($test->can('post_tear_down')) - { - eval - { - $test->post_tear_down($witem->{result}); - }; - my $ex = $@; - if ($ex) - { - $result->add_error($test, - Test::Unit::Error->make_new_from_error($ex)); - } - } - - $self->_restore_stdout(); - if ($annotate_flag) - { - $test->annotate_from_file($witem->{logfile}); - _dump_logfile($witem->{logfile}) if (get_verbose > 1); - unlink($witem->{logfile}) if (!defined $self->{log_directory}); - } -} - -sub _finish_workitem -{ - my ($self, $witem, $result, $runner) = @_; - my ($suite, $test) = $self->_get_suite_and_test($witem); - - $result->start_test($test); - if ($runner->can('fake_start_time')) - { - $runner->fake_start_time($test, $witem->{start_time}); - } - +sub _restore_stdout { + my ($self) = @_; + + ${ \*STDOUT }->flush; + open STDOUT, '>&', $self->{oldout} + or die "Cannot restore STDOUT"; + close $self->{oldout}; + $self->{oldout} = undef; + + ${ \*STDERR }->flush; + open STDERR, '>&', $self->{olderr} + or die "Cannot restore STDERR"; + close $self->{olderr}; + $self->{olderr} = undef; +} + +sub _dump_logfile { + my ($logfile) = @_; + + open LOGFILE, '<', $logfile + or die "Cannot open $logfile for reading: $!"; + while () { + print STDERR $_; + } + close LOGFILE; +} + +sub _get_suite_and_test { + my ($self, $witem) = @_; + my $suite = $self->_get_item($witem->{suite})->_get_loaded_suite(); + my ($test) + = grep { $_->name() eq 'test_' . $witem->{testname}; } @{ $suite->tests() }; + return ($suite, $test); +} + +sub _run_workitem { + my ($self, $witem, $result, $runner, $annotate_flag) = @_; + my ($suite, $test) = $self->_get_suite_and_test($witem); + Cassandane::Unit::TestCase->enable_test($witem->{testname}); + $self->_setup_logfile($witem); + Cassandane::Unit::TestCase->apply_parameter_setting( + $witem->{parameter_setting}); + $suite->run($result, $runner); + + if ($test->can('post_tear_down')) { + eval { $test->post_tear_down($witem->{result}); }; + my $ex = $@; + if ($ex) { + $result->add_error($test, Test::Unit::Error->make_new_from_error($ex)); + } + } + + $self->_restore_stdout(); + if ($annotate_flag) { $test->annotate_from_file($witem->{logfile}); _dump_logfile($witem->{logfile}) if (get_verbose > 1); - unlink($witem->{logfile}) if (!defined $self->{log_directory}); - - if ($witem->{result} eq 'pass') - { - $result->add_pass($test); - } - elsif ($witem->{result} eq 'fail') - { - $witem->{exception}->{'-object'} = $test; - $result->add_failure($test, $witem->{exception}); - } - elsif ($witem->{result} eq 'error') - { - $witem->{exception}->{'-object'} = $test; - $result->add_error($test, $witem->{exception}); - } - $result->end_test($test); -} - -sub _setup_worker_listeners -{ - my ($result, $wlistener) = @_; - - # Remove the output format listener - my @list; - my $found = 0; - foreach my $ll (@{$result->{_Listeners}}) - { - push(@list, $ll) - unless defined $ll->{remove_me_in_cassandane_child}; - $found ||= (ref($ll) eq ref($wlistener)); - } - push(@list, $wlistener) - unless $found; - $result->{_Listeners} = \@list; + unlink($witem->{logfile}) if (!defined $self->{log_directory}); + } +} + +sub _finish_workitem { + my ($self, $witem, $result, $runner) = @_; + my ($suite, $test) = $self->_get_suite_and_test($witem); + + $result->start_test($test); + if ($runner->can('fake_start_time')) { + $runner->fake_start_time($test, $witem->{start_time}); + } + + $test->annotate_from_file($witem->{logfile}); + _dump_logfile($witem->{logfile}) if (get_verbose > 1); + unlink($witem->{logfile}) if (!defined $self->{log_directory}); + + if ($witem->{result} eq 'pass') { + $result->add_pass($test); + } elsif ($witem->{result} eq 'fail') { + $witem->{exception}->{'-object'} = $test; + $result->add_failure($test, $witem->{exception}); + } elsif ($witem->{result} eq 'error') { + $witem->{exception}->{'-object'} = $test; + $result->add_error($test, $witem->{exception}); + } + $result->end_test($test); +} + +sub _setup_worker_listeners { + my ($result, $wlistener) = @_; + + # Remove the output format listener + my @list; + my $found = 0; + foreach my $ll (@{ $result->{_Listeners} }) { + push(@list, $ll) + unless defined $ll->{remove_me_in_cassandane_child}; + $found ||= (ref($ll) eq ref($wlistener)); + } + push(@list, $wlistener) + unless $found; + $result->{_Listeners} = \@list; } # The 'run' method makes this class look sufficiently like a # Test::Unit::TestCase that Test::Unit::TestRunner will happily run it. # This enables us to run all our scheduled tests with a single # TestResult and a single summary of errors. -sub run -{ - my ($self, $result, $runner) = @_; - - my $maxworkers = $self->{maxworkers} || 1; - - # we expand the schedule before forking the - # workers so that we can just hand the reference - # to the worker - my @workitems = $self->_get_schedule(); - - # try to clean up after ourselves on interrupt - my $interrupted = 0; - $SIG{INT} = sub { - $interrupted ++; - # third ^C will terminate without cleanup - $SIG{INT} = 'DEFAULT' if $interrupted >= 2; - }; - - if ($maxworkers > 1) - { - # multi-threaded case: use worker pool - - # we want an error not a signal - $SIG{PIPE} = 'IGNORE'; - - # Just In Case any code samples this in a TestCase c'tor - $ENV{TEST_UNIT_WORKER_ID} = 'invalid'; - - my $wlistener = Cassandane::Unit::WorkerListener->new(); - - my $pool = Cassandane::Unit::WorkerPool->new( - maxworkers => $maxworkers, - handler => sub { - my ($witem) = @_; - $wlistener->{witem} = $witem; - _setup_worker_listeners($result, $wlistener); - $self->_run_workitem($witem, $result, $runner, 0); - }, - ); - my $witem; - $pool->start(); - # first ^C stops spawning new work items - while ($interrupted < 1 && ($witem = shift @workitems)) - { - $pool->assign($witem) - if ($self->{keep_going} || $result->was_successful()); - while ($witem = $pool->retrieve(0)) - { - $self->_finish_workitem($witem, $result, $runner); - } - } - # second ^C stops waiting for work items to finish - while ($interrupted < 2 && ($witem = $pool->retrieve(1))) - { - $self->_finish_workitem($witem, $result, $runner); - } - $pool->stop(); - } - else - { - # single threaded case: just run it all in-process - foreach my $witem (@workitems) - { - $self->_run_workitem($witem, $result, $runner, 1); - last if ($interrupted || !($self->{keep_going} || $result->was_successful())); - } - } - - return $result->was_successful(); +sub run { + my ($self, $result, $runner) = @_; + + my $maxworkers = $self->{maxworkers} || 1; + + # we expand the schedule before forking the + # workers so that we can just hand the reference + # to the worker + my @workitems = $self->_get_schedule(); + + # try to clean up after ourselves on interrupt + my $interrupted = 0; + $SIG{INT} = sub { + $interrupted++; + # third ^C will terminate without cleanup + $SIG{INT} = 'DEFAULT' if $interrupted >= 2; + }; + + if ($maxworkers > 1) { + # multi-threaded case: use worker pool + + # we want an error not a signal + $SIG{PIPE} = 'IGNORE'; + + # Just In Case any code samples this in a TestCase c'tor + $ENV{TEST_UNIT_WORKER_ID} = 'invalid'; + + my $wlistener = Cassandane::Unit::WorkerListener->new(); + + my $pool = Cassandane::Unit::WorkerPool->new( + maxworkers => $maxworkers, + handler => sub { + my ($witem) = @_; + $wlistener->{witem} = $witem; + _setup_worker_listeners($result, $wlistener); + $self->_run_workitem($witem, $result, $runner, 0); + }, + ); + my $witem; + $pool->start(); + # first ^C stops spawning new work items + while ($interrupted < 1 && ($witem = shift @workitems)) { + $pool->assign($witem) + if ($self->{keep_going} || $result->was_successful()); + while ($witem = $pool->retrieve(0)) { + $self->_finish_workitem($witem, $result, $runner); + } + } + # second ^C stops waiting for work items to finish + while ($interrupted < 2 && ($witem = $pool->retrieve(1))) { + $self->_finish_workitem($witem, $result, $runner); + } + $pool->stop(); + } else { + # single threaded case: just run it all in-process + foreach my $witem (@workitems) { + $self->_run_workitem($witem, $result, $runner, 1); + last + if ($interrupted + || !($self->{keep_going} || $result->was_successful())); + } + } + + return $result->was_successful(); } 1; diff --git a/cassandane/Cassandane/Util/DateTime.pm b/cassandane/Cassandane/Util/DateTime.pm index 953e712020..61a1742faa 100644 --- a/cassandane/Cassandane/Util/DateTime.pm +++ b/cassandane/Cassandane/Util/DateTime.pm @@ -44,12 +44,12 @@ use DateTime; use POSIX qw(strftime); use Exporter (); -our @ISA = qw(Exporter); +our @ISA = qw(Exporter); our @EXPORT = qw( - &from_iso8601 &to_iso8601 - &from_rfc822 &to_rfc822 - &from_rfc3501 &to_rfc3501 - ); + &from_iso8601 &to_iso8601 + &from_rfc822 &to_rfc822 + &from_rfc3501 &to_rfc3501 +); # # Construct and return a DateTime object using a string in the @@ -62,42 +62,40 @@ our @EXPORT = qw( # The optional Z suffix indicates Zulu (UTC aka GMT) # time, otherwise localtime is assumed. # -sub from_iso8601($) -{ - my ($s) = @_; - my ($year, $mon, $day, $hour, $min, $sec, $zulu) = - ($s =~ m/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)$/); - return unless defined $sec; - return if ($year < 1970 || $year > 2037); - return if ($mon < 1 || $mon > 12); - return if ($day < 1 || $day > 31); - return if ($hour < 0 || $hour > 23); - return if ($min < 0 || $min > 59); - return if ($sec < 0 || $sec > 60); # allow for leap second - -# printf STDERR "%s -> year=%u mon=%u day=%u hour=%u min=%u sec=%u\n", -# $s, $year, $mon, $day, $hour, $min, $sec; - - my $tz = ($zulu ? 'GMT' : 'local'); - return DateTime->new( - year => $year, - month => $mon, - day => $day, - hour => $hour, - minute => $min, - second => $sec, - time_zone => $tz - ); +sub from_iso8601($) { + my ($s) = @_; + my ($year, $mon, $day, $hour, $min, $sec, $zulu) + = ($s =~ m/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)$/); + return unless defined $sec; + return if ($year < 1970 || $year > 2037); + return if ($mon < 1 || $mon > 12); + return if ($day < 1 || $day > 31); + return if ($hour < 0 || $hour > 23); + return if ($min < 0 || $min > 59); + return if ($sec < 0 || $sec > 60); # allow for leap second + + # printf STDERR "%s -> year=%u mon=%u day=%u hour=%u min=%u sec=%u\n", + # $s, $year, $mon, $day, $hour, $min, $sec; + + my $tz = ($zulu ? 'GMT' : 'local'); + return DateTime->new( + year => $year, + month => $mon, + day => $day, + hour => $hour, + minute => $min, + second => $sec, + time_zone => $tz + ); } # # Given a DateTime, generate and return a string in ISO8601 # combined basic format. # -sub to_iso8601($) -{ - my ($dt) = @_; - return strftime("%Y%m%dT%H%M%SZ", gmtime($dt->epoch)); +sub to_iso8601($) { + my ($dt) = @_; + return strftime("%Y%m%dT%H%M%SZ", gmtime($dt->epoch)); } # Brief sanity test for parse_iso8601_datetime @@ -107,44 +105,25 @@ sub to_iso8601($) # unless (from_iso8601('20101014T161952Z') == 1287073192); our %rfc822_months = ( - Jan => 1, - Feb => 2, - Mar => 3, - Apr => 4, - May => 5, - Jun => 6, - Jul => 7, - Aug => 8, - Sep => 9, - Oct => 10, - Nov => 11, - Dec => 12 - ); + Jan => 1, + Feb => 2, + Mar => 3, + Apr => 4, + May => 5, + Jun => 6, + Jul => 7, + Aug => 8, + Sep => 9, + Oct => 10, + Nov => 11, + Dec => 12 +); our @rfc822_months = ( - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' - ); + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' +); -our @rfc822_days = ( - 'Sun', - 'Mon', - 'Tue', - 'Wed', - 'Thu', - 'Fri', - 'Sat', - 'Sun' - ); +our @rfc822_days = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'); # # Construct and return a DateTime object using a string in the @@ -152,57 +131,55 @@ our @rfc822_days = ( # the internet email message format. # Example: Tue, 05 Oct 2010 11:19:52 +1100 # -sub from_rfc822($) -{ - my ($s) = @_; - my ($wdayn, $day, $mon, $year, $hour, $min, $sec, $tzsign, $tzhour, $tzmin) = - ($s =~ m/^([A-Z][a-z][a-z]), (\d+) ([A-Z][a-z][a-z]) (\d{4}) (\d{2}):(\d{2}):(\d{2}) ([-+])(\d{2})(\d{2})$/); - return unless defined $tzmin; - return if ($year < 1970 || $year > 2037); - $mon = $rfc822_months{$mon}; - return unless defined $mon; - return if ($day < 1 || $day > 31); - return if ($hour < 0 || $hour > 23); - return if ($min < 0 || $min > 59); - return if ($sec < 0 || $sec > 60); # allow for leap second - return if ($tzhour < 0 || $tzhour > 23); - return if ($tzmin < 0 || $tzmin > 59); +sub from_rfc822($) { + my ($s) = @_; + my ($wdayn, $day, $mon, $year, $hour, $min, $sec, $tzsign, $tzhour, $tzmin) + = ($s =~ + m/^([A-Z][a-z][a-z]), (\d+) ([A-Z][a-z][a-z]) (\d{4}) (\d{2}):(\d{2}):(\d{2}) ([-+])(\d{2})(\d{2})$/ + ); + return unless defined $tzmin; + return if ($year < 1970 || $year > 2037); + $mon = $rfc822_months{$mon}; + return unless defined $mon; + return if ($day < 1 || $day > 31); + return if ($hour < 0 || $hour > 23); + return if ($min < 0 || $min > 59); + return if ($sec < 0 || $sec > 60); # allow for leap second + return if ($tzhour < 0 || $tzhour > 23); + return if ($tzmin < 0 || $tzmin > 59); # printf STDERR "%s -> year=%u mon=%u day=%u hour=%u min=%u sec=%u tzsign=%s tzhour=%u tzmin=%u\n", # $s, $year, $mon, $day, $hour, $min, $sec, $tzsign, $tzhour, $tzmin; - return DateTime->new( - year => $year, - month => $mon, - day => $day, - hour => $hour, - minute => $min, - second => $sec, - time_zone => "$tzsign$tzhour$tzmin" - ); + return DateTime->new( + year => $year, + month => $mon, + day => $day, + hour => $hour, + minute => $min, + second => $sec, + time_zone => "$tzsign$tzhour$tzmin" + ); } # # Given a DateTime, generate and return a string in RFC822 format. # -sub to_rfc822($) -{ - my ($dt) = @_; - - # We can't mix DateTime methods and strftime, because other parts of - # Cassandane foolishly construct DateTime using the 'from_epoch' but - # not the 'time_zone' parameters, resulting in a DT object in the - # UTC timezone instead of local. But conversely strftime() doesn't - # have a portable way to emit the fixed (non-local-specific) strings - # that the RFC expects. - my @lt = localtime($dt->epoch); - return strftime($rfc822_days[$lt[6]] . - ", %d " . - $rfc822_months[$lt[4]] . - " %Y %T %z", @lt); +sub to_rfc822($) { + my ($dt) = @_; + + # We can't mix DateTime methods and strftime, because other parts of + # Cassandane foolishly construct DateTime using the 'from_epoch' but + # not the 'time_zone' parameters, resulting in a DT object in the + # UTC timezone instead of local. But conversely strftime() doesn't + # have a portable way to emit the fixed (non-local-specific) strings + # that the RFC expects. + my @lt = localtime($dt->epoch); + return strftime( + $rfc822_days[ $lt[6] ] . ", %d " . $rfc822_months[ $lt[4] ] . " %Y %T %z", + @lt); } - # die "Woops, from_rfc822 is broken" # unless (from_rfc822('Fri, 15 Oct 2010 03:19:52 +1100') == 1287073192); @@ -211,34 +188,35 @@ sub to_rfc822($) # format defined in RFC3501 which defines the IMAP protocol. # Example: " 5-Oct-2010 09:19:52 +1100" (note leading space) # -sub from_rfc3501($) -{ - my ($s) = @_; - my ($day, $mon, $year, $hour, $min, $sec, $tzsign, $tzhour, $tzmin) = - ($s =~ m/^\s*(\d+)-([A-Z][a-z][a-z])-(\d{4}) (\d{2}):(\d{2}):(\d{2}) ([-+])(\d{2})(\d{2})$/); - return unless defined $tzmin; - return if ($year < 1970 || $year > 2037); - $mon = $rfc822_months{$mon}; - return unless defined $mon; - return if ($day < 1 || $day > 31); - return if ($hour < 0 || $hour > 23); - return if ($min < 0 || $min > 59); - return if ($sec < 0 || $sec > 60); # allow for leap second - return if ($tzhour < 0 || $tzhour > 23); - return if ($tzmin < 0 || $tzmin > 59); +sub from_rfc3501($) { + my ($s) = @_; + my ($day, $mon, $year, $hour, $min, $sec, $tzsign, $tzhour, $tzmin) + = ($s =~ + m/^\s*(\d+)-([A-Z][a-z][a-z])-(\d{4}) (\d{2}):(\d{2}):(\d{2}) ([-+])(\d{2})(\d{2})$/ + ); + return unless defined $tzmin; + return if ($year < 1970 || $year > 2037); + $mon = $rfc822_months{$mon}; + return unless defined $mon; + return if ($day < 1 || $day > 31); + return if ($hour < 0 || $hour > 23); + return if ($min < 0 || $min > 59); + return if ($sec < 0 || $sec > 60); # allow for leap second + return if ($tzhour < 0 || $tzhour > 23); + return if ($tzmin < 0 || $tzmin > 59); # printf STDERR "%s -> year=%u mon=%u day=%u hour=%u min=%u sec=%u tzsign=%s tzhour=%u tzmin=%u\n", # $s, $year, $mon, $day, $hour, $min, $sec, $tzsign, $tzhour, $tzmin; - return DateTime->new( - year => $year, - month => $mon, - day => $day, - hour => $hour, - minute => $min, - second => $sec, - time_zone => "$tzsign$tzhour$tzmin" - ); + return DateTime->new( + year => $year, + month => $mon, + day => $day, + hour => $hour, + minute => $min, + second => $sec, + time_zone => "$tzsign$tzhour$tzmin" + ); } # die "Woops, from_rfc3501 is broken" @@ -247,15 +225,11 @@ sub from_rfc3501($) # # Given a DateTime, generate and return a string in RFC3501 format. # -sub to_rfc3501($) -{ - my ($dt) = @_; +sub to_rfc3501($) { + my ($dt) = @_; - my @lt = localtime($dt->epoch); - return strftime("%e-" - . $rfc822_months[$lt[4]] - . "-%Y %T %z", - @lt); + my @lt = localtime($dt->epoch); + return strftime("%e-" . $rfc822_months[ $lt[4] ] . "-%Y %T %z", @lt); } 1; diff --git a/cassandane/Cassandane/Util/Log.pm b/cassandane/Cassandane/Util/Log.pm index 27208010e2..16b1e44fb8 100644 --- a/cassandane/Cassandane/Util/Log.pm +++ b/cassandane/Cassandane/Util/Log.pm @@ -42,65 +42,61 @@ use strict; use warnings; use File::Basename; use Scalar::Util qw(blessed); -use Sys::Syslog qw(:standard :macros); +use Sys::Syslog qw(:standard :macros); use Exporter (); -our @ISA = qw(Exporter); +our @ISA = qw(Exporter); our @EXPORT = qw( - &xlog &set_verbose &get_verbose - ); + &xlog &set_verbose &get_verbose +); my $verbose = 0; openlog('cassandane', '', LOG_LOCAL6) - or die "Cannot openlog"; + or die "Cannot openlog"; -sub xlog -{ - my $id; - my $highlight = 0; +sub xlog { + my $id; + my $highlight = 0; - # if the first argument is an object with an id() method, - # include the id it returns in the log message - if (ref $_[0] && blessed $_[0] && $_[0]->can('id')) { - my $obj = shift @_; - $id = $obj->id(); - } + # if the first argument is an object with an id() method, + # include the id it returns in the log message + if (ref $_[0] && blessed $_[0] && $_[0]->can('id')) { + my $obj = shift @_; + $id = $obj->id(); + } - # if the first output argument starts with XXX, highlight the - # whole line when printing to stderr - if ($_[0] =~ m/^XXX/) { - $highlight = 1; - } + # if the first output argument starts with XXX, highlight the + # whole line when printing to stderr + if ($_[0] =~ m/^XXX/) { + $highlight = 1; + } - # the current line number is in this frame - my (undef, undef, $line) = caller(); - # but the current subroutine name is in the parent frame, - # as the function-the-caller-called - my (undef, undef, undef, $sub) = caller(1); - $sub //= basename($0); - $sub =~ s/^Cassandane:://; - my $msg = "[$$] =====> $sub\[$line] "; - $msg .= "($id) " if $id; - $msg .= join(' ', @_); - if ($highlight) { - print STDERR "\033[33m" . $msg . "\033[0m\n"; - } - else { - print STDERR "$msg\n"; - } - syslog(LOG_ERR, "$msg"); + # the current line number is in this frame + my (undef, undef, $line) = caller(); + # but the current subroutine name is in the parent frame, + # as the function-the-caller-called + my (undef, undef, undef, $sub) = caller(1); + $sub //= basename($0); + $sub =~ s/^Cassandane:://; + my $msg = "[$$] =====> $sub\[$line] "; + $msg .= "($id) " if $id; + $msg .= join(' ', @_); + if ($highlight) { + print STDERR "\033[33m" . $msg . "\033[0m\n"; + } else { + print STDERR "$msg\n"; + } + syslog(LOG_ERR, "$msg"); } -sub set_verbose -{ - my ($v) = @_; - $verbose = 0 + $v; +sub set_verbose { + my ($v) = @_; + $verbose = 0 + $v; } -sub get_verbose -{ - return $verbose; +sub get_verbose { + return $verbose; } 1; diff --git a/cassandane/Cassandane/Util/Metronome.pm b/cassandane/Cassandane/Util/Metronome.pm index 92d328c442..491df09d7f 100644 --- a/cassandane/Cassandane/Util/Metronome.pm +++ b/cassandane/Cassandane/Util/Metronome.pm @@ -45,52 +45,48 @@ use Time::HiRes qw(clock_gettime clock_nanosleep CLOCK_MONOTONIC); use lib '.'; use Cassandane::Util::Log; -sub new -{ - my ($class, %params) = @_; +sub new { + my ($class, %params) = @_; - my $self = { - # These are floating point numbers in seconds - # with presumed nanosecond resolution - interval => 0.0, - error => 0.0, - last_tick => undef, - first_tick => undef, - nticks => 0, - }; + my $self = { + # These are floating point numbers in seconds + # with presumed nanosecond resolution + interval => 0.0, + error => 0.0, + last_tick => undef, + first_tick => undef, + nticks => 0, + }; - $self->{interval} = $params{interval} - if defined $params{interval}; - $self->{interval} = 1.0/$params{rate} - if defined $params{rate}; + $self->{interval} = $params{interval} + if defined $params{interval}; + $self->{interval} = 1.0 / $params{rate} + if defined $params{rate}; - return bless $self, $class; + return bless $self, $class; } -sub tick -{ - my ($self) = @_; +sub tick { + my ($self) = @_; - my $now = clock_gettime(CLOCK_MONOTONIC); - $self->{first_tick} ||= $now; - $self->{nticks}++; - my $next_tick = ($self->{last_tick} || $now) - + $self->{interval}; - my $delay = ($next_tick + $self->{error} - $now); - clock_nanosleep(CLOCK_MONOTONIC, 1e9 * $delay) - if ($delay > 0.0); - $now = clock_gettime(CLOCK_MONOTONIC); - $self->{error} = $next_tick - $now; - $self->{last_tick} = $next_tick; + my $now = clock_gettime(CLOCK_MONOTONIC); + $self->{first_tick} ||= $now; + $self->{nticks}++; + my $next_tick = ($self->{last_tick} || $now) + $self->{interval}; + my $delay = ($next_tick + $self->{error} - $now); + clock_nanosleep(CLOCK_MONOTONIC, 1e9 * $delay) + if ($delay > 0.0); + $now = clock_gettime(CLOCK_MONOTONIC); + $self->{error} = $next_tick - $now; + $self->{last_tick} = $next_tick; } -sub actual_rate -{ - my ($self) = @_; +sub actual_rate { + my ($self) = @_; - return undef if !$self->{nticks}; - return $self->{nticks} / - (clock_gettime(CLOCK_MONOTONIC) - $self->{first_tick}); + return undef if !$self->{nticks}; + return $self->{nticks} / + (clock_gettime(CLOCK_MONOTONIC) - $self->{first_tick}); } 1; diff --git a/cassandane/Cassandane/Util/NetString.pm b/cassandane/Cassandane/Util/NetString.pm index 39d883a5f7..0782686e87 100644 --- a/cassandane/Cassandane/Util/NetString.pm +++ b/cassandane/Cassandane/Util/NetString.pm @@ -55,7 +55,7 @@ use strict; use warnings; use vars qw(@ISA @EXPORT); -@ISA = qw(Exporter); +@ISA = qw(Exporter); @EXPORT = qw(print_netstring get_netstring); sub print_netstring { @@ -66,22 +66,22 @@ sub print_netstring { my $size = length $data; - print $fh "$size:$data," + print $fh "$size:$data,"; } sub get_netstring { my $fh = shift; - my($r, $ns); - my $s = ""; + my ($r, $ns); + my $s = ""; my $len = 0; # read the length for (;;) { defined($r = read($fh, $s, 1)) or return undef; - return "" if !$r; - last if $s eq ":"; + return "" if !$r; + last if $s eq ":"; return undef if $s !~ /^[0-9]$/; $len = 10 * $len + $s; @@ -97,7 +97,7 @@ sub get_netstring { # read the trailing comma defined($r = read($fh, $s, 1)) or return undef; - return "" if !$r; + return "" if !$r; return undef if $s ne ","; return $ns; diff --git a/cassandane/Cassandane/Util/SHA.pm b/cassandane/Cassandane/Util/SHA.pm index 1b51ad20da..a9c4b3e32b 100644 --- a/cassandane/Cassandane/Util/SHA.pm +++ b/cassandane/Cassandane/Util/SHA.pm @@ -46,12 +46,12 @@ use strict; use warnings; use vars qw(@ISA @EXPORT); -@ISA = qw(Exporter); +@ISA = qw(Exporter); @EXPORT = qw(sha1_hex sha1); BEGIN { - eval "use Digest::SHA qw(sha1_hex sha1); 1;" - || eval "use Digest::SHA1 qw(sha1_hex sha1);"; + eval "use Digest::SHA qw(sha1_hex sha1); 1;" + || eval "use Digest::SHA1 qw(sha1_hex sha1);"; } 1; diff --git a/cassandane/Cassandane/Util/Sample.pm b/cassandane/Cassandane/Util/Sample.pm index dd96734c3e..15aa691f2e 100644 --- a/cassandane/Cassandane/Util/Sample.pm +++ b/cassandane/Cassandane/Util/Sample.pm @@ -42,91 +42,84 @@ use strict; use warnings; use overload qw("") => \&as_string; -sub new -{ - my ($class, @args) = @_; +sub new { + my ($class, @args) = @_; - die "Unknown extra arguments" - if scalar(@args); + die "Unknown extra arguments" + if scalar(@args); - my $self = - { - _total => 0.0, - _total2 => 0.0, - _n => 0, - _min => undef, - _max => undef, - }; - return bless($self, $class); + my $self = { + _total => 0.0, + _total2 => 0.0, + _n => 0, + _min => undef, + _max => undef, + }; + return bless($self, $class); } -sub add -{ - my ($self, $x) = @_; +sub add { + my ($self, $x) = @_; - $self->{_total} += $x; - $self->{_total2} += $x * $x; - $self->{_n}++; - $self->{_min} = $x - if (!defined $self->{_min} || $x < $self->{_min}); - $self->{_max} = $x - if (!defined $self->{_max} || $x > $self->{_max}); + $self->{_total} += $x; + $self->{_total2} += $x * $x; + $self->{_n}++; + $self->{_min} = $x + if (!defined $self->{_min} || $x < $self->{_min}); + $self->{_max} = $x + if (!defined $self->{_max} || $x > $self->{_max}); } -sub nsamples -{ - my ($self) = @_; - return $self->{_n}; +sub nsamples { + my ($self) = @_; + return $self->{_n}; } -sub average -{ - my ($self) = @_; - die "No samples yet" if (!$self->{_n}); - return $self->{_total} / $self->{_n}; +sub average { + my ($self) = @_; + die "No samples yet" if (!$self->{_n}); + return $self->{_total} / $self->{_n}; } -sub minimum -{ - my ($self) = @_; - die "No samples yet" if (!$self->{_n}); - return $self->{_min}; +sub minimum { + my ($self) = @_; + die "No samples yet" if (!$self->{_n}); + return $self->{_min}; } -sub maximum -{ - my ($self) = @_; - die "No samples yet" if (!$self->{_n}); - return $self->{_max}; +sub maximum { + my ($self) = @_; + die "No samples yet" if (!$self->{_n}); + return $self->{_max}; } -sub sample_deviation -{ - my ($self) = @_; - die "No samples yet" if ($self->{_n} < 2); - return sqrt( - ($self->{_n} * $self->{_total2} - $self->{_total} * $self->{_total}) - / - ($self->{_n} * ($self->{_n} - 1)) - ); +sub sample_deviation { + my ($self) = @_; + die "No samples yet" if ($self->{_n} < 2); + return + sqrt( + ($self->{_n} * $self->{_total2} - $self->{_total} * $self->{_total}) / + ($self->{_n} * ($self->{_n} - 1))); } -sub as_string -{ - my ($self) = @_; - my $s = "no samples"; - if ($self->{_n} > 0) - { - $s = "count " . $self->nsamples() . - " minimum " . $self->minimum() . - " maximum " . $self->maximum() . - " average " . $self->average(); - if ($self->{_n} > 1) - { - $s .= " sample_deviation " . $self->sample_deviation(); - } +sub as_string { + my ($self) = @_; + my $s = "no samples"; + if ($self->{_n} > 0) { + $s + = "count " + . $self->nsamples() + . " minimum " + . $self->minimum() + . " maximum " + . $self->maximum() + . " average " + . $self->average(); + if ($self->{_n} > 1) { + $s .= " sample_deviation " . $self->sample_deviation(); } - return $s; + } + return $s; } 1; diff --git a/cassandane/Cassandane/Util/Setup.pm b/cassandane/Cassandane/Util/Setup.pm index 20efd29916..1e8a03b8d8 100644 --- a/cassandane/Cassandane/Util/Setup.pm +++ b/cassandane/Cassandane/Util/Setup.pm @@ -50,35 +50,29 @@ use Cassandane::Util::Log; our @EXPORT = qw(&become_cyrus); -my $me = $0; +my $me = $0; my @saved_argv = @ARGV; -sub become_cyrus -{ - my $cyrus = $ENV{CYRUS_USER}; - $cyrus //= 'cyrus'; - my $pw = getpwnam($cyrus); - die "No user named '$cyrus'" - unless defined $pw; - my $uid = getuid(); - if ($uid == $pw->uid) - { - xlog "already running as user $cyrus" if get_verbose; - } - elsif ($uid == 0) - { - xlog "setuid from root to $cyrus" if get_verbose; - setgid($pw->gid) - or die "Cannot setgid to group $pw->gid: $!"; - setuid($pw->uid) - or die "Cannot setuid to group $pw->uid: $!"; - } - else - { - xlog "using sudo to re-run as user $cyrus" if get_verbose; - my @cmd = ( qw(sudo -u), $cyrus, $me, @saved_argv ); - exec(@cmd); - } +sub become_cyrus { + my $cyrus = $ENV{CYRUS_USER}; + $cyrus //= 'cyrus'; + my $pw = getpwnam($cyrus); + die "No user named '$cyrus'" + unless defined $pw; + my $uid = getuid(); + if ($uid == $pw->uid) { + xlog "already running as user $cyrus" if get_verbose; + } elsif ($uid == 0) { + xlog "setuid from root to $cyrus" if get_verbose; + setgid($pw->gid) + or die "Cannot setgid to group $pw->gid: $!"; + setuid($pw->uid) + or die "Cannot setuid to group $pw->uid: $!"; + } else { + xlog "using sudo to re-run as user $cyrus" if get_verbose; + my @cmd = (qw(sudo -u), $cyrus, $me, @saved_argv); + exec(@cmd); + } } 1; diff --git a/cassandane/Cassandane/Util/Slurp.pm b/cassandane/Cassandane/Util/Slurp.pm index 6ed7e2b779..71654e4bc6 100644 --- a/cassandane/Cassandane/Util/Slurp.pm +++ b/cassandane/Cassandane/Util/Slurp.pm @@ -46,17 +46,16 @@ use lib '.'; our @EXPORT = qw(&slurp_file); -sub slurp_file -{ - my ($filename) = @_; +sub slurp_file { + my ($filename) = @_; - local $/; - open my $f, '<', $filename - or die "Cannot open $filename for reading: $!\n"; - my $str = <$f>; - close $f; + local $/; + open my $f, '<', $filename + or die "Cannot open $filename for reading: $!\n"; + my $str = <$f>; + close $f; - return $str; + return $str; } 1; diff --git a/cassandane/Cassandane/Util/Socket.pm b/cassandane/Cassandane/Util/Socket.pm index 082d042eaf..57ae7f6f71 100644 --- a/cassandane/Cassandane/Util/Socket.pm +++ b/cassandane/Cassandane/Util/Socket.pm @@ -50,42 +50,38 @@ use Cassandane::Util::Log; our @EXPORT = qw(create_client_socket); -sub create_client_socket -{ - my ($af, $host, $port) = @_; - my $sock; +sub create_client_socket { + my ($af, $host, $port) = @_; + my $sock; - if ($af eq 'inet') - { - $host ||= '127.0.0.1'; - xlog "create_client_socket INET host=$host port=$port"; - return IO::Socket::INET->new( - Type => SOCK_STREAM, - PeerHost => $host, - PeerPort => $port); - } - elsif ($af eq 'inet6') - { - # IO::Socket::INET6 doesn't have an option to pass - # an address which is explicitly *without* the optional - # :port part, but it does allow us to use the same [] - # syntax for surrounding IPv6 address literals as Cyrus - $host ||= '::1'; - xlog "create_client_socket INET6 addr=[$host]:$port"; - return IO::Socket::INET6->new( - Domain => AF_INET6, - Type => SOCK_STREAM, - PeerAddr => "[$host]:$port"); - } - elsif ($af eq 'unix') - { - xlog "create_client_socket UNIX peer=$port"; - return IO::Socket::UNIX->new( - Type => SOCK_STREAM, - Peer => $port); - } - die "Cannot create sock for address family \"$af\""; + if ($af eq 'inet') { + $host ||= '127.0.0.1'; + xlog "create_client_socket INET host=$host port=$port"; + return IO::Socket::INET->new( + Type => SOCK_STREAM, + PeerHost => $host, + PeerPort => $port + ); + } elsif ($af eq 'inet6') { + # IO::Socket::INET6 doesn't have an option to pass + # an address which is explicitly *without* the optional + # :port part, but it does allow us to use the same [] + # syntax for surrounding IPv6 address literals as Cyrus + $host ||= '::1'; + xlog "create_client_socket INET6 addr=[$host]:$port"; + return IO::Socket::INET6->new( + Domain => AF_INET6, + Type => SOCK_STREAM, + PeerAddr => "[$host]:$port" + ); + } elsif ($af eq 'unix') { + xlog "create_client_socket UNIX peer=$port"; + return IO::Socket::UNIX->new( + Type => SOCK_STREAM, + Peer => $port + ); + } + die "Cannot create sock for address family \"$af\""; } - 1; diff --git a/cassandane/Cassandane/Util/TestUrl.pm b/cassandane/Cassandane/Util/TestUrl.pm index d5ccd7ab4a..f3b9bedf30 100644 --- a/cassandane/Cassandane/Util/TestUrl.pm +++ b/cassandane/Cassandane/Util/TestUrl.pm @@ -10,127 +10,120 @@ use Carp qw(croak); use lib '.'; use Cassandane::PortManager; -sub new -{ - my ($pkg, $args) = @_; +sub new { + my ($pkg, $args) = @_; - my %attrs; + my %attrs; - for my $required (qw(app)) { - $attrs{$required} = delete $args->{$required}; - unless ($attrs{$required}) { - croak("'$required' required for Cassandane::Test::URL->new"); - } + for my $required (qw(app)) { + $attrs{$required} = delete $args->{$required}; + unless ($attrs{$required}) { + croak("'$required' required for Cassandane::Test::URL->new"); } + } - my $self = bless {}, __PACKAGE__; + my $self = bless {}, __PACKAGE__; - $self->update($attrs{app}); + $self->update($attrs{app}); - return $self; + return $self; } -sub url -{ - my ($self) = @_; +sub url { + my ($self) = @_; - if ($self->was_unregistered) { - croak("Cannot call ->url after ->unregister has been called!"); - } + if ($self->was_unregistered) { + croak("Cannot call ->url after ->unregister has been called!"); + } - $self->{url}; + $self->{url}; } sub _guard { shift->{_guard} } -sub unregister -{ - my ($self) = @_; +sub unregister { + my ($self) = @_; - delete $self->{_guard}; - $self->{was_unregistered} = 1; + delete $self->{_guard}; + $self->{was_unregistered} = 1; } sub was_unregistered { shift->{was_unregistered} } -sub update -{ - my ($self, $content_or_app) = @_; - - unless (ref $content_or_app) { - my $content = $content_or_app; - # Plain ol' successful response - $content_or_app = sub { - return [ - 200, - [], - [ $content ], - ]; - }; - } +sub update { + my ($self, $content_or_app) = @_; + + unless (ref $content_or_app) { + my $content = $content_or_app; + # Plain ol' successful response + $content_or_app = sub { + return [ 200, [], [$content], ]; + }; + } + + { - { - package Shutdown::Guard; + package Shutdown::Guard; - use POSIX qw(_exit); + use POSIX qw(_exit); - sub new { bless {}, __PACKAGE__ } + sub new { bless {}, __PACKAGE__ } - sub DESTROY { - _exit(0); - } + sub DESTROY { + _exit(0); } + } - my $host = "127.0.0.1"; + my $host = "127.0.0.1"; - my $app = sub { - # No matter how we we leave this block, the $guard will go out - # of scope while in the run phase, which means it can exit Perl - # before any END block or object destructors are run. - my $guard = Shutdown::Guard->new; + my $app = sub { + # No matter how we we leave this block, the $guard will go out + # of scope while in the run phase, which means it can exit Perl + # before any END block or object destructors are run. + my $guard = Shutdown::Guard->new; - # Test::TCP uses Term to stop us. We could just let that - # terminate Perl cleanly, but in case something has put in a - # signal handle or something else, let's just call exit here. - # This will also cause our $guard above to go out of scope - # in a timely manner during the run phase. - local $SIG{TERM} = sub { exit; }; + # Test::TCP uses Term to stop us. We could just let that + # terminate Perl cleanly, but in case something has put in a + # signal handle or something else, let's just call exit here. + # This will also cause our $guard above to go out of scope + # in a timely manner during the run phase. + local $SIG{TERM} = sub { exit; }; - my $sock_or_port = shift; - my $server = Plack::Loader->auto( - host => $host, - port => $sock_or_port, - ); + my $sock_or_port = shift; + my $server = Plack::Loader->auto( + host => $host, + port => $sock_or_port, + ); - $server->run($content_or_app); - }; + $server->run($content_or_app); + }; - unless ($self->_guard) { - if ($self->was_unregistered) { - # We've already been unregistered. It's no longer safe to call - # update because our port may have been reused by someone else - # and our url is no longer useable - croak("Cannot call ->update after ->unregister has been called!"); - } - - my $guard = Test::TCP->new( - host => $host, - code => $app, - port => Cassandane::PortManager::alloc($host), - ); - - my $port = $guard->port; - - $self->{url} = "http://$host:$port/"; - $self->{_guard} = $guard; - } else { - $self->_guard->{code} = $app; - - $self->_guard->stop; - $self->_guard->start; + unless ($self->_guard) { + if ($self->was_unregistered) { + # We've already been unregistered. It's no longer safe to call + # update because our port may have been reused by someone else + # and our url is no longer useable + croak("Cannot call ->update after ->unregister has been called!"); } - return; + my $guard = Test::TCP->new( + host => $host, + code => $app, + port => Cassandane::PortManager::alloc($host), + ); + + my $port = $guard->port; + + $self->{url} = "http://$host:$port/"; + $self->{_guard} = $guard; + } else { + $self->_guard->{code} = $app; + + $self->_guard->stop; + $self->_guard->start; + } + + return; } 1; diff --git a/cassandane/Cassandane/Util/Wait.pm b/cassandane/Cassandane/Util/Wait.pm index 2028b389f2..680703ca05 100644 --- a/cassandane/Cassandane/Util/Wait.pm +++ b/cassandane/Cassandane/Util/Wait.pm @@ -40,7 +40,7 @@ package Cassandane::Util::Wait; use strict; use warnings; -use base qw(Exporter); +use base qw(Exporter); use Time::HiRes qw(sleep gettimeofday tv_interval); use lib '.'; @@ -48,35 +48,31 @@ use Cassandane::Util::Log; our @EXPORT = qw(&timed_wait); -sub timed_wait -{ - my ($condition, %p) = @_; - $p{delay} = 0.010 # 10 millisec - unless defined $p{delay}; - $p{maxwait} = 20.0 - unless defined $p{maxwait}; - $p{description} = 'unknown condition' - unless defined $p{description}; +sub timed_wait { + my ($condition, %p) = @_; + $p{delay} = 0.010 # 10 millisec + unless defined $p{delay}; + $p{maxwait} = 20.0 + unless defined $p{maxwait}; + $p{description} = 'unknown condition' + unless defined $p{description}; - my $start = [gettimeofday()]; - my $delayed = 0; - while ( ! $condition->() ) - { - die "Timed out waiting for " . $p{description} - if (tv_interval($start, [gettimeofday()]) > $p{maxwait}); - sleep($p{delay}); - $delayed = 1; - $p{delay} *= 1.5; # backoff - } + my $start = [ gettimeofday() ]; + my $delayed = 0; + while (!$condition->()) { + die "Timed out waiting for " . $p{description} + if (tv_interval($start, [ gettimeofday() ]) > $p{maxwait}); + sleep($p{delay}); + $delayed = 1; + $p{delay} *= 1.5; # backoff + } - if ($delayed) - { - my $t = tv_interval($start, [gettimeofday()]); - xlog "Waited $t sec for " . $p{description}; - return $t; - } - return 0.0; + if ($delayed) { + my $t = tv_interval($start, [ gettimeofday() ]); + xlog "Waited $t sec for " . $p{description}; + return $t; + } + return 0.0; } - 1; diff --git a/cassandane/Cassandane/Util/Words.pm b/cassandane/Cassandane/Util/Words.pm index 52ed1a1e8d..7eb272d7f0 100644 --- a/cassandane/Cassandane/Util/Words.pm +++ b/cassandane/Cassandane/Util/Words.pm @@ -42,46 +42,43 @@ use strict; use warnings; use Exporter (); -our @ISA = qw(Exporter); +our @ISA = qw(Exporter); our @EXPORT = qw( - &random_word - ); + &random_word +); my @words; my @remaining; -use constant WORDFILE => '/usr/share/dict/words'; -use constant STRIDE => 7; -use constant MAX_WORDS => 2048; +use constant WORDFILE => '/usr/share/dict/words'; +use constant STRIDE => 7; +use constant MAX_WORDS => 2048; use constant MIN_LENGTH => 2; use constant MAX_LENGTH => 7; # Extract some well-formatted short words from the dictionary file -sub _read_words -{ - my $i = 0; - open DICT,'<',WORDFILE - or die "Cannot open " . WORDFILE . " for reading: $!"; - while () - { - chomp; - $_ = lc; - next unless m/^[a-z]+$/; - next if length $_ > MAX_LENGTH || length $_ < MIN_LENGTH; - next if $i++ < STRIDE; - $i = 0; - push(@words, $_); - last if scalar @words == MAX_WORDS; - } - close DICT; +sub _read_words { + my $i = 0; + open DICT, '<', WORDFILE + or die "Cannot open " . WORDFILE . " for reading: $!"; + while () { + chomp; + $_ = lc; + next unless m/^[a-z]+$/; + next if length $_ > MAX_LENGTH || length $_ < MIN_LENGTH; + next if $i++ < STRIDE; + $i = 0; + push(@words, $_); + last if scalar @words == MAX_WORDS; + } + close DICT; } -sub random_word -{ - _read_words() - if (!scalar @words); - @remaining = @words unless scalar @remaining; - return $remaining[int(rand(scalar @remaining))]; +sub random_word { + _read_words() + if (!scalar @words); + @remaining = @words unless scalar @remaining; + return $remaining[ int(rand(scalar @remaining)) ]; } 1; diff --git a/cassandane/Cyrus/CheckReplication.pm b/cassandane/Cyrus/CheckReplication.pm index 038215f9f4..7e8c58c83a 100755 --- a/cassandane/Cyrus/CheckReplication.pm +++ b/cassandane/Cyrus/CheckReplication.pm @@ -48,13 +48,14 @@ use Carp; use JSON::XS; sub Dump { - local $Data::Dumper::Indent = 1; + local $Data::Dumper::Indent = 1; local $Data::Dumper::Sortkeys = 1; return Data::Dumper::Dumper(@_); } sub deepeq { - return JSON::XS->new->utf8->canonical->encode([$_[0]]) eq JSON::XS->new->utf8->canonical->encode([$_[1]]); + return JSON::XS->new->utf8->canonical->encode([ $_[0] ]) eq + JSON::XS->new->utf8->canonical->encode([ $_[1] ]); } # Hello object orientation, friend of all "need state support in @@ -63,22 +64,24 @@ sub deepeq { # Functions sub new { my $class = shift; - my %Opts = @_; + my %Opts = @_; - die "NEED s1" unless $Opts{IMAPs1}; - die "NEED s2" unless $Opts{IMAPs2}; + die "NEED s1" unless $Opts{IMAPs1}; + die "NEED s2" unless $Opts{IMAPs2}; die "NEED CyrusName" unless $Opts{CyrusName}; # sensible defaults $Opts{NumRepeats} = 3 if not exists $Opts{NumRepeats}; - $Opts{SleepTime} = 2 if not exists $Opts{SleepTime}; - $Opts{Messages} = []; + $Opts{SleepTime} = 2 if not exists $Opts{SleepTime}; + $Opts{Messages} = []; my $Self = bless \%Opts, ref($class) || $class; if ($Opts{TraceImap}) { - $Self->{IMAPs1}->set_tracing(sub { $Self->do_output("IMAP_MASTER: " . shift) }); - $Self->{IMAPs2}->set_tracing(sub { $Self->do_output("IMAP_REPLICA: " . shift) }); + $Self->{IMAPs1} + ->set_tracing(sub { $Self->do_output("IMAP_MASTER: " . shift) }); + $Self->{IMAPs2} + ->set_tracing(sub { $Self->do_output("IMAP_REPLICA: " . shift) }); } $Self->{IMAPs1}->{PreserveINBOX} = 1; @@ -91,8 +94,8 @@ sub new { sub CheckUserReplication { my ($Self, $Level) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; @@ -117,8 +120,12 @@ sub CheckUserReplication { my @Missings2 = grep { !$s2Hash{$_} } @s1List; if (@Missings1 || @Missings2) { - $Self->do_repeat($Repeat, $CyrusName, "Folders mismatch", join(', ', @Missings1), join(', ', @Missings2)) - || goto RepeatCheck; + $Self->do_repeat( + $Repeat, $CyrusName, + "Folders mismatch", + join(', ', @Missings1), + join(', ', @Missings2) + ) || goto RepeatCheck; } # Check subscriptions @@ -136,22 +143,25 @@ sub CheckUserReplication { $IMAPs1->examine($Folder); $IMAPs2->examine($Folder); $Self->CheckFolderFlags($Folder); - if ($Self->{CheckMetadata} && - $IMAPs1->capability()->{'metadata'} && - $IMAPs2->capability()->{'metadata'}) { + if ( $Self->{CheckMetadata} + && $IMAPs1->capability()->{'metadata'} + && $IMAPs2->capability()->{'metadata'}) + { $Self->debug("$CyrusName checking metadata $Folder"); $Self->CheckFolderMetadata($Folder); } # yes, they really did - if ($Self->{CheckAnnotations} && - $IMAPs1->capability()->{'annotate-experiment-1'} && - $IMAPs2->capability()->{'annotate-experiment-1'}) { + if ( $Self->{CheckAnnotations} + && $IMAPs1->capability()->{'annotate-experiment-1'} + && $IMAPs2->capability()->{'annotate-experiment-1'}) + { $Self->debug("$CyrusName checking annotations $Folder"); $Self->CheckFolderAnnots($Folder); } - if ($Self->{CheckConversations} && - $IMAPs1->capability()->{'xconversations'} && - $IMAPs2->capability()->{'xconversations'}) { + if ( $Self->{CheckConversations} + && $IMAPs1->capability()->{'xconversations'} + && $IMAPs2->capability()->{'xconversations'}) + { $Self->debug("$CyrusName checking conversations $Folder"); $Self->CheckFolderConversations($Folder); } @@ -174,9 +184,9 @@ sub CheckUserReplication { } sub CheckUserQuota { - my ($Self) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my ($Self) = @_; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; $Self->debug("$CyrusName checking quota"); @@ -184,28 +194,29 @@ sub CheckUserQuota { my $Repeat = 0; RepeatCheck: - my $s1Quota = $IMAPs1->getquotaroot('INBOX'); + my $s1Quota = $IMAPs1->getquotaroot('INBOX'); my $s1QuotaRoot = $s1Quota->{quotaroot}->[1] || ''; - my (undef, $s1MBUsed, $s1MBTotal) = @{$s1Quota->{$s1QuotaRoot} || []}; - $s1MBUsed ||= 0; + my (undef, $s1MBUsed, $s1MBTotal) = @{ $s1Quota->{$s1QuotaRoot} || [] }; + $s1MBUsed ||= 0; $s1MBTotal ||= 0; - my $s2Quota = $IMAPs2->getquotaroot('INBOX'); + my $s2Quota = $IMAPs2->getquotaroot('INBOX'); my $s2QuotaRoot = $s2Quota->{quotaroot}->[1] || ''; - my (undef, $s2MBUsed, $s2MBTotal) = @{$s2Quota->{$s2QuotaRoot} || []}; - $s2MBUsed ||= 0; + my (undef, $s2MBUsed, $s2MBTotal) = @{ $s2Quota->{$s2QuotaRoot} || [] }; + $s2MBUsed ||= 0; $s2MBTotal ||= 0; if ($s1MBUsed != $s2MBUsed || $s1MBTotal != $s2MBTotal) { - $Self->do_repeat($Repeat, $CyrusName, "Quota mismatch: $s1MBUsed/$s1MBTotal vs $s2MBUsed/$s2MBTotal") + $Self->do_repeat($Repeat, $CyrusName, + "Quota mismatch: $s1MBUsed/$s1MBTotal vs $s2MBUsed/$s2MBTotal") || goto RepeatCheck; } } sub CheckUserSubs { - my ($Self) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my ($Self) = @_; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; $Self->debug("$CyrusName checking subscriptions"); @@ -218,8 +229,8 @@ sub CheckUserSubs { $Self->error("$CyrusName Couldn't subs on master: $@"); return; } - $s1Subs = [] unless ref($s1Subs) eq 'ARRAY'; - @$s1Subs = map { $_->[2] } @$s1Subs; + $s1Subs = [] unless ref($s1Subs) eq 'ARRAY'; + @$s1Subs = map { $_->[2] } @$s1Subs; @$s1Subs = grep { !/^user\./ } @$s1Subs if $Self->{IgnoreSharedSubs}; my %s1data = map { $_ => 1 } @$s1Subs; @@ -228,8 +239,8 @@ sub CheckUserSubs { $Self->error("$CyrusName Couldn't subs on replica: $@"); return; } - $s2Subs = [] unless ref($s2Subs) eq 'ARRAY'; - @$s2Subs = map { $_->[2] } @$s2Subs; + $s2Subs = [] unless ref($s2Subs) eq 'ARRAY'; + @$s2Subs = map { $_->[2] } @$s2Subs; @$s2Subs = grep { !/^user\./ } @$s2Subs if $Self->{IgnoreSharedSubs}; my %s2data = map { $_ => 1 } @$s2Subs; @@ -238,7 +249,8 @@ sub CheckUserSubs { foreach my $id (keys %ids) { if (!$s1data{$id} || !$s2data{$id}) { my $On = !$s1data{$id} ? "master" : "replica"; - $Self->do_repeat($Repeat, $CyrusName, "Missing subscription to $id on $On") + $Self->do_repeat($Repeat, $CyrusName, + "Missing subscription to $id on $On") || goto RepeatCheck; } } @@ -246,19 +258,21 @@ sub CheckUserSubs { sub CheckFolderBasic { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; RepeatCheck: - my $s1Status = $IMAPs1->status($Folder, '(messages uidnext unseen recent uidvalidity highestmodseq)'); + my $s1Status = $IMAPs1->status($Folder, + '(messages uidnext unseen recent uidvalidity highestmodseq)'); if (!$s1Status) { $Self->error("$CyrusName Couldn't get status of '$Folder' on master: $@"); return; } - my $s2Status = $IMAPs2->status($Folder, '(messages uidnext unseen recent uidvalidity highestmodseq)'); + my $s2Status = $IMAPs2->status($Folder, + '(messages uidnext unseen recent uidvalidity highestmodseq)'); if (!$s2Status) { $Self->error("$CyrusName Couldn't get status of '$Folder' on replica: $@"); return; @@ -270,8 +284,11 @@ sub CheckFolderBasic { next; } if ($s1Status->{$_} != $s2Status->{$_}) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched $Folder/$_", "master=$s1Status->{$_}, replica=$s2Status->{$_}") - || goto RepeatCheck; + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched $Folder/$_", + "master=$s1Status->{$_}, replica=$s2Status->{$_}" + ) || goto RepeatCheck; } } @@ -280,8 +297,8 @@ sub CheckFolderBasic { sub CheckFolderConversations { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; @@ -292,30 +309,42 @@ sub CheckFolderConversations { my %ids = (%$s1CIDs, %$s2CIDs); - for (sort {$a <=> $b } keys %ids) { - my $s1c = eval { join(' ', sort map { lc $_ } @{$s1CIDs->{$_}{cid}}) } || ''; - my $s2c = eval { join(' ', sort map { lc $_ } @{$s2CIDs->{$_}{cid}}) } || ''; + for (sort { $a <=> $b } keys %ids) { + my $s1c = eval { + join(' ', sort map { lc $_ } @{ $s1CIDs->{$_}{cid} }); + } || ''; + my $s2c = eval { + join(' ', sort map { lc $_ } @{ $s2CIDs->{$_}{cid} }); + } || ''; if ($s1c ne $s2c) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched cid for $Folder/$_", "master=$s1c, replica=$s2c") - || goto RepeatCheck; + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched cid for $Folder/$_", + "master=$s1c, replica=$s2c" + ) || goto RepeatCheck; } } - my $s1Stat = $IMAPs1->status($Folder, "(xconvmodseq xconvexists xconvunseen)"); - my $s2Stat = $IMAPs2->status($Folder, "(xconvmodseq xconvexists xconvunseen)"); + my $s1Stat + = $IMAPs1->status($Folder, "(xconvmodseq xconvexists xconvunseen)"); + my $s2Stat + = $IMAPs2->status($Folder, "(xconvmodseq xconvexists xconvunseen)"); foreach my $key (qw(xconvmodseq xconvexists xconvunseen)) { if ($s1Stat->{$key} != $s2Stat->{$key}) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched $key for $Folder", "master=$s1Stat->{$key}, replica=$s2Stat->{$key}") - || goto RepeatCheck; + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched $key for $Folder", + "master=$s1Stat->{$key}, replica=$s2Stat->{$key}" + ) || goto RepeatCheck; } } } sub CheckFolderFlags { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; @@ -328,51 +357,67 @@ sub CheckFolderFlags { my %SkipFlags = (); #map { $_ => 1 } qw(\Recent \Seen); - for (sort {$a <=> $b } keys %ids) { - my $s1f = eval { join(' ', sort map { lc $_ } grep { !$SkipFlags{$_} } @{$s1Flags->{$_}{flags}}) } || ''; - my $s2f = eval { join(' ', sort map { lc $_ } grep { !$SkipFlags{$_} } @{$s2Flags->{$_}{flags}}) } || ''; + for (sort { $a <=> $b } keys %ids) { + my $s1f = eval { + join(' ', + sort map { lc $_ } grep { !$SkipFlags{$_} } @{ $s1Flags->{$_}{flags} }); + } || ''; + my $s2f = eval { + join(' ', + sort map { lc $_ } grep { !$SkipFlags{$_} } @{ $s2Flags->{$_}{flags} }); + } || ''; if ($s1f ne $s2f) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched flags for $Folder/$_", "master=$s1f, replica=$s2f") - || goto RepeatCheck; + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched flags for $Folder/$_", + "master=$s1f, replica=$s2f" + ) || goto RepeatCheck; } } } sub CheckFolderAnnots { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; RepeatCheck: - my $s1Annot = $Self->do_fetch($IMAPs1, $CyrusName, '(annotation (* value))') || return; - my $s2Annot = $Self->do_fetch($IMAPs2, $CyrusName, '(annotation (* value))') || return; + my $s1Annot + = $Self->do_fetch($IMAPs1, $CyrusName, '(annotation (* value))') || return; + my $s2Annot + = $Self->do_fetch($IMAPs2, $CyrusName, '(annotation (* value))') || return; my %ids = (%$s1Annot, %$s2Annot); - for (sort {$a <=> $b } keys %ids) { + for (sort { $a <=> $b } keys %ids) { unless (deepeq($s1Annot->{$_}, $s2Annot->{$_})) { my $s1v = Dump($s1Annot->{$_}); my $s2v = Dump($s2Annot->{$_}); - $Self->do_repeat($Repeat, $CyrusName, "mistmatched annots for $Folder/$_", "master=$s1v, replica=$s2v") - || goto RepeatCheck; + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched annots for $Folder/$_", + "master=$s1v, replica=$s2v" + ) || goto RepeatCheck; } } } sub CheckFolderMetadata { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; RepeatCheck: - my $s1data = $IMAPs1->getmetadata($Folder, {DEPTH => 'infinity'}, '/private', '/shared'); - my $s2data = $IMAPs2->getmetadata($Folder, {DEPTH => 'infinity'}, '/private', '/shared'); + my $s1data = $IMAPs1->getmetadata($Folder, { DEPTH => 'infinity' }, + '/private', '/shared'); + my $s2data = $IMAPs2->getmetadata($Folder, { DEPTH => 'infinity' }, + '/private', '/shared'); delete $s1data->{$Folder}{'/shared/vendor/cmu/cyrus-imapd/lastupdate'}; delete $s2data->{$Folder}{'/shared/vendor/cmu/cyrus-imapd/lastupdate'}; @@ -381,15 +426,18 @@ sub CheckFolderMetadata { # this field is not replicated and not consistent... my $s1v = Dump($s1data); my $s2v = Dump($s2data); - $Self->do_repeat($Repeat, $CyrusName, "mistmatched metadata for $Folder", "master=$s1v, replica=$s2v") - || goto RepeatCheck; + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched metadata for $Folder", + "master=$s1v, replica=$s2v" + ) || goto RepeatCheck; } } sub CheckFolderEnvelopes { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; @@ -403,33 +451,41 @@ sub CheckFolderEnvelopes { for (sort { $a <=> $b } keys %ids) { my $s1h = eval { $s1Res->{$_}{'envelope'} } || {}; my $s2h = eval { $s2Res->{$_}{'envelope'} } || {}; - my $s1e = join(' ', map { "($_: " . ($s1h->{$_}||'') . ")" } sort keys %$s1h); - my $s2e = join(' ', map { "($_: " . ($s2h->{$_}||'') . ")" } sort keys %$s2h); + my $s1e + = join(' ', map { "($_: " . ($s1h->{$_} || '') . ")" } sort keys %$s1h); + my $s2e + = join(' ', map { "($_: " . ($s2h->{$_} || '') . ")" } sort keys %$s2h); if ($s1e and not $s2e) { $Self->error("$CyrusName for '$Folder', '$_', exists only on replica"); - } - elsif ($s2e and not $s1e) { - $Self->do_repeat($Repeat, $CyrusName, "only exists on master $Folder/$_", "master=$s1e") - || goto RepeatCheck; - } - elsif ($s1e ne $s2e) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched envelopes for $Folder/$_", "master=$s1e, replica=$s2e") + } elsif ($s2e and not $s1e) { + $Self->do_repeat($Repeat, $CyrusName, "only exists on master $Folder/$_", + "master=$s1e") || goto RepeatCheck; + } elsif ($s1e ne $s2e) { + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched envelopes for $Folder/$_", + "master=$s1e, replica=$s2e" + ) || goto RepeatCheck; } } } sub CheckFolderSizes { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; RepeatCheck: - my $s1Res = $Self->do_fetch($IMAPs1, $CyrusName, ['rfc822.size', 'digest.sha1']) || return; - my $s2Res = $Self->do_fetch($IMAPs2, $CyrusName, ['rfc822.size', 'digest.sha1']) || return; + my $s1Res + = $Self->do_fetch($IMAPs1, $CyrusName, [ 'rfc822.size', 'digest.sha1' ]) + || return; + my $s2Res + = $Self->do_fetch($IMAPs2, $CyrusName, [ 'rfc822.size', 'digest.sha1' ]) + || return; my %ids = (%$s1Res, %$s2Res); @@ -440,26 +496,37 @@ sub CheckFolderSizes { my $s2g = eval { $s2Res->{$_}{'digest.sha1'} } || ''; if ($s1f and not $s2f) { $Self->error("$CyrusName for '$Folder', '$_', exists only on replica"); - } - elsif ($s2f and not $s1f) { - $Self->do_repeat($Repeat, $CyrusName, "only exists on master $Folder/$_", "master=$s1f, $s1g") - || goto RepeatCheck; - } - elsif ($s1f ne $s2f) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched sizes for $Folder/$_", "master=$s1f, replica=$s2f") - || goto RepeatCheck; - } - elsif ($s1g ne $s2g) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched guids for $Folder/$_", "master=$s1g, replica=$s2g") - || goto RepeatCheck; + } elsif ($s2f and not $s1f) { + $Self->do_repeat( + $Repeat, $CyrusName, + "only exists on master $Folder/$_", + "master=$s1f, $s1g" + ) || goto RepeatCheck; + } elsif ($s1f ne $s2f) { + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched sizes for $Folder/$_", + "master=$s1f, replica=$s2f" + ) || goto RepeatCheck; + } elsif ($s1g ne $s2g) { + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched guids for $Folder/$_", + "master=$s1g, replica=$s2g" + ) || goto RepeatCheck; } # every 1000th message - elsif ($s1f and $s1f < 70000 and rand(1000) >= 999) { # 70k seems a resonable limit + elsif ($s1f and $s1f < 70000 and rand(1000) >= 999) + { # 70k seems a resonable limit $Self->debug("Doing sha1 check on $CyrusName/$Folder/$_"); my $s1message = $IMAPs1->fetch($_, 'rfc822.sha1'); my $s2message = $IMAPs2->fetch($_, 'rfc822.sha1'); - next unless ($s1message->{$_}{'rfc822.sha1'} and $s2message->{$_}{'rfc822.sha1'}); # deleted? - unless ($s1message->{$_}{'rfc822.sha1'} eq $s2message->{$_}{'rfc822.sha1'}) { + next + unless ($s1message->{$_}{'rfc822.sha1'} + and $s2message->{$_}{'rfc822.sha1'}); # deleted? + unless ( + $s1message->{$_}{'rfc822.sha1'} eq $s2message->{$_}{'rfc822.sha1'}) + { $Self->error("$CyrusName for '$Folder', '$_', messages do not match"); } } @@ -468,8 +535,8 @@ sub CheckFolderSizes { sub CheckFolderModseq { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; @@ -482,20 +549,23 @@ sub CheckFolderModseq { my %SkipFlags = (); #map { $_ => 1 } qw(\Recent \Seen); - for (sort {$a <=> $b } keys %ids) { + for (sort { $a <=> $b } keys %ids) { my $s1m = $s1ms->{$_}{modseq}[0] || 0; my $s2m = $s2ms->{$_}{modseq}[0] || 0; if ($s1m ne $s2m) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched modseq for $Folder/$_", "master=$s1m, replica=$s2m") - || goto RepeatCheck; + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched modseq for $Folder/$_", + "master=$s1m, replica=$s2m" + ) || goto RepeatCheck; } } } sub CheckFullSHA1 { my ($Self, $Folder) = @_; - my $IMAPs1 = $Self->{IMAPs1}; - my $IMAPs2 = $Self->{IMAPs2}; + my $IMAPs1 = $Self->{IMAPs1}; + my $IMAPs2 = $Self->{IMAPs2}; my $CyrusName = $Self->{CyrusName}; my $Repeat = 0; @@ -510,15 +580,19 @@ sub CheckFullSHA1 { my $s1s = eval { $s1Res->{$_}{'rfc822.sha1'} } || ''; my $s2s = eval { $s2Res->{$_}{'rfc822.sha1'} } || ''; if ($s1s and not $s2s) { - $Self->error("$CyrusName for '$Folder', sha1 of '$_', exists only on replica"); - } - elsif ($s2s and not $s1s) { - $Self->do_repeat($Repeat, $CyrusName, "only sha1 exists on master $Folder/$_", "master=$s1s") - || goto RepeatCheck; - } - elsif ($s1s ne $s2s) { - $Self->do_repeat($Repeat, $CyrusName, "mistmatched sha1 for $Folder/$_", "master=$s1s, replica=$s2s") + $Self->error( + "$CyrusName for '$Folder', sha1 of '$_', exists only on replica"); + } elsif ($s2s and not $s1s) { + $Self->do_repeat($Repeat, $CyrusName, + "only sha1 exists on master $Folder/$_", + "master=$s1s") || goto RepeatCheck; + } elsif ($s1s ne $s2s) { + $Self->do_repeat( + $Repeat, $CyrusName, + "mistmatched sha1 for $Folder/$_", + "master=$s1s, replica=$s2s" + ) || goto RepeatCheck; } } } @@ -534,14 +608,21 @@ sub do_fetch { my $Uids = $IMAP->search('1:*'); if (!$Uids) { - $Self->error("$CyrusName Couldn't search '$IMAP->{CurrentFolder}' on $IMAP->{SType}: $@"); + $Self->error( + "$CyrusName Couldn't search '$IMAP->{CurrentFolder}' on $IMAP->{SType}: $@" + ); return undef; } - my $Res = $Items[0] eq 'flags' ? $IMAP->fetch_flags('1:*') : $IMAP->fetch('1:*', @Items); + my $Res + = $Items[0] eq 'flags' + ? $IMAP->fetch_flags('1:*') + : $IMAP->fetch('1:*', @Items); $Res = {} if !$Res && ref($Uids) && !@$Uids; if (!$Res) { - $Self->error("$CyrusName Couldn't fetch $Items[0] in '$IMAP->{CurrentFolder}' on $IMAP->{SType}: $@"); + $Self->error( + "$CyrusName Couldn't fetch $Items[0] in '$IMAP->{CurrentFolder}' on $IMAP->{SType}: $@" + ); return undef; } @@ -568,14 +649,14 @@ sub do_repeat { sub get_type { my $Msg = shift; return 'QUOTA' if $Msg =~ m/Quota mismatch/; - return 'CONV' if $Msg =~ m/xconv/i; + return 'CONV' if $Msg =~ m/xconv/i; return 'RECONSTRUCT'; } # Logging sub notice { - my $Self = shift; + my $Self = shift; my $Message = shift; unless ($Self->{Quiet}) { $Self->do_output("NOTICE: $Message"); @@ -583,7 +664,7 @@ sub notice { } sub debug { - my $Self = shift; + my $Self = shift; my $Message = shift; if ($Self->{Debug}) { $Self->do_output("DEBUG: $Message"); @@ -591,31 +672,30 @@ sub debug { } sub error { - my $Self = shift; + my $Self = shift; my $Message = shift; $Self->{HasError} = 1; $Self->do_output("ERROR: $Message"); } sub do_output { - my $Self = shift; + my $Self = shift; my $Message = shift; chomp($Message); unless ($Self->{Silent}) { if ($Self->{LogFile}) { $Self->{LogFile}->print("$Message\n"); - } - else { + } else { print "$Message\n"; } } - push @{$Self->{Messages}}, $Message; + push @{ $Self->{Messages} }, $Message; } sub GetMessages { my $Self = shift; $Self->{Messages} ||= []; - return wantarray ? @{$Self->{Messages}} : $Self->{Messages}; + return wantarray ? @{ $Self->{Messages} } : $Self->{Messages}; } sub HasError { @@ -625,7 +705,7 @@ sub HasError { sub _fname { my $CyrusName = shift; - my $Folder = shift; + my $Folder = shift; my $Domain; if ($CyrusName =~ s{\@(.*)}{}) { diff --git a/cassandane/Net/XmtpServer.pm b/cassandane/Net/XmtpServer.pm index 0cf827e950..0989b79dc4 100644 --- a/cassandane/Net/XmtpServer.pm +++ b/cassandane/Net/XmtpServer.pm @@ -106,6 +106,7 @@ use warnings; Pass to $Self->log($Level, "%s", $Msg) =cut + sub xmtplog { # $_[0]->log($_[1], '%s', $_[2]); $_[0]->log($_[1] * 2, $_[2]); @@ -116,13 +117,14 @@ sub xmtplog { Catch configure options =cut + sub post_configure_hook { my ($Self, $Xmtp, $Srv) = ($_[0], $_[0]->{xmtp} ||= {}, $_[0]->{server}); # In old versions of Net::Server, parameters passed to "run" could # be accessed with $Self->{server}->{configure_args}. # In new versions they are available directly in $Self->{server} - my $Config = $Srv->{configure_args}; + my $Config = $Srv->{configure_args}; my %Options = $Config ? @{$Config} : %{$Srv}; # Get config options @@ -131,7 +133,7 @@ sub post_configure_hook { # Set timeout for each transaction $Xmtp->{XmtpTimeout} = $Options{xmtp_timeout} || 300; - $Xmtp->{CmdTimeout} = $Options{cmd_timeout} || 30; + $Xmtp->{CmdTimeout} = $Options{cmd_timeout} || 30; $Xmtp->{DataTimeout} = $Options{data_timeout} || 60; $Xmtp->{MaxMessages} = $Options{max_messages} || 0; @@ -139,10 +141,10 @@ sub post_configure_hook { $Xmtp->{TmpDir} = $Options{xmtp_tmp_dir} || '/tmp'; # Set personality regexp match - my $Personality = $Xmtp->{Personality}; + my $Personality = $Xmtp->{Personality}; my $PersonalityRE = qr/helo|ehlo/i; if ($Personality) { - $PersonalityRE = qr/lhlo/i if $Personality eq 'lmtp'; + $PersonalityRE = qr/lhlo/i if $Personality eq 'lmtp'; $PersonalityRE = qr/helo|ehlo|lhlo/i if $Personality eq 'both'; } $Xmtp->{PersonalityRE} = $PersonalityRE; @@ -154,6 +156,7 @@ sub post_configure_hook { Called after ownership chage. Create dir to hold spool files. =cut + sub pre_loop_hook { my ($Self, $Xmtp, $Srv) = ($_[0], $_[0]->{xmtp}, $_[0]->{server}); @@ -171,6 +174,7 @@ sub pre_loop_hook { Called when a new child is forked. Create temp spool file =cut + sub child_init_hook { my ($Self, $Xmtp, $Srv) = ($_[0], $_[0]->{xmtp}, $_[0]->{server}); @@ -188,7 +192,8 @@ sub child_init_hook { # Create temporary spool file for this child if ($Xmtp->{StoreMsg}) { my $TmpDir = $Xmtp->{TmpDir}; - my ($Fh, $Filename) = tempfile(DIR => $TmpDir, UNLINK => 1, SUFFIX => '.xmtp'); + my ($Fh, $Filename) + = tempfile(DIR => $TmpDir, UNLINK => 1, SUFFIX => '.xmtp'); bless $Fh, "IO::File"; # Same in server properties @@ -207,6 +212,7 @@ If running in debug non-forking mode, then child_init_hook() won't be called, so we try calling it now =cut + sub post_accept_hook { $_[0]->child_init_hook(); } @@ -216,34 +222,35 @@ sub post_accept_hook { Process a new accepted connection from a client =cut + sub process_request { my ($Self, $Xmtp, $Srv) = ($_[0], $_[0]->{xmtp}, $_[0]->{server}); eval { - $Self->start_request(); + $Self->start_request(); - $Self->ClearAlarm(); + $Self->ClearAlarm(); - # Reset any existing state - $Self->reset_state(); + # Reset any existing state + $Self->reset_state(); - # Notify of new client connection - $Self->new_connection(); - $Self->xmtplog(2, "New connection"); + # Notify of new client connection + $Self->new_connection(); + $Self->xmtplog(2, "New connection"); - # Setup timeout handler (after new_connection, which might - # change $SIG{ALRM} itself) - $SIG{ALRM} = sub { - my ($Package, $Filename, $Line, $Sub) = caller(0); - my $LastCmd = $Xmtp->{LastCmd} || ''; - die "Timeout: State=$LastCmd; In=${Sub}; Line=$Line"; - }; + # Setup timeout handler (after new_connection, which might + # change $SIG{ALRM} itself) + $SIG{ALRM} = sub { + my ($Package, $Filename, $Line, $Sub) = caller(0); + my $LastCmd = $Xmtp->{LastCmd} || ''; + die "Timeout: State=$LastCmd; In=${Sub}; Line=$Line"; + }; - # Do all the connection work - $Self->HandleConnection(); + # Do all the connection work + $Self->HandleConnection(); - alarm(0); + alarm(0); }; if (my $Err = $@) { @@ -382,7 +389,7 @@ sub HandleModeData { return $Self->HandleEndOfData() ? 0 : 1; - # Otherwise handle header/mime/data line + # Otherwise handle header/mime/data line } else { # Un-dot-stuff @@ -392,7 +399,7 @@ sub HandleModeData { if (!$HandleMime) { $Self->output_body($Fh, $_) if $Fh; - # Handle MIME phases ... {{{ + # Handle MIME phases ... {{{ } else { if ($InHeader) { @@ -403,27 +410,28 @@ sub HandleModeData { # End of headers if ($_ eq "\n") { - $MessageHdrs = $Self->ProcessHeaders(\$HeadBuffer, \@Boundaries, $MessageHdrs); + $MessageHdrs + = $Self->ProcessHeaders(\$HeadBuffer, \@Boundaries, $MessageHdrs); $Self->end_headers(\$HeadBuffer); $Self->output_headers($Fh, $HeadBuffer) if $Fh; $HeadBuffer = ''; - # If message/rfc822 attachment, then we're immediately into headers again + # If message/rfc822 attachment, then we're immediately into headers again if (!$MessageHdrs) { - $InHeader = 0; + $InHeader = 0; $DoBodyBuffer = $Self->begin_body(); } } - # In 'body' type section + # In 'body' type section } else { # Found boundary string? if (@Boundaries && /$Boundaries[-1]->[1]/) { $Self->end_body(\$BodyBuffer); $Self->output_body($Fh, $BodyBuffer) if $Fh && $DoBodyBuffer; - $BodyBuffer = ''; + $BodyBuffer = ''; $DoBodyBuffer = 0; # Use previous boundary match @@ -467,11 +475,11 @@ sub HandleModeData { } - # Main while loop + # Main while loop } # EOF on input, done/exit mode - return 0 + return 0; } sub ProcessHeaders { @@ -479,8 +487,10 @@ sub ProcessHeaders { # Loop through and list all headers (minus \n) my @Headers; - while ($$HeadBuffer =~ /\G([^\s:]+)(:[ \t]*(?:\n[ \t]+)*)([^\n]*(?:\n[ \t]+[^\n]*)*)\n/gc) { - push @Headers, [ $1, $2, $3 ] + while ($$HeadBuffer =~ + /\G([^\s:]+)(:[ \t]*(?:\n[ \t]+)*)([^\n]*(?:\n[ \t]+[^\n]*)*)\n/gc) + { + push @Headers, [ $1, $2, $3 ]; } my ($Remainder) = $$HeadBuffer =~ /\G(.*)$/s; @@ -490,7 +500,7 @@ sub ProcessHeaders { # Callback for each header (use counter because add_header() might be called) for (my $i = 0; $i < @Headers; $i++) { - $Self->HandleHeader(@{$Headers[$i]}, $Boundaries, $MsgHeaders); + $Self->HandleHeader(@{ $Headers[$i] }, $Boundaries, $MsgHeaders); } # Callback with all headers @@ -500,7 +510,8 @@ sub ProcessHeaders { delete @$Self{qw(HeaderList HeaderMap)}; # Build headers again - $$HeadBuffer = join "", map { !defined $_->[2] ? "" : join("", @$_, "\n") } @Headers; + $$HeadBuffer = join "", + map { !defined $_->[2] ? "" : join("", @$_, "\n") } @Headers; $$HeadBuffer .= $Remainder; # Extract new MIME boundary details in content-type headers @@ -519,7 +530,8 @@ sub ProcessHeaders { } sub HandleHeader { - my ($Self, $HeaderName, $HeaderSep, $HeaderValue, $Boundaries, $MsgHeaders) = @_; + my ($Self, $HeaderName, $HeaderSep, $HeaderValue, $Boundaries, $MsgHeaders) + = @_; # Process existing header if ($HeaderName) { @@ -575,14 +587,16 @@ sub HandleEndOfData { } sub ClearAlarm { - my ($Self, $Xmtp) = ($_[0], $_[0]->{xmtp}); shift; + my ($Self, $Xmtp) = ($_[0], $_[0]->{xmtp}); + shift; alarm(0); - $Xmtp->{TotalTime} = $Xmtp->{XmtpTimeout}; + $Xmtp->{TotalTime} = $Xmtp->{XmtpTimeout}; $Xmtp->{PrevTimeout} = undef; } sub ScheduleAlarm { - my ($Self, $Xmtp) = ($_[0], $_[0]->{xmtp}); shift; + my ($Self, $Xmtp) = ($_[0], $_[0]->{xmtp}); + shift; my $Timeout = shift; # Total time left for transaction @@ -600,7 +614,7 @@ sub ScheduleAlarm { $TotalTime -= $Used; $TotalTime = 1 if $TotalTime < 1; - # No previous timeout value, but there is now + # No previous timeout value, but there is now } else { $Xmtp->{PrevTimeout} = $Timeout; } @@ -622,8 +636,8 @@ sub add_header { my ($Self, $Header, $Value) = @_; my $Data = [ $Header, ": ", $Value ]; - push @{$Self->{HeaderList}}, $Data; - $Self->{HeaderMap}->{lc $Header} = $Data; + push @{ $Self->{HeaderList} }, $Data; + $Self->{HeaderMap}->{ lc $Header } = $Data; } # Callback prototypes {{{ @@ -640,40 +654,40 @@ sub reset_state { $Xmtp->{LastCmd} = "EOD Done"; } -sub start_request { undef; } -sub end_request { undef; } - -sub new_connection { undef; } -sub helo { undef; } -sub noop { $_[0]->send_client_resp(250, "250 2.0.0 ok"); } -sub mail_from { undef; } -sub rcpt_to { undef; } -sub rset { undef; } -sub unknown { undef; } -sub quit { undef; } +sub start_request { undef; } +sub end_request { undef; } + +sub new_connection { undef; } +sub helo { undef; } +sub noop { $_[0]->send_client_resp(250, "250 2.0.0 ok"); } +sub mail_from { undef; } +sub rcpt_to { undef; } +sub rset { undef; } +sub unknown { undef; } +sub quit { undef; } sub close_connection { undef; } -sub begin_data { undef; } -sub end_data { undef; } -sub header { undef; } -sub data_line { undef; } +sub begin_data { undef; } +sub end_data { undef; } +sub header { undef; } +sub data_line { undef; } -sub begin_headers { undef; } -sub end_headers { undef; } -sub all_headers { undef; } -sub begin_body { undef; } -sub end_body { undef; } +sub begin_headers { undef; } +sub end_headers { undef; } +sub all_headers { undef; } +sub begin_body { undef; } +sub end_body { undef; } -sub uuenc_begin { undef; } -sub uuenc_end { undef; } -sub binhex_begin { undef; } -sub binhex_end { undef; } +sub uuenc_begin { undef; } +sub uuenc_end { undef; } +sub binhex_begin { undef; } +sub binhex_end { undef; } -sub output_headers { print {$_[1]} $_[2]; } -sub output_body { print {$_[1]} $_[2]; } +sub output_headers { print { $_[1] } $_[2]; } +sub output_body { print { $_[1] } $_[2]; } -sub timeout { undef; } -sub error { undef; } +sub timeout { undef; } +sub error { undef; } # }}} =item I @@ -681,6 +695,7 @@ sub error { undef; } Send back to the connected client the given code and message =cut + sub send_client_resp { my ($Self, $Code, @MsgLines) = @_; while (@MsgLines > 1) { diff --git a/cassandane/tiny-tests/Caldav/alarm_peruser b/cassandane/tiny-tests/Caldav/alarm_peruser index cad0525fc9..8fb00d6a5f 100644 --- a/cassandane/tiny-tests/Caldav/alarm_peruser +++ b/cassandane/tiny-tests/Caldav/alarm_peruser @@ -2,30 +2,30 @@ use Cassandane::Tiny; sub test_alarm_peruser - :MagicPlus :min_version_3_0 :needs_component_httpd :NoAltNameSpace :NoVirtDomains -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.manifold"); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - - my $service = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $invite = <{caldav}; + + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->create("user.manifold"); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + + my $service = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $invite = < @@ -41,7 +41,7 @@ sub test_alarm_peruser EOF - my $reply = < @@ -52,37 +52,37 @@ EOF EOF - xlog $self, "create calendar"; - my $CalendarId = $mantalk->NewCalendar({name => 'Manifold Calendar'}); - $self->assert_not_null($CalendarId); - - xlog $self, "share to user"; - $mantalk->Request('POST', $CalendarId, $invite, - 'Content-Type' => 'application/davsharing+xml'); - - xlog $self, "fetch invite"; - my ($adds) = $CalDAV->SyncEventLinks("/dav/notifications/user/cassandane"); - $self->assert_equals(scalar %$adds, 1); - my $notification = (keys %$adds)[0]; - - xlog $self, "accept invite"; - $CalDAV->Request('POST', $notification, $reply, - 'Content-Type' => 'application/davsharing+xml'); - - xlog $self, "get calendars as manifold"; - my $ManCal = $mantalk->GetCalendars(); - $self->assert_num_equals(2, scalar @$ManCal); - my $names = join "/", sort map { $_->{name} } @$ManCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); - - xlog $self, "get calendars as cassandane"; - my $CasCal = $CalDAV->GetCalendars(); - $self->assert_num_equals(2, scalar @$CasCal); - $names = join "/", sort map { $_->{name} } @$CasCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); - - my $uuid = 'fb7b57d1-8a49-4af8-8597-2c17bab1f987'; - my $event = <NewCalendar({ name => 'Manifold Calendar' }); + $self->assert_not_null($CalendarId); + + xlog $self, "share to user"; + $mantalk->Request('POST', $CalendarId, $invite, + 'Content-Type' => 'application/davsharing+xml'); + + xlog $self, "fetch invite"; + my ($adds) = $CalDAV->SyncEventLinks("/dav/notifications/user/cassandane"); + $self->assert_equals(scalar %$adds, 1); + my $notification = (keys %$adds)[0]; + + xlog $self, "accept invite"; + $CalDAV->Request('POST', $notification, $reply, + 'Content-Type' => 'application/davsharing+xml'); + + xlog $self, "get calendars as manifold"; + my $ManCal = $mantalk->GetCalendars(); + $self->assert_num_equals(2, scalar @$ManCal); + my $names = join "/", sort map { $_->{name} } @$ManCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); + + xlog $self, "get calendars as cassandane"; + my $CasCal = $CalDAV->GetCalendars(); + $self->assert_num_equals(2, scalar @$CasCal); + $names = join "/", sort map { $_->{name} } @$CasCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); + + my $uuid = 'fb7b57d1-8a49-4af8-8597-2c17bab1f987'; + my $event = <{name} eq 'Manifold Calendar' } @$CasCal; - $CalDAV->Request('PUT', "$cal->{id}/$uuid.ics", $nonallevent, 'Content-Type' => 'text/calendar'); - - my $plusstore = $self->{instance}->get_service('imap')->create_store(username => 'cassandane+dav'); - my $plustalk = $plusstore->get_client(); - - my @list = $plustalk->list("", "*"); - - my @bits = split /\./, $cal->{id}; - $plustalk->select("user.manifold.#calendars.$bits[1]"); - my $res = $plustalk->fetch('1', '(rfc822.peek annotation (/* value.priv))'); - - $self->assert_does_not_match(qr/VALARM/, $res->{1}{'rfc822'}); - $self->assert_matches(qr/VALARM/, $res->{1}{'annotation'}{'/vendor/cmu/cyrus-httpd/per-user-calendar-data'}{'value.priv'}); - - $CalDAV->Request('PUT', "$cal->{id}/$uuid.ics", $allevent, 'Content-Type' => 'text/calendar'); - - $res = $plustalk->fetch('2', '(rfc822.peek annotation (/* value.priv))'); - $self->assert_does_not_match(qr/VALARM/, $res->{2}{'rfc822'}); - $self->assert_matches(qr/VALARM/, $res->{2}{'annotation'}{'/vendor/cmu/cyrus-httpd/per-user-calendar-data'}{'value.priv'}); + my $nonallevent = $event; + $nonallevent =~ s/XXDATESXX/$nonallday/; + my $allevent = $event; + $allevent =~ s/XXDATESXX/$allday/; + + xlog $self, "Create an event as cassandane with an alarm"; + my ($cal) = grep { $_->{name} eq 'Manifold Calendar' } @$CasCal; + $CalDAV->Request('PUT', "$cal->{id}/$uuid.ics", $nonallevent, + 'Content-Type' => 'text/calendar'); + + my $plusstore = $self->{instance}->get_service('imap') + ->create_store(username => 'cassandane+dav'); + my $plustalk = $plusstore->get_client(); + + my @list = $plustalk->list("", "*"); + + my @bits = split /\./, $cal->{id}; + $plustalk->select("user.manifold.#calendars.$bits[1]"); + my $res = $plustalk->fetch('1', '(rfc822.peek annotation (/* value.priv))'); + + $self->assert_does_not_match(qr/VALARM/, $res->{1}{'rfc822'}); + $self->assert_matches( + qr/VALARM/, + $res->{1}{'annotation'}{ + '/vendor/cmu/cyrus-httpd/per-user-calendar-data' + }{'value.priv'} + ); + + $CalDAV->Request('PUT', "$cal->{id}/$uuid.ics", $allevent, + 'Content-Type' => 'text/calendar'); + + $res = $plustalk->fetch('2', '(rfc822.peek annotation (/* value.priv))'); + $self->assert_does_not_match(qr/VALARM/, $res->{2}{'rfc822'}); + $self->assert_matches( + qr/VALARM/, + $res->{2}{'annotation'}{ + '/vendor/cmu/cyrus-httpd/per-user-calendar-data' + }{'value.priv'} + ); } diff --git a/cassandane/tiny-tests/Caldav/apple_location_notz b/cassandane/tiny-tests/Caldav/apple_location_notz index ee15b40999..e00dc7d9f7 100644 --- a/cassandane/tiny-tests/Caldav/apple_location_notz +++ b/cassandane/tiny-tests/Caldav/apple_location_notz @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_apple_location_notz - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my $response = $CalDAV->Request('GET', $href); + my $response = $CalDAV->Request('GET', $href); - my $newcard = $response->{content}; + my $newcard = $response->{content}; - $self->assert_matches(qr/geo:-37.810551,144.962840/, $newcard); + $self->assert_matches(qr/geo:-37.810551,144.962840/, $newcard); } diff --git a/cassandane/tiny-tests/Caldav/apple_location_tz b/cassandane/tiny-tests/Caldav/apple_location_tz index 4d25308a3e..fc43d152e3 100644 --- a/cassandane/tiny-tests/Caldav/apple_location_tz +++ b/cassandane/tiny-tests/Caldav/apple_location_tz @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_apple_location_tz - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my $response = $CalDAV->Request('GET', $href); + my $response = $CalDAV->Request('GET', $href); - my $newcard = $response->{content}; + my $newcard = $response->{content}; - $self->assert_matches(qr/geo:-37.810551,144.962840/, $newcard); + $self->assert_matches(qr/geo:-37.810551,144.962840/, $newcard); } diff --git a/cassandane/tiny-tests/Caldav/attendee_exdate b/cassandane/tiny-tests/Caldav/attendee_exdate index 87348e82f4..c150fd0b1d 100644 --- a/cassandane/tiny-tests/Caldav/attendee_exdate +++ b/cassandane/tiny-tests/Caldav/attendee_exdate @@ -2,25 +2,24 @@ use Cassandane::Tiny; sub test_attendee_exdate - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "recurring event"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_caldav_notified( - { - recipient => "test1\@example.com", - method => 'REPLY', - event => { - uid => $uuid, - replyTo => { imip => "mailto:test1\@example.com" }, - recurrenceOverrides => { '2016-06-08T15:30:00' => undef }, - }, - }, - ); - } + # should this send a PARTSTAT=DECLINED instead? + $self->assert_caldav_notified( + { + recipient => "test1\@example.com", + method => 'REPLY', + event => { + uid => $uuid, + replyTo => { imip => "mailto:test1\@example.com" }, + recurrenceOverrides => { '2016-06-08T15:30:00' => undef }, + }, + }, + ); + } } diff --git a/cassandane/tiny-tests/Caldav/caldavcreate b/cassandane/tiny-tests/Caldav/caldavcreate index 96bd5bcb08..35de00d8d3 100644 --- a/cassandane/tiny-tests/Caldav/caldavcreate +++ b/cassandane/tiny-tests/Caldav/caldavcreate @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_caldavcreate - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); } diff --git a/cassandane/tiny-tests/Caldav/calendar_allprop b/cassandane/tiny-tests/Caldav/calendar_allprop index c41bd346e4..3125b08c8d 100644 --- a/cassandane/tiny-tests/Caldav/calendar_allprop +++ b/cassandane/tiny-tests/Caldav/calendar_allprop @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_calendar_allprop - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'mycalendar'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'mycalendar' }); + $self->assert_not_null($CalendarId); - my $proppatchXml = < @@ -22,30 +21,35 @@ sub test_calendar_allprop EOF - my $propfindXml = < EOF - # Set color. - my $response = $CalDAV->Request('PROPPATCH', "/dav/calendars/user/cassandane/". $CalendarId, - $proppatchXml, 'Content-Type' => 'text/xml'); - - # Assert that color is set. - $response = $CalDAV->Request('PROPFIND', "/dav/calendars/user/cassandane/". $CalendarId, - $propfindXml, 'Content-Type' => 'text/xml'); - my $propstat = $response->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]; - - $self->assert_str_equals('HTTP/1.1 200 OK', - $propstat->{'{DAV:}status'}{content}); - $self->assert(exists $propstat->{'{DAV:}prop'}{'{DAV:}creationdate'}); - $self->assert(exists $propstat->{'{DAV:}prop'}{'{DAV:}getetag'}); - $self->assert(exists $propstat->{'{DAV:}prop'}{'{DAV:}resourcetype'}); - $self->assert_str_equals('mycalendar', - $propstat->{'{DAV:}prop'}{'{DAV:}displayname'}{content}); - $self->assert_str_equals('#2952A3', - $propstat->{'{DAV:}prop'}{'{http://apple.com/ns/ical/}calendar-color'}{content}); + # Set color. + my $response + = $CalDAV->Request('PROPPATCH', + "/dav/calendars/user/cassandane/" . $CalendarId, + $proppatchXml, 'Content-Type' => 'text/xml'); + + # Assert that color is set. + $response + = $CalDAV->Request('PROPFIND', + "/dav/calendars/user/cassandane/" . $CalendarId, + $propfindXml, 'Content-Type' => 'text/xml'); + my $propstat = $response->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]; + + $self->assert_str_equals('HTTP/1.1 200 OK', + $propstat->{'{DAV:}status'}{content}); + $self->assert(exists $propstat->{'{DAV:}prop'}{'{DAV:}creationdate'}); + $self->assert(exists $propstat->{'{DAV:}prop'}{'{DAV:}getetag'}); + $self->assert(exists $propstat->{'{DAV:}prop'}{'{DAV:}resourcetype'}); + $self->assert_str_equals('mycalendar', + $propstat->{'{DAV:}prop'}{'{DAV:}displayname'}{content}); + $self->assert_str_equals('#2952A3', + $propstat->{'{DAV:}prop'}{'{http://apple.com/ns/ical/}calendar-color'} + {content}); } diff --git a/cassandane/tiny-tests/Caldav/calendar_query b/cassandane/tiny-tests/Caldav/calendar_query index 7119bc4e74..e097f9e2fe 100644 --- a/cassandane/tiny-tests/Caldav/calendar_query +++ b/cassandane/tiny-tests/Caldav/calendar_query @@ -2,19 +2,18 @@ use Cassandane::Tiny; sub test_calendar_query - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $Cal = $CalDAV->GetCalendar($CalendarId); + my $Cal = $CalDAV->GetCalendar($CalendarId); - xlog $self, "Load some resources"; - my $vtz = <Request('PUT', $href1, $event1, 'Content-Type' => 'text/calendar'); - $CalDAV->Request('PUT', $href2, $event2, 'Content-Type' => 'text/calendar'); - $CalDAV->Request('PUT', $href3, $event3, 'Content-Type' => 'text/calendar'); - $CalDAV->Request('PUT', $href4, $event4, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href1, $event1, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href2, $event2, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href3, $event3, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href4, $event4, 'Content-Type' => 'text/calendar'); - xlog $self, "Perform calendar-query"; - my $xml = < @@ -178,72 +177,80 @@ EOF EOF - my $res = $CalDAV->Request('REPORT', - "/dav/calendars/user/cassandane/$CalendarId", - $xml, Depth => 1, 'Content-Type' => 'text/xml'); - my $responses = $res->{'{DAV:}response'}; - $self->assert_equals(2, scalar @$responses); - - my $ical = Data::ICal->new(data => - $res->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}); - $self->assert_str_equals('One-Time Event', - $ical->{entries}[0]{properties}{summary}[0]{value}); - $self->assert_str_equals($uuid1, - $ical->{entries}[0]{properties}{uid}[0]{value}); - $self->assert_str_equals('20211215T211500Z', - $ical->{entries}[0]{properties}{dtstart}[0]{value}); - $self->assert_null($ical->{entries}[0]{properties}{dtstamp}); - $self->assert_null($ical->{entries}[0]{properties}{status}); - $self->assert_null($ical->{entries}[0]{properties}{rrule}); - $self->assert_null($ical->{entries}[0]{properties}{'recurrence-id'}); - - $ical = Data::ICal->new(data => - $res->{'{DAV:}response'}[1]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}); - - $self->assert_str_equals('Recurring Event', - $ical->{entries}[0]{properties}{summary}[0]{value}); - $self->assert_str_equals($uuid2, - $ical->{entries}[0]{properties}{uid}[0]{value}); - $self->assert_str_equals('20211215T200000Z', - $ical->{entries}[0]{properties}{dtstart}[0]{value}); - $self->assert_null($ical->{entries}[0]{properties}{dtstamp}); - $self->assert_null($ical->{entries}[0]{properties}{status}); - $self->assert_null($ical->{entries}[0]{properties}{rrule}); - $self->assert_null($ical->{entries}[0]{properties}{'recurrence-id'}); - - $self->assert_str_equals('Recurring Event', - $ical->{entries}[1]{properties}{summary}[0]{value}); - $self->assert_str_equals($uuid2, - $ical->{entries}[1]{properties}{uid}[0]{value}); - $self->assert_str_equals('20211216T200000Z', - $ical->{entries}[1]{properties}{dtstart}[0]{value}); - $self->assert_str_equals('20211216T200000Z', - $ical->{entries}[1]{properties}{'recurrence-id'}[0]{value}); - $self->assert_null($ical->{entries}[1]{properties}{dtstamp}); - $self->assert_null($ical->{entries}[1]{properties}{status}); - $self->assert_null($ical->{entries}[1]{properties}{rrule}); - - $self->assert_str_equals('Recurring Event (exception)', - $ical->{entries}[2]{properties}{summary}[0]{value}); - $self->assert_str_equals($uuid2, - $ical->{entries}[2]{properties}{uid}[0]{value}); - $self->assert_str_equals('20211217T200000Z', - $ical->{entries}[2]{properties}{dtstart}[0]{value}); - $self->assert_str_equals('20211217T200000Z', - $ical->{entries}[2]{properties}{'recurrence-id'}[0]{value}); - $self->assert_null($ical->{entries}[2]{properties}{dtstamp}); - $self->assert_null($ical->{entries}[2]{properties}{status}); - $self->assert_null($ical->{entries}[2]{properties}{rrule}); - - $self->assert_str_equals('Recurring Event', - $ical->{entries}[3]{properties}{summary}[0]{value}); - $self->assert_str_equals($uuid2, - $ical->{entries}[3]{properties}{uid}[0]{value}); - $self->assert_str_equals('20211218T200000Z', - $ical->{entries}[3]{properties}{dtstart}[0]{value}); - $self->assert_str_equals('20211218T200000Z', - $ical->{entries}[3]{properties}{'recurrence-id'}[0]{value}); - $self->assert_null($ical->{entries}[3]{properties}{dtstamp}); - $self->assert_null($ical->{entries}[3]{properties}{status}); - $self->assert_null($ical->{entries}[3]{properties}{rrule}); + my $res = $CalDAV->Request( + 'REPORT', + "/dav/calendars/user/cassandane/$CalendarId", + $xml, + Depth => 1, + 'Content-Type' => 'text/xml' + ); + my $responses = $res->{'{DAV:}response'}; + $self->assert_equals(2, scalar @$responses); + + my $ical + = Data::ICal->new( + data => $res->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}); + $self->assert_str_equals('One-Time Event', + $ical->{entries}[0]{properties}{summary}[0]{value}); + $self->assert_str_equals($uuid1, + $ical->{entries}[0]{properties}{uid}[0]{value}); + $self->assert_str_equals('20211215T211500Z', + $ical->{entries}[0]{properties}{dtstart}[0]{value}); + $self->assert_null($ical->{entries}[0]{properties}{dtstamp}); + $self->assert_null($ical->{entries}[0]{properties}{status}); + $self->assert_null($ical->{entries}[0]{properties}{rrule}); + $self->assert_null($ical->{entries}[0]{properties}{'recurrence-id'}); + + $ical + = Data::ICal->new( + data => $res->{'{DAV:}response'}[1]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}); + + $self->assert_str_equals('Recurring Event', + $ical->{entries}[0]{properties}{summary}[0]{value}); + $self->assert_str_equals($uuid2, + $ical->{entries}[0]{properties}{uid}[0]{value}); + $self->assert_str_equals('20211215T200000Z', + $ical->{entries}[0]{properties}{dtstart}[0]{value}); + $self->assert_null($ical->{entries}[0]{properties}{dtstamp}); + $self->assert_null($ical->{entries}[0]{properties}{status}); + $self->assert_null($ical->{entries}[0]{properties}{rrule}); + $self->assert_null($ical->{entries}[0]{properties}{'recurrence-id'}); + + $self->assert_str_equals('Recurring Event', + $ical->{entries}[1]{properties}{summary}[0]{value}); + $self->assert_str_equals($uuid2, + $ical->{entries}[1]{properties}{uid}[0]{value}); + $self->assert_str_equals('20211216T200000Z', + $ical->{entries}[1]{properties}{dtstart}[0]{value}); + $self->assert_str_equals('20211216T200000Z', + $ical->{entries}[1]{properties}{'recurrence-id'}[0]{value}); + $self->assert_null($ical->{entries}[1]{properties}{dtstamp}); + $self->assert_null($ical->{entries}[1]{properties}{status}); + $self->assert_null($ical->{entries}[1]{properties}{rrule}); + + $self->assert_str_equals('Recurring Event (exception)', + $ical->{entries}[2]{properties}{summary}[0]{value}); + $self->assert_str_equals($uuid2, + $ical->{entries}[2]{properties}{uid}[0]{value}); + $self->assert_str_equals('20211217T200000Z', + $ical->{entries}[2]{properties}{dtstart}[0]{value}); + $self->assert_str_equals('20211217T200000Z', + $ical->{entries}[2]{properties}{'recurrence-id'}[0]{value}); + $self->assert_null($ical->{entries}[2]{properties}{dtstamp}); + $self->assert_null($ical->{entries}[2]{properties}{status}); + $self->assert_null($ical->{entries}[2]{properties}{rrule}); + + $self->assert_str_equals('Recurring Event', + $ical->{entries}[3]{properties}{summary}[0]{value}); + $self->assert_str_equals($uuid2, + $ical->{entries}[3]{properties}{uid}[0]{value}); + $self->assert_str_equals('20211218T200000Z', + $ical->{entries}[3]{properties}{dtstart}[0]{value}); + $self->assert_str_equals('20211218T200000Z', + $ical->{entries}[3]{properties}{'recurrence-id'}[0]{value}); + $self->assert_null($ical->{entries}[3]{properties}{dtstamp}); + $self->assert_null($ical->{entries}[3]{properties}{status}); + $self->assert_null($ical->{entries}[3]{properties}{rrule}); } diff --git a/cassandane/tiny-tests/Caldav/calendar_setcolor b/cassandane/tiny-tests/Caldav/calendar_setcolor index 65dda07508..b6ff389c4c 100644 --- a/cassandane/tiny-tests/Caldav/calendar_setcolor +++ b/cassandane/tiny-tests/Caldav/calendar_setcolor @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_calendar_setcolor - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'mycalendar'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'mycalendar' }); + $self->assert_not_null($CalendarId); - my $proppatchXml = < @@ -22,7 +21,7 @@ sub test_calendar_setcolor EOF - my $propfindXml = < @@ -31,22 +30,34 @@ EOF EOF - # Assert that color isn't set. - my $response = $CalDAV->Request('PROPFIND', "/dav/calendars/user/cassandane/". $CalendarId, - $propfindXml, 'Content-Type' => 'text/xml'); - my $propstat = $response->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]; - $self->assert_str_equals('HTTP/1.1 404 Not Found', $propstat->{'{DAV:}status'}{content}); - $self->assert(exists $propstat->{'{DAV:}prop'}{'{http://apple.com/ns/ical/}calendar-color'}); - - # Set color. - $response = $CalDAV->Request('PROPPATCH', "/dav/calendars/user/cassandane/". $CalendarId, - $proppatchXml, 'Content-Type' => 'text/xml'); - - # Assert color ist set. - $response = $CalDAV->Request('PROPFIND', "/dav/calendars/user/cassandane/". $CalendarId, - $propfindXml, 'Content-Type' => 'text/xml'); - $propstat = $response->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]; - $self->assert_str_equals('HTTP/1.1 200 OK', $propstat->{'{DAV:}status'}{content}); - $self->assert_str_equals('#2952A3', $propstat->{'{DAV:}prop'}{'{http://apple.com/ns/ical/}calendar-color'}{content}); + # Assert that color isn't set. + my $response + = $CalDAV->Request('PROPFIND', + "/dav/calendars/user/cassandane/" . $CalendarId, + $propfindXml, 'Content-Type' => 'text/xml'); + my $propstat = $response->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]; + $self->assert_str_equals('HTTP/1.1 404 Not Found', + $propstat->{'{DAV:}status'}{content}); + $self->assert( + exists $propstat->{'{DAV:}prop'} + {'{http://apple.com/ns/ical/}calendar-color'}); + + # Set color. + $response + = $CalDAV->Request('PROPPATCH', + "/dav/calendars/user/cassandane/" . $CalendarId, + $proppatchXml, 'Content-Type' => 'text/xml'); + + # Assert color ist set. + $response + = $CalDAV->Request('PROPFIND', + "/dav/calendars/user/cassandane/" . $CalendarId, + $propfindXml, 'Content-Type' => 'text/xml'); + $propstat = $response->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]; + $self->assert_str_equals('HTTP/1.1 200 OK', + $propstat->{'{DAV:}status'}{content}); + $self->assert_str_equals('#2952A3', + $propstat->{'{DAV:}prop'}{'{http://apple.com/ns/ical/}calendar-color'} + {content}); } diff --git a/cassandane/tiny-tests/Caldav/calendaradmin_get b/cassandane/tiny-tests/Caldav/calendaradmin_get index 385318e132..8d11ead12e 100644 --- a/cassandane/tiny-tests/Caldav/calendaradmin_get +++ b/cassandane/tiny-tests/Caldav/calendaradmin_get @@ -2,15 +2,18 @@ use Cassandane::Tiny; sub test_calendaradmin_get - :min_version_3_8 :needs_component_httpd :AllowCalendarAdmin -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : min_version_3_8 : needs_component_httpd : AllowCalendarAdmin { + my ($self) = @_; + my $caldav = $self->{caldav}; - my $res = $caldav->ua->request('GET', $caldav->request_url(""), { - headers => { - 'Authorization' => $caldav->auth_header(), - } - }); - $self->assert_str_equals('200', $res->{status}); + my $res = $caldav->ua->request( + 'GET', + $caldav->request_url(""), + { + headers => { + 'Authorization' => $caldav->auth_header(), + } + } + ); + $self->assert_str_equals('200', $res->{status}); } diff --git a/cassandane/tiny-tests/Caldav/calendareventnotification_no_sharee b/cassandane/tiny-tests/Caldav/calendareventnotification_no_sharee index 92bcc44156..d05aa18573 100644 --- a/cassandane/tiny-tests/Caldav/calendareventnotification_no_sharee +++ b/cassandane/tiny-tests/Caldav/calendareventnotification_no_sharee @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_calendareventnotification_no_sharee - :needs_component_httpd :min_version_3_7 -{ - my ($self) = @_; + : needs_component_httpd : min_version_3_7 { + my ($self) = @_; - my $admin = $self->{adminstore}->get_client(); + my $admin = $self->{adminstore}->get_client(); - $admin->create('user.cassandane.#jmapnotification') or die; - $admin->setacl('user.cassandane.#jmapnotification', - 'cassandane' => 'lrswipkxtecdan') or die; + $admin->create('user.cassandane.#jmapnotification') or die; + $admin->setacl('user.cassandane.#jmapnotification', + 'cassandane' => 'lrswipkxtecdan') + or die; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $href = "$CalendarId/uid1.ics"; - my $card = < 'text/calendar', + 'Content-Type' => 'text/calendar', 'Authorization' => $CalDAV->auth_header(), ); xlog "Create event"; - my $Response = $CalDAV->{ua}->request('PUT', $CalDAV->request_url($href), { - content => $card, - headers => \%Headers, - }); + my $Response = $CalDAV->{ua}->request( + 'PUT', + $CalDAV->request_url($href), + { + content => $card, + headers => \%Headers, + } + ); $self->assert_num_equals(201, $Response->{status}); $self->assert_num_equals(0, - $admin->message_count('user.cassandane.#jmapnotification')); + $admin->message_count('user.cassandane.#jmapnotification')); xlog "Update event"; $card =~ s/foo/bar/s; - $Response = $CalDAV->{ua}->request('PUT', $CalDAV->request_url($href), { - content => $card, - headers => \%Headers, - }); + $Response = $CalDAV->{ua}->request( + 'PUT', + $CalDAV->request_url($href), + { + content => $card, + headers => \%Headers, + } + ); $self->assert_num_equals(204, $Response->{status}); $self->assert_num_equals(0, - $admin->message_count('user.cassandane.#jmapnotification')); + $admin->message_count('user.cassandane.#jmapnotification')); xlog "Delete event"; - $Response = $CalDAV->{ua}->request('DELETE', $CalDAV->request_url($href), { - headers => \%Headers, - }); + $Response = $CalDAV->{ua}->request( + 'DELETE', + $CalDAV->request_url($href), + { + headers => \%Headers, + } + ); $self->assert_num_equals(204, $Response->{status}); $self->assert_num_equals(0, - $admin->message_count('user.cassandane.#jmapnotification')); + $admin->message_count('user.cassandane.#jmapnotification')); } diff --git a/cassandane/tiny-tests/Caldav/changes_add b/cassandane/tiny-tests/Caldav/changes_add index 84d14a438e..d3e462124b 100644 --- a/cassandane/tiny-tests/Caldav/changes_add +++ b/cassandane/tiny-tests/Caldav/changes_add @@ -2,20 +2,19 @@ use Cassandane::Tiny; sub test_changes_add - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $Cal = $CalDAV->GetCalendar($CalendarId); + my $Cal = $CalDAV->GetCalendar($CalendarId); - my $uuid = "d4643cf9-4552-4a3e-8d6c-5f318bcc5b79"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my ($adds, $removes, $errors) = $CalDAV->SyncEvents($CalendarId, syncToken => $Cal->{syncToken}); + my ($adds, $removes, $errors) + = $CalDAV->SyncEvents($CalendarId, syncToken => $Cal->{syncToken}); - $self->assert_equals(scalar @$adds, 1); - $self->assert_str_equals($adds->[0]{uid}, $uuid); - $self->assert_deep_equals($removes, []); - $self->assert_deep_equals($errors, []); + $self->assert_equals(scalar @$adds, 1); + $self->assert_str_equals($adds->[0]{uid}, $uuid); + $self->assert_deep_equals($removes, []); + $self->assert_deep_equals($errors, []); } diff --git a/cassandane/tiny-tests/Caldav/changes_remove b/cassandane/tiny-tests/Caldav/changes_remove index feefc25cd2..38152f709f 100644 --- a/cassandane/tiny-tests/Caldav/changes_remove +++ b/cassandane/tiny-tests/Caldav/changes_remove @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_changes_remove - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid = "d4643cf9-4552-4a3e-8d6c-5f318bcc5b79"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my $Cal = $CalDAV->GetCalendar($CalendarId); + my $Cal = $CalDAV->GetCalendar($CalendarId); - $CalDAV->DeleteEvent($href); + $CalDAV->DeleteEvent($href); - my ($adds, $removes, $errors) = $CalDAV->SyncEvents($CalendarId, syncToken => $Cal->{syncToken}); + my ($adds, $removes, $errors) + = $CalDAV->SyncEvents($CalendarId, syncToken => $Cal->{syncToken}); - $self->assert_deep_equals([], $adds); - $self->assert_equals(1, scalar @$removes); - $self->assert_str_equals("/dav/calendars/user/cassandane/" . $href, $removes->[0]); - $self->assert_deep_equals([], $errors); + $self->assert_deep_equals([], $adds); + $self->assert_equals(1, scalar @$removes); + $self->assert_str_equals("/dav/calendars/user/cassandane/" . $href, + $removes->[0]); + $self->assert_deep_equals([], $errors); } diff --git a/cassandane/tiny-tests/Caldav/conditional_delete_collection b/cassandane/tiny-tests/Caldav/conditional_delete_collection index fa38d0c9c4..35b24ad097 100644 --- a/cassandane/tiny-tests/Caldav/conditional_delete_collection +++ b/cassandane/tiny-tests/Caldav/conditional_delete_collection @@ -2,34 +2,41 @@ use Cassandane::Tiny; sub test_conditional_delete_collection - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $caldav = $self->{caldav}; + my $caldav = $self->{caldav}; - my $calid = $caldav->NewCalendar({name => 'foo'}); - $self->assert_not_null($calid); + my $calid = $caldav->NewCalendar({ name => 'foo' }); + $self->assert_not_null($calid); - my $res = $caldav->GetCalendar($calid); - $self->assert_not_null($res); - my $synctoken = $res->{syncToken}; + my $res = $caldav->GetCalendar($calid); + $self->assert_not_null($res); + my $synctoken = $res->{syncToken}; - xlog $self, "Try to delete collection with bogus state token"; - $res = $caldav->ua->request('DELETE', $caldav->request_url($calid), { - headers => { - 'Authorization' => $caldav->auth_header(), - 'If' => '()' - } - }); - $self->assert_str_equals('412', $res->{status}); + xlog $self, "Try to delete collection with bogus state token"; + $res = $caldav->ua->request( + 'DELETE', + $caldav->request_url($calid), + { + headers => { + 'Authorization' => $caldav->auth_header(), + 'If' => '()' + } + } + ); + $self->assert_str_equals('412', $res->{status}); - xlog $self, "Delete collection with bogus sync token"; - $res = $caldav->ua->request('DELETE', $caldav->request_url($calid), { - headers => { - 'Authorization' => $caldav->auth_header(), - 'If' => "(<$synctoken>)" - } - }); - $self->assert_str_equals('204', $res->{status}); + xlog $self, "Delete collection with bogus sync token"; + $res = $caldav->ua->request( + 'DELETE', + $caldav->request_url($calid), + { + headers => { + 'Authorization' => $caldav->auth_header(), + 'If' => "(<$synctoken>)" + } + } + ); + $self->assert_str_equals('204', $res->{status}); } diff --git a/cassandane/tiny-tests/Caldav/dav_bind b/cassandane/tiny-tests/Caldav/dav_bind index c5b9753e35..d235cc958f 100644 --- a/cassandane/tiny-tests/Caldav/dav_bind +++ b/cassandane/tiny-tests/Caldav/dav_bind @@ -2,37 +2,37 @@ use Cassandane::Tiny; sub test_dav_bind - :min_version_3_9 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_9 : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.manifold"); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + $admintalk->create("user.manifold"); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - my $service = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "create calendar"; - my $CalendarId = $mantalk->NewCalendar({name => 'Manifold Calendar'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $mantalk->NewCalendar({ name => 'Manifold Calendar' }); + $self->assert_not_null($CalendarId); - xlog $self, "share to user (without 'k' or 'x')"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId", "cassandane" => 'lrspwiten'); + xlog $self, "share to user (without 'k' or 'x')"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId", + "cassandane" => 'lrspwiten'); - my $propfindXml = < @@ -41,10 +41,12 @@ sub test_dav_bind EOF - # Assert that {DAV:}bind and {DAV:}unbind are present. - my $res = $CalDAV->Request('PROPFIND', "/dav/calendars/user/cassandane/manifold.". $CalendarId, - $propfindXml, 'Content-Type' => 'text/xml'); - my $text = Dumper($res); - $self->assert_matches(qr/{DAV:}bind/, $text); - $self->assert_matches(qr/{DAV:}unbind/, $text); + # Assert that {DAV:}bind and {DAV:}unbind are present. + my $res + = $CalDAV->Request('PROPFIND', + "/dav/calendars/user/cassandane/manifold." . $CalendarId, + $propfindXml, 'Content-Type' => 'text/xml'); + my $text = Dumper($res); + $self->assert_matches(qr/{DAV:}bind/, $text); + $self->assert_matches(qr/{DAV:}unbind/, $text); } diff --git a/cassandane/tiny-tests/Caldav/davsharing b/cassandane/tiny-tests/Caldav/davsharing index 4d6e906998..912b4dfa70 100644 --- a/cassandane/tiny-tests/Caldav/davsharing +++ b/cassandane/tiny-tests/Caldav/davsharing @@ -2,30 +2,29 @@ use Cassandane::Tiny; sub test_davsharing - :min_version_3_0 :needs_component_httpd :NoVirtDomains -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.manifold"); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - - my $service = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $invite = <{caldav}; + + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->create("user.manifold"); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + + my $service = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $invite = < @@ -41,7 +40,7 @@ sub test_davsharing EOF - my $reply = < @@ -52,86 +51,87 @@ EOF EOF - xlog $self, "create calendar"; - my $CalendarId = $mantalk->NewCalendar({name => 'Manifold Calendar'}); - $self->assert_not_null($CalendarId); - - xlog $self, "share to user"; - $mantalk->Request('POST', $CalendarId, $invite, - 'Content-Type' => 'application/davsharing+xml'); - - xlog $self, "fetch invite"; - my ($adds) = $CalDAV->SyncEventLinks("/dav/notifications/user/cassandane"); - $self->assert_equals(scalar %$adds, 1); - my $notification = (keys %$adds)[0]; - - xlog $self, "accept invite"; - $CalDAV->Request('POST', $notification, $reply, - 'Content-Type' => 'application/davsharing+xml'); - - xlog $self, "fetch invite reply"; - ($adds) = $mantalk->SyncEventLinks("/dav/notifications/user/manifold"); - $self->assert_equals(scalar %$adds, 1); - $notification = (keys %$adds)[0]; - - my $res = $mantalk->Request('GET', $notification); - my $xml = xmlToHash($res->{content}); - my $CS = 'http://calendarserver.org/ns/'; - $reply = $xml->{"{$CS}invite-reply"}; - $self->assert_not_null($reply); - $self->assert_not_null($reply->{"{$CS}invite-accepted"}); - $self->assert_str_equals($mantalk->fullpath($CalendarId) . "/", - $reply->{"{$CS}hosturl"}{'{DAV:}href'}{content}); - $self->assert_str_equals(basename($notification), - $reply->{"{$CS}in-reply-to"}{content}); - - # need to version-gate features that aren't in 3.0... - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min >= 9)) { - $self->assert_str_equals('Test User', - $reply->{"{$CS}common-name"}{content}); - } - - xlog $self, "get calendars as manifold"; - my $ManCal = $mantalk->GetCalendars(); - $self->assert_num_equals(2, scalar @$ManCal); - my $names = join "/", sort map { $_->{name} } @$ManCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); - - xlog $self, "get calendars as cassandane"; - my $CasCal = $CalDAV->GetCalendars(); - $self->assert_num_equals(2, scalar @$CasCal); - $names = join "/", sort map { $_->{name} } @$CasCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); - - xlog $self, "Update calendar name as cassandane"; - my ($CasId) = map { $_->{id} } grep { $_->{name} eq 'Manifold Calendar' } @$CasCal; - $CalDAV->UpdateCalendar({id => $CasId, name => "Cassandane Name"}); - - xlog $self, "changed as cassandane"; - $CasCal = $CalDAV->GetCalendars(); - $self->assert_num_equals(2, scalar @$CasCal); - $names = join "/", sort map { $_->{name} } @$CasCal; - $self->assert_str_equals($names, "Cassandane Name/personal"); - - xlog $self, "unchanged as manifold"; - $ManCal = $mantalk->GetCalendars(); - $self->assert_num_equals(2, scalar @$ManCal); - $names = join "/", sort map { $_->{name} } @$ManCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); - - xlog $self, "delete calendar as cassandane"; - $CalDAV->DeleteCalendar($CasId); - - xlog $self, "changed as cassandane"; - $CasCal = $CalDAV->GetCalendars(); - $self->assert_num_equals(1, scalar @$CasCal); - $names = join "/", sort map { $_->{name} } @$CasCal; - $self->assert_str_equals($names, "personal"); - - xlog $self, "unchanged as manifold"; - $ManCal = $mantalk->GetCalendars(); - $self->assert_num_equals(2, scalar @$ManCal); - $names = join "/", sort map { $_->{name} } @$ManCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); + xlog $self, "create calendar"; + my $CalendarId = $mantalk->NewCalendar({ name => 'Manifold Calendar' }); + $self->assert_not_null($CalendarId); + + xlog $self, "share to user"; + $mantalk->Request('POST', $CalendarId, $invite, + 'Content-Type' => 'application/davsharing+xml'); + + xlog $self, "fetch invite"; + my ($adds) = $CalDAV->SyncEventLinks("/dav/notifications/user/cassandane"); + $self->assert_equals(scalar %$adds, 1); + my $notification = (keys %$adds)[0]; + + xlog $self, "accept invite"; + $CalDAV->Request('POST', $notification, $reply, + 'Content-Type' => 'application/davsharing+xml'); + + xlog $self, "fetch invite reply"; + ($adds) = $mantalk->SyncEventLinks("/dav/notifications/user/manifold"); + $self->assert_equals(scalar %$adds, 1); + $notification = (keys %$adds)[0]; + + my $res = $mantalk->Request('GET', $notification); + my $xml = xmlToHash($res->{content}); + my $CS = 'http://calendarserver.org/ns/'; + $reply = $xml->{"{$CS}invite-reply"}; + $self->assert_not_null($reply); + $self->assert_not_null($reply->{"{$CS}invite-accepted"}); + $self->assert_str_equals($mantalk->fullpath($CalendarId) . "/", + $reply->{"{$CS}hosturl"}{'{DAV:}href'}{content}); + $self->assert_str_equals(basename($notification), + $reply->{"{$CS}in-reply-to"}{content}); + + # need to version-gate features that aren't in 3.0... + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min >= 9)) { + $self->assert_str_equals('Test User', + $reply->{"{$CS}common-name"}{content}); + } + + xlog $self, "get calendars as manifold"; + my $ManCal = $mantalk->GetCalendars(); + $self->assert_num_equals(2, scalar @$ManCal); + my $names = join "/", sort map { $_->{name} } @$ManCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); + + xlog $self, "get calendars as cassandane"; + my $CasCal = $CalDAV->GetCalendars(); + $self->assert_num_equals(2, scalar @$CasCal); + $names = join "/", sort map { $_->{name} } @$CasCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); + + xlog $self, "Update calendar name as cassandane"; + my ($CasId) + = map { $_->{id} } grep { $_->{name} eq 'Manifold Calendar' } @$CasCal; + $CalDAV->UpdateCalendar({ id => $CasId, name => "Cassandane Name" }); + + xlog $self, "changed as cassandane"; + $CasCal = $CalDAV->GetCalendars(); + $self->assert_num_equals(2, scalar @$CasCal); + $names = join "/", sort map { $_->{name} } @$CasCal; + $self->assert_str_equals($names, "Cassandane Name/personal"); + + xlog $self, "unchanged as manifold"; + $ManCal = $mantalk->GetCalendars(); + $self->assert_num_equals(2, scalar @$ManCal); + $names = join "/", sort map { $_->{name} } @$ManCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); + + xlog $self, "delete calendar as cassandane"; + $CalDAV->DeleteCalendar($CasId); + + xlog $self, "changed as cassandane"; + $CasCal = $CalDAV->GetCalendars(); + $self->assert_num_equals(1, scalar @$CasCal); + $names = join "/", sort map { $_->{name} } @$CasCal; + $self->assert_str_equals($names, "personal"); + + xlog $self, "unchanged as manifold"; + $ManCal = $mantalk->GetCalendars(); + $self->assert_num_equals(2, scalar @$ManCal); + $names = join "/", sort map { $_->{name} } @$ManCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); } diff --git a/cassandane/tiny-tests/Caldav/defaultalarms b/cassandane/tiny-tests/Caldav/defaultalarms index 72e30c104a..36643afcc5 100644 --- a/cassandane/tiny-tests/Caldav/defaultalarms +++ b/cassandane/tiny-tests/Caldav/defaultalarms @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_defaultalarms - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - # For JMAP calendars, we refactored CalDAV default alarm property - # handling from a regular dead DAV property to a structured value. - # This test asserts that CalDAV clients won't notice the difference. + # For JMAP calendars, we refactored CalDAV default alarm property + # handling from a regular dead DAV property to a structured value. + # This test asserts that CalDAV clients won't notice the difference. - my $rawAlarmDateTime = < @@ -53,11 +52,13 @@ $rawAlarmDate EOF - $CalDAV->Request('PROPPATCH', "/dav/calendars/user/cassandane/Default", - $proppatchXml, 'Content-Type' => 'text/xml'); + $CalDAV->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane/Default", + $proppatchXml, 'Content-Type' => 'text/xml' + ); - xlog "Get alarms"; - my $propfindXml = < @@ -66,28 +67,34 @@ EOF EOF - my $Response = $CalDAV->Request('PROPFIND', "/dav/calendars/user/cassandane/Default", - $propfindXml, 'Content-Type' => 'text/xml'); + my $Response = $CalDAV->Request( + 'PROPFIND', "/dav/calendars/user/cassandane/Default", + $propfindXml, 'Content-Type' => 'text/xml' + ); - xlog "Assert alarm values"; - my $assert_propval = sub { - my ($Response, $propname, $wantVal, $wantStatus) = @_; - my $propStat = $Response->{'{DAV:}response'}[0]->{'{DAV:}propstat'}[0]; - my $prop = $propStat->{'{DAV:}prop'}; - $wantVal =~ s/^\s+|\s+$//g; - my $got = $prop->{'{urn:ietf:params:xml:ns:caldav}'. $propname}->{content}; - $got =~ s/^\s+|\s+$//g; - $self->assert_str_equals($wantVal, $got); - my $status = $propStat->{'{DAV:}status'}; - $self->assert_str_equals($wantStatus, $status->{content}); - }; - $assert_propval->($Response, 'default-alarm-vevent-datetime', - $rawAlarmDateTime, 'HTTP/1.1 200 OK'); - $assert_propval->($Response, 'default-alarm-vevent-date', - $rawAlarmDate, 'HTTP/1.1 200 OK'); + xlog "Assert alarm values"; + my $assert_propval = sub { + my ($Response, $propname, $wantVal, $wantStatus) = @_; + my $propStat = $Response->{'{DAV:}response'}[0]->{'{DAV:}propstat'}[0]; + my $prop = $propStat->{'{DAV:}prop'}; + $wantVal =~ s/^\s+|\s+$//g; + my $got + = $prop->{ '{urn:ietf:params:xml:ns:caldav}' . $propname }->{content}; + $got =~ s/^\s+|\s+$//g; + $self->assert_str_equals($wantVal, $got); + my $status = $propStat->{'{DAV:}status'}; + $self->assert_str_equals($wantStatus, $status->{content}); + }; + $assert_propval->( + $Response, 'default-alarm-vevent-datetime', + $rawAlarmDateTime, 'HTTP/1.1 200 OK' + ); + $assert_propval->( + $Response, 'default-alarm-vevent-date', $rawAlarmDate, 'HTTP/1.1 200 OK' + ); - xlog "Remove alarms"; - $proppatchXml = < @@ -98,11 +105,13 @@ EOF EOF - $CalDAV->Request('PROPPATCH', "/dav/calendars/user/cassandane/Default", - $proppatchXml, 'Content-Type' => 'text/xml'); + $CalDAV->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane/Default", + $proppatchXml, 'Content-Type' => 'text/xml' + ); - xlog "Get alarms"; - $propfindXml = < @@ -111,12 +120,17 @@ EOF EOF - $Response = $CalDAV->Request('PROPFIND', "/dav/calendars/user/cassandane/Default", - $propfindXml, 'Content-Type' => 'text/xml'); + $Response = $CalDAV->Request( + 'PROPFIND', "/dav/calendars/user/cassandane/Default", + $propfindXml, 'Content-Type' => 'text/xml' + ); - xlog "Assert alarm values do not exist"; - $assert_propval->($Response, 'default-alarm-vevent-datetime', - '', 'HTTP/1.1 404 Not Found'); - $assert_propval->($Response, 'default-alarm-vevent-date', - '', 'HTTP/1.1 404 Not Found'); + xlog "Assert alarm values do not exist"; + $assert_propval->( + $Response, 'default-alarm-vevent-datetime', + '', 'HTTP/1.1 404 Not Found' + ); + $assert_propval->( + $Response, 'default-alarm-vevent-date', '', 'HTTP/1.1 404 Not Found' + ); } diff --git a/cassandane/tiny-tests/Caldav/delete_recur_extraattendee b/cassandane/tiny-tests/Caldav/delete_recur_extraattendee index 3a8332ce6f..715a8f4c4e 100644 --- a/cassandane/tiny-tests/Caldav/delete_recur_extraattendee +++ b/cassandane/tiny-tests/Caldav/delete_recur_extraattendee @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_delete_recur_extraattendee - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "set up event"; - my $uuid = $CalDAV->genuuid(); - my $overrides = <genuuid(); + my $overrides = <_put_event($CalendarId, uuid => $uuid, lines => < $overrides); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < $overrides); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test1\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test3\@example.com RRULE:FREQ=WEEKLY ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->{instance}->getnotify(); - my $href = "$CalendarId/$uuid.ics"; - $self->{caldav}->Request('DELETE', $href); + $self->{instance}->getnotify(); + my $href = "$CalendarId/$uuid.ics"; + $self->{caldav}->Request('DELETE', $href); - my $except = { - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test2\@example.com" => { email => "test2\@example.com" }, - "test3\@example.com" => { email => "test3\@example.com" }, - }, - }; + my $except = { + participants => { + "cassandane\@example.com" => { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test2\@example.com" => { email => "test2\@example.com" }, + "test3\@example.com" => { email => "test3\@example.com" }, + }, + }; - my $regular = { - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test3\@example.com" => { email => "test3\@example.com" }, - }, - recurrenceOverrides => { - '2016-06-08T15:30:00' => $except, - '2016-06-15T15:30:00' => $except, - }, - }; + my $regular = { + participants => { + "cassandane\@example.com" => { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test3\@example.com" => { email => "test3\@example.com" }, + }, + recurrenceOverrides => { + '2016-06-08T15:30:00' => $except, + '2016-06-15T15:30:00' => $except, + }, + }; - $self->assert_caldav_notified( - { - method => 'CANCEL', - recipient => "test1\@example.com", - event => $regular, - }, - { - method => 'CANCEL', - recipient => "test2\@example.com", - event => { - recurrenceOverrides => { - '2016-06-08T15:30:00' => $except, - '2016-06-15T15:30:00' => $except, - }, - }, - }, - { - method => 'CANCEL', - recipient => "test3\@example.com", - event => $regular, + $self->assert_caldav_notified( + { + method => 'CANCEL', + recipient => "test1\@example.com", + event => $regular, + }, + { + method => 'CANCEL', + recipient => "test2\@example.com", + event => { + recurrenceOverrides => { + '2016-06-08T15:30:00' => $except, + '2016-06-15T15:30:00' => $except, }, - ); + }, + }, + { + method => 'CANCEL', + recipient => "test3\@example.com", + event => $regular, + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/empty_summary b/cassandane/tiny-tests/Caldav/empty_summary index dd022f6349..389ac4ab9a 100644 --- a/cassandane/tiny-tests/Caldav/empty_summary +++ b/cassandane/tiny-tests/Caldav/empty_summary @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_empty_summary - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => ''}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => '' }); + $self->assert_not_null($CalendarId); - my $uuid = "2b82ea51-50b0-4c6b-a9b4-e8ff0f931ba2"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); } diff --git a/cassandane/tiny-tests/Caldav/event_move b/cassandane/tiny-tests/Caldav/event_move index 042e7a6184..0b20a833ca 100644 --- a/cassandane/tiny-tests/Caldav/event_move +++ b/cassandane/tiny-tests/Caldav/event_move @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_event_move - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid1 = "d4643cf9-4552-4a3e-8d6c-5f318bcc5b79"; - my $href = "$CalendarId/$uuid1.ics"; - my $card1 = <Request('PUT', $href, $card1, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card1, 'Content-Type' => 'text/calendar'); - my $DestCal = $CalDAV->GetCalendar($CalendarId); + my $DestCal = $CalDAV->GetCalendar($CalendarId); - my $uuid2 = "event2\@example.com"; - my $card2 = <Request('PUT', $href, $card2, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card2, 'Content-Type' => 'text/calendar'); - my $SrcCal = $CalDAV->GetCalendar('Default'); + my $SrcCal = $CalDAV->GetCalendar('Default'); - $CalDAV->MoveEvent($href, $CalendarId); + $CalDAV->MoveEvent($href, $CalendarId); - my ($adds, $removes, $errors) = $CalDAV->SyncEvents('Default', syncToken => $SrcCal->{syncToken}); - $self->assert_deep_equals([], $adds); - $self->assert_equals(1, scalar @$removes); - $self->assert_str_equals("/dav/calendars/user/cassandane/" . $href, $removes->[0]); - $self->assert_deep_equals([], $errors); + my ($adds, $removes, $errors) + = $CalDAV->SyncEvents('Default', syncToken => $SrcCal->{syncToken}); + $self->assert_deep_equals([], $adds); + $self->assert_equals(1, scalar @$removes); + $self->assert_str_equals("/dav/calendars/user/cassandane/" . $href, + $removes->[0]); + $self->assert_deep_equals([], $errors); - ($adds, $removes, $errors) = $CalDAV->SyncEvents($CalendarId, syncToken => $DestCal->{syncToken}); + ($adds, $removes, $errors) + = $CalDAV->SyncEvents($CalendarId, syncToken => $DestCal->{syncToken}); - $self->assert_equals(1, scalar @$adds); - $self->assert_str_equals($adds->[0]{uid}, $uuid2); - $self->assert_deep_equals([], $removes); - $self->assert_deep_equals([], $errors); + $self->assert_equals(1, scalar @$adds); + $self->assert_str_equals($adds->[0]{uid}, $uuid2); + $self->assert_deep_equals([], $removes); + $self->assert_deep_equals([], $errors); } diff --git a/cassandane/tiny-tests/Caldav/fantastical_strip_prior_overrides b/cassandane/tiny-tests/Caldav/fantastical_strip_prior_overrides index 0e1c3877bb..f310d500b1 100644 --- a/cassandane/tiny-tests/Caldav/fantastical_strip_prior_overrides +++ b/cassandane/tiny-tests/Caldav/fantastical_strip_prior_overrides @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_fantastical_strip_prior_overrides - :needs_component_httpd :min_version_3_9 -{ - my ($self) = @_; + : needs_component_httpd : min_version_3_9 { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid = "BB19B9E8-CDFB-4163-873E-EE0B9714F919"; - my $href = "$CalendarId/$uuid.ics"; - my $event = <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Add event with overrides prior to start of RRULE"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Make sure prior overrides are removed but subsequent remain"; - my $response = $CalDAV->Request('GET', $href); - my $newevent = $response->{content}; + xlog $self, "Make sure prior overrides are removed but subsequent remain"; + my $response = $CalDAV->Request('GET', $href); + my $newevent = $response->{content}; - $self->assert_does_not_match(qr|RECURRENCE-ID;TZID=America/New_York:20230313T140000|, $newevent); - $self->assert_does_not_match(qr|RECURRENCE-ID;TZID=America/New_York:20230315T140000|, $newevent); - $self->assert_matches(qr|RECURRENCE-ID;TZID=America/New_York:20230317T140000|, $newevent); - $self->assert_matches(qr|RECURRENCE-ID;TZID=America/New_York:20230312T120000|, $newevent); - $self->assert_matches(qr|SUMMARY:Test override of RDATE before DTSTART|, $newevent); + $self->assert_does_not_match( + qr|RECURRENCE-ID;TZID=America/New_York:20230313T140000|, $newevent); + $self->assert_does_not_match( + qr|RECURRENCE-ID;TZID=America/New_York:20230315T140000|, $newevent); + $self->assert_matches(qr|RECURRENCE-ID;TZID=America/New_York:20230317T140000|, + $newevent); + $self->assert_matches(qr|RECURRENCE-ID;TZID=America/New_York:20230312T120000|, + $newevent); + $self->assert_matches(qr|SUMMARY:Test override of RDATE before DTSTART|, + $newevent); } diff --git a/cassandane/tiny-tests/Caldav/fastmailsharing b/cassandane/tiny-tests/Caldav/fastmailsharing index 488e3e9c41..6a1d6292da 100644 --- a/cassandane/tiny-tests/Caldav/fastmailsharing +++ b/cassandane/tiny-tests/Caldav/fastmailsharing @@ -2,76 +2,77 @@ use Cassandane::Tiny; sub test_fastmailsharing - :FastmailSharing :ReverseACLs :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : FastmailSharing : ReverseACLs : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.manifold"); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + $admintalk->create("user.manifold"); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - my $service = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "create calendar"; - my $CalendarId = $mantalk->NewCalendar({name => 'Manifold Calendar'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $mantalk->NewCalendar({ name => 'Manifold Calendar' }); + $self->assert_not_null($CalendarId); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId", "cassandane" => 'lrswipcdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId", + "cassandane" => 'lrswipcdn'); - xlog $self, "get calendars as cassandane"; - my $CasCal = $CalDAV->GetCalendars(); - $self->assert_num_equals(2, scalar @$CasCal); - my $names = join "/", sort map { $_->{name} } @$CasCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); + xlog $self, "get calendars as cassandane"; + my $CasCal = $CalDAV->GetCalendars(); + $self->assert_num_equals(2, scalar @$CasCal); + my $names = join "/", sort map { $_->{name} } @$CasCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); - xlog $self, "get calendars as manifold"; - my $ManCal = $mantalk->GetCalendars(); - $self->assert_num_equals(2, scalar @$ManCal); - $names = join "/", sort map { $_->{name} } @$ManCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); + xlog $self, "get calendars as manifold"; + my $ManCal = $mantalk->GetCalendars(); + $self->assert_num_equals(2, scalar @$ManCal); + $names = join "/", sort map { $_->{name} } @$ManCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); - xlog $self, "Update calendar name as cassandane"; - my ($CasId) = map { $_->{id} } grep { $_->{name} eq 'Manifold Calendar' } @$CasCal; - $CalDAV->UpdateCalendar({id => $CasId, name => "Cassandane Name"}); + xlog $self, "Update calendar name as cassandane"; + my ($CasId) + = map { $_->{id} } grep { $_->{name} eq 'Manifold Calendar' } @$CasCal; + $CalDAV->UpdateCalendar({ id => $CasId, name => "Cassandane Name" }); - xlog $self, "changed as cassandane"; - $CasCal = $CalDAV->GetCalendars(); - $self->assert_num_equals(2, scalar @$CasCal); - $names = join "/", sort map { $_->{name} } @$CasCal; - $self->assert_str_equals($names, "Cassandane Name/personal"); + xlog $self, "changed as cassandane"; + $CasCal = $CalDAV->GetCalendars(); + $self->assert_num_equals(2, scalar @$CasCal); + $names = join "/", sort map { $_->{name} } @$CasCal; + $self->assert_str_equals($names, "Cassandane Name/personal"); - xlog $self, "unchanged as manifold"; - $ManCal = $mantalk->GetCalendars(); - $self->assert_num_equals(2, scalar @$ManCal); - $names = join "/", sort map { $_->{name} } @$ManCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); + xlog $self, "unchanged as manifold"; + $ManCal = $mantalk->GetCalendars(); + $self->assert_num_equals(2, scalar @$ManCal); + $names = join "/", sort map { $_->{name} } @$ManCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); - xlog $self, "delete calendar as cassandane"; - $CalDAV->DeleteCalendar($CasId); + xlog $self, "delete calendar as cassandane"; + $CalDAV->DeleteCalendar($CasId); - xlog $self, "changed as cassandane"; - $CasCal = $CalDAV->GetCalendars(); - $self->assert_num_equals(1, scalar @$CasCal); - $names = join "/", sort map { $_->{name} } @$CasCal; - $self->assert_str_equals($names, "personal"); + xlog $self, "changed as cassandane"; + $CasCal = $CalDAV->GetCalendars(); + $self->assert_num_equals(1, scalar @$CasCal); + $names = join "/", sort map { $_->{name} } @$CasCal; + $self->assert_str_equals($names, "personal"); - xlog $self, "unchanged as manifold"; - $ManCal = $mantalk->GetCalendars(); - $self->assert_num_equals(2, scalar @$ManCal); - $names = join "/", sort map { $_->{name} } @$ManCal; - $self->assert_str_equals($names, "Manifold Calendar/personal"); + xlog $self, "unchanged as manifold"; + $ManCal = $mantalk->GetCalendars(); + $self->assert_num_equals(2, scalar @$ManCal); + $names = join "/", sort map { $_->{name} } @$ManCal; + $self->assert_str_equals($names, "Manifold Calendar/personal"); } diff --git a/cassandane/tiny-tests/Caldav/freebusy b/cassandane/tiny-tests/Caldav/freebusy index c36e2d4ce9..a364a45f25 100644 --- a/cassandane/tiny-tests/Caldav/freebusy +++ b/cassandane/tiny-tests/Caldav/freebusy @@ -2,32 +2,37 @@ use Cassandane::Tiny; sub test_freebusy - :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - $CalDAV->NewEvent($CalendarId, { - timeZone => 'Etc/UTC', - start => '2015-01-01T12:00:00', - duration => 'PT1H', - summary => 'waterfall', - }); + $CalDAV->NewEvent( + $CalendarId, + { + timeZone => 'Etc/UTC', + start => '2015-01-01T12:00:00', + duration => 'PT1H', + summary => 'waterfall', + } + ); - $CalDAV->NewEvent($CalendarId, { - timeZone => 'America/New_York', - start => '2015-02-01T12:00:00', - duration => 'PT1H', - summary => 'waterfall2', - }); + $CalDAV->NewEvent( + $CalendarId, + { + timeZone => 'America/New_York', + start => '2015-02-01T12:00:00', + duration => 'PT1H', + summary => 'waterfall2', + } + ); - my ($data, $errors) = $CalDAV->GetFreeBusy($CalendarId); + my ($data, $errors) = $CalDAV->GetFreeBusy($CalendarId); - $self->assert_str_equals('2015-01-01T12:00:00', $data->[0]{start}); - $self->assert_str_equals('2015-02-01T17:00:00', $data->[1]{start}); - $self->assert_num_equals(2, scalar @$data); + $self->assert_str_equals('2015-01-01T12:00:00', $data->[0]{start}); + $self->assert_str_equals('2015-02-01T17:00:00', $data->[1]{start}); + $self->assert_num_equals(2, scalar @$data); } diff --git a/cassandane/tiny-tests/Caldav/freebusy_floating b/cassandane/tiny-tests/Caldav/freebusy_floating index c173e3fd5d..434e92a8c3 100644 --- a/cassandane/tiny-tests/Caldav/freebusy_floating +++ b/cassandane/tiny-tests/Caldav/freebusy_floating @@ -2,37 +2,43 @@ use Cassandane::Tiny; sub test_freebusy_floating - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $CalendarId = $CalDAV->NewCalendar({name => 'foo', timeZone => $self->MELBOURNE}); - $self->assert_not_null($CalendarId); - - $CalDAV->NewEvent($CalendarId, { - start => '2015-01-01T12:00:00', - duration => 'PT1H', - summary => 'waterfall', - }); - - $CalDAV->NewEvent($CalendarId, { - start => '2015-02-01T12:00:00', - duration => 'PT1H', - summary => 'waterfall2', - }); - - my ($data, $errors) = $CalDAV->GetFreeBusy($CalendarId); - - $self->assert_str_equals('2015-01-01T01:00:00', $data->[0]{start}); - $self->assert_str_equals('2015-02-01T01:00:00', $data->[1]{start}); - $self->assert_num_equals(2, scalar @$data); - - my $new_york = $self->NEW_YORK; - - # Change floating time zone on the calendar - my $xml = <{caldav}; + + my $CalendarId + = $CalDAV->NewCalendar({ name => 'foo', timeZone => $self->MELBOURNE }); + $self->assert_not_null($CalendarId); + + $CalDAV->NewEvent( + $CalendarId, + { + start => '2015-01-01T12:00:00', + duration => 'PT1H', + summary => 'waterfall', + } + ); + + $CalDAV->NewEvent( + $CalendarId, + { + start => '2015-02-01T12:00:00', + duration => 'PT1H', + summary => 'waterfall2', + } + ); + + my ($data, $errors) = $CalDAV->GetFreeBusy($CalendarId); + + $self->assert_str_equals('2015-01-01T01:00:00', $data->[0]{start}); + $self->assert_str_equals('2015-02-01T01:00:00', $data->[1]{start}); + $self->assert_num_equals(2, scalar @$data); + + my $new_york = $self->NEW_YORK; + + # Change floating time zone on the calendar + my $xml = < @@ -43,13 +49,14 @@ sub test_freebusy_floating EOF - my $res = $CalDAV->Request('PROPPATCH', - "/dav/calendars/user/cassandane/". $CalendarId, - $xml, 'Content-Type' => 'text/xml'); + my $res + = $CalDAV->Request('PROPPATCH', + "/dav/calendars/user/cassandane/" . $CalendarId, + $xml, 'Content-Type' => 'text/xml'); - ($data, $errors) = $CalDAV->GetFreeBusy($CalendarId); + ($data, $errors) = $CalDAV->GetFreeBusy($CalendarId); - $self->assert_str_equals('2015-01-01T17:00:00', $data->[0]{start}); - $self->assert_str_equals('2015-02-01T17:00:00', $data->[1]{start}); - $self->assert_num_equals(2, scalar @$data); + $self->assert_str_equals('2015-01-01T17:00:00', $data->[0]{start}); + $self->assert_str_equals('2015-02-01T17:00:00', $data->[1]{start}); + $self->assert_num_equals(2, scalar @$data); } diff --git a/cassandane/tiny-tests/Caldav/freebusy_overrides b/cassandane/tiny-tests/Caldav/freebusy_overrides index 696fe076ef..c660407340 100644 --- a/cassandane/tiny-tests/Caldav/freebusy_overrides +++ b/cassandane/tiny-tests/Caldav/freebusy_overrides @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_freebusy_overrides - :min_version_3_9 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_9 : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $ical = <Request('PUT', '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $CalDAV->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - my ($data, $errors) = $CalDAV->GetFreeBusy('Default'); - $self->assert_str_equals('2023-01-01T16:00:00', $data->[0]{start}); - $self->assert_str_equals('PT1H', $data->[0]{duration}); - $self->assert_str_equals('2023-12-31T16:00:00', $data->[1]{start}); - $self->assert_str_equals('PT2H', $data->[1]{duration}); - $self->assert_num_equals(2, scalar @$data); + my ($data, $errors) = $CalDAV->GetFreeBusy('Default'); + $self->assert_str_equals('2023-01-01T16:00:00', $data->[0]{start}); + $self->assert_str_equals('PT1H', $data->[0]{duration}); + $self->assert_str_equals('2023-12-31T16:00:00', $data->[1]{start}); + $self->assert_str_equals('PT2H', $data->[1]{duration}); + $self->assert_num_equals(2, scalar @$data); } diff --git a/cassandane/tiny-tests/Caldav/get_control_char b/cassandane/tiny-tests/Caldav/get_control_char index 1e67d5baad..86da244a8e 100644 --- a/cassandane/tiny-tests/Caldav/get_control_char +++ b/cassandane/tiny-tests/Caldav/get_control_char @@ -2,19 +2,19 @@ use Cassandane::Tiny; sub test_get_control_char - :min_version_3_9 :needs_component_httpd :needs_ical_ctrl :MagicPlus -{ - my ($self) = @_; + : min_version_3_9 : needs_component_httpd : needs_ical_ctrl : MagicPlus { + my ($self) = @_; - my $caldav = $self->{caldav}; - my $plusstore = $self->{instance}->get_service('imap' - )->create_store(username => 'cassandane+dav'); - my $imap = $plusstore->get_client(); + my $caldav = $self->{caldav}; + my $plusstore = $self->{instance}->get_service( + 'imap' + )->create_store(username => 'cassandane+dav'); + my $imap = $plusstore->get_client(); - # Assert that CONTROL chars are omitted when reading - # iCalendar data from disk. + # Assert that CONTROL chars are omitted when reading + # iCalendar data from disk. - my $mimeMsg = < Subject: test @@ -44,9 +44,10 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $mimeMsg =~ s/\r?\n/\r\n/gs; - $imap->append('#calendars.Default', $mimeMsg) || die $@; + $mimeMsg =~ s/\r?\n/\r\n/gs; + $imap->append('#calendars.Default', $mimeMsg) || die $@; - my $res = $caldav->Request('GET', '/dav/calendars/user/cassandane/Default/test.ics'); - $self->assert_matches(qr/DESCRIPTION:ctrl/, $res->{content}); + my $res = $caldav->Request('GET', + '/dav/calendars/user/cassandane/Default/test.ics'); + $self->assert_matches(qr/DESCRIPTION:ctrl/, $res->{content}); } diff --git a/cassandane/tiny-tests/Caldav/get_legacy_defaultalarm_no_uid b/cassandane/tiny-tests/Caldav/get_legacy_defaultalarm_no_uid index 6fbe697796..0db300b018 100644 --- a/cassandane/tiny-tests/Caldav/get_legacy_defaultalarm_no_uid +++ b/cassandane/tiny-tests/Caldav/get_legacy_defaultalarm_no_uid @@ -2,24 +2,25 @@ use Cassandane::Tiny; sub test_get_legacy_defaultalarm_no_uid - :min_version_3_9 :needs_component_jmap :MagicPlus -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : MagicPlus { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $plusstore = $self->{instance}->get_service('imap' - )->create_store(username => 'cassandane+dav'); - my $imap = $plusstore->get_client(); + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $plusstore = $self->{instance}->get_service( + 'imap' + )->create_store(username => 'cassandane+dav'); + my $imap = $plusstore->get_client(); - xlog $self, "Pretend as if JMAP default alarm migration never happened"; - $imap->setmetadata("#calendars.Default", - '/private/vendor/cmu/cyrus-jmap/defaultalerts', ''); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); + xlog $self, "Pretend as if JMAP default alarm migration never happened"; + $imap->setmetadata("#calendars.Default", + '/private/vendor/cmu/cyrus-jmap/defaultalerts', ''); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); - xlog $self, "Create event with a VALARM having no UID and default alerts enabled"; - my $eventUid = '4c9aff9c-91df-4859-8026-772a82f52094'; - my $ical = <Request('PUT', - "/dav/calendars/user/cassandane/Default/test.ics", - $ical, - 'Content-Type' => 'text/calendar', - 'X-Cyrus-rewrite-usedefaultalerts' => 'false', - ); + my $res = $caldav->Request( + 'PUT', + "/dav/calendars/user/cassandane/Default/test.ics", + $ical, + 'Content-Type' => 'text/calendar', + 'X-Cyrus-rewrite-usedefaultalerts' => 'false', + ); - xlog $self, "Set CalDAV default alarms with VALARM having no UID"; - $imap->setmetadata("#calendars.Default", - '/shared/vendor/cmu/cyrus-httpd/' . - 'default-alarm-vevent-datetime', - <setmetadata( + "#calendars.Default", + '/shared/vendor/cmu/cyrus-httpd/' + . 'default-alarm-vevent-datetime', + <assert_str_equals('ok', $imap->get_last_completion_response()); + ); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); - xlog $self, "Assert VALARM in event has UID"; - $res = $caldav->Request('GET', - "/dav/calendars/user/cassandane/Default/test.ics"); + xlog $self, "Assert VALARM in event has UID"; + $res = $caldav->Request('GET', + "/dav/calendars/user/cassandane/Default/test.ics"); - my $vcal = Text::VCardFast::vcard2hash($res->{content}); - my @valarms = grep { $_->{type} eq 'valarm' } - @{$vcal->{objects}[0]->{objects}[0]->{objects}}; - $self->assert_num_equals(1, scalar @valarms); - $self->assert_not_null($valarms[0]{properties}{uid}); + my $vcal = Text::VCardFast::vcard2hash($res->{content}); + my @valarms = grep { $_->{type} eq 'valarm' } + @{ $vcal->{objects}[0]->{objects}[0]->{objects} }; + $self->assert_num_equals(1, scalar @valarms); + $self->assert_not_null($valarms[0]{properties}{uid}); } diff --git a/cassandane/tiny-tests/Caldav/header_cache_control b/cassandane/tiny-tests/Caldav/header_cache_control index 875373352b..a25e8a0ab9 100644 --- a/cassandane/tiny-tests/Caldav/header_cache_control +++ b/cassandane/tiny-tests/Caldav/header_cache_control @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_header_cache_control - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - # Create an event - my $href = "$CalendarId/event1.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # Check that we can get the event via the CalDAV module - my $response = $CalDAV->Request('GET', $href); - $self->assert_matches(qr{An Event}, $response->{content}); + # Check that we can get the event via the CalDAV module + my $response = $CalDAV->Request('GET', $href); + $self->assert_matches(qr{An Event}, $response->{content}); - my %Headers = ( - 'Authorization' => $CalDAV->auth_header(), - ); - my $URI = $CalDAV->request_url($href); + my %Headers = ('Authorization' => $CalDAV->auth_header(),); + my $URI = $CalDAV->request_url($href); - # Request the event without an authorization header - $response = $CalDAV->{ua}->get($URI, { headers => {} }); + # Request the event without an authorization header + $response = $CalDAV->{ua}->get($URI, { headers => {} }); - # Should be rejected - $self->assert_num_equals(401, $response->{status}); - $self->assert_str_equals('Unauthorized', $response->{reason}); + # Should be rejected + $self->assert_num_equals(401, $response->{status}); + $self->assert_str_equals('Unauthorized', $response->{reason}); - # Request the event with an authorization header - $response = $CalDAV->{ua}->get($URI, { headers => \%Headers }); + # Request the event with an authorization header + $response = $CalDAV->{ua}->get($URI, { headers => \%Headers }); - # Should have Cache-Control: private set - $self->assert_matches(qr{An Event}, $response->{content}); - $self->assert_matches(qr{\bprivate\b}, - $response->{headers}->{'cache-control'}); + # Should have Cache-Control: private set + $self->assert_matches(qr{An Event}, $response->{content}); + $self->assert_matches(qr{\bprivate\b}, + $response->{headers}->{'cache-control'}); } diff --git a/cassandane/tiny-tests/Caldav/imap_magicplus_withdomain b/cassandane/tiny-tests/Caldav/imap_magicplus_withdomain index 18222e9df2..93edf085cd 100644 --- a/cassandane/tiny-tests/Caldav/imap_magicplus_withdomain +++ b/cassandane/tiny-tests/Caldav/imap_magicplus_withdomain @@ -2,31 +2,32 @@ use Cassandane::Tiny; sub test_imap_magicplus_withdomain - :MagicPlus :VirtDomains :min_version_3_0 :needs_component_httpd :NoAltNameSpace -{ - my ($self) = @_; + : MagicPlus : VirtDomains : min_version_3_0 : needs_component_httpd : + NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user.domuser@example.com'); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user.domuser@example.com'); - my $service = $self->{instance}->get_service("http"); - my $domdav = Net::CalDAVTalk->new( - user => 'domuser@example.com', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $domdav = Net::CalDAVTalk->new( + user => 'domuser@example.com', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $domdav->NewCalendar({name => 'magicplus'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $domdav->NewCalendar({ name => 'magicplus' }); + $self->assert_not_null($CalendarId); - my $plusstore = $self->{instance}->get_service('imap')->create_store(username => 'domuser+dav@example.com'); - my $talk = $plusstore->get_client(); + my $plusstore = $self->{instance}->get_service('imap') + ->create_store(username => 'domuser+dav@example.com'); + my $talk = $plusstore->get_client(); - my $list = $talk->list('', '*'); - my ($this) = grep { $_->[2] eq "INBOX.#calendars.$CalendarId" } @$list; - $self->assert_not_null($this); + my $list = $talk->list('', '*'); + my ($this) = grep { $_->[2] eq "INBOX.#calendars.$CalendarId" } @$list; + $self->assert_not_null($this); } diff --git a/cassandane/tiny-tests/Caldav/imap_plusdav b/cassandane/tiny-tests/Caldav/imap_plusdav index 348919c1aa..9d7cf061a3 100644 --- a/cassandane/tiny-tests/Caldav/imap_plusdav +++ b/cassandane/tiny-tests/Caldav/imap_plusdav @@ -2,19 +2,20 @@ use Cassandane::Tiny; sub test_imap_plusdav - :MagicPlus :VirtDomains :min_version_3_0 :needs_component_httpd :NoAltNameSpace -{ - my ($self) = @_; + : MagicPlus : VirtDomains : min_version_3_0 : needs_component_httpd : + NoAltNameSpace { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'magicplus'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'magicplus' }); + $self->assert_not_null($CalendarId); - my $plusstore = $self->{instance}->get_service('imap')->create_store(username => 'cassandane+dav'); - my $talk = $plusstore->get_client(); + my $plusstore = $self->{instance}->get_service('imap') + ->create_store(username => 'cassandane+dav'); + my $talk = $plusstore->get_client(); - my $list = $talk->list('', '*'); - my ($this) = grep { $_->[2] eq "INBOX.#calendars.$CalendarId" } @$list; - $self->assert_not_null($this); + my $list = $talk->list('', '*'); + my ($this) = grep { $_->[2] eq "INBOX.#calendars.$CalendarId" } @$list; + $self->assert_not_null($this); } diff --git a/cassandane/tiny-tests/Caldav/imap_plusdav_novirt b/cassandane/tiny-tests/Caldav/imap_plusdav_novirt index a1dbfea582..588afa1a62 100644 --- a/cassandane/tiny-tests/Caldav/imap_plusdav_novirt +++ b/cassandane/tiny-tests/Caldav/imap_plusdav_novirt @@ -2,19 +2,19 @@ use Cassandane::Tiny; sub test_imap_plusdav_novirt - :MagicPlus :min_version_3_0 :needs_component_httpd :NoAltNameSpace -{ - my ($self) = @_; + : MagicPlus : min_version_3_0 : needs_component_httpd : NoAltNameSpace { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'magicplus'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'magicplus' }); + $self->assert_not_null($CalendarId); - my $plusstore = $self->{instance}->get_service('imap')->create_store(username => 'cassandane+dav'); - my $talk = $plusstore->get_client(); + my $plusstore = $self->{instance}->get_service('imap') + ->create_store(username => 'cassandane+dav'); + my $talk = $plusstore->get_client(); - my $list = $talk->list('', '*'); - my ($this) = grep { $_->[2] eq "INBOX.#calendars.$CalendarId" } @$list; - $self->assert_not_null($this); + my $list = $talk->list('', '*'); + my ($this) = grep { $_->[2] eq "INBOX.#calendars.$CalendarId" } @$list; + $self->assert_not_null($this); } diff --git a/cassandane/tiny-tests/Caldav/invite b/cassandane/tiny-tests/Caldav/invite index f32dd9d9c4..d05a306a1b 100644 --- a/cassandane/tiny-tests/Caldav/invite +++ b/cassandane/tiny-tests/Caldav/invite @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/invite_add_another b/cassandane/tiny-tests/Caldav/invite_add_another index aa900adc6c..4e36245ae9 100644 --- a/cassandane/tiny-tests/Caldav/invite_add_another +++ b/cassandane/tiny-tests/Caldav/invite_add_another @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_add_another - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - $card =~ s/ORGANIZER/ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:friend2\@example.com\nORGANIZER/; - $card =~ s/SEQUENCE:0/SEQUENCE:1/; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $card =~ + s/ORGANIZER/ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:friend2\@example.com\nORGANIZER/; + $card =~ s/SEQUENCE:0/SEQUENCE:1/; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/invite_add_another_to_override b/cassandane/tiny-tests/Caldav/invite_add_another_to_override index 79a43517cc..4bf2df1be0 100644 --- a/cassandane/tiny-tests/Caldav/invite_add_another_to_override +++ b/cassandane/tiny-tests/Caldav/invite_add_another_to_override @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_add_another_to_override - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - $card =~ s/SEQUENCE:0/SEQUENCE:1/; - $card =~ s/RECURRENCE-ID/ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:friend2\@example.com\nRECURRENCE-ID/; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $card =~ s/SEQUENCE:0/SEQUENCE:1/; + $card =~ + s/RECURRENCE-ID/ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:friend2\@example.com\nRECURRENCE-ID/; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/invite_change_organizer b/cassandane/tiny-tests/Caldav/invite_change_organizer index ed3367cdcd..10752d4aff 100644 --- a/cassandane/tiny-tests/Caldav/invite_change_organizer +++ b/cassandane/tiny-tests/Caldav/invite_change_organizer @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_change_organizer - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - # change organizer and move the event 1 hour later - $card = <Request('PUT', $href, $card, - 'Content-Type' => 'text/calendar', - 'Schedule-Address' => 'otherme@example.com', - 'Allow-Organizer-Change' => 'yes', - ); + $CalDAV->Request( + 'PUT', $href, $card, + 'Content-Type' => 'text/calendar', + 'Schedule-Address' => 'otherme@example.com', + 'Allow-Organizer-Change' => 'yes', + ); - $self->assert_caldav_notified( - { - recipient => "friend\@example.com", - is_update => JSON::true, - method => 'REQUEST', - event => { - replyTo => { - imip => 'mailto:otherme@example.com', - }, - }, + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::true, + method => 'REQUEST', + event => { + replyTo => { + imip => 'mailto:otherme@example.com', }, - { recipient => "cassandane\@example.com", is_update => JSON::false, method => 'CANCEL' }, - ); + }, + }, + { + recipient => "cassandane\@example.com", + is_update => JSON::false, + method => 'CANCEL' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/invite_change_organizer_recur b/cassandane/tiny-tests/Caldav/invite_change_organizer_recur index ed98f8d24e..bba5895387 100644 --- a/cassandane/tiny-tests/Caldav/invite_change_organizer_recur +++ b/cassandane/tiny-tests/Caldav/invite_change_organizer_recur @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_change_organizer_recur - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8401"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - # change organizer and make the event 1 hour shorter, removing recurrence - $card = <Request('PUT', $href, $card, - 'Content-Type' => 'text/calendar', - 'Schedule-Address' => 'otherme@example.com', - 'Allow-Organizer-Change' => 'yes', - ); + $CalDAV->Request( + 'PUT', $href, $card, + 'Content-Type' => 'text/calendar', + 'Schedule-Address' => 'otherme@example.com', + 'Allow-Organizer-Change' => 'yes', + ); - $self->assert_caldav_notified( - { - recipient => "friend\@example.com", - is_update => JSON::true, - method => 'REQUEST', - event => { - replyTo => { - imip => 'mailto:otherme@example.com', - }, - }, + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::true, + method => 'REQUEST', + event => { + replyTo => { + imip => 'mailto:otherme@example.com', }, - { recipient => "cassandane\@example.com", is_update => JSON::false, method => 'CANCEL' }, - ); + }, + }, + { + recipient => "cassandane\@example.com", + is_update => JSON::false, + method => 'CANCEL' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/invite_from_nonsched b/cassandane/tiny-tests/Caldav/invite_from_nonsched index 5e0d84032f..5d0b2f2c40 100644 --- a/cassandane/tiny-tests/Caldav/invite_from_nonsched +++ b/cassandane/tiny-tests/Caldav/invite_from_nonsched @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_from_nonsched - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my $data = $self->{instance}->getnotify(); + my $data = $self->{instance}->getnotify(); - my $extra = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $card =~ s/SEQUENCE:0/$extra/; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/invite_fullvirtual b/cassandane/tiny-tests/Caldav/invite_fullvirtual index dd1342d69f..042f8dc773 100644 --- a/cassandane/tiny-tests/Caldav/invite_fullvirtual +++ b/cassandane/tiny-tests/Caldav/invite_fullvirtual @@ -2,30 +2,29 @@ use Cassandane::Tiny; sub test_invite_fullvirtual - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user.domuser@example.com'); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user.domuser@example.com'); - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "domuser\@example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "domuser\@example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <{instance}->getnotify(); + my $data = $self->{instance}->getnotify(); - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my $newdata = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$newdata; - $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); + my $newdata = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$newdata; + $self->assert_not_null($imip); + my $payload = decode_json($imip->{MESSAGE}); - $self->assert_str_equals($payload->{recipient}, "friend\@example.com"); + $self->assert_str_equals($payload->{recipient}, "friend\@example.com"); } diff --git a/cassandane/tiny-tests/Caldav/invite_samelocalpart b/cassandane/tiny-tests/Caldav/invite_samelocalpart index 14f4a2bbe4..451e7e77eb 100644 --- a/cassandane/tiny-tests/Caldav/invite_samelocalpart +++ b/cassandane/tiny-tests/Caldav/invite_samelocalpart @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_samelocalpart - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "cassandane\@othersite.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "cassandane\@othersite.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/invite_switch_duration_to_dtend b/cassandane/tiny-tests/Caldav/invite_switch_duration_to_dtend index 93803ab5f7..b8c3dabe11 100644 --- a/cassandane/tiny-tests/Caldav/invite_switch_duration_to_dtend +++ b/cassandane/tiny-tests/Caldav/invite_switch_duration_to_dtend @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_switch_duration_to_dtend - :VirtDomains :min_version_3_7 :needs_component_httpd -{ - my ($self) = @_; - - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); - - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); + + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$CalendarId/$uuid.ics"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog $self, "make sure an invite is sent to attendee"; - $self->assert_caldav_notified( - { recipient => "friend\@example.com", - is_update => JSON::false, method => 'REQUEST' }, - ); - - xlog $self, "update event using DTEND"; - $card =~ s|DURATION:PT3H|DTEND;TZID=Australia/Melbourne:20160831T183000|; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog $self, "make sure an invite is NOT sent to attendee"; - my $newdata = $self->{instance}->getnotify(); - my @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; - $self->assert_num_equals(0, scalar(@imip)); - - xlog $self, "update event using DURATION"; - $card =~ s|DTEND;TZID=Australia/Melbourne:20160831T183000|DURATION:PT3H|; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog $self, "make sure an invite is NOT sent to attendee"; - $newdata = $self->{instance}->getnotify(); - @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; - $self->assert_num_equals(0, scalar(@imip)); - - xlog $self, "update event using DTEND with different TZID"; - $card =~ s|DURATION:PT3H|DTEND;TZID=Australia/Sydney:20160831T183000|; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog $self, "make sure an invite is sent to attendee"; - $self->assert_caldav_notified( - { recipient => "friend\@example.com", - is_update => JSON::true, method => 'REQUEST' }, - ); - - xlog $self, "update event using DURATION"; - $card =~ s|DTEND;TZID=Australia/Sydney:20160831T183000|DURATION:PT3H|; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog $self, "make sure an invite is sent to attendee"; - $self->assert_caldav_notified( - { recipient => "friend\@example.com", - is_update => JSON::true, method => 'REQUEST' }, - ); - - xlog $self, "update event using DTEND with same TZID"; - $card =~ s|DURATION:PT3H|DTEND;TZID=Australia/Melbourne:20160831T183000|; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog $self, "make sure an invite is NOT sent to attendee"; - $newdata = $self->{instance}->getnotify(); - @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; - $self->assert_num_equals(0, scalar(@imip)); - - xlog $self, "update event changing TZID on DTEND"; - $card =~ s|DTEND;TZID=Australia/Melbourne|DTEND;TZID=Australia/Sydney|; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog $self, "make sure an invite is sent to attendee"; - $self->assert_caldav_notified( - { recipient => "friend\@example.com", - is_update => JSON::true, method => 'REQUEST' }, - ); + xlog $self, "create event using DURATION"; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog $self, "make sure an invite is sent to attendee"; + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); + + xlog $self, "update event using DTEND"; + $card =~ s|DURATION:PT3H|DTEND;TZID=Australia/Melbourne:20160831T183000|; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog $self, "make sure an invite is NOT sent to attendee"; + my $newdata = $self->{instance}->getnotify(); + my @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; + $self->assert_num_equals(0, scalar(@imip)); + + xlog $self, "update event using DURATION"; + $card =~ s|DTEND;TZID=Australia/Melbourne:20160831T183000|DURATION:PT3H|; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog $self, "make sure an invite is NOT sent to attendee"; + $newdata = $self->{instance}->getnotify(); + @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; + $self->assert_num_equals(0, scalar(@imip)); + + xlog $self, "update event using DTEND with different TZID"; + $card =~ s|DURATION:PT3H|DTEND;TZID=Australia/Sydney:20160831T183000|; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog $self, "make sure an invite is sent to attendee"; + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::true, + method => 'REQUEST' + }, + ); + + xlog $self, "update event using DURATION"; + $card =~ s|DTEND;TZID=Australia/Sydney:20160831T183000|DURATION:PT3H|; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog $self, "make sure an invite is sent to attendee"; + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::true, + method => 'REQUEST' + }, + ); + + xlog $self, "update event using DTEND with same TZID"; + $card =~ s|DURATION:PT3H|DTEND;TZID=Australia/Melbourne:20160831T183000|; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog $self, "make sure an invite is NOT sent to attendee"; + $newdata = $self->{instance}->getnotify(); + @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; + $self->assert_num_equals(0, scalar(@imip)); + + xlog $self, "update event changing TZID on DTEND"; + $card =~ s|DTEND;TZID=Australia/Melbourne|DTEND;TZID=Australia/Sydney|; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog $self, "make sure an invite is sent to attendee"; + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::true, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/invite_switch_implicit_allday_to_dtend b/cassandane/tiny-tests/Caldav/invite_switch_implicit_allday_to_dtend index 0756057056..6a3c7f5769 100644 --- a/cassandane/tiny-tests/Caldav/invite_switch_implicit_allday_to_dtend +++ b/cassandane/tiny-tests/Caldav/invite_switch_implicit_allday_to_dtend @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_switch_implicit_allday_to_dtend - :VirtDomains :min_version_3_7 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_7 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + xlog $self, "create implicit allday event"; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - xlog $self, "make sure an invite is sent to attendee"; - $self->assert_caldav_notified( - { recipient => "friend\@example.com", - is_update => JSON::false, method => 'REQUEST' }, - ); + xlog $self, "make sure an invite is sent to attendee"; + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - xlog $self, "update event using DTEND"; - $card =~ s|DTSTAMP|DTEND;VALUE=DATE:20160901\r\nDTSTAMP|; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + xlog $self, "update event using DTEND"; + $card =~ s|DTSTAMP|DTEND;VALUE=DATE:20160901\r\nDTSTAMP|; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - xlog $self, "make sure an invite is NOT sent to attendee"; - my $newdata = $self->{instance}->getnotify(); - my @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; - $self->assert_num_equals(0, scalar(@imip)); + xlog $self, "make sure an invite is NOT sent to attendee"; + my $newdata = $self->{instance}->getnotify(); + my @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; + $self->assert_num_equals(0, scalar(@imip)); - xlog $self, "update event removing DTEND"; - $card =~ s|DTEND;VALUE=DATE:20160901\r\n||; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + xlog $self, "update event removing DTEND"; + $card =~ s|DTEND;VALUE=DATE:20160901\r\n||; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - xlog $self, "make sure an invite is NOT sent to attendee"; - $newdata = $self->{instance}->getnotify(); - @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; - $self->assert_num_equals(0, scalar(@imip)); + xlog $self, "make sure an invite is NOT sent to attendee"; + $newdata = $self->{instance}->getnotify(); + @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; + $self->assert_num_equals(0, scalar(@imip)); - xlog $self, "update event to be multiple days"; - $card =~ s|DTSTAMP|DTEND;VALUE=DATE:20160902\r\nDTSTAMP|; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + xlog $self, "update event to be multiple days"; + $card =~ s|DTSTAMP|DTEND;VALUE=DATE:20160902\r\nDTSTAMP|; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - xlog $self, "make sure an invite is sent to attendee"; - $newdata = $self->{instance}->getnotify(); - @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; - $self->assert_num_equals(1, scalar(@imip)); + xlog $self, "make sure an invite is sent to attendee"; + $newdata = $self->{instance}->getnotify(); + @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; + $self->assert_num_equals(1, scalar(@imip)); - xlog $self, "update event removing DTEND"; - $card =~ s|DTEND;VALUE=DATE:20160902\r\n||; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + xlog $self, "update event removing DTEND"; + $card =~ s|DTEND;VALUE=DATE:20160902\r\n||; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - xlog $self, "make sure an invite is sent to attendee"; - $newdata = $self->{instance}->getnotify(); - @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; - $self->assert_num_equals(1, scalar(@imip)); + xlog $self, "make sure an invite is sent to attendee"; + $newdata = $self->{instance}->getnotify(); + @imip = grep { $_->{METHOD} eq 'imip' } @$newdata; + $self->assert_num_equals(1, scalar(@imip)); } diff --git a/cassandane/tiny-tests/Caldav/invite_withheader b/cassandane/tiny-tests/Caldav/invite_withheader index b99e20627c..5128142cdf 100644 --- a/cassandane/tiny-tests/Caldav/invite_withheader +++ b/cassandane/tiny-tests/Caldav/invite_withheader @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_invite_withheader - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <{instance}->getnotify(); + my $data = $self->{instance}->getnotify(); - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar', 'Schedule-Address' => 'cassandane@example.net'); + $CalDAV->Request( + 'PUT', $href, $card, + 'Content-Type' => 'text/calendar', + 'Schedule-Address' => 'cassandane@example.net' + ); - my $newdata = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$newdata; - my $payload = decode_json($imip->{MESSAGE}); + my $newdata = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$newdata; + my $payload = decode_json($imip->{MESSAGE}); - $self->assert_str_equals($payload->{recipient}, "friend\@example.com"); + $self->assert_str_equals($payload->{recipient}, "friend\@example.com"); } diff --git a/cassandane/tiny-tests/Caldav/managed_attachment_itip b/cassandane/tiny-tests/Caldav/managed_attachment_itip index 1dbde5159f..6253fd2492 100644 --- a/cassandane/tiny-tests/Caldav/managed_attachment_itip +++ b/cassandane/tiny-tests/Caldav/managed_attachment_itip @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_managed_attachment_itip - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + my $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -27,27 +26,34 @@ SEQUENCE:0 END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $rawIcal, - 'Content-Type' => 'text/calendar'); - my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; + $caldav->Request('PUT', 'Default/test.ics', $rawIcal, + 'Content-Type' => 'text/calendar'); + my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog "Add attachment via CalDAV"; - my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; - my $res = $caldav->ua->post($url, { - headers => { - 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment;filename=test', - 'Prefer' => 'return=representation', - 'Authorization' => $caldav->auth_header(), - }, - content => 'davattach', - }); - $self->assert_str_equals('201', $res->{status}); + xlog "Add attachment via CalDAV"; + my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; + my $res = $caldav->ua->post( + $url, + { + headers => { + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment;filename=test', + 'Prefer' => 'return=representation', + 'Authorization' => $caldav->auth_header(), + }, + content => 'davattach', + } + ); + $self->assert_str_equals('201', $res->{status}); - $self->assert_caldav_notified( - { recipient => 'attendee@local', is_update => JSON::true, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => 'attendee@local', + is_update => JSON::true, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/managed_attachments b/cassandane/tiny-tests/Caldav/managed_attachments index 668fd58f4a..7833fe487e 100644 --- a/cassandane/tiny-tests/Caldav/managed_attachments +++ b/cassandane/tiny-tests/Caldav/managed_attachments @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_managed_attachments - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + my $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -25,96 +24,123 @@ SEQUENCE:0 END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $rawIcal, - 'Content-Type' => 'text/calendar'); - my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; - - xlog "Add attachment via CalDAV"; - my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; - my $res = $caldav->ua->post($url, { - headers => { - 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment;filename=test', - 'Prefer' => 'return=representation', - 'Authorization' => $caldav->auth_header(), - }, - content => 'davattach', - }); - $self->assert_str_equals('201', $res->{status}); - - my $hash = Text::VCardFast::vcard2hash($res->{content}); - my $attach = $hash->{objects}[0]{objects}[0]{properties}{attach}[0]; - - $self->assert_not_null($attach); - $self->assert_str_equals('test', $attach->{params}{filename}[0]); - $self->assert_str_equals('9', $attach->{params}{size}[0]); - $self->assert_str_equals('application/octet-stream', - $attach->{params}{fmttype}[0]); - - my $managedid = $attach->{params}{'managed-id'}[0]; - my $attachHref = $attach->{value}; - - xlog "Fetch new attachment"; - $res = $caldav->ua->request('GET', $attachHref, { - headers => { - 'Authorization' => $caldav->auth_header() - } - }); - $self->assert_str_equals('davattach', $res->{content}); - - xlog "Update attachment via CalDAV"; - $url = $caldav->request_url($eventHref) . '?action=attachment-update&managed-id=' . $managedid; - $res = $caldav->ua->post($url, { - headers => { - 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment;filename=test2', - 'Prefer' => 'return=representation', - 'Authorization' => $caldav->auth_header(), - }, - content => 'davattach2', - }); - $self->assert_str_equals('200', $res->{status}); - - $hash = Text::VCardFast::vcard2hash($res->{content}); - $attach = $hash->{objects}[0]{objects}[0]{properties}{attach}[0]; - - $self->assert_not_null($attach); - $self->assert_str_equals('test2', $attach->{params}{filename}[0]); - $self->assert_str_equals('10', $attach->{params}{size}[0]); - $self->assert_str_equals('application/octet-stream', - $attach->{params}{fmttype}[0]); - - $managedid = $attach->{params}{'managed-id'}[0]; - $attachHref = $attach->{value}; - - xlog "Fetch updated attachment"; - $res = $caldav->ua->request('GET', $attachHref, { - headers => { - 'Authorization' => $caldav->auth_header() - } - }); - $self->assert_str_equals('davattach2', $res->{content}); - - xlog "Delete attachment via CalDAV"; - $url = $caldav->request_url($eventHref) . '?action=attachment-remove&managed-id=' . $managedid; - $res = $caldav->ua->post($url, { - headers => { - 'Prefer' => 'return=representation', - 'Authorization' => $caldav->auth_header(), - }, - }); - $self->assert_str_equals('200', $res->{status}); - - $hash = Text::VCardFast::vcard2hash($res->{content}); - $attach = $hash->{objects}[0]{objects}[0]{properties}{attach}; - - $self->assert_null($attach); - - xlog "Attempt to fetch deleted attachment"; - $res = $caldav->ua->request('GET', $attachHref, { - headers => { - 'Authorization' => $caldav->auth_header() - } - }); - $self->assert_str_equals('404', $res->{status}); + $caldav->Request('PUT', 'Default/test.ics', $rawIcal, + 'Content-Type' => 'text/calendar'); + my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; + + xlog "Add attachment via CalDAV"; + my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; + my $res = $caldav->ua->post( + $url, + { + headers => { + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment;filename=test', + 'Prefer' => 'return=representation', + 'Authorization' => $caldav->auth_header(), + }, + content => 'davattach', + } + ); + $self->assert_str_equals('201', $res->{status}); + + my $hash = Text::VCardFast::vcard2hash($res->{content}); + my $attach = $hash->{objects}[0]{objects}[0]{properties}{attach}[0]; + + $self->assert_not_null($attach); + $self->assert_str_equals('test', $attach->{params}{filename}[0]); + $self->assert_str_equals('9', $attach->{params}{size}[0]); + $self->assert_str_equals('application/octet-stream', + $attach->{params}{fmttype}[0]); + + my $managedid = $attach->{params}{'managed-id'}[0]; + my $attachHref = $attach->{value}; + + xlog "Fetch new attachment"; + $res = $caldav->ua->request( + 'GET', + $attachHref, + { + headers => { + 'Authorization' => $caldav->auth_header() + } + } + ); + $self->assert_str_equals('davattach', $res->{content}); + + xlog "Update attachment via CalDAV"; + $url + = $caldav->request_url($eventHref) + . '?action=attachment-update&managed-id=' + . $managedid; + $res = $caldav->ua->post( + $url, + { + headers => { + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment;filename=test2', + 'Prefer' => 'return=representation', + 'Authorization' => $caldav->auth_header(), + }, + content => 'davattach2', + } + ); + $self->assert_str_equals('200', $res->{status}); + + $hash = Text::VCardFast::vcard2hash($res->{content}); + $attach = $hash->{objects}[0]{objects}[0]{properties}{attach}[0]; + + $self->assert_not_null($attach); + $self->assert_str_equals('test2', $attach->{params}{filename}[0]); + $self->assert_str_equals('10', $attach->{params}{size}[0]); + $self->assert_str_equals('application/octet-stream', + $attach->{params}{fmttype}[0]); + + $managedid = $attach->{params}{'managed-id'}[0]; + $attachHref = $attach->{value}; + + xlog "Fetch updated attachment"; + $res = $caldav->ua->request( + 'GET', + $attachHref, + { + headers => { + 'Authorization' => $caldav->auth_header() + } + } + ); + $self->assert_str_equals('davattach2', $res->{content}); + + xlog "Delete attachment via CalDAV"; + $url + = $caldav->request_url($eventHref) + . '?action=attachment-remove&managed-id=' + . $managedid; + $res = $caldav->ua->post( + $url, + { + headers => { + 'Prefer' => 'return=representation', + 'Authorization' => $caldav->auth_header(), + }, + } + ); + $self->assert_str_equals('200', $res->{status}); + + $hash = Text::VCardFast::vcard2hash($res->{content}); + $attach = $hash->{objects}[0]{objects}[0]{properties}{attach}; + + $self->assert_null($attach); + + xlog "Attempt to fetch deleted attachment"; + $res = $caldav->ua->request( + 'GET', + $attachHref, + { + headers => { + 'Authorization' => $caldav->auth_header() + } + } + ); + $self->assert_str_equals('404', $res->{status}); } diff --git a/cassandane/tiny-tests/Caldav/multiinvite_add_person_changes b/cassandane/tiny-tests/Caldav/multiinvite_add_person_changes index 0f6642a433..ea2eac11ca 100644 --- a/cassandane/tiny-tests/Caldav/multiinvite_add_person_changes +++ b/cassandane/tiny-tests/Caldav/multiinvite_add_person_changes @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_multiinvite_add_person_changes - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'invite2'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'invite2' }); + $self->assert_not_null($CalendarId); - my $uuid = "a684f618-da72-4254-9274-d11f4180696b"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "test1\@example.com", is_update => JSON::false, method => 'REQUEST' }, - { recipient => "test2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "test1\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + { + recipient => "test2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - # add an override instance - $card =~ s/An Event/An Event just us/; - $card =~ s/SEQUENCE:0/SEQUENCE:1/; - my $override = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "test1\@example.com", - is_update => JSON::true, - method => 'REQUEST', - event => { - uid => $uuid, - replyTo => { imip => "mailto:cassandane\@example.com" }, - recurrenceOverrides => { - '2016-06-08T15:30:00' => { - title => "An Event with a different friend", - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test3\@example.com" => { email => "test3\@example.com" }, - }, - }, - }, - start => '2016-06-01T15:30:00', - title => "An Event just us", + $self->assert_caldav_notified( + { + recipient => "test1\@example.com", + is_update => JSON::true, + method => 'REQUEST', + event => { + uid => $uuid, + replyTo => { imip => "mailto:cassandane\@example.com" }, + recurrenceOverrides => { + '2016-06-08T15:30:00' => { + title => "An Event with a different friend", participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test2\@example.com" => { email => "test2\@example.com" }, + "cassandane\@example.com" => + { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test3\@example.com" => { email => "test3\@example.com" }, }, }, }, - { recipient => "test2\@example.com", - is_update => JSON::true, - method => 'REQUEST', - event => { - uid => $uuid, - replyTo => { imip => "mailto:cassandane\@example.com" }, - recurrenceOverrides => { - '2016-06-08T15:30:00' => undef, - }, - start => '2016-06-01T15:30:00', - title => "An Event just us", - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test2\@example.com" => { email => "test2\@example.com" }, - }, - }, + start => '2016-06-01T15:30:00', + title => "An Event just us", + participants => { + "cassandane\@example.com" => { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test2\@example.com" => { email => "test2\@example.com" }, + }, + }, + }, + { + recipient => "test2\@example.com", + is_update => JSON::true, + method => 'REQUEST', + event => { + uid => $uuid, + replyTo => { imip => "mailto:cassandane\@example.com" }, + recurrenceOverrides => { + '2016-06-08T15:30:00' => undef, }, - { recipient => "test3\@example.com", - is_update => JSON::false, - method => 'REQUEST', - event => { - uid => $uuid, - replyTo => { imip => "mailto:cassandane\@example.com" }, - recurrenceOverrides => { - '2016-06-08T15:30:00' => { - title => "An Event with a different friend", - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test3\@example.com" => { email => "test3\@example.com" }, - }, - }, + start => '2016-06-01T15:30:00', + title => "An Event just us", + participants => { + "cassandane\@example.com" => { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test2\@example.com" => { email => "test2\@example.com" }, + }, + }, + }, + { + recipient => "test3\@example.com", + is_update => JSON::false, + method => 'REQUEST', + event => { + uid => $uuid, + replyTo => { imip => "mailto:cassandane\@example.com" }, + recurrenceOverrides => { + '2016-06-08T15:30:00' => { + title => "An Event with a different friend", + participants => { + "cassandane\@example.com" => + { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test3\@example.com" => { email => "test3\@example.com" }, }, }, }, - ); + }, + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/multiinvite_add_person_only b/cassandane/tiny-tests/Caldav/multiinvite_add_person_only index cb16785f39..c2ca36e69a 100644 --- a/cassandane/tiny-tests/Caldav/multiinvite_add_person_only +++ b/cassandane/tiny-tests/Caldav/multiinvite_add_person_only @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_multiinvite_add_person_only - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'invite3'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'invite3' }); + $self->assert_not_null($CalendarId); - my $uuid = "db5c26fd-238f-41e4-a679-54cc9d9c8efc"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "test1\@example.com", is_update => JSON::false, method => 'REQUEST' }, - { recipient => "test2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "test1\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + { + recipient => "test2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - # add an override instance - my $override = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # only test3 is notified - $self->assert_caldav_notified( - { recipient => "test3\@example.com", - is_update => JSON::false, - method => 'REQUEST', - event => { - uid => $uuid, - replyTo => { imip => "mailto:cassandane\@example.com" }, - recurrenceOverrides => { - '2016-06-08T15:30:00' => { - title => "An Event", - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test3\@example.com" => { email => "test3\@example.com" }, - }, - }, + # only test3 is notified + $self->assert_caldav_notified( + { + recipient => "test3\@example.com", + is_update => JSON::false, + method => 'REQUEST', + event => { + uid => $uuid, + replyTo => { imip => "mailto:cassandane\@example.com" }, + recurrenceOverrides => { + '2016-06-08T15:30:00' => { + title => "An Event", + participants => { + "cassandane\@example.com" => + { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test3\@example.com" => { email => "test3\@example.com" }, }, }, }, - ); + }, + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/multiinvite_remove_person_only b/cassandane/tiny-tests/Caldav/multiinvite_remove_person_only index e93a8375c7..5406fc457e 100644 --- a/cassandane/tiny-tests/Caldav/multiinvite_remove_person_only +++ b/cassandane/tiny-tests/Caldav/multiinvite_remove_person_only @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_multiinvite_remove_person_only - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'invite3'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'invite3' }); + $self->assert_not_null($CalendarId); - my $uuid = "db5c26fd-238f-41e4-a679-54cc9d9c8efc"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "test1\@example.com", is_update => JSON::false, method => 'REQUEST' }, - { recipient => "test2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - { recipient => "test3\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "test1\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + { + recipient => "test2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + { + recipient => "test3\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - # add an override instance - my $override = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # only test3 is notified with an RDATE - $self->assert_caldav_notified( - { recipient => "test3\@example.com", - is_update => JSON::true, - method => 'REQUEST', - event => { - uid => $uuid, - replyTo => { imip => "mailto:cassandane\@example.com" }, - recurrenceOverrides => { - '2016-06-08T15:30:00' => undef, - }, - }, + # only test3 is notified with an RDATE + $self->assert_caldav_notified( + { + recipient => "test3\@example.com", + is_update => JSON::true, + method => 'REQUEST', + event => { + uid => $uuid, + replyTo => { imip => "mailto:cassandane\@example.com" }, + recurrenceOverrides => { + '2016-06-08T15:30:00' => undef, }, - ); + }, + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/netcaldavtalktests_fromical b/cassandane/tiny-tests/Caldav/netcaldavtalktests_fromical index 66186a5c27..60990c9470 100644 --- a/cassandane/tiny-tests/Caldav/netcaldavtalktests_fromical +++ b/cassandane/tiny-tests/Caldav/netcaldavtalktests_fromical @@ -2,44 +2,52 @@ use Cassandane::Tiny; sub test_netcaldavtalktests_fromical - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $cassini = Cassandane::Cassini->instance(); - my $basedir = $cassini->val('caldavtalk', 'basedir'); - - unless ($basedir) { - xlog $self, "Not running test, no caldavtalk"; - return; - } - - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); - - my $Calendar = $CalDAV->GetCalendar($CalendarId); - - my $testdir = "$basedir/testdata"; - opendir(DH, $testdir); - my @list; - while (my $item = readdir(DH)) { - next unless $item =~ m/(.*).ics/; - push @list, $1; - } - closedir(DH); - - foreach my $name (sort @list) { - my $ical = slurp($testdir, $name, 'ics'); - my $api = slurp($testdir, $name, 'je'); - my $data = decode_json($api); - my $uid = $data->[0]{uid}; - - xlog $self, "put $name as text/calendar and fetch back as JSON"; - $CalDAV->Request("PUT", "$CalendarId/$uid.ics", $ical, 'Content-Type' => 'text/calendar'); - my $serverapi = $CalDAV->Request("GET", "$CalendarId/$uid.ics", '', 'Accept' => 'application/event+json'); - my $serverdata = decode_json($serverapi->{content}); - $self->assert_deep_equals($CalDAV->NormaliseEvent($data->[0]), $CalDAV->NormaliseEvent($serverdata)); - } + : min_version_3_1 : needs_component_httpd { + my ($self) = @_; + + my $CalDAV = $self->{caldav}; + + my $cassini = Cassandane::Cassini->instance(); + my $basedir = $cassini->val('caldavtalk', 'basedir'); + + unless ($basedir) { + xlog $self, "Not running test, no caldavtalk"; + return; + } + + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); + + my $Calendar = $CalDAV->GetCalendar($CalendarId); + + my $testdir = "$basedir/testdata"; + opendir(DH, $testdir); + my @list; + while (my $item = readdir(DH)) { + next unless $item =~ m/(.*).ics/; + push @list, $1; + } + closedir(DH); + + foreach my $name (sort @list) { + my $ical = slurp($testdir, $name, 'ics'); + my $api = slurp($testdir, $name, 'je'); + my $data = decode_json($api); + my $uid = $data->[0]{uid}; + + xlog $self, "put $name as text/calendar and fetch back as JSON"; + $CalDAV->Request( + "PUT", "$CalendarId/$uid.ics", + $ical, 'Content-Type' => 'text/calendar' + ); + my $serverapi = $CalDAV->Request( + "GET", "$CalendarId/$uid.ics", + '', 'Accept' => 'application/event+json' + ); + my $serverdata = decode_json($serverapi->{content}); + $self->assert_deep_equals( + $CalDAV->NormaliseEvent($data->[0]), + $CalDAV->NormaliseEvent($serverdata) + ); + } } diff --git a/cassandane/tiny-tests/Caldav/netcaldavtalktests_fromje b/cassandane/tiny-tests/Caldav/netcaldavtalktests_fromje index f0473adbcf..a0c8f58d89 100644 --- a/cassandane/tiny-tests/Caldav/netcaldavtalktests_fromje +++ b/cassandane/tiny-tests/Caldav/netcaldavtalktests_fromje @@ -2,43 +2,49 @@ use Cassandane::Tiny; sub test_netcaldavtalktests_fromje - :min_version_3_1 :needs_component_httpd -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $cassini = Cassandane::Cassini->instance(); - my $basedir = $cassini->val('caldavtalk', 'basedir'); - - unless ($basedir) { - xlog $self, "Not running test, no caldavtalk"; - return; - } - - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); - - my $Calendar = $CalDAV->GetCalendar($CalendarId); - - my $testdir = "$basedir/testdata"; - opendir(DH, $testdir); - my @list; - while (my $item = readdir(DH)) { - next unless $item =~ m/(.*).ics/; - push @list, $1; - } - closedir(DH); - - foreach my $name (sort @list) { - my $api = slurp($testdir, $name, 'je'); - my $data = decode_json($api); - my $uid = $data->[0]{uid}; - - xlog $self, "put $name as application/event+json and fetch back as JSON"; - $CalDAV->Request("PUT", "$CalendarId/$uid.ics", $api, 'Content-Type' => 'application/event+json'); - my $serverapi = $CalDAV->Request("GET", "$CalendarId/$uid.ics", '', 'Accept' => 'application/event+json'); - my $serverdata = decode_json($serverapi->{content}); - $self->assert_deep_equals($CalDAV->NormaliseEvent($data->[0]), $CalDAV->NormaliseEvent($serverdata)); - } + : min_version_3_1 : needs_component_httpd { + my ($self) = @_; + + my $CalDAV = $self->{caldav}; + + my $cassini = Cassandane::Cassini->instance(); + my $basedir = $cassini->val('caldavtalk', 'basedir'); + + unless ($basedir) { + xlog $self, "Not running test, no caldavtalk"; + return; + } + + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); + + my $Calendar = $CalDAV->GetCalendar($CalendarId); + + my $testdir = "$basedir/testdata"; + opendir(DH, $testdir); + my @list; + while (my $item = readdir(DH)) { + next unless $item =~ m/(.*).ics/; + push @list, $1; + } + closedir(DH); + + foreach my $name (sort @list) { + my $api = slurp($testdir, $name, 'je'); + my $data = decode_json($api); + my $uid = $data->[0]{uid}; + + xlog $self, "put $name as application/event+json and fetch back as JSON"; + $CalDAV->Request("PUT", "$CalendarId/$uid.ics", $api, + 'Content-Type' => 'application/event+json'); + my $serverapi = $CalDAV->Request( + "GET", "$CalendarId/$uid.ics", + '', 'Accept' => 'application/event+json' + ); + my $serverdata = decode_json($serverapi->{content}); + $self->assert_deep_equals( + $CalDAV->NormaliseEvent($data->[0]), + $CalDAV->NormaliseEvent($serverdata) + ); + } } diff --git a/cassandane/tiny-tests/Caldav/propfind_principal b/cassandane/tiny-tests/Caldav/propfind_principal index 9f9dea67f3..deaed8d958 100644 --- a/cassandane/tiny-tests/Caldav/propfind_principal +++ b/cassandane/tiny-tests/Caldav/propfind_principal @@ -2,35 +2,35 @@ use Cassandane::Tiny; sub test_propfind_principal - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.reallyprivateuser"); - $admintalk->setacl("user.reallyprivateuser", "reallyprivateuser" => "lrswipkxtecda"); + $admintalk->create("user.reallyprivateuser"); + $admintalk->setacl("user.reallyprivateuser", + "reallyprivateuser" => "lrswipkxtecda"); - my $service = $self->{instance}->get_service("http"); - my $caltalk = Net::CalDAVTalk->new( - user => "reallyprivateuser", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $caltalk = Net::CalDAVTalk->new( + user => "reallyprivateuser", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "create calendar"; - my $CalendarId = $caltalk->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $caltalk->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - xlog $self, "principal property search"; + xlog $self, "principal property search"; - my $xml = < @@ -45,7 +45,11 @@ sub test_propfind_principal EOF - my $res = $CalDAV->Request('REPORT', '/dav/principals', $xml, Depth => 0, 'Content-Type' => 'text/xml'); - my $text = Dumper($res); - $self->assert_does_not_match(qr/reallyprivateuser/, $text); + my $res = $CalDAV->Request( + 'REPORT', '/dav/principals', $xml, + Depth => 0, + 'Content-Type' => 'text/xml' + ); + my $text = Dumper($res); + $self->assert_does_not_match(qr/reallyprivateuser/, $text); } diff --git a/cassandane/tiny-tests/Caldav/put_changes_etag b/cassandane/tiny-tests/Caldav/put_changes_etag index fd45d37fd5..5a20efbd59 100644 --- a/cassandane/tiny-tests/Caldav/put_changes_etag +++ b/cassandane/tiny-tests/Caldav/put_changes_etag @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_put_changes_etag - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $href = "$CalendarId/uid1.ics"; - my $card = < 'text/calendar', + 'Content-Type' => 'text/calendar', 'Authorization' => $CalDAV->auth_header(), ); - my $Response = $CalDAV->{ua}->request('PUT', $CalDAV->request_url($href), { - content => $card, - headers => \%Headers, - }); + my $Response = $CalDAV->{ua}->request( + 'PUT', + $CalDAV->request_url($href), + { + content => $card, + headers => \%Headers, + } + ); $self->assert_num_equals(201, $Response->{status}); my $etag = $Response->{headers}{etag}; $self->assert_not_null($etag); - $Response = $CalDAV->{ua}->request('HEAD', $CalDAV->request_url($href), { - headers => \%Headers, - }); + $Response = $CalDAV->{ua}->request( + 'HEAD', + $CalDAV->request_url($href), + { + headers => \%Headers, + } + ); # the etag shouldn't have changed $self->assert_num_equals(200, $Response->{status}); @@ -57,10 +64,14 @@ EOF $card =~ s/HasUID1/HasUID2/s; - $Response = $CalDAV->{ua}->request('PUT', $CalDAV->request_url($href), { - content => $card, - headers => \%Headers, - }); + $Response = $CalDAV->{ua}->request( + 'PUT', + $CalDAV->request_url($href), + { + content => $card, + headers => \%Headers, + } + ); # no content, we're replacing a thing $self->assert_num_equals(204, $Response->{status}); @@ -70,9 +81,13 @@ EOF # the content has changed, so the etag MUST change $self->assert_str_not_equals($etag, $etag3); - $Response = $CalDAV->{ua}->request('HEAD', $CalDAV->request_url($href), { - headers => \%Headers, - }); + $Response = $CalDAV->{ua}->request( + 'HEAD', + $CalDAV->request_url($href), + { + headers => \%Headers, + } + ); # the etag shouldn't have changed again $self->assert_num_equals(200, $Response->{status}); diff --git a/cassandane/tiny-tests/Caldav/put_control_char b/cassandane/tiny-tests/Caldav/put_control_char index cbdd35b3e0..dfe5d6d565 100644 --- a/cassandane/tiny-tests/Caldav/put_control_char +++ b/cassandane/tiny-tests/Caldav/put_control_char @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_put_control_char - :min_version_3_9 :needs_component_httpd :needs_ical_ctrl :MagicPlus -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : min_version_3_9 : needs_component_httpd : needs_ical_ctrl : MagicPlus { + my ($self) = @_; + my $caldav = $self->{caldav}; - # Assert that CONTROL chars are omitted when reading - # iCalendar data during PUT. + # Assert that CONTROL chars are omitted when reading + # iCalendar data during PUT. - my $ical = <Request('PUT', '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - my $res = $caldav->Request('GET', '/dav/calendars/user/cassandane/Default/test.ics'); - $self->assert_matches(qr/DESCRIPTION:ctrl/, $res->{content}); + my $res = $caldav->Request('GET', + '/dav/calendars/user/cassandane/Default/test.ics'); + $self->assert_matches(qr/DESCRIPTION:ctrl/, $res->{content}); } diff --git a/cassandane/tiny-tests/Caldav/put_date_with_tzid b/cassandane/tiny-tests/Caldav/put_date_with_tzid index 0d4bb1b67a..c2bdf63716 100644 --- a/cassandane/tiny-tests/Caldav/put_date_with_tzid +++ b/cassandane/tiny-tests/Caldav/put_date_with_tzid @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_put_date_with_tzid - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $href = "$CalendarId/datewith.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); } diff --git a/cassandane/tiny-tests/Caldav/put_nouid b/cassandane/tiny-tests/Caldav/put_nouid index a29b5791ac..f4c9b9a945 100644 --- a/cassandane/tiny-tests/Caldav/put_nouid +++ b/cassandane/tiny-tests/Caldav/put_nouid @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_put_nouid - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $href = "$CalendarId/nouid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar') }; - my $Err = $@; - $self->assert_matches(qr/valid-calendar-object-resource/, $Err); + eval { + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + }; + my $Err = $@; + $self->assert_matches(qr/valid-calendar-object-resource/, $Err); } diff --git a/cassandane/tiny-tests/Caldav/put_strip_scheduleforcesend b/cassandane/tiny-tests/Caldav/put_strip_scheduleforcesend index 9d2a14e2c2..061954ffb0 100644 --- a/cassandane/tiny-tests/Caldav/put_strip_scheduleforcesend +++ b/cassandane/tiny-tests/Caldav/put_strip_scheduleforcesend @@ -2,24 +2,23 @@ use Cassandane::Tiny; sub test_put_strip_scheduleforcesend - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "Default/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my $response = $CalDAV->Request('GET', $href); - $self->assert(not ($response->{content} =~ m/SCHEDULE-FORCE-SEND/)); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + my $response = $CalDAV->Request('GET', $href); + $self->assert(not($response->{content} =~ m/SCHEDULE-FORCE-SEND/)); } diff --git a/cassandane/tiny-tests/Caldav/put_toolarge b/cassandane/tiny-tests/Caldav/put_toolarge index ae93572439..47f2420012 100644 --- a/cassandane/tiny-tests/Caldav/put_toolarge +++ b/cassandane/tiny-tests/Caldav/put_toolarge @@ -2,19 +2,18 @@ use Cassandane::Tiny; sub test_put_toolarge - :min_version_3_5 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_5 : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid = "d4643cf9-4552-4a3e-8d6c-5f318bcc5b79"; - my $href = "$CalendarId/$uuid.ics"; - my $desc = ('x') x 100000; - my $event = <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar') }; - my $Err = $@; - $self->assert_matches(qr/max-resource-size/, $Err); + eval { + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + }; + my $Err = $@; + $self->assert_matches(qr/max-resource-size/, $Err); } diff --git a/cassandane/tiny-tests/Caldav/put_usedefaultalerts_no_etag b/cassandane/tiny-tests/Caldav/put_usedefaultalerts_no_etag index 4589fd4ad5..681c449b18 100644 --- a/cassandane/tiny-tests/Caldav/put_usedefaultalerts_no_etag +++ b/cassandane/tiny-tests/Caldav/put_usedefaultalerts_no_etag @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_put_usedefaultalerts_no_etag - :min_version_3_7 -{ - my ($self) = @_; + : min_version_3_7 { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "PROPPATCH default alarms on the calendar"; - my $proppatchXml = < @@ -27,16 +26,18 @@ END:VALARM EOF - $caldav->Request('PROPPATCH', "/dav/calendars/user/cassandane/Default", - $proppatchXml, 'Content-Type' => 'text/xml'); + $caldav->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane/Default", + $proppatchXml, 'Content-Type' => 'text/xml' + ); - my %Headers = ( - 'Content-Type' => 'text/calendar', - 'Authorization' => $caldav->auth_header(), - ); + my %Headers = ( + 'Content-Type' => 'text/calendar', + 'Authorization' => $caldav->auth_header(), + ); - xlog "PUT event with useDefaultAlerts set"; - my $ical = <<'EOF'; + xlog "PUT event with useDefaultAlerts set"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -53,22 +54,30 @@ X-JMAP-USEDEFAULTALERTS;VALUE=BOOLEAN:TRUE END:VEVENT END:VCALENDAR EOF - my $href = $caldav->request_url('/dav/calendars/user/cassandane/Default/test1.ics'); - my $res = $caldav->{ua}->request('PUT', $href, { - content => $ical, headers => \%Headers, - }); + my $href + = $caldav->request_url('/dav/calendars/user/cassandane/Default/test1.ics'); + my $res = $caldav->{ua}->request( + 'PUT', $href, + { + content => $ical, + headers => \%Headers, + } + ); - xlog "Assert no ETag is returned"; - $self->assert_null($res->{headers}{etag}); + xlog "Assert no ETag is returned"; + $self->assert_null($res->{headers}{etag}); - xlog "Assert ETag is returned for HEAD"; - $res = $caldav->{ua}->request('HEAD', $href, { - headers => \%Headers, - }); - $self->assert_not_null($res->{headers}{etag}); + xlog "Assert ETag is returned for HEAD"; + $res = $caldav->{ua}->request( + 'HEAD', $href, + { + headers => \%Headers, + } + ); + $self->assert_not_null($res->{headers}{etag}); - xlog "PUT event without useDefaultAlerts set"; - $ical = <<'EOF'; + xlog "PUT event without useDefaultAlerts set"; + $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -84,18 +93,26 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $href = $caldav->request_url('/dav/calendars/user/cassandane/Default/test2.ics'); - $res = $caldav->{ua}->request('PUT', $href, { - content => $ical, headers => \%Headers, - }); + $href + = $caldav->request_url('/dav/calendars/user/cassandane/Default/test2.ics'); + $res = $caldav->{ua}->request( + 'PUT', $href, + { + content => $ical, + headers => \%Headers, + } + ); - xlog "Assert ETag is returned"; - my $etag = $res->{headers}{etag}; - $self->assert_not_null($etag); + xlog "Assert ETag is returned"; + my $etag = $res->{headers}{etag}; + $self->assert_not_null($etag); - xlog "Assert ETag matches for HEAD"; - $res = $caldav->{ua}->request('HEAD', $href, { - headers => \%Headers, - }); - $self->assert_str_equals($etag, $res->{headers}{etag}); + xlog "Assert ETag matches for HEAD"; + $res = $caldav->{ua}->request( + 'HEAD', $href, + { + headers => \%Headers, + } + ); + $self->assert_str_equals($etag, $res->{headers}{etag}); } diff --git a/cassandane/tiny-tests/Caldav/recurring_freebusy b/cassandane/tiny-tests/Caldav/recurring_freebusy index 0625fbda38..893d704989 100644 --- a/cassandane/tiny-tests/Caldav/recurring_freebusy +++ b/cassandane/tiny-tests/Caldav/recurring_freebusy @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_recurring_freebusy - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4319-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my ($Data) = $CalDAV->GetFreeBusy($CalendarId); + my ($Data) = $CalDAV->GetFreeBusy($CalendarId); - $self->assert(@$Data > 50); - $self->assert_str_equals("Etc/UTC", $Data->[0]{timeZone}); - $self->assert_str_equals("Etc/UTC", $Data->[1]{timeZone}); - $self->assert_str_equals("Etc/UTC", $Data->[2]{timeZone}); - # etc - $self->assert_str_equals("2016-08-31T05:30:00", $Data->[0]{start}); - $self->assert_str_equals("2016-09-14T06:30:00", $Data->[1]{start}); - $self->assert_str_equals("2016-09-21T05:30:00", $Data->[2]{start}); - # and so on - $self->assert_str_equals("PT3H", $Data->[0]{duration}); - $self->assert_str_equals("PT2H", $Data->[1]{duration}); - $self->assert_str_equals("PT3H", $Data->[2]{duration}); + $self->assert(@$Data > 50); + $self->assert_str_equals("Etc/UTC", $Data->[0]{timeZone}); + $self->assert_str_equals("Etc/UTC", $Data->[1]{timeZone}); + $self->assert_str_equals("Etc/UTC", $Data->[2]{timeZone}); + # etc + $self->assert_str_equals("2016-08-31T05:30:00", $Data->[0]{start}); + $self->assert_str_equals("2016-09-14T06:30:00", $Data->[1]{start}); + $self->assert_str_equals("2016-09-21T05:30:00", $Data->[2]{start}); + # and so on + $self->assert_str_equals("PT3H", $Data->[0]{duration}); + $self->assert_str_equals("PT2H", $Data->[1]{duration}); + $self->assert_str_equals("PT3H", $Data->[2]{duration}); } diff --git a/cassandane/tiny-tests/Caldav/remove_oneattendee_recurring b/cassandane/tiny-tests/Caldav/remove_oneattendee_recurring index aeecd82e6c..025854457d 100644 --- a/cassandane/tiny-tests/Caldav/remove_oneattendee_recurring +++ b/cassandane/tiny-tests/Caldav/remove_oneattendee_recurring @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_remove_oneattendee_recurring - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "recurring event"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - my $overrides = <{instance}->getnotify(); + my $overrides = <_put_event($CalendarId, uuid => $uuid, lines => < $overrides); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < $overrides); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test1\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test2\@example.com @@ -48,56 +50,58 @@ RRULE:FREQ=WEEKLY ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified( - { - method => 'REQUEST', - recipient => "test1\@example.com", - is_update => JSON::true, - event => { - recurrenceOverrides => { - '2016-06-08T15:30:00' => { - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test3\@example.com" => { email => "test3\@example.com" }, - }, - start => '2016-06-08T16:00:00', - }, - }, - }, + $self->assert_caldav_notified( + { + method => 'REQUEST', + recipient => "test1\@example.com", + is_update => JSON::true, + event => { + recurrenceOverrides => { + '2016-06-08T15:30:00' => { + participants => { + "cassandane\@example.com" => + { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test3\@example.com" => { email => "test3\@example.com" }, + }, + start => '2016-06-08T16:00:00', }, - { - method => 'REQUEST', - recipient => "test2\@example.com", - is_update => JSON::true, - event => { - start => '2016-06-01T15:30:00', - recurrenceOverrides => { '2016-06-08T15:30:00' => undef }, - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test2\@example.com" => { email => "test2\@example.com" }, - "test3\@example.com" => { email => "test3\@example.com" }, - }, - }, + }, + }, + }, + { + method => 'REQUEST', + recipient => "test2\@example.com", + is_update => JSON::true, + event => { + start => '2016-06-01T15:30:00', + recurrenceOverrides => { '2016-06-08T15:30:00' => undef }, + participants => { + "cassandane\@example.com" => { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test2\@example.com" => { email => "test2\@example.com" }, + "test3\@example.com" => { email => "test3\@example.com" }, + }, + }, + }, + { + method => 'REQUEST', + recipient => "test3\@example.com", + is_update => JSON::true, + event => { + recurrenceOverrides => { + '2016-06-08T15:30:00' => { + participants => { + "cassandane\@example.com" => + { email => "cassandane\@example.com" }, + "test1\@example.com" => { email => "test1\@example.com" }, + "test3\@example.com" => { email => "test3\@example.com" }, + }, + start => '2016-06-08T16:00:00', }, - { - method => 'REQUEST', - recipient => "test3\@example.com", - is_update => JSON::true, - event => { - recurrenceOverrides => { - '2016-06-08T15:30:00' => { - participants => { - "cassandane\@example.com" => { email => "cassandane\@example.com" }, - "test1\@example.com" => { email => "test1\@example.com" }, - "test3\@example.com" => { email => "test3\@example.com" }, - }, - start => '2016-06-08T16:00:00', - }, - }, - }, - }, - ); - } + }, + }, + }, + ); + } } diff --git a/cassandane/tiny-tests/Caldav/rename b/cassandane/tiny-tests/Caldav/rename index 6a9d7a6118..a83c1817fe 100644 --- a/cassandane/tiny-tests/Caldav/rename +++ b/cassandane/tiny-tests/Caldav/rename @@ -2,31 +2,30 @@ use Cassandane::Tiny; sub test_rename - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - xlog $self, "create calendar"; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - xlog $self, "fetch again"; - my $Calendar = $CalDAV->GetCalendar($CalendarId); - $self->assert_not_null($Calendar); + xlog $self, "fetch again"; + my $Calendar = $CalDAV->GetCalendar($CalendarId); + $self->assert_not_null($Calendar); - xlog $self, "check name matches"; - $self->assert_str_equals('foo', $Calendar->{name}); + xlog $self, "check name matches"; + $self->assert_str_equals('foo', $Calendar->{name}); - xlog $self, "change name"; - my $NewId = $CalDAV->UpdateCalendar({ id => $CalendarId, name => 'bar'}); - $self->assert_str_equals($CalendarId, $NewId); + xlog $self, "change name"; + my $NewId = $CalDAV->UpdateCalendar({ id => $CalendarId, name => 'bar' }); + $self->assert_str_equals($CalendarId, $NewId); - xlog $self, "fetch again"; - my $NewCalendar = $CalDAV->GetCalendar($NewId); - $self->assert_not_null($NewCalendar); + xlog $self, "fetch again"; + my $NewCalendar = $CalDAV->GetCalendar($NewId); + $self->assert_not_null($NewCalendar); - xlog $self, "check new name stuck"; - $self->assert_str_equals('bar', $NewCalendar->{name}); + xlog $self, "check new name stuck"; + $self->assert_str_equals('bar', $NewCalendar->{name}); } diff --git a/cassandane/tiny-tests/Caldav/replication_delete b/cassandane/tiny-tests/Caldav/replication_delete index 78f502fe54..6d91404a09 100644 --- a/cassandane/tiny-tests/Caldav/replication_delete +++ b/cassandane/tiny-tests/Caldav/replication_delete @@ -2,20 +2,19 @@ use Cassandane::Tiny; sub test_replication_delete - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - my $href = "$CalendarId/event1.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - my $response = $CalDAV->Request('GET', $href); - my $value = $response->{content}; - $self->assert_matches(qr/An Event/, $value); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + my $response = $CalDAV->Request('GET', $href); + my $value = $response->{content}; + $self->assert_matches(qr/An Event/, $value); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - $CalDAV->DeleteCalendar($CalendarId); + $CalDAV->DeleteCalendar($CalendarId); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); } diff --git a/cassandane/tiny-tests/Caldav/reply b/cassandane/tiny-tests/Caldav/reply index c440f8a023..f7f7a691f2 100644 --- a/cassandane/tiny-tests/Caldav/reply +++ b/cassandane/tiny-tests/Caldav/reply @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_reply - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # we don't say anything when we add a NEEDS-ACTION item - $self->assert_caldav_notified(); + # we don't say anything when we add a NEEDS-ACTION item + $self->assert_caldav_notified(); - $card =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $card =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # we sent a reply - $self->assert_caldav_notified( - { - method => 'REPLY', - recipient => 'friend@example.com', - event => { - participants => { - 'cassandane@example.com' => { - 'scheduleStatus' => 'accepted', - 'email' => 'cassandane@example.com' - }, - }, - }, + # we sent a reply + $self->assert_caldav_notified( + { + method => 'REPLY', + recipient => 'friend@example.com', + event => { + participants => { + 'cassandane@example.com' => { + 'scheduleStatus' => 'accepted', + 'email' => 'cassandane@example.com' + }, }, - ); + }, + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/reply_scheduleaddress b/cassandane/tiny-tests/Caldav/reply_scheduleaddress index 34bc9523af..4997b17bb7 100644 --- a/cassandane/tiny-tests/Caldav/reply_scheduleaddress +++ b/cassandane/tiny-tests/Caldav/reply_scheduleaddress @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_reply_scheduleaddress - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar', 'Schedule-Address' => 'othercas@example.com'); + $CalDAV->Request( + 'PUT', $href, $card, + 'Content-Type' => 'text/calendar', + 'Schedule-Address' => 'othercas@example.com' + ); - # we don't say anything when we add a NEEDS-ACTION item - $self->assert_caldav_notified(); + # we don't say anything when we add a NEEDS-ACTION item + $self->assert_caldav_notified(); - $card =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar', 'Schedule-Address' => 'othercas@example.com'); + $card =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; + $CalDAV->Request( + 'PUT', $href, $card, + 'Content-Type' => 'text/calendar', + 'Schedule-Address' => 'othercas@example.com' + ); - # we sent a reply from the correct address - $self->assert_caldav_notified( - { - method => 'REPLY', - recipient => 'friend@example.com', - event => { - participants => { - 'othercas@example.com' => { - 'scheduleStatus' => 'accepted', - 'email' => 'othercas@example.com', - }, - }, - }, + # we sent a reply from the correct address + $self->assert_caldav_notified( + { + method => 'REPLY', + recipient => 'friend@example.com', + event => { + participants => { + 'othercas@example.com' => { + 'scheduleStatus' => 'accepted', + 'email' => 'othercas@example.com', + }, }, - ); + }, + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/reply_withothers b/cassandane/tiny-tests/Caldav/reply_withothers index 646bcf2f20..4b6829140e 100644 --- a/cassandane/tiny-tests/Caldav/reply_withothers +++ b/cassandane/tiny-tests/Caldav/reply_withothers @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_reply_withothers - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'hello'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'hello' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # we don't say anything when we add a NEEDS-ACTION item - $self->assert_caldav_notified(); + # we don't say anything when we add a NEEDS-ACTION item + $self->assert_caldav_notified(); - $card =~ s/PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:cassandane/PARTSTAT=ACCEPTED:MAILTO:cassandane/; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $card =~ + s/PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:cassandane/PARTSTAT=ACCEPTED:MAILTO:cassandane/; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # we sent a reply - $self->assert_caldav_notified( - { - method => 'REPLY', - recipient => 'friend@example.com', - event => { - participants => { - 'cassandane@example.com' => { - 'scheduleStatus' => 'accepted', - 'email' => 'cassandane@example.com' - }, - }, - }, + # we sent a reply + $self->assert_caldav_notified( + { + method => 'REPLY', + recipient => 'friend@example.com', + event => { + participants => { + 'cassandane@example.com' => { + 'scheduleStatus' => 'accepted', + 'email' => 'cassandane@example.com' + }, }, - ); + }, + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_1_create b/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_1_create index 6f1eb1e7f9..74568ba123 100644 --- a/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_1_create +++ b/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_1_create @@ -2,53 +2,68 @@ use Cassandane::Tiny; sub test_rfc6638_3_2_1_1_create - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "default schedule agent -> REQUEST"; - $self->_put_event($CalendarId, lines => < REQUEST"; + $self->_put_event($CalendarId, lines => <assert_caldav_notified( - { recipient => "test1\@example.com", is_update => JSON::false, method => 'REQUEST' }, - { recipient => "test2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "test1\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + { + recipient => "test2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - xlog $self, "schedule agent SERVER -> REQUEST"; - $self->_put_event($CalendarId, lines => < REQUEST"; + $self->_put_event($CalendarId, lines => <assert_caldav_notified( - { recipient => "test1\@example.com", is_update => JSON::false, method => 'REQUEST' }, - { recipient => "test2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "test1\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + { + recipient => "test2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); - xlog $self, "schedule agent CLIENT -> nothing"; - $self->_put_event($CalendarId, lines => < nothing"; + $self->_put_event($CalendarId, lines => <assert_caldav_notified(); + $self->assert_caldav_notified(); - xlog $self, "schedule agent NONE -> nothing"; - $self->_put_event($CalendarId, lines => < nothing"; + $self->_put_event($CalendarId, lines => <assert_caldav_notified(); + $self->assert_caldav_notified(); } diff --git a/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_2_modify b/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_2_modify index caca761bef..6ca25735b4 100644 --- a/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_2_modify +++ b/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_2_modify @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_rfc6638_3_2_1_2_modify - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); # 4 x 4 matrix: # +---------------+-----------------------------------------------+ @@ -30,305 +29,357 @@ sub test_rfc6638_3_2_1_2_modify # | | | | ADD | | | # +---+-----------+-----------+-----------+-----------+-----------+ - xlog $self, " / "; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < / "; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, " / SERVER"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < / SERVER"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified( - { recipient => "test1\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); - } + $self->assert_caldav_notified( + { + recipient => "test1\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); + } - xlog $self, " / CLIENT"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < / CLIENT"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-AGENT=CLIENT:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, " / NONE"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < / NONE"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-AGENT=NONE:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, "SERVER / "; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified( - { recipient => "test1\@example.com", method => 'CANCEL' }, - ); - } + $self->assert_caldav_notified( + { recipient => "test1\@example.com", method => 'CANCEL' },); + } - xlog $self, "SERVER / SERVER"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified( - { recipient => "test1\@example.com", is_update => JSON::true }, - ); - } + $self->assert_caldav_notified( + { recipient => "test1\@example.com", is_update => JSON::true },); + } - xlog $self, "SERVER / CLIENT"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-AGENT=CLIENT:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified( - { recipient => "test1\@example.com", method => 'CANCEL' }, - ); - } + $self->assert_caldav_notified( + { recipient => "test1\@example.com", method => 'CANCEL' },); + } - xlog $self, "SERVER / NONE"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-AGENT=NONE:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified( - { recipient => "test1\@example.com", method => 'CANCEL' }, - ); - } + $self->assert_caldav_notified( + { recipient => "test1\@example.com", method => 'CANCEL' },); + } - xlog $self, "CLIENT / "; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, "CLIENT / SERVER"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - # XXX - should be a new request is_update => true - $self->assert_caldav_notified( - #{ recipient => "test1\@example.com", is_update => JSON::true, method => 'REQUEST' }, - { recipient => "test1\@example.com", method => 'REQUEST' }, - ); - } + # XXX - should be a new request is_update => true + $self->assert_caldav_notified( +#{ recipient => "test1\@example.com", is_update => JSON::true, method => 'REQUEST' }, + { recipient => "test1\@example.com", method => 'REQUEST' }, + ); + } - xlog $self, "CLIENT / CLIENT"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-AGENT=CLIENT:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, "CLIENT / NONE"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-AGENT=NONE:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, "NONE / "; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, "NONE / SERVER"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - # XXX - should be a new request is_update => true - $self->assert_caldav_notified( - #{ recipient => "test1\@example.com", is_update => JSON::true, method => 'REQUEST' }, - { recipient => "test1\@example.com", method => 'REQUEST' }, - ); - } + # XXX - should be a new request is_update => true + $self->assert_caldav_notified( +#{ recipient => "test1\@example.com", is_update => JSON::true, method => 'REQUEST' }, + { recipient => "test1\@example.com", method => 'REQUEST' }, + ); + } - xlog $self, "NONE / CLIENT"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-AGENT=CLIENT:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, "NONE / NONE"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); + $self->{instance}->getnotify(); + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-AGENT=NONE:MAILTO:test1\@example.com ORGANIZER;CN=Test User:MAILTO:cassandane\@example.com EOF - $self->assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - # XXX - check that the SCHEDULE-STATUS property is set correctly... + # XXX - check that the SCHEDULE-STATUS property is set correctly... - xlog $self, "Forbidden organizer change"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - eval { $self->_put_event($CalendarId, uuid => $uuid, lines => < "update"); }; + $self->{instance}->getnotify(); + eval { + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "update"); }; ATTENDEE;CN=Test User;PARTSTAT=ACCEPTED:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:test1\@example.com ORGANIZER:MAILTO:test1\@example.com EOF - my $err = $@; - $self->assert_matches(qr/allowed-attendee-scheduling-object-change/, $err); - } + my $err = $@; + $self->assert_matches(qr/allowed-attendee-scheduling-object-change/, $err); + } } diff --git a/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_3_remove b/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_3_remove index 848ab98783..1e7302fab0 100644 --- a/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_3_remove +++ b/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_3_remove @@ -2,68 +2,65 @@ use Cassandane::Tiny; sub test_rfc6638_3_2_1_3_remove - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "default => CANCEL"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < CANCEL"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $CalDAV->Request('DELETE', "$CalendarId/$uuid.ics"); - $self->assert_caldav_notified( - { recipient => "test1\@example.com", method => 'CANCEL' }, - ); - } + $self->{instance}->getnotify(); + $CalDAV->Request('DELETE', "$CalendarId/$uuid.ics"); + $self->assert_caldav_notified( + { recipient => "test1\@example.com", method => 'CANCEL' },); + } - xlog $self, "SERVER => CANCEL"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < CANCEL"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $CalDAV->Request('DELETE', "$CalendarId/$uuid.ics"); - $self->assert_caldav_notified( - { recipient => "test1\@example.com", method => 'CANCEL' }, - ); - } + $self->{instance}->getnotify(); + $CalDAV->Request('DELETE', "$CalendarId/$uuid.ics"); + $self->assert_caldav_notified( + { recipient => "test1\@example.com", method => 'CANCEL' },); + } - xlog $self, "CLIENT => nothing"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < nothing"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $CalDAV->Request('DELETE', "$CalendarId/$uuid.ics"); - $self->assert_caldav_notified(); - } + $self->{instance}->getnotify(); + $CalDAV->Request('DELETE', "$CalendarId/$uuid.ics"); + $self->assert_caldav_notified(); + } - xlog $self, "NONE => nothing"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => < nothing"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $CalDAV->Request('DELETE', "$CalendarId/$uuid.ics"); - $self->assert_caldav_notified(); - } + $self->{instance}->getnotify(); + $CalDAV->Request('DELETE', "$CalendarId/$uuid.ics"); + $self->assert_caldav_notified(); + } } diff --git a/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_setpartstat_agentclient b/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_setpartstat_agentclient index 1c8a017029..152bcddaf5 100644 --- a/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_setpartstat_agentclient +++ b/cassandane/tiny-tests/Caldav/rfc6638_3_2_1_setpartstat_agentclient @@ -2,22 +2,26 @@ use Cassandane::Tiny; sub test_rfc6638_3_2_1_setpartstat_agentclient - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "attempt to set the partstat to something other than NEEDS-ACTION, agent was client"; - $self->_put_event($CalendarId, lines => <_put_event($CalendarId, lines => <assert_caldav_notified( - { recipient => "test2\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "test2\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_1_attendee_allowed_changes b/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_1_attendee_allowed_changes index 32a06d90b8..28c7ca04c0 100644 --- a/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_1_attendee_allowed_changes +++ b/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_1_attendee_allowed_changes @@ -2,48 +2,51 @@ use Cassandane::Tiny; sub test_rfc6638_3_2_2_1_attendee_allowed_changes - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "change summary"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - eval { $self->_put_event($CalendarId, uuid => $uuid, lines => < "updated event"); }; + $self->{instance}->getnotify(); + eval { + $self->_put_event( + $CalendarId, + uuid => $uuid, + lines => < "updated event"); }; ATTENDEE;CN=Test User;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:cassandane\@example.com ATTENDEE;PARTSTAT=ACCEPTED:MAILTO:test1\@example.com ORGANIZER:MAILTO:test1\@example.com EOF - my $err = $@; - # XXX - changing summary isn't rejected yet, should be - #$self->assert_matches(qr/allowed-attendee-scheduling-object-change/, $err); - } + my $err = $@; + # XXX - changing summary isn't rejected yet, should be + #$self->assert_matches(qr/allowed-attendee-scheduling-object-change/, $err); + } - xlog $self, "change organizer"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - eval { $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); + eval { $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_matches(qr/allowed-attendee-scheduling-object-change/, $err); - } + my $err = $@; + $self->assert_matches(qr/allowed-attendee-scheduling-object-change/, $err); + } } diff --git a/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_2_attendee_create b/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_2_attendee_create index bc65079945..d9e3fccd5f 100644 --- a/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_2_attendee_create +++ b/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_2_attendee_create @@ -2,59 +2,56 @@ use Cassandane::Tiny; sub test_rfc6638_3_2_2_2_attendee_create - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "agent "; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <"; + { + my $uuid = $CalDAV->genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_caldav_notified( - { recipient => "test1\@example.com", method => 'REPLY' }, - ); - } + $self->assert_caldav_notified( + { recipient => "test1\@example.com", method => 'REPLY' },); + } - xlog $self, "agent SERVER"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_caldav_notified( - { recipient => "test1\@example.com", method => 'REPLY' }, - ); - } + $self->assert_caldav_notified( + { recipient => "test1\@example.com", method => 'REPLY' },); + } - xlog $self, "agent CLIENT"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, "agent NONE"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } } diff --git a/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_3_attendee_modify b/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_3_attendee_modify index cabb1164f9..b61b38d996 100644 --- a/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_3_attendee_modify +++ b/cassandane/tiny-tests/Caldav/rfc6638_3_2_2_3_attendee_modify @@ -2,64 +2,62 @@ use Cassandane::Tiny; sub test_rfc6638_3_2_2_3_attendee_modify - :needs_component_httpd -{ - my ($self) = @_; - my $CalDAV = $self->{caldav}; + : needs_component_httpd { + my ($self) = @_; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'test'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'test' }); + $self->assert_not_null($CalendarId); - xlog $self, "attendee-modify"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_caldav_notified( - { recipient => "test1\@example.com", method => 'REPLY' }, - ); - } + $self->assert_caldav_notified( + { recipient => "test1\@example.com", method => 'REPLY' },); + } - xlog $self, "attendee-modify CLIENT"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } - xlog $self, "attendee-modify NONE"; - { - my $uuid = $CalDAV->genuuid(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <genuuid(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); - $self->_put_event($CalendarId, uuid => $uuid, lines => <{instance}->getnotify(); + $self->_put_event($CalendarId, uuid => $uuid, lines => <assert_caldav_notified(); - } + $self->assert_caldav_notified(); + } } diff --git a/cassandane/tiny-tests/Caldav/sched_busytime_query b/cassandane/tiny-tests/Caldav/sched_busytime_query index bb8b84875c..16e4988aab 100644 --- a/cassandane/tiny-tests/Caldav/sched_busytime_query +++ b/cassandane/tiny-tests/Caldav/sched_busytime_query @@ -2,30 +2,29 @@ use Cassandane::Tiny; sub test_sched_busytime_query - :min_version_3_4 :needs_component_httpd :NoVirtDomains -{ - my ($self) = @_; + : min_version_3_4 : needs_component_httpd : NoVirtDomains { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.friend"); - $admintalk->setacl("user.friend", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.friend", friend => 'lrswipkxtecdn'); + $admintalk->create("user.friend"); + $admintalk->setacl("user.friend", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.friend", friend => 'lrswipkxtecdn'); - my $service = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "friend", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "friend", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $query = <Request('POST', 'Outbox', - $query, 'Content-Type' => 'text/calendar'); - my $text = Dumper($res); - $self->assert_matches(qr/schedule-response/, $text); + xlog $self, "freebusy query"; + my $res = $CalDAV->Request('POST', 'Outbox', + $query, 'Content-Type' => 'text/calendar'); + my $text = Dumper($res); + $self->assert_matches(qr/schedule-response/, $text); } diff --git a/cassandane/tiny-tests/Caldav/shared_invite_as_secretary b/cassandane/tiny-tests/Caldav/shared_invite_as_secretary index b23ffa1e7b..6d6bc1f08b 100644 --- a/cassandane/tiny-tests/Caldav/shared_invite_as_secretary +++ b/cassandane/tiny-tests/Caldav/shared_invite_as_secretary @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_shared_invite_as_secretary - :VirtDomains :min_version_3_1 :needs_component_httpd :NoAltNameSpace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.test"); - $admintalk->setacl("user.test", "test" => "lrswipkxtecda"); - - my $service = $self->{instance}->get_service("http"); - my $testtalk = Net::CalDAVTalk->new( - user => "test", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $xml = <{adminstore}->get_client(); + + $admintalk->create("user.test"); + $admintalk->setacl("user.test", "test" => "lrswipkxtecda"); + + my $service = $self->{instance}->get_service("http"); + my $testtalk = Net::CalDAVTalk->new( + user => "test", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $xml = < @@ -35,42 +34,44 @@ sub test_shared_invite_as_secretary EOF - $testtalk->Request('PROPPATCH', "/dav/principals/user/test", $xml, - 'Content-Type' => 'text/xml'); - - xlog $self, "create calendar"; - my $CalendarId = $testtalk->NewCalendar({name => 'Team Calendar'}); - $self->assert_not_null($CalendarId); - - xlog $self, "set calendar-user-address-set for all sharees"; - $testtalk->Request('PROPPATCH', "/dav/calendars/user/test/$CalendarId", $xml, - 'Content-Type' => 'text/xml'); - - xlog $self, "share to user"; - $admintalk->setacl("user.test.#calendars.$CalendarId", - "cassandane" => 'lrswipcdn'); - - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "subscribe to shared calendar"; - my $imapstore = $self->{instance}->get_service('imap')->create_store( - username => "cassandane"); - my $imaptalk = $imapstore->get_client(); - $imaptalk->subscribe("user.test.#calendars.$CalendarId"); - - xlog $self, "get calendars as cassandane"; - my $CasCal = $CalDAV->GetCalendars(); - my $sharedCalendarId = $CasCal->[1]{href}; - - $xml = <Request( + 'PROPPATCH', "/dav/principals/user/test", + $xml, 'Content-Type' => 'text/xml' + ); + + xlog $self, "create calendar"; + my $CalendarId = $testtalk->NewCalendar({ name => 'Team Calendar' }); + $self->assert_not_null($CalendarId); + + xlog $self, "set calendar-user-address-set for all sharees"; + $testtalk->Request('PROPPATCH', "/dav/calendars/user/test/$CalendarId", + $xml, 'Content-Type' => 'text/xml'); + + xlog $self, "share to user"; + $admintalk->setacl("user.test.#calendars.$CalendarId", + "cassandane" => 'lrswipcdn'); + + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "subscribe to shared calendar"; + my $imapstore = $self->{instance}->get_service('imap') + ->create_store(username => "cassandane"); + my $imaptalk = $imapstore->get_client(); + $imaptalk->subscribe("user.test.#calendars.$CalendarId"); + + xlog $self, "get calendars as cassandane"; + my $CasCal = $CalDAV->GetCalendars(); + my $sharedCalendarId = $CasCal->[1]{href}; + + $xml = < @@ -83,9 +84,9 @@ EOF EOF - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$sharedCalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REPLY' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REPLY' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/shared_multiget b/cassandane/tiny-tests/Caldav/shared_multiget index a670221a54..69ce48a9db 100644 --- a/cassandane/tiny-tests/Caldav/shared_multiget +++ b/cassandane/tiny-tests/Caldav/shared_multiget @@ -2,55 +2,60 @@ use Cassandane::Tiny; sub test_shared_multiget - :needs_component_httpd :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_httpd : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Create second user"; - $admintalk->create("user.test"); - $admintalk->setacl("user.test", "test" => "lrswipkxtean"); + xlog $self, "Create second user"; + $admintalk->create("user.test"); + $admintalk->setacl("user.test", "test" => "lrswipkxtean"); - xlog $self, "Provision calendars user"; - my $service = $self->{instance}->get_service("http"); - my $testtalk = Net::CalDAVTalk->new( - user => "test", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + xlog $self, "Provision calendars user"; + my $service = $self->{instance}->get_service("http"); + my $testtalk = Net::CalDAVTalk->new( + user => "test", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "Share default calendar to cassandane"; - $admintalk->setacl("user.test.#calendars.Default", "cassandane" => 'lrswin'); + xlog $self, "Share default calendar to cassandane"; + $admintalk->setacl("user.test.#calendars.Default", "cassandane" => 'lrswin'); - xlog $self, "Subscribe to shared calendar"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->subscribe("user.test.#calendars.Default"); + xlog $self, "Subscribe to shared calendar"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->subscribe("user.test.#calendars.Default"); - xlog $self, "Get calendars as cassandane"; - my $CalDAV = $self->{caldav}; - my $CasCal = $CalDAV->GetCalendars(); - my $sharedId = $CasCal->[1]{href}; + xlog $self, "Get calendars as cassandane"; + my $CalDAV = $self->{caldav}; + my $CasCal = $CalDAV->GetCalendars(); + my $sharedId = $CasCal->[1]{href}; - my $href = $CalDAV->NewEvent('Default', { - timeZone => 'Etc/UTC', - start => '2015-01-01T12:00:00', - duration => 'PT1H', - summary => 'waterfall', - }); + my $href = $CalDAV->NewEvent( + 'Default', + { + timeZone => 'Etc/UTC', + start => '2015-01-01T12:00:00', + duration => 'PT1H', + summary => 'waterfall', + } + ); - my $sharedHref = $CalDAV->NewEvent($sharedId, { - timeZone => 'America/New_York', - start => '2015-02-01T12:00:00', - duration => 'PT1H', - summary => 'waterfall2', - }); + my $sharedHref = $CalDAV->NewEvent( + $sharedId, + { + timeZone => 'America/New_York', + start => '2015-02-01T12:00:00', + duration => 'PT1H', + summary => 'waterfall2', + } + ); - my $xmlMultiget = < @@ -61,14 +66,17 @@ sub test_shared_multiget EOF - xlog "Run calendar-multiget report"; - $mgRes = $CalDAV->Request('REPORT', 'Default', $xmlMultiget, - 'Content-Type' => 'application/xml', - ); + xlog "Run calendar-multiget report"; + $mgRes = $CalDAV->Request('REPORT', 'Default', $xmlMultiget, + 'Content-Type' => 'application/xml',); - my $icaldata = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}; - $self->assert_matches(qr|DTSTART:20150101T120000Z|, $icaldata); + my $icaldata + = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}; + $self->assert_matches(qr|DTSTART:20150101T120000Z|, $icaldata); - $icaldata = $mgRes->{'{DAV:}response'}[1]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}; - $self->assert_matches(qr|DTSTART;TZID=America/New_York:20150201T120000|, $icaldata); + $icaldata = $mgRes->{'{DAV:}response'}[1]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}; + $self->assert_matches(qr|DTSTART;TZID=America/New_York:20150201T120000|, + $icaldata); } diff --git a/cassandane/tiny-tests/Caldav/shared_reply_as_secretary b/cassandane/tiny-tests/Caldav/shared_reply_as_secretary index 7d8281d0e7..d15074281a 100644 --- a/cassandane/tiny-tests/Caldav/shared_reply_as_secretary +++ b/cassandane/tiny-tests/Caldav/shared_reply_as_secretary @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_shared_reply_as_secretary - :VirtDomains :min_version_3_1 :needs_component_httpd :NoAltNameSpace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.test"); - $admintalk->setacl("user.test", "test" => "lrswipkxtecda"); - - my $service = $self->{instance}->get_service("http"); - my $testtalk = Net::CalDAVTalk->new( - user => "test", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $xml = <{adminstore}->get_client(); + + $admintalk->create("user.test"); + $admintalk->setacl("user.test", "test" => "lrswipkxtecda"); + + my $service = $self->{instance}->get_service("http"); + my $testtalk = Net::CalDAVTalk->new( + user => "test", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $xml = < @@ -35,42 +34,44 @@ sub test_shared_reply_as_secretary EOF - $testtalk->Request('PROPPATCH', "/dav/principals/user/test", $xml, - 'Content-Type' => 'text/xml'); - - xlog $self, "create calendar"; - my $CalendarId = $testtalk->NewCalendar({name => 'Team Calendar'}); - $self->assert_not_null($CalendarId); - - xlog $self, "set calendar-user-address-set for all sharees"; - $testtalk->Request('PROPPATCH', "/dav/calendars/user/test/$CalendarId", $xml, - 'Content-Type' => 'text/xml'); - - xlog $self, "share to user"; - $admintalk->setacl("user.test.#calendars.$CalendarId", - "cassandane" => 'lrswipcdn'); - - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "subscribe to shared calendar"; - my $imapstore = $self->{instance}->get_service('imap')->create_store( - username => "cassandane"); - my $imaptalk = $imapstore->get_client(); - $imaptalk->subscribe("user.test.#calendars.$CalendarId"); - - xlog $self, "get calendars as cassandane"; - my $CasCal = $CalDAV->GetCalendars(); - my $sharedCalendarId = $CasCal->[1]{href}; - - $xml = <Request( + 'PROPPATCH', "/dav/principals/user/test", + $xml, 'Content-Type' => 'text/xml' + ); + + xlog $self, "create calendar"; + my $CalendarId = $testtalk->NewCalendar({ name => 'Team Calendar' }); + $self->assert_not_null($CalendarId); + + xlog $self, "set calendar-user-address-set for all sharees"; + $testtalk->Request('PROPPATCH', "/dav/calendars/user/test/$CalendarId", + $xml, 'Content-Type' => 'text/xml'); + + xlog $self, "share to user"; + $admintalk->setacl("user.test.#calendars.$CalendarId", + "cassandane" => 'lrswipcdn'); + + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "subscribe to shared calendar"; + my $imapstore = $self->{instance}->get_service('imap') + ->create_store(username => "cassandane"); + my $imaptalk = $imapstore->get_client(); + $imaptalk->subscribe("user.test.#calendars.$CalendarId"); + + xlog $self, "get calendars as cassandane"; + my $CasCal = $CalDAV->GetCalendars(); + my $sharedCalendarId = $CasCal->[1]{href}; + + $xml = < @@ -83,9 +84,9 @@ EOF EOF - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$sharedCalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - $self->assert_caldav_notified( - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); + $self->assert_caldav_notified( + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/shared_team_invite_sharee b/cassandane/tiny-tests/Caldav/shared_team_invite_sharee index 9c18c94b02..d2964bdd15 100644 --- a/cassandane/tiny-tests/Caldav/shared_team_invite_sharee +++ b/cassandane/tiny-tests/Caldav/shared_team_invite_sharee @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_shared_team_invite_sharee - :VirtDomains :min_version_3_1 :needs_component_httpd :NoAltNameSpace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.test"); - $admintalk->setacl("user.test", "test" => "lrswipkxtecda"); - - my $service = $self->{instance}->get_service("http"); - my $testtalk = Net::CalDAVTalk->new( - user => "test", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $xml = <{adminstore}->get_client(); + + $admintalk->create("user.test"); + $admintalk->setacl("user.test", "test" => "lrswipkxtecda"); + + my $service = $self->{instance}->get_service("http"); + my $testtalk = Net::CalDAVTalk->new( + user => "test", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $xml = < @@ -35,40 +34,42 @@ sub test_shared_team_invite_sharee EOF - $testtalk->Request('PROPPATCH', "/dav/principals/user/test", $xml, - 'Content-Type' => 'text/xml'); - - xlog $self, "create calendar"; - my $CalendarId = $testtalk->NewCalendar({name => 'Team Calendar'}); - $self->assert_not_null($CalendarId); - - xlog $self, "share to user"; - $admintalk->setacl("user.test.#calendars.$CalendarId", - "cassandane" => 'lrswipcdn'); - - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "subscribe to shared calendar"; - my $imapstore = $self->{instance}->get_service('imap')->create_store( - username => "cassandane"); - my $imaptalk = $imapstore->get_client(); - $imaptalk->subscribe("user.test.#calendars.$CalendarId"); - - xlog $self, "get calendars as cassandane"; - my $CasCal = $CalDAV->GetCalendars(); - my $sharedCalendarId = $CasCal->[1]{href}; - - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "/dav/calendars/user/test/$CalendarId/$uuid.ics"; - my $card = <Request( + 'PROPPATCH', "/dav/principals/user/test", + $xml, 'Content-Type' => 'text/xml' + ); + + xlog $self, "create calendar"; + my $CalendarId = $testtalk->NewCalendar({ name => 'Team Calendar' }); + $self->assert_not_null($CalendarId); + + xlog $self, "share to user"; + $admintalk->setacl("user.test.#calendars.$CalendarId", + "cassandane" => 'lrswipcdn'); + + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "subscribe to shared calendar"; + my $imapstore = $self->{instance}->get_service('imap') + ->create_store(username => "cassandane"); + my $imaptalk = $imapstore->get_client(); + $imaptalk->subscribe("user.test.#calendars.$CalendarId"); + + xlog $self, "get calendars as cassandane"; + my $CasCal = $CalDAV->GetCalendars(); + my $sharedCalendarId = $CasCal->[1]{href}; + + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "/dav/calendars/user/test/$CalendarId/$uuid.ics"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - $self->assert_caldav_notified( - { recipient => "cassandane\@example.com", is_update => JSON::false, method => 'REQUEST' }, - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); - - xlog $self, "update PARTSTAT as sharee"; - $href = "$sharedCalendarId/$uuid.ics"; - $card =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; - - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - $self->assert_caldav_notified( - { recipient => "test\@example.com", is_update => JSON::false, method => 'REPLY' }, - ); + xlog $self, "add event as sharer, inviting sharee"; + $testtalk->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + $self->assert_caldav_notified( + { + recipient => "cassandane\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); + + xlog $self, "update PARTSTAT as sharee"; + $href = "$sharedCalendarId/$uuid.ics"; + $card =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; + + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + $self->assert_caldav_notified( + { + recipient => "test\@example.com", + is_update => JSON::false, + method => 'REPLY' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/shared_team_invite_sharer b/cassandane/tiny-tests/Caldav/shared_team_invite_sharer index e73de38c8c..c5a9c9c6f0 100644 --- a/cassandane/tiny-tests/Caldav/shared_team_invite_sharer +++ b/cassandane/tiny-tests/Caldav/shared_team_invite_sharer @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_shared_team_invite_sharer - :VirtDomains :min_version_3_1 :needs_component_httpd :NoAltNameSpace -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.test"); - $admintalk->setacl("user.test", "test" => "lrswipkxtecda"); - - my $service = $self->{instance}->get_service("http"); - my $testtalk = Net::CalDAVTalk->new( - user => "test", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $xml = <{adminstore}->get_client(); + + $admintalk->create("user.test"); + $admintalk->setacl("user.test", "test" => "lrswipkxtecda"); + + my $service = $self->{instance}->get_service("http"); + my $testtalk = Net::CalDAVTalk->new( + user => "test", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $xml = < @@ -35,40 +34,42 @@ sub test_shared_team_invite_sharer EOF - $testtalk->Request('PROPPATCH', "/dav/principals/user/test", $xml, - 'Content-Type' => 'text/xml'); - - xlog $self, "create calendar"; - my $CalendarId = $testtalk->NewCalendar({name => 'Team Calendar'}); - $self->assert_not_null($CalendarId); - - xlog $self, "share to user"; - $admintalk->setacl("user.test.#calendars.$CalendarId", - "cassandane" => 'lrswipcdn'); - - my $CalDAV = Net::CalDAVTalk->new( - user => "cassandane", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "subscribe to shared calendar"; - my $imapstore = $self->{instance}->get_service('imap')->create_store( - username => "cassandane"); - my $imaptalk = $imapstore->get_client(); - $imaptalk->subscribe("user.test.#calendars.$CalendarId"); - - xlog $self, "get calendars as cassandane"; - my $CasCal = $CalDAV->GetCalendars(); - my $sharedCalendarId = $CasCal->[1]{href}; - - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$sharedCalendarId/$uuid.ics"; - my $card = <Request( + 'PROPPATCH', "/dav/principals/user/test", + $xml, 'Content-Type' => 'text/xml' + ); + + xlog $self, "create calendar"; + my $CalendarId = $testtalk->NewCalendar({ name => 'Team Calendar' }); + $self->assert_not_null($CalendarId); + + xlog $self, "share to user"; + $admintalk->setacl("user.test.#calendars.$CalendarId", + "cassandane" => 'lrswipcdn'); + + my $CalDAV = Net::CalDAVTalk->new( + user => "cassandane", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "subscribe to shared calendar"; + my $imapstore = $self->{instance}->get_service('imap') + ->create_store(username => "cassandane"); + my $imaptalk = $imapstore->get_client(); + $imaptalk->subscribe("user.test.#calendars.$CalendarId"); + + xlog $self, "get calendars as cassandane"; + my $CasCal = $CalDAV->GetCalendars(); + my $sharedCalendarId = $CasCal->[1]{href}; + + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$sharedCalendarId/$uuid.ics"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - $self->assert_caldav_notified( - { recipient => "test\@example.com", is_update => JSON::false, method => 'REQUEST' }, - { recipient => "friend\@example.com", is_update => JSON::false, method => 'REQUEST' }, - ); - - xlog $self, "update PARTSTAT as sharer"; - $href = "/dav/calendars/user/test/$CalendarId/$uuid.ics"; - $card =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; - - $testtalk->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - $self->assert_caldav_notified( - { recipient => "cassandane\@example.com", is_update => JSON::false, method => 'REPLY' }, - ); + xlog $self, "add event as sharee, inviting sharer"; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + $self->assert_caldav_notified( + { + recipient => "test\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + { + recipient => "friend\@example.com", + is_update => JSON::false, + method => 'REQUEST' + }, + ); + + xlog $self, "update PARTSTAT as sharer"; + $href = "/dav/calendars/user/test/$CalendarId/$uuid.ics"; + $card =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; + + $testtalk->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + $self->assert_caldav_notified( + { + recipient => "cassandane\@example.com", + is_update => JSON::false, + method => 'REPLY' + }, + ); } diff --git a/cassandane/tiny-tests/Caldav/summary_with_embedded_newlines b/cassandane/tiny-tests/Caldav/summary_with_embedded_newlines index a1eab8cf76..207b9f7b2c 100644 --- a/cassandane/tiny-tests/Caldav/summary_with_embedded_newlines +++ b/cassandane/tiny-tests/Caldav/summary_with_embedded_newlines @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_summary_with_embedded_newlines - :needs_component_httpd :MagicPlus :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_httpd : MagicPlus : NoAltNameSpace { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = < 'text/calendar', - 'Authorization' => $CalDAV->auth_header(), - ); + my %Headers = ( + 'Content-Type' => 'text/calendar', + 'Authorization' => $CalDAV->auth_header(), + ); - xlog "Create event"; - my $Response = $CalDAV->{ua}->request('PUT', $CalDAV->request_url($href), { + xlog "Create event"; + my $Response = $CalDAV->{ua}->request( + 'PUT', + $CalDAV->request_url($href), + { content => $card, headers => \%Headers, - }); + } + ); - # This only succeeds if we properly encode the SUMMARY - # as a Subject header field when constructing the message on disk - $self->assert_num_equals(201, $Response->{status}); + # This only succeeds if we properly encode the SUMMARY + # as a Subject header field when constructing the message on disk + $self->assert_num_equals(201, $Response->{status}); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); - xlog "Check Subject header"; - my $subject = "=?UTF-8?Q?Send_image_for_61st_anniversary_exhibition_at_gallery_--_Inclu?=\r\n"; - $subject .= " =?UTF-8?Q?deyour_name,_the_title_and_the_media.__To_be_of_appropriate_q?=\r\n"; - $subject .= " =?UTF-8?Q?uality,_theideal_image_file_size_should_be_300_-_500_kilobyte?=\r\n"; - $subject .= " =?UTF-8?Q?s.=0A=0AUse_your_lastname_and_an_abbreviated_title_as_the_fil?=\r\n"; - $subject .= " =?UTF-8?Q?e_name(Lastname=5FTitle.jpg).=0A=0APlease_send_to:___foo\@exam?=\r\n"; - $subject .= " =?UTF-8?Q?ple.net=0A=0A?="; + xlog "Check Subject header"; + my $subject + = "=?UTF-8?Q?Send_image_for_61st_anniversary_exhibition_at_gallery_--_Inclu?=\r\n"; + $subject + .= " =?UTF-8?Q?deyour_name,_the_title_and_the_media.__To_be_of_appropriate_q?=\r\n"; + $subject + .= " =?UTF-8?Q?uality,_theideal_image_file_size_should_be_300_-_500_kilobyte?=\r\n"; + $subject + .= " =?UTF-8?Q?s.=0A=0AUse_your_lastname_and_an_abbreviated_title_as_the_fil?=\r\n"; + $subject + .= " =?UTF-8?Q?e_name(Lastname=5FTitle.jpg).=0A=0APlease_send_to:___foo\@exam?=\r\n"; + $subject .= " =?UTF-8?Q?ple.net=0A=0A?="; - my $store = $self->{instance}->get_service('imap')->create_store(username => 'cassandane+dav'); - my $imaptalk = $store->get_client(); - $imaptalk->select("INBOX.#calendars.$CalendarId"); - $Response = $imaptalk->fetch(1, '(BODY.PEEK[HEADER.FIELDS (SUBJECT)])'); - $self->assert_str_equals($Response->{1}->{headers}->{subject}[0], $subject); + my $store = $self->{instance}->get_service('imap') + ->create_store(username => 'cassandane+dav'); + my $imaptalk = $store->get_client(); + $imaptalk->select("INBOX.#calendars.$CalendarId"); + $Response = $imaptalk->fetch(1, '(BODY.PEEK[HEADER.FIELDS (SUBJECT)])'); + $self->assert_str_equals($Response->{1}->{headers}->{subject}[0], $subject); } diff --git a/cassandane/tiny-tests/Caldav/summary_with_trailing_newlines b/cassandane/tiny-tests/Caldav/summary_with_trailing_newlines index 7391fae8c5..752b5301d6 100644 --- a/cassandane/tiny-tests/Caldav/summary_with_trailing_newlines +++ b/cassandane/tiny-tests/Caldav/summary_with_trailing_newlines @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_summary_with_trailing_newlines - :needs_component_httpd :MagicPlus :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_httpd : MagicPlus : NoAltNameSpace { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = < 'text/calendar', - 'Authorization' => $CalDAV->auth_header(), - ); + my %Headers = ( + 'Content-Type' => 'text/calendar', + 'Authorization' => $CalDAV->auth_header(), + ); - xlog "Create event"; - my $Response = $CalDAV->{ua}->request('PUT', $CalDAV->request_url($href), { + xlog "Create event"; + my $Response = $CalDAV->{ua}->request( + 'PUT', + $CalDAV->request_url($href), + { content => $card, headers => \%Headers, - }); + } + ); - # This only succeeds if we strip trailing newlines from the SUMMARY - # when used as a Subject header field when constructing the message on disk - $self->assert_num_equals(201, $Response->{status}); + # This only succeeds if we strip trailing newlines from the SUMMARY + # when used as a Subject header field when constructing the message on disk + $self->assert_num_equals(201, $Response->{status}); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); } diff --git a/cassandane/tiny-tests/Caldav/supports_event b/cassandane/tiny-tests/Caldav/supports_event index d549c1b05c..bb3b398e73 100644 --- a/cassandane/tiny-tests/Caldav/supports_event +++ b/cassandane/tiny-tests/Caldav/supports_event @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_supports_event - :min_version_3_1 :needs_component_httpd :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_httpd : needs_component_jmap { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $Calendar = $CalDAV->GetCalendar($CalendarId); + my $Calendar = $CalDAV->GetCalendar($CalendarId); - $self->assert($Calendar->{_can_event}); + $self->assert($Calendar->{_can_event}); } diff --git a/cassandane/tiny-tests/Caldav/url_nodomains b/cassandane/tiny-tests/Caldav/url_nodomains index 0b0c1df7e4..7759fdc230 100644 --- a/cassandane/tiny-tests/Caldav/url_nodomains +++ b/cassandane/tiny-tests/Caldav/url_nodomains @@ -2,22 +2,22 @@ use Cassandane::Tiny; sub test_url_nodomains - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "create calendar"; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - xlog $self, "fetch again"; - my $Calendar = $CalDAV->GetCalendar($CalendarId); - $self->assert_not_null($Calendar); + xlog $self, "fetch again"; + my $Calendar = $CalDAV->GetCalendar($CalendarId); + $self->assert_not_null($Calendar); - xlog $self, "check that the href has no domain"; - $self->assert_str_equals("/dav/calendars/user/cassandane/$CalendarId/", $Calendar->{href}); + xlog $self, "check that the href has no domain"; + $self->assert_str_equals("/dav/calendars/user/cassandane/$CalendarId/", + $Calendar->{href}); } diff --git a/cassandane/tiny-tests/Caldav/url_virtdom_domain b/cassandane/tiny-tests/Caldav/url_virtdom_domain index 79e94d09fb..7808848c03 100644 --- a/cassandane/tiny-tests/Caldav/url_virtdom_domain +++ b/cassandane/tiny-tests/Caldav/url_virtdom_domain @@ -2,34 +2,35 @@ use Cassandane::Tiny; sub test_url_virtdom_domain - :VirtDomains :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : needs_component_httpd { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.test\@example.com"); - $admintalk->setacl("user.test\@example.com", "test\@example.com" => "lrswipkxtecda"); + $admintalk->create("user.test\@example.com"); + $admintalk->setacl("user.test\@example.com", + "test\@example.com" => "lrswipkxtecda"); - my $service = $self->{instance}->get_service("http"); - my $caltalk = Net::CalDAVTalk->new( - user => "test\@example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $caltalk = Net::CalDAVTalk->new( + user => "test\@example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "create calendar"; - my $CalendarId = $caltalk->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $caltalk->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - xlog $self, "fetch again"; - my $Calendar = $caltalk->GetCalendar($CalendarId); - $self->assert_not_null($Calendar); + xlog $self, "fetch again"; + my $Calendar = $caltalk->GetCalendar($CalendarId); + $self->assert_not_null($Calendar); - xlog $self, "check that the href has domain"; - $self->assert_str_equals("/dav/calendars/user/test\@example.com/$CalendarId/", $Calendar->{href}); + xlog $self, "check that the href has domain"; + $self->assert_str_equals("/dav/calendars/user/test\@example.com/$CalendarId/", + $Calendar->{href}); } diff --git a/cassandane/tiny-tests/Caldav/url_virtdom_extradomain b/cassandane/tiny-tests/Caldav/url_virtdom_extradomain index bedae2cff3..8b8eff4950 100644 --- a/cassandane/tiny-tests/Caldav/url_virtdom_extradomain +++ b/cassandane/tiny-tests/Caldav/url_virtdom_extradomain @@ -2,31 +2,32 @@ use Cassandane::Tiny; sub test_url_virtdom_extradomain - :VirtDomains :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - my $caltalk = Net::CalDAVTalk->new( - user => "cassandane%example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $caltalk = Net::CalDAVTalk->new( + user => "cassandane%example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "create calendar"; - my $CalendarId = $caltalk->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $caltalk->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - xlog $self, "fetch again"; - my $Calendar = $caltalk->GetCalendar($CalendarId); - $self->assert_not_null($Calendar); + xlog $self, "fetch again"; + my $Calendar = $caltalk->GetCalendar($CalendarId); + $self->assert_not_null($Calendar); - xlog $self, "check that the href has domain"; - $self->assert_str_equals("/dav/calendars/user/cassandane\@example.com/$CalendarId/", $Calendar->{href}); + xlog $self, "check that the href has domain"; + $self->assert_str_equals( + "/dav/calendars/user/cassandane\@example.com/$CalendarId/", + $Calendar->{href}); } diff --git a/cassandane/tiny-tests/Caldav/url_virtdom_nodomain b/cassandane/tiny-tests/Caldav/url_virtdom_nodomain index 1580874c75..2e2e08b8e6 100644 --- a/cassandane/tiny-tests/Caldav/url_virtdom_nodomain +++ b/cassandane/tiny-tests/Caldav/url_virtdom_nodomain @@ -2,22 +2,22 @@ use Cassandane::Tiny; sub test_url_virtdom_nodomain - :VirtDomains :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "create calendar"; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - xlog $self, "fetch again"; - my $Calendar = $CalDAV->GetCalendar($CalendarId); - $self->assert_not_null($Calendar); + xlog $self, "fetch again"; + my $Calendar = $CalDAV->GetCalendar($CalendarId); + $self->assert_not_null($Calendar); - xlog $self, "check that the href has no domain"; - $self->assert_str_equals("/dav/calendars/user/cassandane/$CalendarId/", $Calendar->{href}); + xlog $self, "check that the href has no domain"; + $self->assert_str_equals("/dav/calendars/user/cassandane/$CalendarId/", + $Calendar->{href}); } diff --git a/cassandane/tiny-tests/Caldav/user_rename b/cassandane/tiny-tests/Caldav/user_rename index 3848dcb700..807d5e923c 100644 --- a/cassandane/tiny-tests/Caldav/user_rename +++ b/cassandane/tiny-tests/Caldav/user_rename @@ -2,43 +2,42 @@ use Cassandane::Tiny; sub test_user_rename - :AllowMoves :needs_component_httpd -{ - my ($self) = @_; + : AllowMoves : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "create calendar"; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - xlog $self, "fetch again"; - my $Calendar = $CalDAV->GetCalendar($CalendarId); - $self->assert_not_null($Calendar); + xlog $self, "fetch again"; + my $Calendar = $CalDAV->GetCalendar($CalendarId); + $self->assert_not_null($Calendar); - xlog $self, "check name matches"; - $self->assert_str_equals('foo', $Calendar->{name}); + xlog $self, "check name matches"; + $self->assert_str_equals('foo', $Calendar->{name}); - xlog $self, "rename user"; - $admintalk->rename("user.cassandane", "user.newuser"); + xlog $self, "rename user"; + $admintalk->rename("user.cassandane", "user.newuser"); - my $service = $self->{instance}->get_service("http"); - my $newtalk = Net::CalDAVTalk->new( - user => 'newuser', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $newtalk = Net::CalDAVTalk->new( + user => 'newuser', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "fetch as new user $CalendarId"; - my $NewCalendar = $newtalk->GetCalendar($CalendarId); - $self->assert_not_null($NewCalendar); + xlog $self, "fetch as new user $CalendarId"; + my $NewCalendar = $newtalk->GetCalendar($CalendarId); + $self->assert_not_null($NewCalendar); - xlog $self, "check new name stuck"; - $self->assert_str_equals($NewCalendar->{name}, 'foo'); + xlog $self, "check new name stuck"; + $self->assert_str_equals($NewCalendar->{name}, 'foo'); } diff --git a/cassandane/tiny-tests/Caldav/user_rename_dom b/cassandane/tiny-tests/Caldav/user_rename_dom index 30dd4d92b7..546c0b0478 100644 --- a/cassandane/tiny-tests/Caldav/user_rename_dom +++ b/cassandane/tiny-tests/Caldav/user_rename_dom @@ -2,54 +2,54 @@ use Cassandane::Tiny; sub test_user_rename_dom - :AllowMoves :VirtDomains :needs_component_httpd -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->create("user.test\@example.com"); - $admintalk->setacl("user.test\@example.com", "test\@example.com" => "lrswipkxtecda"); - - my $service = $self->{instance}->get_service("http"); - my $oldtalk = Net::CalDAVTalk->new( - user => "test\@example.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "create calendar"; - my $CalendarId = $oldtalk->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); - - xlog $self, "fetch again"; - my $Calendar = $oldtalk->GetCalendar($CalendarId); - $self->assert_not_null($Calendar); - - xlog $self, "check name matches"; - $self->assert_str_equals($Calendar->{name}, 'foo'); - - xlog $self, "rename user"; - $admintalk->rename("user.test\@example.com", "user.test2\@example2.com"); - - my $newtalk = Net::CalDAVTalk->new( - user => "test2\@example2.com", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "fetch as new user $CalendarId"; - my $NewCalendar = $newtalk->GetCalendar($CalendarId); - $self->assert_not_null($NewCalendar); - - xlog $self, "check new name stuck"; - $self->assert_str_equals($NewCalendar->{name}, 'foo'); + : AllowMoves : VirtDomains : needs_component_httpd { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->create("user.test\@example.com"); + $admintalk->setacl("user.test\@example.com", + "test\@example.com" => "lrswipkxtecda"); + + my $service = $self->{instance}->get_service("http"); + my $oldtalk = Net::CalDAVTalk->new( + user => "test\@example.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "create calendar"; + my $CalendarId = $oldtalk->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); + + xlog $self, "fetch again"; + my $Calendar = $oldtalk->GetCalendar($CalendarId); + $self->assert_not_null($Calendar); + + xlog $self, "check name matches"; + $self->assert_str_equals($Calendar->{name}, 'foo'); + + xlog $self, "rename user"; + $admintalk->rename("user.test\@example.com", "user.test2\@example2.com"); + + my $newtalk = Net::CalDAVTalk->new( + user => "test2\@example2.com", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "fetch as new user $CalendarId"; + my $NewCalendar = $newtalk->GetCalendar($CalendarId); + $self->assert_not_null($NewCalendar); + + xlog $self, "check new name stuck"; + $self->assert_str_equals($NewCalendar->{name}, 'foo'); } diff --git a/cassandane/tiny-tests/Caldav/utf8_url b/cassandane/tiny-tests/Caldav/utf8_url index a2c07786f1..88548a6d4e 100644 --- a/cassandane/tiny-tests/Caldav/utf8_url +++ b/cassandane/tiny-tests/Caldav/utf8_url @@ -2,19 +2,18 @@ use Cassandane::Tiny; sub test_utf8_url - :min_version_3_9 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_9 : needs_component_httpd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uid = "%E2%98%83"; # percent-encoded ☃"; - my $href = $CalDAV->request_url('') . "/$CalendarId/$uid.ics"; + my $uid = "%E2%98%83"; # percent-encoded ☃"; + my $href = $CalDAV->request_url('') . "/$CalendarId/$uid.ics"; - my $event = < 'text/calendar; charset=utf-8', - 'Authorization' => $CalDAV->auth_header()); - - utf8::encode($event); - - # This will fail if the UTF-8 resource name isn't handled properly - my $res = $CalDAV->{ua}->request('PUT', $href, { - headers => \%headers, - content => $event - }); - $self->assert_str_equals('201', $res->{status}); - - $res = $CalDAV->{ua}->request('GET', $href, { - headers => \%headers - }); - $self->assert_str_equals('200', $res->{status}); + my %headers = ( + 'Content-Type' => 'text/calendar; charset=utf-8', + 'Authorization' => $CalDAV->auth_header() + ); + + utf8::encode($event); + + # This will fail if the UTF-8 resource name isn't handled properly + my $res = $CalDAV->{ua}->request( + 'PUT', $href, + { + headers => \%headers, + content => $event + } + ); + $self->assert_str_equals('201', $res->{status}); + + $res = $CalDAV->{ua}->request( + 'GET', $href, + { + headers => \%headers + } + ); + $self->assert_str_equals('200', $res->{status}); } diff --git a/cassandane/tiny-tests/CaldavAlarm/allday_notz b/cassandane/tiny-tests/CaldavAlarm/allday_notz index 7b081f916f..8906eabc62 100644 --- a/cassandane/tiny-tests/CaldavAlarm/allday_notz +++ b/cassandane/tiny-tests/CaldavAlarm/allday_notz @@ -2,39 +2,38 @@ use Cassandane::Tiny; sub test_allday_notz - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start today - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(days => 1)); - $startdt->truncate(to => 'day'); - my $start = $startdt->strftime('%Y%m%d'); + # define the event to start today + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(days => 1)); + $startdt->truncate(to => 'day'); + my $start = $startdt->strftime('%Y%m%d'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(days => 1)); - my $end = $enddt->strftime('%Y%m%d'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(days => 1)); + my $end = $enddt->strftime('%Y%m%d'); - my $utc = DateTime::Format::ISO8601->new->parse_datetime($start . 'T000000Z'); + my $utc = DateTime::Format::ISO8601->new->parse_datetime($start . 'T000000Z'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "95989f3d-575f-4828-9610-6f16b9d54d04"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $utc->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $utc->epoch() - 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $utc->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $utc->epoch() + 60); - $self->assert_alarms({summary => 'allday', start => $start, timezone => '[floating]'}); + $self->assert_alarms( + { summary => 'allday', start => $start, timezone => '[floating]' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/allday_sametz b/cassandane/tiny-tests/CaldavAlarm/allday_sametz index 04a69cc405..2aa082ede9 100644 --- a/cassandane/tiny-tests/CaldavAlarm/allday_sametz +++ b/cassandane/tiny-tests/CaldavAlarm/allday_sametz @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_allday_sametz - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $tz = <NewCalendar({name => 'foo', timeZone => $tz}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo', timeZone => $tz }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Brisbane'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Brisbane'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in 2 days - # (need to go 2 days out to account for the timezone of the testing location, - # otherwise we may store the event locally AFTER the adjusted alarm trigger) - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(days => 2)); - $startdt->truncate(to => 'day'); - my $start = $startdt->strftime('%Y%m%d'); + # define the event to start in 2 days + # (need to go 2 days out to account for the timezone of the testing location, + # otherwise we may store the event locally AFTER the adjusted alarm trigger) + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(days => 2)); + $startdt->truncate(to => 'day'); + my $start = $startdt->strftime('%Y%m%d'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(days => 1)); - my $end = $enddt->strftime('%Y%m%d'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(days => 1)); + my $end = $enddt->strftime('%Y%m%d'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "95989f3d-575f-4828-9610-6f16b9d54d04"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch() + 60); - $self->assert_alarms({summary => 'allday', start => $start, timezone => 'Australia/Brisbane'}); + $self->assert_alarms( + { summary => 'allday', start => $start, timezone => 'Australia/Brisbane' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/disable_high_freq b/cassandane/tiny-tests/CaldavAlarm/disable_high_freq index 62ec645dae..be6f050c5d 100644 --- a/cassandane/tiny-tests/CaldavAlarm/disable_high_freq +++ b/cassandane/tiny-tests/CaldavAlarm/disable_high_freq @@ -3,46 +3,45 @@ use Cassandane::Tiny; # this test depends on calendar_min_alarm_interval=61 which is configured in new() sub test_disable_high_freq - :min_version_3_7 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_7 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Etc/UTC'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Etc/UTC'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%SZ'); - my $startsec = $startdt->second; - my $startmin = $startdt->minute; + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%SZ'); + my $startsec = $startdt->second; + my $startmin = $startdt->minute; - # create hourly, minutely and secondly occurring events - # - # hourly with interval= - # 1,31,61: should result in an alarm since there is a > 60s interval - # - # minutely with interval= - # 1: should NOT result in an alarm since there is only a 60s interval - # 31,61: should NOT result in an alarm since there is a > 60s interval - # - # secondly with interval= - # 1,31: should NOT result in an alarm since there is a < 60s interval - # 61 should result in an alarm since there is a 60s interval - # - for my $freq (qw(HOURLY MINUTELY SECONDLY)) { - for (my $int = 1; $int < 90; $int += 30) { - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9-$freq-$int"; - my $href = "$CalendarId/$uuid.ics"; - my $card = < 60s interval + # + # minutely with interval= + # 1: should NOT result in an alarm since there is only a 60s interval + # 31,61: should NOT result in an alarm since there is a > 60s interval + # + # secondly with interval= + # 1,31: should NOT result in an alarm since there is a < 60s interval + # 61 should result in an alarm since there is a 60s interval + # + for my $freq (qw(HOURLY MINUTELY SECONDLY)) { + for (my $int = 1; $int < 90; $int += 30) { + my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9-$freq-$int"; + my $href = "$CalendarId/$uuid.ics"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - } + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); } + } - # create minutely occurring events with bysecond=startsec - # - # interval=1 should NOT result in an alarm since there is only a 60s interval - # - # interval=2 should result in an alarm since there is a 120s interval - # - for (my $int = 1; $int < 3; $int += 1) { - my $freq = 'MINUTELY'; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9-$freq-$int-$startsec"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - } + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + } - # create hourly occurring events with a set of byminute - # - # byminute=startmin and +1 should fail since there is only a 60s interval - # - # byminute=startmin and +2 should succeed since there is a 120s interval - # - my $bymin_ok = ($startmin + 2) % 60; - foreach my $addend (1..2) { - my $bymin = ($startmin + $addend) % 60; + # create hourly occurring events with a set of byminute + # + # byminute=startmin and +1 should fail since there is only a 60s interval + # + # byminute=startmin and +2 should succeed since there is a 120s interval + # + my $bymin_ok = ($startmin + 2) % 60; + foreach my $addend (1 .. 2) { + my $bymin = ($startmin + $addend) % 60; - my $freq = 'HOURLY'; - my $int = 1; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9-$freq-$int-$bymin"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - } + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + } - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - # assert that only the alarms that fire at >= 61s intervals are created - $self->assert_alarms({summary => 'HOURLY-1', start => $start }, - {summary => 'HOURLY-31', start => $start }, - {summary => 'HOURLY-61', start => $start }, - {summary => 'MINUTELY-31', start => $start }, - {summary => 'MINUTELY-61', start => $start }, - {summary => 'SECONDLY-61', start => $start }, - {summary => "MINUTELY-2-$startsec", start => $start }, - {summary => "HOURLY-1-$bymin_ok", start => $start }); + # assert that only the alarms that fire at >= 61s intervals are created + $self->assert_alarms( + { summary => 'HOURLY-1', start => $start }, + { summary => 'HOURLY-31', start => $start }, + { summary => 'HOURLY-61', start => $start }, + { summary => 'MINUTELY-31', start => $start }, + { summary => 'MINUTELY-61', start => $start }, + { summary => 'SECONDLY-61', start => $start }, + { summary => "MINUTELY-2-$startsec", start => $start }, + { summary => "HOURLY-1-$bymin_ok", start => $start } + ); } diff --git a/cassandane/tiny-tests/CaldavAlarm/floating_differenttz b/cassandane/tiny-tests/CaldavAlarm/floating_differenttz index 6ae51d7799..6c0304dd02 100644 --- a/cassandane/tiny-tests/CaldavAlarm/floating_differenttz +++ b/cassandane/tiny-tests/CaldavAlarm/floating_differenttz @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_floating_differenttz - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; - return if not $self->{test_calalarmd}; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; + return if not $self->{test_calalarmd}; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $tz = <NewCalendar({name => 'foo', timeZone => $tz}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo', timeZone => $tz }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - my $syd = DateTime::TimeZone->new( name => 'Australia/Sydney' ); - my $ny = DateTime::TimeZone->new( name => 'America/New_York' ); - my $offset = $syd->offset_for_datetime($now) - $ny->offset_for_datetime($now); + my $syd = DateTime::TimeZone->new(name => 'Australia/Sydney'); + my $ny = DateTime::TimeZone->new(name => 'America/New_York'); + my $offset = $syd->offset_for_datetime($now) - $ny->offset_for_datetime($now); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "95989f3d-575f-4828-9610-6f16b9d54d04"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - # no alarms - $self->assert_alarms(); + # no alarms + $self->assert_alarms(); - # trigger processing in New York - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 + $offset ); + # trigger processing in New York + $self->{instance}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $now->epoch() + 60 + $offset); - # alarm fires - $self->assert_alarms({summary => 'Floating', timezone => 'America/New_York', start => $start}); + # alarm fires + $self->assert_alarms( + { summary => 'Floating', timezone => 'America/New_York', start => $start }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/floating_notz b/cassandane/tiny-tests/CaldavAlarm/floating_notz index 23a7bc49a1..df48a07e52 100644 --- a/cassandane/tiny-tests/CaldavAlarm/floating_notz +++ b/cassandane/tiny-tests/CaldavAlarm/floating_notz @@ -2,38 +2,37 @@ use Cassandane::Tiny; sub test_floating_notz - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $utc = DateTime::Format::ISO8601->new->parse_datetime($start . 'Z'); + my $utc = DateTime::Format::ISO8601->new->parse_datetime($start . 'Z'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "95989f3d-575f-4828-9610-6f16b9d54d04"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $utc->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $utc->epoch() - 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $utc->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $utc->epoch() + 60); - $self->assert_alarms({summary => 'Floating', start => $start, timezone => '[floating]'}); + $self->assert_alarms( + { summary => 'Floating', start => $start, timezone => '[floating]' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/floating_sametz b/cassandane/tiny-tests/CaldavAlarm/floating_sametz index bde2759b65..ab2e6a67c0 100644 --- a/cassandane/tiny-tests/CaldavAlarm/floating_sametz +++ b/cassandane/tiny-tests/CaldavAlarm/floating_sametz @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_floating_sametz - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $tz = <NewCalendar({name => 'foo', timeZone => $tz}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo', timeZone => $tz }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "95989f3d-575f-4828-9610-6f16b9d54d04"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Floating'}); + $self->assert_alarms({ summary => 'Floating' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/override b/cassandane/tiny-tests/CaldavAlarm/override index 2d00c1c3e2..f524651ff2 100644 --- a/cassandane/tiny-tests/CaldavAlarm/override +++ b/cassandane/tiny-tests/CaldavAlarm/override @@ -2,48 +2,47 @@ use Cassandane::Tiny; sub test_override - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define an event that started almost an hour ago and repeats hourly - my $startdt = $now->clone(); - $startdt->subtract(DateTime::Duration->new(minutes => 59, seconds => 55)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define an event that started almost an hour ago and repeats hourly + my $startdt = $now->clone(); + $startdt->subtract(DateTime::Duration->new(minutes => 59, seconds => 55)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # the next event will start in a few seconds - my $recuriddt = $now->clone(); - $recuriddt->add(DateTime::Duration->new(seconds => 5)); - my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); + # the next event will start in a few seconds + my $recuriddt = $now->clone(); + $recuriddt->add(DateTime::Duration->new(seconds => 5)); + my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); - my $rstartdt = $recuriddt->clone(); - my $recurstart = $recuriddt->strftime('%Y%m%dT%H%M%S'); + my $rstartdt = $recuriddt->clone(); + my $recurstart = $recuriddt->strftime('%Y%m%dT%H%M%S'); - my $renddt = $rstartdt->clone(); - $renddt->add(DateTime::Duration->new(seconds => 15)); - my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); + my $renddt = $rstartdt->clone(); + $renddt->add(DateTime::Duration->new(seconds => 15)); + my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - # trigger processing of alarms - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + # trigger processing of alarms + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'exception', start => $recurstart}); + $self->assert_alarms({ summary => 'exception', start => $recurstart }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/override_double b/cassandane/tiny-tests/CaldavAlarm/override_double index 28eb431c31..45a7b6ad0f 100644 --- a/cassandane/tiny-tests/CaldavAlarm/override_double +++ b/cassandane/tiny-tests/CaldavAlarm/override_double @@ -2,52 +2,51 @@ use Cassandane::Tiny; sub test_override_double - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define an event that started almost an hour ago and repeats hourly - my $startdt = $now->clone(); - $startdt->subtract(DateTime::Duration->new(minutes => 59, seconds => 55)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define an event that started almost an hour ago and repeats hourly + my $startdt = $now->clone(); + $startdt->subtract(DateTime::Duration->new(minutes => 59, seconds => 55)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # the next event will start in a few seconds - my $recuriddt = $now->clone(); - $recuriddt->add(DateTime::Duration->new(seconds => 5)); - my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); + # the next event will start in a few seconds + my $recuriddt = $now->clone(); + $recuriddt->add(DateTime::Duration->new(seconds => 5)); + my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); - my $rstartdt = $recuriddt->clone(); - my $recurstart = $recuriddt->strftime('%Y%m%dT%H%M%S'); + my $rstartdt = $recuriddt->clone(); + my $recurstart = $recuriddt->strftime('%Y%m%dT%H%M%S'); - my $renddt = $rstartdt->clone(); - $renddt->add(DateTime::Duration->new(seconds => 15)); - my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); + my $renddt = $rstartdt->clone(); + $renddt->add(DateTime::Duration->new(seconds => 15)); + my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); - my $lastrepl = $recuriddt->clone(); - $lastrepl->add(DateTime::Duration->new(minutes => 60)); - my $lastalarm = $lastrepl->strftime('%Y%m%dT%H%M%S'); + my $lastrepl = $recuriddt->clone(); + $lastrepl->add(DateTime::Duration->new(minutes => 60)); + my $lastalarm = $lastrepl->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - # trigger processing of alarms - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 6000 ); + # trigger processing of alarms + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 6000); - $self->assert_alarms({summary => 'exception', start => $recurstart}, {summary => 'main', start => $lastalarm}); + $self->assert_alarms( + { summary => 'exception', start => $recurstart }, + { summary => 'main', start => $lastalarm } + ); } diff --git a/cassandane/tiny-tests/CaldavAlarm/override_exception b/cassandane/tiny-tests/CaldavAlarm/override_exception index 0213d34ee4..e5f2ccca89 100644 --- a/cassandane/tiny-tests/CaldavAlarm/override_exception +++ b/cassandane/tiny-tests/CaldavAlarm/override_exception @@ -2,50 +2,49 @@ use Cassandane::Tiny; sub test_override_exception - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define an event that started almost an hour ago and repeats hourly - my $startdt = $now->clone(); - $startdt->subtract(DateTime::Duration->new(minutes => 59, seconds => 55)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define an event that started almost an hour ago and repeats hourly + my $startdt = $now->clone(); + $startdt->subtract(DateTime::Duration->new(minutes => 59, seconds => 55)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # the next event will start in a few seconds - my $recuriddt = $now->clone(); - $recuriddt->add(DateTime::Duration->new(seconds => 5)); - my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); + # the next event will start in a few seconds + my $recuriddt = $now->clone(); + $recuriddt->add(DateTime::Duration->new(seconds => 5)); + my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); - # but it starts a few seconds after the regular start - my $rstartdt = $now->clone(); - $rstartdt->add(DateTime::Duration->new(seconds => 15)); - my $recurstart = $rstartdt->strftime('%Y%m%dT%H%M%S'); + # but it starts a few seconds after the regular start + my $rstartdt = $now->clone(); + $rstartdt->add(DateTime::Duration->new(seconds => 15)); + my $recurstart = $rstartdt->strftime('%Y%m%dT%H%M%S'); - my $renddt = $rstartdt->clone(); - $renddt->add(DateTime::Duration->new(seconds => 15)); - my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); + my $renddt = $rstartdt->clone(); + $renddt->add(DateTime::Duration->new(seconds => 15)); + my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - # trigger processing of alarms - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + # trigger processing of alarms + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'exception', start => $recurstart}); + $self->assert_alarms({ summary => 'exception', start => $recurstart }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/override_multiuser b/cassandane/tiny-tests/CaldavAlarm/override_multiuser index 3aa824e441..e59ab9f017 100644 --- a/cassandane/tiny-tests/CaldavAlarm/override_multiuser +++ b/cassandane/tiny-tests/CaldavAlarm/override_multiuser @@ -2,67 +2,67 @@ use Cassandane::Tiny; sub test_override_multiuser - :min_version_3_1 :needs_component_calalarmd :NoAltNameSpace -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); - - my $AdminTalk = $self->{adminstore}->get_client(); - $AdminTalk->create("user.foo"); - $AdminTalk->setacl("user.cassandane.#calendars.$CalendarId", "foo", "lrswipkxtecdn789"); - - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo"); - my $footalk = $foostore->get_client(); - $footalk->subscribe("user.cassandane.#calendars.$CalendarId"); - - my $service = $self->{instance}->get_service("http"); - my $FooDAV = Net::CalDAVTalk->new( - user => 'foo', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $cal = $FooDAV->GetCalendar("cassandane.$CalendarId"); - $self->assert_not_null($cal); - - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); - - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - my $nextweekdt = $now->clone(); - $nextweekdt->add(DateTime::Duration->new(days => 7)); - my $nextweek = $nextweekdt->strftime('%Y%m%dT%H%M%S'); - - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - - my $nwenddt = $nextweekdt->clone(); - $nwenddt->add(DateTime::Duration->new(seconds => 15)); - my $nwend = $nwenddt->strftime('%Y%m%dT%H%M%S'); - - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; - - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $cardtmpl = <{caldav}; + + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); + + my $AdminTalk = $self->{adminstore}->get_client(); + $AdminTalk->create("user.foo"); + $AdminTalk->setacl("user.cassandane.#calendars.$CalendarId", + "foo", "lrswipkxtecdn789"); + + my $foostore + = $self->{instance}->get_service('imap')->create_store(username => "foo"); + my $footalk = $foostore->get_client(); + $footalk->subscribe("user.cassandane.#calendars.$CalendarId"); + + my $service = $self->{instance}->get_service("http"); + my $FooDAV = Net::CalDAVTalk->new( + user => 'foo', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $cal = $FooDAV->GetCalendar("cassandane.$CalendarId"); + $self->assert_not_null($cal); + + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); + + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + my $nextweekdt = $now->clone(); + $nextweekdt->add(DateTime::Duration->new(days => 7)); + my $nextweek = $nextweekdt->strftime('%Y%m%dT%H%M%S'); + + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + + my $nwenddt = $nextweekdt->clone(); + $nwenddt->add(DateTime::Duration->new(seconds => 15)); + my $nwend = $nwenddt->strftime('%Y%m%dT%H%M%S'); + + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; + + my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; + my $href = "$CalendarId/$uuid.ics"; + my $cardtmpl = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - my $Events = $CalDAV->GetEvents("$CalendarId"); - my $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); - $self->assert_num_equals(1, scalar @$Events); - $self->assert_num_equals(1, scalar @$FooEvents); - $self->assert_null($Events->[0]{alerts}); - $self->assert_null($FooEvents->[0]{alerts}); - - my $foocard = $cardtmpl; - $foocard =~ s/SUMMARY:EV2/SUMMARY:EV2\n$latealarm/; - $FooDAV->Request('PUT', $FooEvents->[0]{href}, $foocard, 'Content-Type' => 'text/calendar'); - - my $cascard = $cardtmpl; - $cascard =~ s/SUMMARY:EV1/SUMMARY:EV1\n$alarm/; - $CalDAV->Request('PUT', $Events->[0]{href}, $cascard, 'Content-Type' => 'text/calendar'); - - $Events = $CalDAV->GetEvents("$CalendarId"); - $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); - $self->assert_num_equals(1, scalar @$Events); - $self->assert_num_equals(1, scalar @$FooEvents); - $self->assert_null($Events->[0]{alerts}); - $self->assert_null($FooEvents->[0]{alerts}); - - # XXX - assert the recurrences - - # clean notification cache - $self->{instance}->getnotify(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); - - $self->assert_alarms(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); - - $self->assert_alarms({summary => 'EV1', userId => 'cassandane', alarmTime => $start, action => 'email', start => $start}); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $nextweekdt->epoch() - 60 ); - - $self->assert_alarms(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $nextweekdt->epoch() + 60 ); - - # need to version-gate features that aren't in 3.1... - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min >= 9)) { - $self->assert_alarms({summary => 'EV2', userId => 'foo', calendarOwner => 'cassandane', alarmTime => $nextweek, action => 'display', start => $nextweek}); - } - else { - $self->assert_alarms({summary => 'EV2', userId => 'foo', alarmTime => $nextweek, action => 'display', start => $nextweek}); + my $card = $cardtmpl; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + my $Events = $CalDAV->GetEvents("$CalendarId"); + my $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); + $self->assert_num_equals(1, scalar @$Events); + $self->assert_num_equals(1, scalar @$FooEvents); + $self->assert_null($Events->[0]{alerts}); + $self->assert_null($FooEvents->[0]{alerts}); + + my $foocard = $cardtmpl; + $foocard =~ s/SUMMARY:EV2/SUMMARY:EV2\n$latealarm/; + $FooDAV->Request('PUT', $FooEvents->[0]{href}, + $foocard, 'Content-Type' => 'text/calendar'); + + my $cascard = $cardtmpl; + $cascard =~ s/SUMMARY:EV1/SUMMARY:EV1\n$alarm/; + $CalDAV->Request('PUT', $Events->[0]{href}, + $cascard, 'Content-Type' => 'text/calendar'); + + $Events = $CalDAV->GetEvents("$CalendarId"); + $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); + $self->assert_num_equals(1, scalar @$Events); + $self->assert_num_equals(1, scalar @$FooEvents); + $self->assert_null($Events->[0]{alerts}); + $self->assert_null($FooEvents->[0]{alerts}); + + # XXX - assert the recurrences + + # clean notification cache + $self->{instance}->getnotify(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); + + $self->assert_alarms(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + + $self->assert_alarms( + { + summary => 'EV1', + userId => 'cassandane', + alarmTime => $start, + action => 'email', + start => $start } + ); + + $self->{instance}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $nextweekdt->epoch() - 60); + + $self->assert_alarms(); + + $self->{instance}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $nextweekdt->epoch() + 60); + + # need to version-gate features that aren't in 3.1... + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min >= 9)) { + $self->assert_alarms( + { + summary => 'EV2', + userId => 'foo', + calendarOwner => 'cassandane', + alarmTime => $nextweek, + action => 'display', + start => $nextweek + } + ); + } else { + $self->assert_alarms( + { + summary => 'EV2', + userId => 'foo', + alarmTime => $nextweek, + action => 'display', + start => $nextweek + } + ); + } } diff --git a/cassandane/tiny-tests/CaldavAlarm/recurring_absolute_trigger b/cassandane/tiny-tests/CaldavAlarm/recurring_absolute_trigger index f6a13dd54b..aa62d423d1 100644 --- a/cassandane/tiny-tests/CaldavAlarm/recurring_absolute_trigger +++ b/cassandane/tiny-tests/CaldavAlarm/recurring_absolute_trigger @@ -2,41 +2,40 @@ use Cassandane::Tiny; sub test_recurring_absolute_trigger - :min_version_3_7 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_7 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%SZ'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%SZ'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%SZ'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%SZ'); - # set the trigger to notify us at the start of the event - my $trigger = $startdt->strftime('%Y%m%dT%H%M%SZ'); + # set the trigger to notify us at the start of the event + my $trigger = $startdt->strftime('%Y%m%dT%H%M%SZ'); - # calculate start time for second instance - my $recuriddt = $startdt->clone(); - $recuriddt->add(DateTime::Duration->new(days => 1)); - my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%SZ'); + # calculate start time for second instance + my $recuriddt = $startdt->clone(); + $recuriddt->add(DateTime::Duration->new(days => 1)); + my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%SZ'); - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - # adjust now to UTC - $now->add(DateTime::Duration->new(seconds => $now->offset())); + # adjust now to UTC + $now->add(DateTime::Duration->new(seconds => $now->offset())); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); - $self->assert_alarms(); + $self->assert_alarms(); - # fire alarms for first instance - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + # fire alarms for first instance + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Simple', start => $start, action => 'display'}, - {summary => 'Simple', start => $start, action => 'email'}); + $self->assert_alarms( + { summary => 'Simple', start => $start, action => 'display' }, + { summary => 'Simple', start => $start, action => 'email' } + ); - # fire alarm for second instance - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 86400 + 60 ); + # fire alarm for second instance + $self->{instance}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $now->epoch() + 86400 + 60); - $self->assert_alarms({summary => 'Simple', start => $recurid, action => 'email'}); + $self->assert_alarms( + { summary => 'Simple', start => $recurid, action => 'email' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/recurring_allday_floating b/cassandane/tiny-tests/CaldavAlarm/recurring_allday_floating index f7023e9aca..75432310f9 100644 --- a/cassandane/tiny-tests/CaldavAlarm/recurring_allday_floating +++ b/cassandane/tiny-tests/CaldavAlarm/recurring_allday_floating @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_recurring_allday_floating - :min_version_3_9 :needs_component_calalarmd :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_calalarmd : needs_component_jmap { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; -my $UTC = <NewCalendar({name => 'foo', timeZone => $UTC}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo', timeZone => $UTC }); + $self->assert_not_null($CalendarId); - my $now = DateTime->today(); - $now->set_time_zone('Etc/UTC'); + my $now = DateTime->today(); + $now->set_time_zone('Etc/UTC'); - # define the event to start next week - my $startdt = $now->clone(); - $startdt->add(days => 7); - my $start = $startdt->strftime('%Y%m%d'); + # define the event to start next week + my $startdt = $now->clone(); + $startdt->add(days => 7); + my $start = $startdt->strftime('%Y%m%d'); - # set the trigger to notify us 5 hours before the event - my $trigger="-PT5H"; + # set the trigger to notify us 5 hours before the event + my $trigger = "-PT5H"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $now->subtract(hours => 5); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60 ); + $now->subtract(hours => 5); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms(); + $self->assert_alarms(); - $now->add(days => 7); + $now->add(days => 7); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Simple', start => $start}); + $self->assert_alarms({ summary => 'Simple', start => $start }); - $now->add(days => 7); - $startdt->add(days => 7); - $start = $startdt->strftime('%Y%m%d'); + $now->add(days => 7); + $startdt->add(days => 7); + $start = $startdt->strftime('%Y%m%d'); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Simple', start => $start}); + $self->assert_alarms({ summary => 'Simple', start => $start }); - # Change floating time zone on the calendar 2 hours to the east - my $xml = < @@ -103,41 +102,48 @@ EOF EOF - my $res = $CalDAV->Request('PROPPATCH', - "/dav/calendars/user/cassandane/". $CalendarId, - $xml, 'Content-Type' => 'text/xml'); - - $now->add(days => 7); - $startdt->add(days => 7); - $start = $startdt->strftime('%Y%m%d'); - - # Need to trigger 2 hours earlier - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() - 7200 + 60); - - $self->assert_alarms({summary => 'Simple', start => $start}); - - # Change floating time zone on the calendar 2 more hours to the east - $res = $jmap->CallMethods([ - ['Calendar/set', {update => {$CalendarId => { - timeZone => "Etc/GMT-4" - }}}, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_not_null($res->[0][1]{updated}); - - $now->add(days => 7); - $startdt->add(days => 7); - $start = $startdt->strftime('%Y%m%d'); - - # Need to trigger 4 hours earlier - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() - 14400 + 60); - - $self->assert_alarms({summary => 'Simple', start => $start}); - - # Change floating time zone on the calendar back to original - $xml = <Request('PROPPATCH', + "/dav/calendars/user/cassandane/" . $CalendarId, + $xml, 'Content-Type' => 'text/xml'); + + $now->add(days => 7); + $startdt->add(days => 7); + $start = $startdt->strftime('%Y%m%d'); + + # Need to trigger 2 hours earlier + $self->{instance}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $now->epoch() - 7200 + 60); + + $self->assert_alarms({ summary => 'Simple', start => $start }); + + # Change floating time zone on the calendar 2 more hours to the east + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + update => { + $CalendarId => { + timeZone => "Etc/GMT-4" + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_not_null($res->[0][1]{updated}); + + $now->add(days => 7); + $startdt->add(days => 7); + $start = $startdt->strftime('%Y%m%d'); + + # Need to trigger 4 hours earlier + $self->{instance}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $now->epoch() - 14400 + 60); + + $self->assert_alarms({ summary => 'Simple', start => $start }); + + # Change floating time zone on the calendar back to original + $xml = < @@ -148,16 +154,17 @@ EOF EOF - $res = $CalDAV->Request('PROPPATCH', - "/dav/calendars/user/cassandane/". $CalendarId, - $xml, 'Content-Type' => 'text/xml'); + $res + = $CalDAV->Request('PROPPATCH', + "/dav/calendars/user/cassandane/" . $CalendarId, + $xml, 'Content-Type' => 'text/xml'); - $now->add(days => 7); - $startdt->add(days => 7); - $start = $startdt->strftime('%Y%m%d'); + $now->add(days => 7); + $startdt->add(days => 7); + $start = $startdt->strftime('%Y%m%d'); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Simple', start => $start}); + $self->assert_alarms({ summary => 'Simple', start => $start }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/replication_at1 b/cassandane/tiny-tests/CaldavAlarm/replication_at1 index 9729cb163e..8b8da0c525 100644 --- a/cassandane/tiny-tests/CaldavAlarm/replication_at1 +++ b/cassandane/tiny-tests/CaldavAlarm/replication_at1 @@ -2,52 +2,51 @@ use Cassandane::Tiny; sub test_replication_at1 - :min_version_3_0 :needs_component_calalarmd :NoReplicaOnly -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd : NoReplicaOnly { + my ($self) = @_; - $self->assert_not_null($self->{replica}); + $self->assert_not_null($self->{replica}); - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define an event that starts now and repeats hourly - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 60)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define an event that starts now and repeats hourly + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 60)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 60)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 60)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # the next event will start in a few seconds - my $recuriddt = $startdt->clone(); - $recuriddt->add(DateTime::Duration->new(minutes => 60)); - my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); + # the next event will start in a few seconds + my $recuriddt = $startdt->clone(); + $recuriddt->add(DateTime::Duration->new(minutes => 60)); + my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); - # but it starts a few seconds after the regular start - my $rstartdt = $recuriddt->clone(); - $rstartdt->add(DateTime::Duration->new(seconds => 15)); - my $recurstart = $recuriddt->strftime('%Y%m%dT%H%M%S'); + # but it starts a few seconds after the regular start + my $rstartdt = $recuriddt->clone(); + $rstartdt->add(DateTime::Duration->new(seconds => 15)); + my $recurstart = $recuriddt->strftime('%Y%m%dT%H%M%S'); - my $renddt = $rstartdt->clone(); - $renddt->add(DateTime::Duration->new(seconds => 60)); - my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); + my $renddt = $rstartdt->clone(); + $renddt->add(DateTime::Duration->new(seconds => 60)); + my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # replicate to the other end - $self->run_replication(); + # replicate to the other end + $self->run_replication(); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - # trigger processing of alarms - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 500 ); - $self->assert_alarms({summary => 'main'}); + # trigger processing of alarms + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 500); + $self->assert_alarms({ summary => 'main' }); - # no alarm when you run the second time - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 500 ); - $self->assert_alarms(); + # no alarm when you run the second time + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 500); + $self->assert_alarms(); - # replicate to the other end - $self->run_replication(); + # replicate to the other end + $self->run_replication(); - # running on the replica gets the exception, not the first instance - $self->{replica}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5000 ); - $self->assert_alarms({summary => 'exception'}); + # running on the replica gets the exception, not the first instance + $self->{replica} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5000); + $self->assert_alarms({ summary => 'exception' }); - # no alarm when you run the second time - $self->{replica}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5000 ); - $self->assert_alarms(); + # no alarm when you run the second time + $self->{replica} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5000); + $self->assert_alarms(); - # running on the master still gets the exception, because it doesn't know about the change - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5000 ); - $self->assert_alarms({summary => 'exception'}); +# running on the master still gets the exception, because it doesn't know about the change + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5000); + $self->assert_alarms({ summary => 'exception' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/replication_withalarms_in_tz_with_dst b/cassandane/tiny-tests/CaldavAlarm/replication_withalarms_in_tz_with_dst index eab3a7b5e9..b5e4fc9612 100644 --- a/cassandane/tiny-tests/CaldavAlarm/replication_withalarms_in_tz_with_dst +++ b/cassandane/tiny-tests/CaldavAlarm/replication_withalarms_in_tz_with_dst @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_replication_withalarms_in_tz_with_dst - :min_version_3_0 :needs_component_calalarmd :NoReplicaOnly -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd : NoReplicaOnly { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event which recurs weekly - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event which recurs weekly + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5 ); + # clean notification cache + $self->{instance}->getnotify(); - # clean notification cache - $self->{instance}->getnotify(); + $self->run_replication(nosyncback => 1); - $self->run_replication(nosyncback => 1); + $self->assert_alarms(); - $self->assert_alarms(); + $self->{replica} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5); - $self->{replica}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5 ); + $self->assert_alarms(); - $self->assert_alarms(); + $self->{replica} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5); - $self->{replica}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5 ); + $self->assert_alarms(); - $self->assert_alarms(); + $self->run_replication(nosyncback => 1); - $self->run_replication(nosyncback => 1); + $self->assert_alarms(); - $self->assert_alarms(); + # Check if DST is applicable + my $tdt = $now->clone(); + $tdt->add(DateTime::Duration->new(seconds => ((86400 * 7) + 5))); + if (!$tdt->is_dst()) { + # Not in DST, add an hour + $tdt = $tdt->add(DateTime::Duration->new(seconds => (60 * 60))); + } - # Check if DST is applicable - my $tdt = $now->clone(); - $tdt->add(DateTime::Duration->new(seconds => ((86400 * 7) + 5))); - if (!$tdt->is_dst()) { - # Not in DST, add an hour - $tdt = $tdt->add(DateTime::Duration->new(seconds => (60 * 60))); - } + $self->{replica} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $tdt->epoch()); - $self->{replica}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $tdt->epoch()); - - # should be a new alarm here - $self->assert_alarms({summary => 'Simple'}); + # should be a new alarm here + $self->assert_alarms({ summary => 'Simple' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/replication_withalarms_in_tz_without_dst b/cassandane/tiny-tests/CaldavAlarm/replication_withalarms_in_tz_without_dst index ea2e24dd41..45cdc60fcd 100644 --- a/cassandane/tiny-tests/CaldavAlarm/replication_withalarms_in_tz_without_dst +++ b/cassandane/tiny-tests/CaldavAlarm/replication_withalarms_in_tz_without_dst @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_replication_withalarms_in_tz_without_dst - :min_version_3_0 :needs_component_calalarmd :NoReplicaOnly -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd : NoReplicaOnly { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Asia/Singapore'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Asia/Singapore'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event which recurs weekly - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event which recurs weekly + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5 ); + # clean notification cache + $self->{instance}->getnotify(); - # clean notification cache - $self->{instance}->getnotify(); + $self->run_replication(nosyncback => 1); - $self->run_replication(nosyncback => 1); + $self->assert_alarms(); - $self->assert_alarms(); + $self->{replica} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5); - $self->{replica}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5 ); + $self->assert_alarms(); - $self->assert_alarms(); + $self->{replica} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5); - $self->{replica}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 5 ); + $self->assert_alarms(); - $self->assert_alarms(); + $self->run_replication(nosyncback => 1); - $self->run_replication(nosyncback => 1); + $self->assert_alarms(); - $self->assert_alarms(); + $self->{replica}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $now->epoch() + (86400 * 7) + 5); - $self->{replica}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + (86400*7) + 5); - - # should be a new alarm here - $self->assert_alarms({summary => 'Simple'}); + # should be a new alarm here + $self->assert_alarms({ summary => 'Simple' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/reschedule_exception b/cassandane/tiny-tests/CaldavAlarm/reschedule_exception index fec28d88b1..44fd257034 100644 --- a/cassandane/tiny-tests/CaldavAlarm/reschedule_exception +++ b/cassandane/tiny-tests/CaldavAlarm/reschedule_exception @@ -2,52 +2,51 @@ use Cassandane::Tiny; sub test_reschedule_exception - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; - - # FIXME disable this test until calalarmd is fixed - return; - - my $CalDAV = $self->{caldav}; - - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); - - my $now = DateTime->now(); - $now->set_time_zone('Europe/Vienna'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); - - # define an event that started yesterday and repeats daily - my $startdt = $now->clone(); - $startdt->subtract(DateTime::Duration->new(hours => 23)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(minutes => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - - # the next event will start in one hour - my $recuriddt = $now->clone(); - $recuriddt->add(DateTime::Duration->new(hours => 1)); - my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); - - # but it exceptionally starts in two hours - my $rstartdt = $now->clone(); - $rstartdt->add(DateTime::Duration->new(hours => 2)); - my $recurstart = $rstartdt->strftime('%Y%m%dT%H%M%S'); - my $renddt = $rstartdt->clone(); - $renddt->add(DateTime::Duration->new(minutes => 15)); - my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); - - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; - - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <{caldav}; + + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); + + my $now = DateTime->now(); + $now->set_time_zone('Europe/Vienna'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); + + # define an event that started yesterday and repeats daily + my $startdt = $now->clone(); + $startdt->subtract(DateTime::Duration->new(hours => 23)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(minutes => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + + # the next event will start in one hour + my $recuriddt = $now->clone(); + $recuriddt->add(DateTime::Duration->new(hours => 1)); + my $recurid = $recuriddt->strftime('%Y%m%dT%H%M%S'); + + # but it exceptionally starts in two hours + my $rstartdt = $now->clone(); + $rstartdt->add(DateTime::Duration->new(hours => 2)); + my $recurstart = $rstartdt->strftime('%Y%m%dT%H%M%S'); + my $renddt = $rstartdt->clone(); + $renddt->add(DateTime::Duration->new(minutes => 15)); + my $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); + + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; + + my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; + my $href = "$CalendarId/$uuid.ics"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + xlog $self, "PUT VEVENT"; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - # trigger processing of alarms: wall clock for calalarmd is 10 seconds *after* - # the occurrence of the exception. This will trigger it to fire its alarm. - xlog $self, "run calalarmd"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $rstartdt->epoch() + 10 ); + # trigger processing of alarms: wall clock for calalarmd is 10 seconds *after* + # the occurrence of the exception. This will trigger it to fire its alarm. + xlog $self, "run calalarmd"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $rstartdt->epoch() + 10); - $self->assert_alarms({summary => 'exception', start => $recurstart}); + $self->assert_alarms({ summary => 'exception', start => $recurstart }); - # reschedule the exception event to start one hour later than the original - # exceptional start time. - $rstartdt->add(DateTime::Duration->new(hours => 1)); - $recurstart = $rstartdt->strftime('%Y%m%dT%H%M%S'); - $renddt = $rstartdt->clone(); - $renddt->add(DateTime::Duration->new(minutes => 15)); - $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); + # reschedule the exception event to start one hour later than the original + # exceptional start time. + $rstartdt->add(DateTime::Duration->new(hours => 1)); + $recurstart = $rstartdt->strftime('%Y%m%dT%H%M%S'); + $renddt = $rstartdt->clone(); + $renddt->add(DateTime::Duration->new(minutes => 15)); + $recurend = $renddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + xlog $self, "PUT rescheduled VEVENT"; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "Re-run calalarmd"; - # trigger processing of alarms: wall clock now is 10 seconds after the - # newly scheduled exception time - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $rstartdt->epoch() + 10 ); + xlog $self, "Re-run calalarmd"; + # trigger processing of alarms: wall clock now is 10 seconds after the + # newly scheduled exception time + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $rstartdt->epoch() + 10); - $self->assert_alarms({summary => 'rescheduled', start => $recurstart}); + $self->assert_alarms({ summary => 'rescheduled', start => $recurstart }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/reschedule_later b/cassandane/tiny-tests/CaldavAlarm/reschedule_later index 0bebc74816..4d86a7304a 100644 --- a/cassandane/tiny-tests/CaldavAlarm/reschedule_later +++ b/cassandane/tiny-tests/CaldavAlarm/reschedule_later @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_reschedule_later - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Simple', start => $start}); + $self->assert_alarms({ summary => 'Simple', start => $start }); - # define the event to start in a few seconds - my $newstartdt = $startdt->clone(); - $newstartdt->add(DateTime::Duration->new(seconds => 86400)); - my $newstart = $newstartdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $newstartdt = $startdt->clone(); + $newstartdt->add(DateTime::Duration->new(seconds => 86400)); + my $newstart = $newstartdt->strftime('%Y%m%dT%H%M%S'); - my $newenddt = $enddt->clone(); - $newenddt->add(DateTime::Duration->new(seconds => 86400)); - my $newend = $newenddt->strftime('%Y%m%dT%H%M%S'); + my $newenddt = $enddt->clone(); + $newenddt->add(DateTime::Duration->new(seconds => 86400)); + my $newend = $newenddt->strftime('%Y%m%dT%H%M%S'); - $card =~ s/$start/$newstart/; - $card =~ s/$end/$newend/; + $card =~ s/$start/$newstart/; + $card =~ s/$end/$newend/; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # nothing happens 1 second later - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 61 ); + # nothing happens 1 second later + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 61); - $self->assert_alarms(); + $self->assert_alarms(); - # alarm happens one day later - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 + 86400 ); + # alarm happens one day later + $self->{instance}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $now->epoch() + 60 + 86400); - $self->assert_alarms({summary => 'Simple', start => $newstart}); + $self->assert_alarms({ summary => 'Simple', start => $newstart }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple b/cassandane/tiny-tests/CaldavAlarm/simple index d452a17bd3..e6a2e58eb9 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple +++ b/cassandane/tiny-tests/CaldavAlarm/simple @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_simple - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Simple', start => $start, timezone => 'Australia/Sydney'}); + $self->assert_alarms( + { summary => 'Simple', start => $start, timezone => 'Australia/Sydney' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple_attendee_no_partstat b/cassandane/tiny-tests/CaldavAlarm/simple_attendee_no_partstat index a57a8a0f54..c0224c12ca 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple_attendee_no_partstat +++ b/cassandane/tiny-tests/CaldavAlarm/simple_attendee_no_partstat @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_simple_attendee_no_partstat - :min_version_3_7 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_7 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Simple', start => $start, timezone => 'Australia/Sydney'}); + $self->assert_alarms( + { summary => 'Simple', start => $start, timezone => 'Australia/Sydney' }); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple_cancelled b/cassandane/tiny-tests/CaldavAlarm/simple_cancelled index 06642abc10..1b47c8e890 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple_cancelled +++ b/cassandane/tiny-tests/CaldavAlarm/simple_cancelled @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_simple_cancelled - :min_version_3_7 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_7 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms(); + $self->assert_alarms(); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple_declined b/cassandane/tiny-tests/CaldavAlarm/simple_declined index 81e24f2338..af42de2943 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple_declined +++ b/cassandane/tiny-tests/CaldavAlarm/simple_declined @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_simple_declined - :min_version_3_7 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_7 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms(); + $self->assert_alarms(); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple_declined_alias b/cassandane/tiny-tests/CaldavAlarm/simple_declined_alias index 98cfd27fa8..53e03ea0b3 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple_declined_alias +++ b/cassandane/tiny-tests/CaldavAlarm/simple_declined_alias @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_simple_declined_alias - :min_version_3_7 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_7 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $xml = < @@ -22,33 +21,35 @@ sub test_simple_declined_alias EOF - $CalDAV->Request('PROPPATCH', "/dav/principals/user/cassandane", $xml, - 'Content-Type' => 'text/xml'); + $CalDAV->Request( + 'PROPPATCH', "/dav/principals/user/cassandane", + $xml, 'Content-Type' => 'text/xml' + ); - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms(); + $self->assert_alarms(); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple_multiuser b/cassandane/tiny-tests/CaldavAlarm/simple_multiuser index 9b653cf805..2881eafac3 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple_multiuser +++ b/cassandane/tiny-tests/CaldavAlarm/simple_multiuser @@ -2,63 +2,63 @@ use Cassandane::Tiny; sub test_simple_multiuser - :min_version_3_1 :needs_component_calalarmd :NoAltNameSpace -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); - - my $AdminTalk = $self->{adminstore}->get_client(); - $AdminTalk->create("user.foo"); - $AdminTalk->setacl("user.cassandane.#calendars.$CalendarId", "foo", "lrswipkxtecdn789"); - - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo"); - my $footalk = $foostore->get_client(); - $footalk->subscribe("user.cassandane.#calendars.$CalendarId"); - - my $service = $self->{instance}->get_service("http"); - my $FooDAV = Net::CalDAVTalk->new( - user => 'foo', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $cal = $FooDAV->GetCalendar("cassandane.$CalendarId"); - $self->assert_not_null($cal); - - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); - - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - - my $latedt = $startdt->clone(); - $latedt->add(DateTime::Duration->new(seconds => 300)); - my $late = $latedt->strftime('%Y%m%dT%H%M%S'); - - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; - - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $cardtmpl = <{caldav}; + + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); + + my $AdminTalk = $self->{adminstore}->get_client(); + $AdminTalk->create("user.foo"); + $AdminTalk->setacl("user.cassandane.#calendars.$CalendarId", + "foo", "lrswipkxtecdn789"); + + my $foostore + = $self->{instance}->get_service('imap')->create_store(username => "foo"); + my $footalk = $foostore->get_client(); + $footalk->subscribe("user.cassandane.#calendars.$CalendarId"); + + my $service = $self->{instance}->get_service("http"); + my $FooDAV = Net::CalDAVTalk->new( + user => 'foo', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $cal = $FooDAV->GetCalendar("cassandane.$CalendarId"); + $self->assert_not_null($cal); + + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); + + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + + my $latedt = $startdt->clone(); + $latedt->add(DateTime::Duration->new(seconds => 300)); + my $late = $latedt->strftime('%Y%m%dT%H%M%S'); + + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; + + my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; + my $href = "$CalendarId/$uuid.ics"; + my $cardtmpl = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - my $Events = $CalDAV->GetEvents("$CalendarId"); - my $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); - $self->assert_num_equals(1, scalar @$Events); - $self->assert_num_equals(1, scalar @$FooEvents); - $self->assert_not_null($Events->[0]{alerts}); - # foo event does not yet have alarms - $self->assert_null($FooEvents->[0]{alerts}); - - my $foocard = $cardtmpl; - $foocard =~ s/XXALARMDATAXX/$latealarm/; - $FooDAV->Request('PUT', $FooEvents->[0]{href}, $foocard, 'Content-Type' => 'text/calendar'); - - $Events = $CalDAV->GetEvents("$CalendarId"); - $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); - $self->assert_num_equals(1, scalar @$Events); - $self->assert_num_equals(1, scalar @$FooEvents); - $self->assert_not_null($Events->[0]{alerts}); - # foo event has alarms - $self->assert_not_null($FooEvents->[0]{alerts}); - - # clean notification cache - $self->{instance}->getnotify(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); - - $self->assert_alarms(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); - - $self->assert_alarms({summary => 'Simple', userId => 'cassandane', alarmTime => $start, start => $start}); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 600 ); - - $self->assert_alarms({summary => 'Simple', userId => 'foo', alarmTime => $late, start => $start}); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 900 ); - - $self->assert_alarms(); + my $card = $cardtmpl; + $card =~ s/XXALARMDATAXX/$alarm/; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + my $Events = $CalDAV->GetEvents("$CalendarId"); + my $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); + $self->assert_num_equals(1, scalar @$Events); + $self->assert_num_equals(1, scalar @$FooEvents); + $self->assert_not_null($Events->[0]{alerts}); + # foo event does not yet have alarms + $self->assert_null($FooEvents->[0]{alerts}); + + my $foocard = $cardtmpl; + $foocard =~ s/XXALARMDATAXX/$latealarm/; + $FooDAV->Request('PUT', $FooEvents->[0]{href}, + $foocard, 'Content-Type' => 'text/calendar'); + + $Events = $CalDAV->GetEvents("$CalendarId"); + $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); + $self->assert_num_equals(1, scalar @$Events); + $self->assert_num_equals(1, scalar @$FooEvents); + $self->assert_not_null($Events->[0]{alerts}); + # foo event has alarms + $self->assert_not_null($FooEvents->[0]{alerts}); + + # clean notification cache + $self->{instance}->getnotify(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); + + $self->assert_alarms(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + + $self->assert_alarms( + { + summary => 'Simple', + userId => 'cassandane', + alarmTime => $start, + start => $start + } + ); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 600); + + $self->assert_alarms( + { + summary => 'Simple', + userId => 'foo', + alarmTime => $late, + start => $start + } + ); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 900); + + $self->assert_alarms(); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple_multiuser_sametime b/cassandane/tiny-tests/CaldavAlarm/simple_multiuser_sametime index a2cd552902..1af8dabf54 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple_multiuser_sametime +++ b/cassandane/tiny-tests/CaldavAlarm/simple_multiuser_sametime @@ -2,59 +2,59 @@ use Cassandane::Tiny; sub test_simple_multiuser_sametime - :min_version_3_1 :needs_component_calalarmd :NoAltNameSpace -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); - - my $AdminTalk = $self->{adminstore}->get_client(); - $AdminTalk->create("user.foo"); - $AdminTalk->setacl("user.cassandane.#calendars.$CalendarId", "foo", "lrswipkxtecdn789"); - - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo"); - my $footalk = $foostore->get_client(); - $footalk->subscribe("user.cassandane.#calendars.$CalendarId"); - - my $service = $self->{instance}->get_service("http"); - my $FooDAV = Net::CalDAVTalk->new( - user => 'foo', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $cal = $FooDAV->GetCalendar("cassandane.$CalendarId"); - $self->assert_not_null($cal); - - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); - - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; - - my $uuid = "c10bea47-f280-4fba-b627-d1bc263c7666"; - my $href = "$CalendarId/$uuid.ics"; - my $cardtmpl = <{caldav}; + + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); + + my $AdminTalk = $self->{adminstore}->get_client(); + $AdminTalk->create("user.foo"); + $AdminTalk->setacl("user.cassandane.#calendars.$CalendarId", + "foo", "lrswipkxtecdn789"); + + my $foostore + = $self->{instance}->get_service('imap')->create_store(username => "foo"); + my $footalk = $foostore->get_client(); + $footalk->subscribe("user.cassandane.#calendars.$CalendarId"); + + my $service = $self->{instance}->get_service("http"); + my $FooDAV = Net::CalDAVTalk->new( + user => 'foo', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $cal = $FooDAV->GetCalendar("cassandane.$CalendarId"); + $self->assert_not_null($cal); + + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); + + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; + + my $uuid = "c10bea47-f280-4fba-b627-d1bc263c7666"; + my $href = "$CalendarId/$uuid.ics"; + my $cardtmpl = <Request('PUT', "cassandane.$href", $foocard, 'Content-Type' => 'text/calendar'); - - my $Events = $CalDAV->GetEvents("$CalendarId"); - my $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); - $self->assert_num_equals(1, scalar @$Events); - $self->assert_num_equals(1, scalar @$FooEvents); - # cassandane event does not yet have alarms - $self->assert_null($Events->[0]{alerts}); - $self->assert_not_null($FooEvents->[0]{alerts}); - - my $card = $cardtmpl; - $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - $Events = $CalDAV->GetEvents("$CalendarId"); - $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); - $self->assert_num_equals(1, scalar @$Events); - $self->assert_num_equals(1, scalar @$FooEvents); - # now both have alarms - $self->assert_not_null($Events->[0]{alerts}); - $self->assert_not_null($FooEvents->[0]{alerts}); - - # clean notification cache - $self->{instance}->getnotify(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); - - $self->assert_alarms(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); - - $self->assert_alarms({summary => 'Simple', userId => 'foo', alarmTime => $start, start => $start}, - {summary => 'Simple', userId => 'cassandane', alarmTime => $start, start => $start}); + my $foocard = $cardtmpl; + $FooDAV->Request('PUT', "cassandane.$href", $foocard, + 'Content-Type' => 'text/calendar'); + + my $Events = $CalDAV->GetEvents("$CalendarId"); + my $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); + $self->assert_num_equals(1, scalar @$Events); + $self->assert_num_equals(1, scalar @$FooEvents); + # cassandane event does not yet have alarms + $self->assert_null($Events->[0]{alerts}); + $self->assert_not_null($FooEvents->[0]{alerts}); + + my $card = $cardtmpl; + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + $Events = $CalDAV->GetEvents("$CalendarId"); + $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); + $self->assert_num_equals(1, scalar @$Events); + $self->assert_num_equals(1, scalar @$FooEvents); + # now both have alarms + $self->assert_not_null($Events->[0]{alerts}); + $self->assert_not_null($FooEvents->[0]{alerts}); + + # clean notification cache + $self->{instance}->getnotify(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); + + $self->assert_alarms(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + + $self->assert_alarms( + { + summary => 'Simple', + userId => 'foo', + alarmTime => $start, + start => $start + }, + { + summary => 'Simple', + userId => 'cassandane', + alarmTime => $start, + start => $start + } + ); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple_multiuser_secretary b/cassandane/tiny-tests/CaldavAlarm/simple_multiuser_secretary index 73a9852b20..13f6f53389 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple_multiuser_secretary +++ b/cassandane/tiny-tests/CaldavAlarm/simple_multiuser_secretary @@ -2,60 +2,61 @@ use Cassandane::Tiny; sub test_simple_multiuser_secretary - :min_version_3_3 :needs_component_calalarmd :NoAltNameSpace :needs_component_jmap -{ - my ($self) = @_; - - my $CalDAV = $self->{caldav}; - - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); - - my $AdminTalk = $self->{adminstore}->get_client(); - $AdminTalk->create("user.foo"); - $AdminTalk->setacl("user.cassandane.#calendars.$CalendarId", "foo", "lrswipkxtecdn789"); - - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo"); - my $footalk = $foostore->get_client(); - $footalk->subscribe("user.cassandane.#calendars.$CalendarId"); - - my $service = $self->{instance}->get_service("http"); - my $FooDAV = Net::CalDAVTalk->new( - user => 'foo', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $cal = $FooDAV->GetCalendar("cassandane.$CalendarId"); - $self->assert_not_null($cal); - - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - - my $latedt = $startdt->clone(); - $latedt->add(DateTime::Duration->new(seconds => 300)); - my $late = $latedt->strftime('%Y%m%dT%H%M%S'); - - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; - - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $cardtmpl = <{caldav}; + + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); + + my $AdminTalk = $self->{adminstore}->get_client(); + $AdminTalk->create("user.foo"); + $AdminTalk->setacl("user.cassandane.#calendars.$CalendarId", + "foo", "lrswipkxtecdn789"); + + my $foostore + = $self->{instance}->get_service('imap')->create_store(username => "foo"); + my $footalk = $foostore->get_client(); + $footalk->subscribe("user.cassandane.#calendars.$CalendarId"); + + my $service = $self->{instance}->get_service("http"); + my $FooDAV = Net::CalDAVTalk->new( + user => 'foo', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $cal = $FooDAV->GetCalendar("cassandane.$CalendarId"); + $self->assert_not_null($cal); + + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + + my $latedt = $startdt->clone(); + $latedt->add(DateTime::Duration->new(seconds => 300)); + my $late = $latedt->strftime('%Y%m%dT%H%M%S'); + + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; + + my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; + my $href = "$CalendarId/$uuid.ics"; + my $cardtmpl = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - my $Events = $CalDAV->GetEvents("$CalendarId"); - my $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); - $self->assert_num_equals(1, scalar @$Events); - $self->assert_num_equals(1, scalar @$FooEvents); - $self->assert_not_null($Events->[0]{alerts}); - # foo event does not yet have alarms - $self->assert_null($FooEvents->[0]{alerts}); - - my $foocard = $cardtmpl; - $foocard =~ s/XXALARMDATAXX/$latealarm/; - $FooDAV->Request('PUT', $FooEvents->[0]{href}, $foocard, 'Content-Type' => 'text/calendar'); - - $Events = $CalDAV->GetEvents("$CalendarId"); - $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); - $self->assert_num_equals(1, scalar @$Events); - $self->assert_num_equals(1, scalar @$FooEvents); - $self->assert_not_null($Events->[0]{alerts}); - # foo event has alarms - $self->assert_not_null($FooEvents->[0]{alerts}); - - # set secretary mode - my $xml = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + my $Events = $CalDAV->GetEvents("$CalendarId"); + my $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); + $self->assert_num_equals(1, scalar @$Events); + $self->assert_num_equals(1, scalar @$FooEvents); + $self->assert_not_null($Events->[0]{alerts}); + # foo event does not yet have alarms + $self->assert_null($FooEvents->[0]{alerts}); + + my $foocard = $cardtmpl; + $foocard =~ s/XXALARMDATAXX/$latealarm/; + $FooDAV->Request('PUT', $FooEvents->[0]{href}, + $foocard, 'Content-Type' => 'text/calendar'); + + $Events = $CalDAV->GetEvents("$CalendarId"); + $FooEvents = $FooDAV->GetEvents("cassandane.$CalendarId"); + $self->assert_num_equals(1, scalar @$Events); + $self->assert_num_equals(1, scalar @$FooEvents); + $self->assert_not_null($Events->[0]{alerts}); + # foo event has alarms + $self->assert_not_null($FooEvents->[0]{alerts}); + + # set secretary mode + my $xml = < @@ -144,17 +146,28 @@ EOF EOF - $CalDAV->Request('PROPPATCH', "/dav/calendars/user/cassandane", $xml, - 'Content-Type' => 'text/xml'); - - # clean notification cache - $self->{instance}->getnotify(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); - - $self->assert_alarms({summary => 'Simple', userId => 'cassandane', alarmTime => $start, start => $start}); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 600 ); - - $self->assert_alarms(); + $CalDAV->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane", + $xml, 'Content-Type' => 'text/xml' + ); + + # clean notification cache + $self->{instance}->getnotify(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + + $self->assert_alarms( + { + summary => 'Simple', + userId => 'cassandane', + alarmTime => $start, + start => $start + } + ); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 600); + + $self->assert_alarms(); } diff --git a/cassandane/tiny-tests/CaldavAlarm/simple_reconstruct b/cassandane/tiny-tests/CaldavAlarm/simple_reconstruct index a9a00a4379..7117b9f1f5 100644 --- a/cassandane/tiny-tests/CaldavAlarm/simple_reconstruct +++ b/cassandane/tiny-tests/CaldavAlarm/simple_reconstruct @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_simple_reconstruct - :min_version_3_0 :needs_component_calalarmd -{ - my ($self) = @_; + : min_version_3_0 : needs_component_calalarmd { + my ($self) = @_; - my $CalDAV = $self->{caldav}; + my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $enddt = $startdt->clone(); - $enddt->add(DateTime::Duration->new(seconds => 15)); - my $end = $enddt->strftime('%Y%m%dT%H%M%S'); + my $enddt = $startdt->clone(); + $enddt->add(DateTime::Duration->new(seconds => 15)); + my $end = $enddt->strftime('%Y%m%dT%H%M%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$CalendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); - $self->{instance}->run_command({ cyrus => 1 }, 'dav_reconstruct', 'cassandane'); + $self->{instance} + ->run_command({ cyrus => 1 }, 'dav_reconstruct', 'cassandane'); - $self->assert_alarms(); + $self->assert_alarms(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms({summary => 'Simple', start => $start}); + $self->assert_alarms({ summary => 'Simple', start => $start }); - $self->{instance}->run_command({ cyrus => 1 }, 'dav_reconstruct', 'cassandane'); + $self->{instance} + ->run_command({ cyrus => 1 }, 'dav_reconstruct', 'cassandane'); - $self->assert_alarms(); + $self->assert_alarms(); } diff --git a/cassandane/tiny-tests/CaldavAlarm/suppress_duplicates b/cassandane/tiny-tests/CaldavAlarm/suppress_duplicates index 34888cbd63..f6ed113b60 100644 --- a/cassandane/tiny-tests/CaldavAlarm/suppress_duplicates +++ b/cassandane/tiny-tests/CaldavAlarm/suppress_duplicates @@ -2,19 +2,18 @@ use Cassandane::Tiny; sub test_suppress_duplicates - :needs_component_calalarmd -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : needs_component_calalarmd { + my ($self) = @_; + my $caldav = $self->{caldav}; - my $now = DateTime->now(); - $now->set_time_zone('Etc/UTC'); + my $now = DateTime->now(); + $now->set_time_zone('Etc/UTC'); - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%SZ'); + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%SZ'); - my $ical = <Request('PUT', 'Default/test.ics', $ical, - 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', 'Default/test.ics', $ical, + 'Content-Type' => 'text/calendar'); - $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + $self->{instance}->getnotify(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - $self->assert_alarms( - {action => 'display', alarmTime => $start, alertId => 'displayAlert1' }, - {action => 'email', alarmTime => $start, alertId => 'emailAlert1' }, - ); + $self->assert_alarms( + { action => 'display', alarmTime => $start, alertId => 'displayAlert1' }, + { action => 'email', alarmTime => $start, alertId => 'emailAlert1' }, + ); } diff --git a/cassandane/tiny-tests/Carddav/addressbook_default_name b/cassandane/tiny-tests/Carddav/addressbook_default_name index 7c691a1b65..031b407834 100644 --- a/cassandane/tiny-tests/Carddav/addressbook_default_name +++ b/cassandane/tiny-tests/Carddav/addressbook_default_name @@ -2,53 +2,51 @@ use Cassandane::Tiny; sub test_addressbook_default_name - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $carddav = $self->{carddav}; + my $carddav = $self->{carddav}; - xlog $self, 'PROPFIND default displayname'; - my $res = $carddav->Request( - 'PROPFIND', - 'Default', - x('D:propfind', $carddav->NS(), - x('D:prop', - x('D:displayname'), - ), - ), - 'Content-Type' => 'application/xml', - 'Depth' => '0' - ); + xlog $self, 'PROPFIND default displayname'; + my $res = $carddav->Request( + 'PROPFIND', + 'Default', + x('D:propfind', $carddav->NS(), + x('D:prop', + x('D:displayname'), + ), + ), + 'Content-Type' => 'application/xml', + 'Depth' => '0' + ); - $self->assert_str_equals('Personal', $res->{'{DAV:}response'}[0]{ - '{DAV:}propstat'}[0]{'{DAV:}prop'}{'{DAV:}displayname'}{content}); + $self->assert_str_equals('Personal', + $res->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{DAV:}displayname'}{content}); - xlog $self, "PROPPATCH remove displayname"; - $res = $carddav->Request( - 'PROPPATCH', - 'Default', - x('D:propertyupdate', $carddav->NS(), - x('D:remove', - x('D:prop', - x('D:displayname'), - ) - ) - ), - ); + xlog $self, "PROPPATCH remove displayname"; + $res = $carddav->Request( + 'PROPPATCH', + 'Default', + x( + 'D:propertyupdate', $carddav->NS(), + x('D:remove', x('D:prop', x('D:displayname'),)) + ), + ); - xlog $self, 'PROPFIND default displayname'; - $res = $carddav->Request( - 'PROPFIND', - 'Default', - x('D:propfind', $carddav->NS(), - x('D:prop', - x('D:displayname'), - ), - ), - 'Content-Type' => 'application/xml', - 'Depth' => '0' - ); - $self->assert_str_equals('Default', $res->{'{DAV:}response'}[0]{ - '{DAV:}propstat'}[0]{'{DAV:}prop'}{'{DAV:}displayname'}{content}); + xlog $self, 'PROPFIND default displayname'; + $res = $carddav->Request( + 'PROPFIND', + 'Default', + x('D:propfind', $carddav->NS(), + x('D:prop', + x('D:displayname'), + ), + ), + 'Content-Type' => 'application/xml', + 'Depth' => '0' + ); + $self->assert_str_equals('Default', + $res->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{DAV:}displayname'}{content}); } diff --git a/cassandane/tiny-tests/Carddav/bulk_import_export b/cassandane/tiny-tests/Carddav/bulk_import_export index 3a3dc82df6..65fa92a358 100644 --- a/cassandane/tiny-tests/Carddav/bulk_import_export +++ b/cassandane/tiny-tests/Carddav/bulk_import_export @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_bulk_import_export - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); - my $uid1 = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; - my $uid2 = "addr1\@example.com"; - my $uid3 = "addr2\@example.com"; + my $uid1 = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; + my $uid2 = "addr1\@example.com"; + my $uid3 = "addr2\@example.com"; - my $single = < 'text/vcard', - 'Authorization' => $CardDAV->auth_header(), - ); + my %Headers = ( + 'Content-Type' => 'text/vcard', + 'Authorization' => $CardDAV->auth_header(), + ); - xlog $self, "Import a single vCard"; - my $res = $CardDAV->{ua}->request('POST', $CardDAV->request_url($Id), { - content => $single, - headers => \%Headers, - }); - $self->assert_num_equals(207, $res->{status}); + xlog $self, "Import a single vCard"; + my $res = $CardDAV->{ua}->request( + 'POST', + $CardDAV->request_url($Id), + { + content => $single, + headers => \%Headers, + } + ); + $self->assert_num_equals(207, $res->{status}); - my $xml = XMLin($res->{content}); - $self->assert_str_equals($uid1, $xml->{'D:response'}{'D:propstat'}{'D:prop'}{'CS:uid'}); + my $xml = XMLin($res->{content}); + $self->assert_str_equals($uid1, + $xml->{'D:response'}{'D:propstat'}{'D:prop'}{'CS:uid'}); - xlog $self, "Import multiple vCards"; - $res = $CardDAV->{ua}->request('POST', $CardDAV->request_url($Id), { - content => $multiple, - headers => \%Headers, - }); - $self->assert_num_equals(207, $res->{status}); + xlog $self, "Import multiple vCards"; + $res = $CardDAV->{ua}->request( + 'POST', + $CardDAV->request_url($Id), + { + content => $multiple, + headers => \%Headers, + } + ); + $self->assert_num_equals(207, $res->{status}); - $xml = XMLin($res->{content}); - $self->assert_str_equals($uid2, $xml->{'D:response'}[0]{'D:propstat'}{'D:prop'}{'CS:uid'}); - $self->assert_str_equals($uid3, $xml->{'D:response'}[1]{'D:propstat'}{'D:prop'}{'CS:uid'}); + $xml = XMLin($res->{content}); + $self->assert_str_equals($uid2, + $xml->{'D:response'}[0]{'D:propstat'}{'D:prop'}{'CS:uid'}); + $self->assert_str_equals($uid3, + $xml->{'D:response'}[1]{'D:propstat'}{'D:prop'}{'CS:uid'}); - xlog $self, "Export the vCards"; - $res = $CardDAV->{ua}->request('GET', $CardDAV->request_url($Id), { - headers => \%Headers, - }); - $self->assert_num_equals(200, $res->{status}); - $self->assert_matches(qr/UID:$uid1\r\nN:Gump/, $res->{content}); - $self->assert_matches(qr/UID:$uid2\r\nFN:Cyrus Daboo/, $res->{content}); - $self->assert_matches(qr/UID:$uid3\r\nFN:Eric York/, $res->{content}); + xlog $self, "Export the vCards"; + $res = $CardDAV->{ua}->request( + 'GET', + $CardDAV->request_url($Id), + { + headers => \%Headers, + } + ); + $self->assert_num_equals(200, $res->{status}); + $self->assert_matches(qr/UID:$uid1\r\nN:Gump/, $res->{content}); + $self->assert_matches(qr/UID:$uid2\r\nFN:Cyrus Daboo/, $res->{content}); + $self->assert_matches(qr/UID:$uid3\r\nFN:Eric York/, $res->{content}); } diff --git a/cassandane/tiny-tests/Carddav/carddavcreate b/cassandane/tiny-tests/Carddav/carddavcreate index 13d2c039e6..ad4ac2fa72 100644 --- a/cassandane/tiny-tests/Carddav/carddavcreate +++ b/cassandane/tiny-tests/Carddav/carddavcreate @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_carddavcreate - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; + my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); } diff --git a/cassandane/tiny-tests/Carddav/control_chars_repaired b/cassandane/tiny-tests/Carddav/control_chars_repaired index 8042f25178..a6c050417a 100644 --- a/cassandane/tiny-tests/Carddav/control_chars_repaired +++ b/cassandane/tiny-tests/Carddav/control_chars_repaired @@ -2,38 +2,37 @@ use Cassandane::Tiny; sub test_control_chars_repaired - :min_version_3_0 :needs_component_httpd :NoStartInstances -{ - my ($self) = @_; + : min_version_3_0 : needs_component_httpd : NoStartInstances { + my ($self) = @_; - # from 3.0-3.2, this behaviour was optional and required the - # carddav_repair_vcard switch to be set - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj == 3 && ($min >= 0 && $min <= 2)) { - $self->{instance}->{config}->set('carddav_repair_vcard' => 'yes'); - } - $self->_start_instances(); + # from 3.0-3.2, this behaviour was optional and required the + # carddav_repair_vcard switch to be set + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj == 3 && ($min >= 0 && $min <= 2)) { + $self->{instance}->{config}->set('carddav_repair_vcard' => 'yes'); + } + $self->_start_instances(); - # :NoStartInstances magic means set_up() didn't do this bit for us - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - $self->{carddav} = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + # :NoStartInstances magic means set_up() didn't do this bit for us + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + $self->{carddav} = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); - my $href = "$Id/bar.vcf"; + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); + my $href = "$Id/bar.vcf"; - my $card = <new_fromstring($card); - my $path = $CardDAV->NewContact($Id, $VCard); - my $res = $CardDAV->GetContact($path); - $self->assert_str_equals($res->{properties}{fn}[0]{value}, 'Forrest Gump'); + # the \b should be repaired out + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($card); + my $path = $CardDAV->NewContact($Id, $VCard); + my $res = $CardDAV->GetContact($path); + $self->assert_str_equals($res->{properties}{fn}[0]{value}, 'Forrest Gump'); } diff --git a/cassandane/tiny-tests/Carddav/control_chars_unrepaired b/cassandane/tiny-tests/Carddav/control_chars_unrepaired index 257f2ebe2a..574dd122e5 100644 --- a/cassandane/tiny-tests/Carddav/control_chars_unrepaired +++ b/cassandane/tiny-tests/Carddav/control_chars_unrepaired @@ -2,35 +2,34 @@ use Cassandane::Tiny; sub test_control_chars_unrepaired - :min_version_3_0 :max_version_3_2 :needs_component_httpd - :NoStartInstances -{ - my ($self) = @_; + : min_version_3_0 : max_version_3_2 : needs_component_httpd + : NoStartInstances { + my ($self) = @_; - # make sure we don't try to repair by default - $self->{instance}->{config}->set('carddav_repair_vcard' => 'no'); - $self->_start_instances(); + # make sure we don't try to repair by default + $self->{instance}->{config}->set('carddav_repair_vcard' => 'no'); + $self->_start_instances(); - # :NoStartInstances magic means set_up() didn't do this bit for us - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - $self->{carddav} = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + # :NoStartInstances magic means set_up() didn't do this bit for us + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + $self->{carddav} = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); - my $href = "$Id/bar.vcf"; + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); + my $href = "$Id/bar.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard') }; - my $Err = $@; - $self->assert_matches(qr/valid-address-data/, $Err); + # vcard containing control character should be rejected + eval { + $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + }; + my $Err = $@; + $self->assert_matches(qr/valid-address-data/, $Err); } diff --git a/cassandane/tiny-tests/Carddav/counters b/cassandane/tiny-tests/Carddav/counters index 0dd63f4502..2a65f8fb15 100644 --- a/cassandane/tiny-tests/Carddav/counters +++ b/cassandane/tiny-tests/Carddav/counters @@ -2,34 +2,37 @@ use Cassandane::Tiny; sub test_counters - :Conversations :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; - my $KEY = "/private/vendor/cmu/cyrus-imapd/usercounters"; - my ($maj, $min) = Cassandane::Instance->get_version(); + : Conversations : min_version_3_0 : needs_component_httpd { + my ($self) = @_; + my $KEY = "/private/vendor/cmu/cyrus-imapd/usercounters"; + my ($maj, $min) = Cassandane::Instance->get_version(); - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $counters1 = $talk->getmetadata("", $KEY); - $counters1 = $counters1->{''}{$KEY}; - #"3 22 20 16 22 0 20 14 22 0 1571356860") + my $counters1 = $talk->getmetadata("", $KEY); + $counters1 = $counters1->{''}{$KEY}; + #"3 22 20 16 22 0 20 14 22 0 1571356860") + my ( + $v1, $all1, $mail1, $cal1, + $card1, $notes1, $mailfolders1, $calfolders1, + $cardfolders1, $notesfolders1, $quota1, $racl1, + $valid1, $nothing1 + ) = split / /, $counters1; - my ($v1, $all1, $mail1, $cal1, $card1, $notes1, $mailfolders1, $calfolders1, $cardfolders1, $notesfolders1, $quota1, $racl1, $valid1, $nothing1) = split / /, $counters1; + if ($maj < 3 || $maj == 3 && $min == 0) { + # 3.0 and earlier did not have quotamodseq or raclmodseq, but + # uidvalidity was still the last field + $valid1 = $quota1; + $quota1 = undef; + } - if ($maj < 3 || $maj == 3 && $min == 0) { - # 3.0 and earlier did not have quotamodseq or raclmodseq, but - # uidvalidity was still the last field - $valid1 = $quota1; - $quota1 = undef; - } - - my $VCard = Net::CardDAVTalk::VCard->new_fromstring(<new_fromstring(<NewContact($Id, $VCard); + $CardDAV->NewContact($Id, $VCard); + + my $counters2 = $talk->getmetadata("", $KEY); + $counters2 = $counters2->{''}{$KEY}; - my $counters2 = $talk->getmetadata("", $KEY); - $counters2 = $counters2->{''}{$KEY}; + my ( + $v2, $all2, $mail2, $cal2, + $card2, $notes2, $mailfolders2, $calfolders2, + $cardfolders2, $notesfolders2, $quota2, $racl2, + $valid2, $nothing2 + ) = split / /, $counters2; - my ($v2, $all2, $mail2, $cal2, $card2, $notes2, $mailfolders2, $calfolders2, $cardfolders2, $notesfolders2, $quota2, $racl2, $valid2, $nothing2) = split / /, $counters2; + if ($maj < 3 || $maj == 3 && $min == 0) { + # 3.0 and earlier did not have quotamodseq or raclmodseq, but + # uidvalidity was still the last field + $valid2 = $quota2; + $quota2 = undef; + } - if ($maj < 3 || $maj == 3 && $min == 0) { - # 3.0 and earlier did not have quotamodseq or raclmodseq, but - # uidvalidity was still the last field - $valid2 = $quota2; - $quota2 = undef; - } + $self->assert_num_equals($v1, $v2); + $self->assert_num_not_equals($all1, $all2); + $self->assert_num_equals($mail1, $mail2); + $self->assert_num_equals($cal1, $cal2); + $self->assert_num_not_equals($card1, $card2); + $self->assert_num_equals($notes1, $notes2); + $self->assert_num_equals($mailfolders1, $mailfolders2); + $self->assert_num_equals($calfolders1, $calfolders2); + $self->assert_num_equals($cardfolders1, $cardfolders2); + $self->assert_num_equals($notesfolders1, $notesfolders2); - $self->assert_num_equals($v1, $v2); - $self->assert_num_not_equals($all1, $all2); - $self->assert_num_equals($mail1, $mail2); - $self->assert_num_equals($cal1, $cal2); - $self->assert_num_not_equals($card1, $card2); - $self->assert_num_equals($notes1, $notes2); - $self->assert_num_equals($mailfolders1, $mailfolders2); - $self->assert_num_equals($calfolders1, $calfolders2); - $self->assert_num_equals($cardfolders1, $cardfolders2); - $self->assert_num_equals($notesfolders1, $notesfolders2); - if ($maj > 3 || $maj == 3 && $min >= 1) { - # quotamodseq and raclmodseq added in 3.1 - $self->assert_num_equals($quota1, $quota2); - $self->assert_num_equals($racl1, $racl2); - } - else { - $self->assert_null($quota2); - $self->assert_null($racl2); - } - $self->assert_num_equals($valid1, $valid2); - $self->assert_null($nothing1); - $self->assert_null($nothing2); + if ($maj > 3 || $maj == 3 && $min >= 1) { + # quotamodseq and raclmodseq added in 3.1 + $self->assert_num_equals($quota1, $quota2); + $self->assert_num_equals($racl1, $racl2); + } else { + $self->assert_null($quota2); + $self->assert_null($racl2); + } + $self->assert_num_equals($valid1, $valid2); + $self->assert_null($nothing1); + $self->assert_null($nothing2); } diff --git a/cassandane/tiny-tests/Carddav/dblookup_email2uids b/cassandane/tiny-tests/Carddav/dblookup_email2uids index 070cb2605a..eeb7f7cd89 100644 --- a/cassandane/tiny-tests/Carddav/dblookup_email2uids +++ b/cassandane/tiny-tests/Carddav/dblookup_email2uids @@ -2,22 +2,25 @@ use Cassandane::Tiny; sub test_dblookup_email2uids - :needs_component_httpd :min_version_3_9 -{ + : needs_component_httpd : min_version_3_9 { - my ($self) = @_; - my $carddav = $self->{carddav}; + my ($self) = @_; + my $carddav = $self->{carddav}; - my @testCases = ({ - uid => '05d07827-b63e-4dd8-9c99-eae2a2fde81b', - email => 'test1@local', - }, { - uid => '6badd234-32c0-47ed-8c2e-a036ce5f245e', - email => "randomemail_7246_1_1698767683\@subdomain1-cef511bc-7805-11ee-9a8d-1695e2485a5b.example.com", - }); + my @testCases = ( + { + uid => '05d07827-b63e-4dd8-9c99-eae2a2fde81b', + email => 'test1@local', + }, + { + uid => '6badd234-32c0-47ed-8c2e-a036ce5f245e', + email => + "randomemail_7246_1_1698767683\@subdomain1-cef511bc-7805-11ee-9a8d-1695e2485a5b.example.com", + } + ); - for my $tc (@testCases) { - my $card = <{uid} @@ -27,22 +30,26 @@ REV:20220217T152253Z END:VCARD EOF - $card =~ s/\r?\n/\r\n/gs; - $carddav->Request('PUT', "Default/$tc->{uid}.vcf", - $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', "Default/$tc->{uid}.vcf", + $card, 'Content-Type' => 'text/vcard'); - my $httpService = $self->{instance}->get_service("http"); - my $dbLookupUrl = "http://" - . $httpService->host . ":" . $httpService->port - . "/dblookup/email2uids"; - my $httpRes = $carddav->ua->get($dbLookupUrl, { - headers => { - User => 'cassandane', - Key => $tc->{email}, - Mailbox => 'Default', - }, - }); - $self->assert_deep_equals([$tc->{uid}], - decode_json($httpRes->{content})); - } + my $httpService = $self->{instance}->get_service("http"); + my $dbLookupUrl + = "http://" + . $httpService->host . ":" + . $httpService->port + . "/dblookup/email2uids"; + my $httpRes = $carddav->ua->get( + $dbLookupUrl, + { + headers => { + User => 'cassandane', + Key => $tc->{email}, + Mailbox => 'Default', + }, + } + ); + $self->assert_deep_equals([ $tc->{uid} ], decode_json($httpRes->{content})); + } } diff --git a/cassandane/tiny-tests/Carddav/delete_default_addressbook b/cassandane/tiny-tests/Carddav/delete_default_addressbook index 9f4173ed45..3db8c81aa1 100644 --- a/cassandane/tiny-tests/Carddav/delete_default_addressbook +++ b/cassandane/tiny-tests/Carddav/delete_default_addressbook @@ -2,24 +2,21 @@ use Cassandane::Tiny; sub test_delete_default_addressbook - :min_version_3_6 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_6 : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; + my $CardDAV = $self->{carddav}; - my %Headers = ( - 'Authorization' => $CardDAV->auth_header() - ); + my %Headers = ('Authorization' => $CardDAV->auth_header()); - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); - my $href = $CardDAV->request_url($Id); - my $res = $CardDAV->ua->request('DELETE', $href, { headers => \%Headers }); - $self->assert_num_equals(204, $res->{status}); + my $href = $CardDAV->request_url($Id); + my $res = $CardDAV->ua->request('DELETE', $href, { headers => \%Headers }); + $self->assert_num_equals(204, $res->{status}); - $href = $CardDAV->request_url('Default'); - $res = $CardDAV->ua->request('DELETE', $href, { headers => \%Headers }); - $self->assert_num_equals(405, $res->{status}); + $href = $CardDAV->request_url('Default'); + $res = $CardDAV->ua->request('DELETE', $href, { headers => \%Headers }); + $self->assert_num_equals(405, $res->{status}); } diff --git a/cassandane/tiny-tests/Carddav/empty_filter b/cassandane/tiny-tests/Carddav/empty_filter index b6c241b122..bb4c8f8510 100644 --- a/cassandane/tiny-tests/Carddav/empty_filter +++ b/cassandane/tiny-tests/Carddav/empty_filter @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_empty_filter - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); - my $xml = < @@ -22,7 +21,7 @@ sub test_empty_filter EOF - my $Str = <new_fromstring($Str); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($Str); - $CardDAV->NewContact($Id, $VCard); + $CardDAV->NewContact($Id, $VCard); - my $res = $CardDAV->Request('REPORT', "/dav/addressbooks/user/cassandane/$Id", $xml, Depth => 0, 'Content-Type' => 'text/xml'); + my $res = $CardDAV->Request( + 'REPORT', "/dav/addressbooks/user/cassandane/$Id", $xml, + Depth => 0, + 'Content-Type' => 'text/xml' + ); - $self->assert_not_null($res->{"{DAV:}response"}); + $self->assert_not_null($res->{"{DAV:}response"}); } diff --git a/cassandane/tiny-tests/Carddav/filter b/cassandane/tiny-tests/Carddav/filter index 5c9cbd355f..7942d7dde0 100644 --- a/cassandane/tiny-tests/Carddav/filter +++ b/cassandane/tiny-tests/Carddav/filter @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_filter - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; + my $CardDAV = $self->{carddav}; - my $xml1 = < @@ -20,7 +19,7 @@ sub test_filter EOF - my $xml2 = < @@ -32,7 +31,7 @@ EOF EOF - my $xml3 = < @@ -44,14 +43,14 @@ EOF EOF - my $homeset = "/dav/addressbooks/user/cassandane"; - my $bookId = "Default"; + my $homeset = "/dav/addressbooks/user/cassandane"; + my $bookId = "Default"; - my $uid1 = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; - my $uid2 = "addr1\@example.com"; - my $uid3 = "addr2\@example.com"; + my $uid1 = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; + my $uid2 = "addr1\@example.com"; + my $uid3 = "addr2\@example.com"; - my $vcard1 = Net::CardDAVTalk::VCard->new_fromstring(<new_fromstring(<new_fromstring(<new_fromstring(<new_fromstring() doesn't split multi-valued properties - my $vcard3 = <new_fromstring() doesn't split multi-valued properties + my $vcard3 = <NewContact($bookId, $vcard1); - my $href2 = $CardDAV->NewContact($bookId, $vcard2); - - my $href3 = "$bookId/$uid3.vcf"; - eval { $CardDAV->Request('PUT', $href3, $vcard3, 'Content-Type' => 'text/vcard') }; - - # test multi-valued property using CardDAV record - my $res = $CardDAV->Request('REPORT', "$homeset/$bookId", - $xml1, Depth => 0, 'Content-Type' => 'text/xml'); - - $self->assert_str_equals("$homeset/$href3", - $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); - - # test by parsing resource - $xml1 =~ s|||; - - $res = $CardDAV->Request('REPORT', "$homeset/$bookId", - $xml1, Depth => 0, 'Content-Type' => 'text/xml'); - - $self->assert_str_equals("$homeset/$href3", - $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); - - # test structured property using CardDAV record - $res = $CardDAV->Request('REPORT', "$homeset/$bookId", - $xml2, Depth => 0, 'Content-Type' => 'text/xml'); - - $self->assert_str_equals("$homeset/$href1", - $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); - - # test by parsing resource - $xml2 =~ s|||; - - $res = $CardDAV->Request('REPORT', "$homeset/$bookId", - $xml2, Depth => 0, 'Content-Type' => 'text/xml'); - - $self->assert_str_equals("$homeset/$href1", - $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); - - # test string property using CardDAV record - $res = $CardDAV->Request('REPORT', "$homeset/$bookId", - $xml3, Depth => 0, 'Content-Type' => 'text/xml'); - - $self->assert_str_equals("$homeset/$href2", - $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); - - # test by parsing resource - $xml3 =~ s|||; - - $res = $CardDAV->Request('REPORT', "$homeset/$bookId", - $xml3, Depth => 0, 'Content-Type' => 'text/xml'); - - $self->assert_str_equals("$homeset/$href2", - $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); - $self->assert_str_equals("$homeset/$href1", - $res->{"{DAV:}response"}[1]{"{DAV:}href"}{content}); + my $href1 = $CardDAV->NewContact($bookId, $vcard1); + my $href2 = $CardDAV->NewContact($bookId, $vcard2); + + my $href3 = "$bookId/$uid3.vcf"; + eval { + $CardDAV->Request('PUT', $href3, $vcard3, 'Content-Type' => 'text/vcard'); + }; + + # test multi-valued property using CardDAV record + my $res = $CardDAV->Request( + 'REPORT', "$homeset/$bookId", + $xml1, + Depth => 0, + 'Content-Type' => 'text/xml' + ); + + $self->assert_str_equals("$homeset/$href3", + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + + # test by parsing resource + $xml1 =~ s|||; + + $res = $CardDAV->Request( + 'REPORT', "$homeset/$bookId", + $xml1, + Depth => 0, + 'Content-Type' => 'text/xml' + ); + + $self->assert_str_equals("$homeset/$href3", + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + + # test structured property using CardDAV record + $res = $CardDAV->Request( + 'REPORT', "$homeset/$bookId", + $xml2, + Depth => 0, + 'Content-Type' => 'text/xml' + ); + + $self->assert_str_equals("$homeset/$href1", + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + + # test by parsing resource + $xml2 =~ s|||; + + $res = $CardDAV->Request( + 'REPORT', "$homeset/$bookId", + $xml2, + Depth => 0, + 'Content-Type' => 'text/xml' + ); + + $self->assert_str_equals("$homeset/$href1", + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + + # test string property using CardDAV record + $res = $CardDAV->Request( + 'REPORT', "$homeset/$bookId", + $xml3, + Depth => 0, + 'Content-Type' => 'text/xml' + ); + + $self->assert_str_equals("$homeset/$href2", + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + + # test by parsing resource + $xml3 =~ s|||; + + $res = $CardDAV->Request( + 'REPORT', "$homeset/$bookId", + $xml3, + Depth => 0, + 'Content-Type' => 'text/xml' + ); + + $self->assert_str_equals("$homeset/$href2", + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + $self->assert_str_equals("$homeset/$href1", + $res->{"{DAV:}response"}[1]{"{DAV:}href"}{content}); } diff --git a/cassandane/tiny-tests/Carddav/filter_x_props b/cassandane/tiny-tests/Carddav/filter_x_props index 638ad72941..32efef49ba 100644 --- a/cassandane/tiny-tests/Carddav/filter_x_props +++ b/cassandane/tiny-tests/Carddav/filter_x_props @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_filter_x_props - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; + my $CardDAV = $self->{carddav}; - my $uid1 = "addr1\@example.com"; - my $uid2 = "addr2\@example.com"; - my $uid3 = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; + my $uid1 = "addr1\@example.com"; + my $uid2 = "addr2\@example.com"; + my $uid3 = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; - my $xml1 = < @@ -24,7 +23,7 @@ sub test_filter_x_props EOF - my $xml2 = <<"EOF"; + my $xml2 = <<"EOF"; @@ -36,10 +35,10 @@ EOF EOF - my $homeset = "/dav/addressbooks/user/cassandane"; - my $bookId = "Default"; + my $homeset = "/dav/addressbooks/user/cassandane"; + my $bookId = "Default"; - my $vcard1 = Net::CardDAVTalk::VCard->new_fromstring(<new_fromstring(<new_fromstring(<new_fromstring(<new_fromstring(<new_fromstring(<NewContact($bookId, $vcard1); - my $href2 = $CardDAV->NewContact($bookId, $vcard2); - my $href3 = $CardDAV->NewContact($bookId, $vcard3); + my $href1 = $CardDAV->NewContact($bookId, $vcard1); + my $href2 = $CardDAV->NewContact($bookId, $vcard2); + my $href3 = $CardDAV->NewContact($bookId, $vcard3); - my $res = $CardDAV->Request('REPORT', "$homeset/$bookId", - $xml1, Depth => 0, 'Content-Type' => 'text/xml'); + my $res = $CardDAV->Request( + 'REPORT', "$homeset/$bookId", + $xml1, + Depth => 0, + 'Content-Type' => 'text/xml' + ); - $self->assert_str_equals("$homeset/$href3", - $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + $self->assert_str_equals("$homeset/$href3", + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); - $res = $CardDAV->Request('REPORT', "$homeset/$bookId", - $xml2, Depth => 0, 'Content-Type' => 'text/xml'); + $res = $CardDAV->Request( + 'REPORT', "$homeset/$bookId", + $xml2, + Depth => 0, + 'Content-Type' => 'text/xml' + ); - $self->assert_str_equals("$homeset/$href3", - $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + $self->assert_str_equals("$homeset/$href3", + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); } diff --git a/cassandane/tiny-tests/Carddav/homeset_extradomain b/cassandane/tiny-tests/Carddav/homeset_extradomain index 21269f48b4..2b556d932a 100644 --- a/cassandane/tiny-tests/Carddav/homeset_extradomain +++ b/cassandane/tiny-tests/Carddav/homeset_extradomain @@ -2,20 +2,20 @@ use Cassandane::Tiny; sub test_homeset_extradomain - :ReverseACLs :min_version_3_0 :needs_component_httpd -{ - my ($self) = @_; + : ReverseACLs : min_version_3_0 : needs_component_httpd { + my ($self) = @_; - my $service = $self->{instance}->get_service("http"); - my $talk = Net::CardDAVTalk->new( - user => 'cassandane%extradomain.com', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $talk = Net::CardDAVTalk->new( + user => 'cassandane%extradomain.com', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $self->assert_str_equals($talk->{basepath}, "/dav/addressbooks/user/cassandane\@extradomain.com"); + $self->assert_str_equals($talk->{basepath}, + "/dav/addressbooks/user/cassandane\@extradomain.com"); } diff --git a/cassandane/tiny-tests/Carddav/huge_group b/cassandane/tiny-tests/Carddav/huge_group index 990072b029..f078df3037 100644 --- a/cassandane/tiny-tests/Carddav/huge_group +++ b/cassandane/tiny-tests/Carddav/huge_group @@ -5,23 +5,22 @@ use Cassandane::Tiny; # If we overrun the libical ring buffer, this test might fail, # but it will definitely cause valgrind errors. sub test_huge_group - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; + my $CardDAV = $self->{carddav}; - my $members; + my $members; - for (1..2500) { - my $ug = Data::UUID->new; - my $uuid = $ug->create_str(); - $members .= "MEMBER:urn:uuid:$_\r\n"; - } + for (1 .. 2500) { + my $ug = Data::UUID->new; + my $uuid = $ug->create_str(); + $members .= "MEMBER:urn:uuid:$_\r\n"; + } - my $uid = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; - my $href = "Default/group.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $response = $CardDAV->Request('GET', $href); - my $value = $response->{content}; - $self->assert_matches(qr/$uid/, $value); + $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + my $response = $CardDAV->Request('GET', $href); + my $value = $response->{content}; + $self->assert_matches(qr/$uid/, $value); - $card =~ s/FN:/NOTE:2500 members\r\nFN:/; + $card =~ s/FN:/NOTE:2500 members\r\nFN:/; - $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - $response = $CardDAV->Request('GET', $href); - $value = $response->{content}; - $self->assert_matches(qr/$uid/, $value); - $self->assert_matches(qr/2500 members/, $value); + $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $response = $CardDAV->Request('GET', $href); + $value = $response->{content}; + $self->assert_matches(qr/$uid/, $value); + $self->assert_matches(qr/2500 members/, $value); } diff --git a/cassandane/tiny-tests/Carddav/many_emails b/cassandane/tiny-tests/Carddav/many_emails index 74b7e0da4a..6b672f969d 100644 --- a/cassandane/tiny-tests/Carddav/many_emails +++ b/cassandane/tiny-tests/Carddav/many_emails @@ -2,19 +2,21 @@ use Cassandane::Tiny; sub test_many_emails - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); - my $Phones = join("\r\n", map { sprintf("TEL;TYPE=HOME:(101) 555-%04d", $_) } (1..1000)); - my $Emails = join("\r\n", map { sprintf("EMAIL;TYPE=INTERNET:user%04d\@example.com", $_) } (1..1000)); + my $Phones = join("\r\n", + map { sprintf("TEL;TYPE=HOME:(101) 555-%04d", $_) } (1 .. 1000)); + my $Emails = join("\r\n", + map { sprintf("EMAIL;TYPE=INTERNET:user%04d\@example.com", $_) } + (1 .. 1000)); - my $Str = <new_fromstring($Str); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($Str); - $CardDAV->NewContact($Id, $VCard); + $CardDAV->NewContact($Id, $VCard); } diff --git a/cassandane/tiny-tests/Carddav/multiget b/cassandane/tiny-tests/Carddav/multiget index e9fe645bec..74faf9582d 100644 --- a/cassandane/tiny-tests/Carddav/multiget +++ b/cassandane/tiny-tests/Carddav/multiget @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_multiget - :needs_component_httpd :min_version_3_7 -{ - my ($self) = @_; + : needs_component_httpd : min_version_3_7 { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); - my $Str = <new_fromstring($Str); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($Str); - my $path = $CardDAV->NewContact($Id, $VCard); + my $path = $CardDAV->NewContact($Id, $VCard); - my $xml = < @@ -44,23 +43,51 @@ EOF EOF - my $res = $CardDAV->Request('REPORT', "/dav/addressbooks/user/cassandane/$Id", $xml, Depth => 0, 'Content-Type' => 'text/xml'); + my $res = $CardDAV->Request( + 'REPORT', "/dav/addressbooks/user/cassandane/$Id", $xml, + Depth => 0, + 'Content-Type' => 'text/xml' + ); - $self->assert_not_null($res->{"{DAV:}response"}); - $self->assert_str_equals('nonsense', $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); - $self->assert_str_equals('HTTP/1.1 403 Forbidden', $res->{"{DAV:}response"}[0]{"{DAV:}status"}{content}); - $self->assert_str_equals('/dav/addressbooks/', $res->{"{DAV:}response"}[1]{"{DAV:}href"}{content}); - $self->assert_str_equals('HTTP/1.1 403 Forbidden', $res->{"{DAV:}response"}[1]{"{DAV:}status"}{content}); - $self->assert_str_equals('/dav/addressbooks/user/', $res->{"{DAV:}response"}[2]{"{DAV:}href"}{content}); - $self->assert_str_equals('HTTP/1.1 403 Forbidden', $res->{"{DAV:}response"}[2]{"{DAV:}status"}{content}); - $self->assert_str_equals('/dav/addressbooks/user/cassandane/', $res->{"{DAV:}response"}[3]{"{DAV:}href"}{content}); - $self->assert_str_equals('HTTP/1.1 403 Forbidden', $res->{"{DAV:}response"}[3]{"{DAV:}status"}{content}); - $self->assert_str_equals("/dav/addressbooks/user/cassandane/$Id/", $res->{"{DAV:}response"}[4]{"{DAV:}href"}{content}); - $self->assert_str_equals('HTTP/1.1 403 Forbidden', $res->{"{DAV:}response"}[4]{"{DAV:}status"}{content}); - $self->assert_str_equals("/dav/addressbooks/user/cassandane/$path", $res->{"{DAV:}response"}[5]{"{DAV:}href"}{content}); - $self->assert_str_equals('HTTP/1.1 200 OK', $res->{"{DAV:}response"}[5]{"{DAV:}propstat"}[0]{"{DAV:}status"}{content}); - $self->assert_str_equals("/dav/addressbooks/user/cassandane/$Id/nonexistent", $res->{"{DAV:}response"}[6]{"{DAV:}href"}{content}); - $self->assert_str_equals('HTTP/1.1 404 Not Found', $res->{"{DAV:}response"}[6]{"{DAV:}status"}{content}); - $self->assert_str_equals('/dav/addressbooks/user/cassandane/nonexistent/', $res->{"{DAV:}response"}[7]{"{DAV:}href"}{content}); - $self->assert_str_equals('HTTP/1.1 404 Not Found', $res->{"{DAV:}response"}[7]{"{DAV:}status"}{content}); + $self->assert_not_null($res->{"{DAV:}response"}); + $self->assert_str_equals('nonsense', + $res->{"{DAV:}response"}[0]{"{DAV:}href"}{content}); + $self->assert_str_equals('HTTP/1.1 403 Forbidden', + $res->{"{DAV:}response"}[0]{"{DAV:}status"}{content}); + $self->assert_str_equals('/dav/addressbooks/', + $res->{"{DAV:}response"}[1]{"{DAV:}href"}{content}); + $self->assert_str_equals('HTTP/1.1 403 Forbidden', + $res->{"{DAV:}response"}[1]{"{DAV:}status"}{content}); + $self->assert_str_equals('/dav/addressbooks/user/', + $res->{"{DAV:}response"}[2]{"{DAV:}href"}{content}); + $self->assert_str_equals('HTTP/1.1 403 Forbidden', + $res->{"{DAV:}response"}[2]{"{DAV:}status"}{content}); + $self->assert_str_equals('/dav/addressbooks/user/cassandane/', + $res->{"{DAV:}response"}[3]{"{DAV:}href"}{content}); + $self->assert_str_equals('HTTP/1.1 403 Forbidden', + $res->{"{DAV:}response"}[3]{"{DAV:}status"}{content}); + $self->assert_str_equals( + "/dav/addressbooks/user/cassandane/$Id/", + $res->{"{DAV:}response"}[4]{"{DAV:}href"}{content} + ); + $self->assert_str_equals('HTTP/1.1 403 Forbidden', + $res->{"{DAV:}response"}[4]{"{DAV:}status"}{content}); + $self->assert_str_equals( + "/dav/addressbooks/user/cassandane/$path", + $res->{"{DAV:}response"}[5]{"{DAV:}href"}{content} + ); + $self->assert_str_equals('HTTP/1.1 200 OK', + $res->{"{DAV:}response"}[5]{"{DAV:}propstat"}[0]{"{DAV:}status"}{content}); + $self->assert_str_equals( + "/dav/addressbooks/user/cassandane/$Id/nonexistent", + $res->{"{DAV:}response"}[6]{"{DAV:}href"}{content} + ); + $self->assert_str_equals('HTTP/1.1 404 Not Found', + $res->{"{DAV:}response"}[6]{"{DAV:}status"}{content}); + $self->assert_str_equals( + '/dav/addressbooks/user/cassandane/nonexistent/', + $res->{"{DAV:}response"}[7]{"{DAV:}href"}{content} + ); + $self->assert_str_equals('HTTP/1.1 404 Not Found', + $res->{"{DAV:}response"}[7]{"{DAV:}status"}{content}); } diff --git a/cassandane/tiny-tests/Carddav/no_filter b/cassandane/tiny-tests/Carddav/no_filter index 48da105cff..0b7e793a0f 100644 --- a/cassandane/tiny-tests/Carddav/no_filter +++ b/cassandane/tiny-tests/Carddav/no_filter @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_no_filter - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); - my $xml = < @@ -21,7 +20,7 @@ sub test_no_filter EOF - my $Str = <new_fromstring($Str); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($Str); - $CardDAV->NewContact($Id, $VCard); + $CardDAV->NewContact($Id, $VCard); - my $res = $CardDAV->Request('REPORT', "/dav/addressbooks/user/cassandane/$Id", $xml, Depth => 0, 'Content-Type' => 'text/xml'); + my $res = $CardDAV->Request( + 'REPORT', "/dav/addressbooks/user/cassandane/$Id", $xml, + Depth => 0, + 'Content-Type' => 'text/xml' + ); - $self->assert_not_null($res->{"{DAV:}response"}); + $self->assert_not_null($res->{"{DAV:}response"}); } diff --git a/cassandane/tiny-tests/Carddav/put_get_v3_v4 b/cassandane/tiny-tests/Carddav/put_get_v3_v4 index 19bee715d7..102799dfa5 100644 --- a/cassandane/tiny-tests/Carddav/put_get_v3_v4 +++ b/cassandane/tiny-tests/Carddav/put_get_v3_v4 @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_put_get_v3_v4 - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); - my $href = "$Id/bar.vcf"; - my $uid = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; - my $photo = "R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; - my $logo = "http://bubbagump.com/logo.jpg"; - my $sound = "ABCDEF"; - my $lat = "30.3912"; - my $lon = "-88.8610"; - my $tel = "+1-800-555-1212"; - my $email1 = "shrimp\@bubbagump.com"; - my $email2 = "bubba\@bubbagump.com"; - my $tzid = "America/New_York"; + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); + my $href = "$Id/bar.vcf"; + my $uid = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; + my $photo = "R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + my $logo = "http://bubbagump.com/logo.jpg"; + my $sound = "ABCDEF"; + my $lat = "30.3912"; + my $lon = "-88.8610"; + my $tel = "+1-800-555-1212"; + my $email1 = "shrimp\@bubbagump.com"; + my $email2 = "bubba\@bubbagump.com"; + my $tzid = "America/New_York"; - my $card = < 'text/vcard', - 'Authorization' => $CardDAV->auth_header(), - ); + my %Headers = ( + 'Content-Type' => 'text/vcard', + 'Authorization' => $CardDAV->auth_header(), + ); - xlog $self, "PUT vCard v3 with text UID"; - my $Response = $CardDAV->{ua}->request('PUT', $CardDAV->request_url($href), { - content => $card, - headers => \%Headers, - }); - $self->assert_num_equals(201, $Response->{status}); + xlog $self, "PUT vCard v3 with text UID"; + my $Response = $CardDAV->{ua}->request( + 'PUT', + $CardDAV->request_url($href), + { + content => $card, + headers => \%Headers, + } + ); + $self->assert_num_equals(201, $Response->{status}); - xlog $self, "GET as vCard v4"; - my $response = $CardDAV->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - my $newcard = $response->{content}; - $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/UID:urn:uuid:$uid/, $newcard); - $self->assert_matches(qr/PHOTO;TYPE=$video:data:image\/gif;base64,$photo/, - $newcard); - $self->assert_matches(qr/LOGO;MEDIATYPE=image\/jpeg:$logo/, $newcard); - $self->assert_matches(qr/GEO(;VALUE=$uri)?:geo:$lat,$lon/, $newcard); - $self->assert_matches(qr/TEL;TYPE=foo$typesep$work;PREF=1:/, $newcard); - $self->assert_matches(qr/EMAIL;PREF=1:$email1/, $newcard); - $self->assert_matches(qr/EMAIL:$email2/, $newcard); - $self->assert_matches(qr/TZ;VALUE=$utcoff:-0500/, $newcard); - $self->assert_matches(qr/TZ(;VALUE=$text)?:$tzid/, $newcard); + xlog $self, "GET as vCard v4"; + my $response = $CardDAV->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + my $newcard = $response->{content}; + $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties + $self->assert_matches(qr/UID:urn:uuid:$uid/, $newcard); + $self->assert_matches(qr/PHOTO;TYPE=$video:data:image\/gif;base64,$photo/, + $newcard); + $self->assert_matches(qr/LOGO;MEDIATYPE=image\/jpeg:$logo/, $newcard); + $self->assert_matches(qr/GEO(;VALUE=$uri)?:geo:$lat,$lon/, $newcard); + $self->assert_matches(qr/TEL;TYPE=foo$typesep$work;PREF=1:/, $newcard); + $self->assert_matches(qr/EMAIL;PREF=1:$email1/, $newcard); + $self->assert_matches(qr/EMAIL:$email2/, $newcard); + $self->assert_matches(qr/TZ;VALUE=$utcoff:-0500/, $newcard); + $self->assert_matches(qr/TZ(;VALUE=$text)?:$tzid/, $newcard); - xlog $self, "PUT same vCard as v4 with some edits"; - $newcard =~ s|END:|SOUND;MEDIATYPE=audio/mp3:data:;base64,$sound\r\nEND:|; - $newcard =~ s/:$tel/;VALUE=URL:tel:$tel/; - $newcard =~ s/EMAIL;PREF=1:/EMAIL;PREF=2:/; - $newcard =~ s/:$email2/;PREF=1:$email2/; -# $newcard =~ s/-0500/-05/; #not supported by libicalvcard yet - $newcard =~ s/TZ;VALUE=TEXT:/TZ:/; + xlog $self, "PUT same vCard as v4 with some edits"; + $newcard =~ s|END:|SOUND;MEDIATYPE=audio/mp3:data:;base64,$sound\r\nEND:|; + $newcard =~ s/:$tel/;VALUE=URL:tel:$tel/; + $newcard =~ s/EMAIL;PREF=1:/EMAIL;PREF=2:/; + $newcard =~ s/:$email2/;PREF=1:$email2/; + # $newcard =~ s/-0500/-05/; #not supported by libicalvcard yet + $newcard =~ s/TZ;VALUE=TEXT:/TZ:/; - $Response = $CardDAV->{ua}->request('PUT', $CardDAV->request_url($href), { - content => $newcard, - headers => \%Headers, - }); - $self->assert_num_equals(204, $Response->{status}); + $Response = $CardDAV->{ua}->request( + 'PUT', + $CardDAV->request_url($href), + { + content => $newcard, + headers => \%Headers, + } + ); + $self->assert_num_equals(204, $Response->{status}); - xlog $self, "GET as vCard v3"; - $tel =~ s/\+/\\+/; # escape the '+' for matching + xlog $self, "GET as vCard v3"; + $tel =~ s/\+/\\+/; # escape the '+' for matching - $response = $CardDAV->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=3.0'); - $newcard = $response->{content}; - $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/UID:$uid/, $newcard); - $self->assert_matches(qr/PHOTO;TYPE=$video(,$gif)?;$binary(;TYPE=$gif)?:$photo/, - $newcard); - $self->assert_matches(qr/LOGO;VALUE=$uri;TYPE=JPEG:$logo/, $newcard); - $self->assert_matches(qr/SOUND;$binary;TYPE=$mp3:$sound/, $newcard); - $self->assert_matches(qr/GEO:$lat;$lon/, $newcard); - $self->assert_matches(qr/TEL;TYPE=foo$typesep$work$typesep$pref:$tel/, - $newcard); - $self->assert_matches(qr/EMAIL:$email1/, $newcard); - $self->assert_matches(qr/EMAIL;TYPE=$pref:$email2/, $newcard); - $self->assert_matches(qr/TZ(;VALUE=$utcoff)?:-05(:)?00/, $newcard); - $self->assert_matches(qr/TZ;VALUE=$text:$tzid/, $newcard); + $response = $CardDAV->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=3.0'); + $newcard = $response->{content}; + $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties + $self->assert_matches(qr/UID:$uid/, $newcard); + $self->assert_matches( + qr/PHOTO;TYPE=$video(,$gif)?;$binary(;TYPE=$gif)?:$photo/, $newcard); + $self->assert_matches(qr/LOGO;VALUE=$uri;TYPE=JPEG:$logo/, $newcard); + $self->assert_matches(qr/SOUND;$binary;TYPE=$mp3:$sound/, $newcard); + $self->assert_matches(qr/GEO:$lat;$lon/, $newcard); + $self->assert_matches(qr/TEL;TYPE=foo$typesep$work$typesep$pref:$tel/, + $newcard); + $self->assert_matches(qr/EMAIL:$email1/, $newcard); + $self->assert_matches(qr/EMAIL;TYPE=$pref:$email2/, $newcard); + $self->assert_matches(qr/TZ(;VALUE=$utcoff)?:-05(:)?00/, $newcard); + $self->assert_matches(qr/TZ;VALUE=$text:$tzid/, $newcard); } diff --git a/cassandane/tiny-tests/Carddav/replication b/cassandane/tiny-tests/Carddav/replication index 4a7d1b82b4..bf5b984e25 100644 --- a/cassandane/tiny-tests/Carddav/replication +++ b/cassandane/tiny-tests/Carddav/replication @@ -2,21 +2,20 @@ use Cassandane::Tiny; sub test_replication - :needs_component_httpd :needs_component_replication -{ - my ($self) = @_; + : needs_component_httpd : needs_component_replication { + my ($self) = @_; - my $CardDAV = $self->{carddav}; + my $CardDAV = $self->{carddav}; - my $ABookId = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($ABookId); + my $ABookId = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($ABookId); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - my $uid = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; - my $href = "$ABookId/card.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $response = $CardDAV->Request('GET', $href); - my $value = $response->{content}; - $self->assert_matches(qr/$uid/, $value); + $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + my $response = $CardDAV->Request('GET', $href); + my $value = $response->{content}; + $self->assert_matches(qr/$uid/, $value); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - $card =~ s/;FOO=bar:Forrest Gump/:/; + $card =~ s/;FOO=bar:Forrest Gump/:/; - $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - $card =~ s/REV:/NICKNAME:Captain\r\nREV:/; + $card =~ s/REV:/NICKNAME:Captain\r\nREV:/; - $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - $CardDAV->DeleteContact($href); + $CardDAV->DeleteContact($href); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - $CardDAV->DeleteAddressBook($ABookId); + $CardDAV->DeleteAddressBook($ABookId); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); } diff --git a/cassandane/tiny-tests/Carddav/sharing_contactpaths b/cassandane/tiny-tests/Carddav/sharing_contactpaths index f9ed7c587a..7c63b252e2 100644 --- a/cassandane/tiny-tests/Carddav/sharing_contactpaths +++ b/cassandane/tiny-tests/Carddav/sharing_contactpaths @@ -2,39 +2,40 @@ use Cassandane::Tiny; sub test_sharing_contactpaths - :VirtDomains :CrossDomains :FastMailSharing :ReverseACLs :min_version_3_0 - :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : CrossDomains : FastMailSharing : ReverseACLs : min_version_3_0 + : needs_component_httpd { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.user1\@example.com"); - $admintalk->setacl("user.user1\@example.com", "user1\@example.com", 'lrswipkxtecdan'); - $admintalk->create("user.user2\@example.org"); - $admintalk->setacl("user.user2\@example.org", "user2\@example.org", 'lrswipkxtecdan'); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user.user1\@example.com"); + $admintalk->setacl("user.user1\@example.com", "user1\@example.com", + 'lrswipkxtecdan'); + $admintalk->create("user.user2\@example.org"); + $admintalk->setacl("user.user2\@example.org", "user2\@example.org", + 'lrswipkxtecdan'); - my $service = $self->{instance}->get_service("http"); - my $talk1 = Net::CardDAVTalk->new( - user => 'user1@example.com', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $talk2 = Net::CardDAVTalk->new( - user => 'user2@example.org', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $talk1 = Net::CardDAVTalk->new( + user => 'user1@example.com', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $talk2 = Net::CardDAVTalk->new( + user => 'user2@example.org', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $talk2->NewAddressBook("Shared", name => "Shared Address Book"); - my $VCard = Net::CardDAVTalk::VCard->new_fromstring(<NewAddressBook("Shared", name => "Shared Address Book"); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring(<NewContact('Default', $VCard); - $talk2->NewContact('Shared', $VCard); + $talk1->NewContact('Default', $VCard); + $talk2->NewContact('Shared', $VCard); - $admintalk->setacl("user.user2.#addressbooks.Shared\@example.org", "user1\@example.com", 'lrsn'); + $admintalk->setacl("user.user2.#addressbooks.Shared\@example.org", + "user1\@example.com", 'lrsn'); - my $Addressbooks = $talk1->GetAddressBooks(); + my $Addressbooks = $talk1->GetAddressBooks(); - $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); - $self->assert_str_equals('Default', $Addressbooks->[0]{path}); - $self->assert_str_equals('/dav/addressbooks/user/user1@example.com/Default/', $Addressbooks->[0]{href}); - $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); + $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); + $self->assert_str_equals('Default', $Addressbooks->[0]{path}); + $self->assert_str_equals('/dav/addressbooks/user/user1@example.com/Default/', + $Addressbooks->[0]{href}); + $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); - $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); - $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.org/Shared', $Addressbooks->[1]{path}); - $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.org/Shared/', $Addressbooks->[1]{href}); - $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); + $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); + $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.org/Shared', + $Addressbooks->[1]{path}); + $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.org/Shared/', + $Addressbooks->[1]{href}); + $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); - # check Default, and Shared: both zzzz and user versions - my @paths = ($Addressbooks->[0]{path}, $Addressbooks->[1]{path}, $Addressbooks->[1]{path}); - $paths[2] =~ s/zzzz/user/; - foreach my $path (@paths) { - my $Events = $talk1->GetContacts($path); - # is a subpath of the contact - $self->assert_matches(qr/^$path/, $Events->[0]{CPath}); - } + # check Default, and Shared: both zzzz and user versions + my @paths = ( + $Addressbooks->[0]{path}, + $Addressbooks->[1]{path}, + $Addressbooks->[1]{path} + ); + $paths[2] =~ s/zzzz/user/; + foreach my $path (@paths) { + my $Events = $talk1->GetContacts($path); + # is a subpath of the contact + $self->assert_matches(qr/^$path/, $Events->[0]{CPath}); + } } diff --git a/cassandane/tiny-tests/Carddav/sharing_crossdomain b/cassandane/tiny-tests/Carddav/sharing_crossdomain index 3047ea9369..b5d435215e 100644 --- a/cassandane/tiny-tests/Carddav/sharing_crossdomain +++ b/cassandane/tiny-tests/Carddav/sharing_crossdomain @@ -2,49 +2,54 @@ use Cassandane::Tiny; sub test_sharing_crossdomain - :VirtDomains :CrossDomains :FastMailSharing :ReverseACLs :min_version_3_0 - :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : CrossDomains : FastMailSharing : ReverseACLs : min_version_3_0 + : needs_component_httpd { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.user1\@example.com"); - $admintalk->setacl("user.user1\@example.com", "user1\@example.com", 'lrswipkxtecdan'); - $admintalk->create("user.user2\@example.org"); - $admintalk->setacl("user.user2\@example.org", "user2\@example.org", 'lrswipkxtecdan'); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user.user1\@example.com"); + $admintalk->setacl("user.user1\@example.com", "user1\@example.com", + 'lrswipkxtecdan'); + $admintalk->create("user.user2\@example.org"); + $admintalk->setacl("user.user2\@example.org", "user2\@example.org", + 'lrswipkxtecdan'); - my $service = $self->{instance}->get_service("http"); - my $talk1 = Net::CardDAVTalk->new( - user => 'user1@example.com', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $talk2 = Net::CardDAVTalk->new( - user => 'user2@example.org', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $talk1 = Net::CardDAVTalk->new( + user => 'user1@example.com', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $talk2 = Net::CardDAVTalk->new( + user => 'user2@example.org', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $talk2->NewAddressBook("Shared", name => "Shared Address Book"); - $admintalk->setacl("user.user2.#addressbooks.Shared\@example.org", "user1\@example.com", 'lrsn'); + $talk2->NewAddressBook("Shared", name => "Shared Address Book"); + $admintalk->setacl("user.user2.#addressbooks.Shared\@example.org", + "user1\@example.com", 'lrsn'); - my $Addressbooks = $talk1->GetAddressBooks(); + my $Addressbooks = $talk1->GetAddressBooks(); - $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); - $self->assert_str_equals('Default', $Addressbooks->[0]{path}); - $self->assert_str_equals('/dav/addressbooks/user/user1@example.com/Default/', $Addressbooks->[0]{href}); - $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); + $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); + $self->assert_str_equals('Default', $Addressbooks->[0]{path}); + $self->assert_str_equals('/dav/addressbooks/user/user1@example.com/Default/', + $Addressbooks->[0]{href}); + $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); - $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); - $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.org/Shared', $Addressbooks->[1]{path}); - $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.org/Shared/', $Addressbooks->[1]{href}); - $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); + $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); + $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.org/Shared', + $Addressbooks->[1]{path}); + $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.org/Shared/', + $Addressbooks->[1]{href}); + $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); } diff --git a/cassandane/tiny-tests/Carddav/sharing_rename_sharee b/cassandane/tiny-tests/Carddav/sharing_rename_sharee index 4e1647c943..ce3f48a7e8 100644 --- a/cassandane/tiny-tests/Carddav/sharing_rename_sharee +++ b/cassandane/tiny-tests/Carddav/sharing_rename_sharee @@ -2,72 +2,74 @@ use Cassandane::Tiny; sub test_sharing_rename_sharee - :AllowMoves :NoAltNamespace :ReverseACLs :min_version_3_7 - :needs_component_httpd -{ - my ($self) = @_; + : AllowMoves : NoAltNamespace : ReverseACLs : min_version_3_7 + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; + my $CardDAV = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.foo"); - $admintalk->setacl("user.foo", "foo", 'lrswipkxtecdan'); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user.foo"); + $admintalk->setacl("user.foo", "foo", 'lrswipkxtecdan'); - my $service = $self->{instance}->get_service("http"); - my $cardtalk = Net::CardDAVTalk->new( - user => 'foo', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $cardtalk = Net::CardDAVTalk->new( + user => 'foo', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $CardDAV->NewAddressBook("Shared", name => "Shared Address Book"); - $admintalk->setacl("user.cassandane.#addressbooks.Shared", "foo", 'lrsn'); + $CardDAV->NewAddressBook("Shared", name => "Shared Address Book"); + $admintalk->setacl("user.cassandane.#addressbooks.Shared", "foo", 'lrsn'); - xlog $self, "subscribe to shared calendar"; - my $imapstore = $self->{instance}->get_service('imap')->create_store( - username => "foo"); - my $imaptalk = $imapstore->get_client(); - $imaptalk->subscribe("user.cassandane.#addressbooks.Shared"); + xlog $self, "subscribe to shared calendar"; + my $imapstore + = $self->{instance}->get_service('imap')->create_store(username => "foo"); + my $imaptalk = $imapstore->get_client(); + $imaptalk->subscribe("user.cassandane.#addressbooks.Shared"); - my $Addressbooks = $cardtalk->GetAddressBooks(); + my $Addressbooks = $cardtalk->GetAddressBooks(); - $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); - $self->assert_str_equals('Default', $Addressbooks->[0]{path}); - $self->assert_str_equals('/dav/addressbooks/user/foo/Default/', $Addressbooks->[0]{href}); - $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); + $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); + $self->assert_str_equals('Default', $Addressbooks->[0]{path}); + $self->assert_str_equals('/dav/addressbooks/user/foo/Default/', + $Addressbooks->[0]{href}); + $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); - $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); - $self->assert_str_equals('cassandane.Shared', $Addressbooks->[1]{path}); - $self->assert_str_equals('/dav/addressbooks/user/foo/cassandane.Shared/', $Addressbooks->[1]{href}); - $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); + $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); + $self->assert_str_equals('cassandane.Shared', $Addressbooks->[1]{path}); + $self->assert_str_equals('/dav/addressbooks/user/foo/cassandane.Shared/', + $Addressbooks->[1]{href}); + $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); - $admintalk->rename('user.foo', 'user.bar'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + $admintalk->rename('user.foo', 'user.bar'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $cardtalk = Net::CardDAVTalk->new( - user => 'bar', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + $cardtalk = Net::CardDAVTalk->new( + user => 'bar', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $Addressbooks = $cardtalk->GetAddressBooks(); + $Addressbooks = $cardtalk->GetAddressBooks(); - $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); - $self->assert_str_equals('Default', $Addressbooks->[0]{path}); - $self->assert_str_equals('/dav/addressbooks/user/bar/Default/', $Addressbooks->[0]{href}); - $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); + $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); + $self->assert_str_equals('Default', $Addressbooks->[0]{path}); + $self->assert_str_equals('/dav/addressbooks/user/bar/Default/', + $Addressbooks->[0]{href}); + $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); - $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); - $self->assert_str_equals('cassandane.Shared', $Addressbooks->[1]{path}); - $self->assert_str_equals('/dav/addressbooks/user/bar/cassandane.Shared/', $Addressbooks->[1]{href}); - $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); + $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); + $self->assert_str_equals('cassandane.Shared', $Addressbooks->[1]{path}); + $self->assert_str_equals('/dav/addressbooks/user/bar/cassandane.Shared/', + $Addressbooks->[1]{href}); + $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); } diff --git a/cassandane/tiny-tests/Carddav/sharing_samedomain b/cassandane/tiny-tests/Carddav/sharing_samedomain index f1f1325508..3f42bf404c 100644 --- a/cassandane/tiny-tests/Carddav/sharing_samedomain +++ b/cassandane/tiny-tests/Carddav/sharing_samedomain @@ -2,49 +2,54 @@ use Cassandane::Tiny; sub test_sharing_samedomain - :VirtDomains :FastMailSharing :ReverseACLs :min_version_3_0 - :needs_component_httpd -{ - my ($self) = @_; + : VirtDomains : FastMailSharing : ReverseACLs : min_version_3_0 + : needs_component_httpd { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.user1\@example.com"); - $admintalk->setacl("user.user1\@example.com", "user1\@example.com", 'lrswipkxtecdan'); - $admintalk->create("user.user2\@example.com"); - $admintalk->setacl("user.user2\@example.com", "user2\@example.com", 'lrswipkxtecdan'); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user.user1\@example.com"); + $admintalk->setacl("user.user1\@example.com", "user1\@example.com", + 'lrswipkxtecdan'); + $admintalk->create("user.user2\@example.com"); + $admintalk->setacl("user.user2\@example.com", "user2\@example.com", + 'lrswipkxtecdan'); - my $service = $self->{instance}->get_service("http"); - my $talk1 = Net::CardDAVTalk->new( - user => 'user1@example.com', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $talk2 = Net::CardDAVTalk->new( - user => 'user2@example.com', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + my $talk1 = Net::CardDAVTalk->new( + user => 'user1@example.com', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $talk2 = Net::CardDAVTalk->new( + user => 'user2@example.com', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $talk2->NewAddressBook("Shared", name => "Shared Address Book"); - $admintalk->setacl("user.user2.#addressbooks.Shared\@example.com", "user1\@example.com", 'lrsn'); + $talk2->NewAddressBook("Shared", name => "Shared Address Book"); + $admintalk->setacl("user.user2.#addressbooks.Shared\@example.com", + "user1\@example.com", 'lrsn'); - my $Addressbooks = $talk1->GetAddressBooks(); + my $Addressbooks = $talk1->GetAddressBooks(); - $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); - $self->assert_str_equals('Default', $Addressbooks->[0]{path}); - $self->assert_str_equals('/dav/addressbooks/user/user1@example.com/Default/', $Addressbooks->[0]{href}); - $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); + $self->assert_str_equals('Personal', $Addressbooks->[0]{name}); + $self->assert_str_equals('Default', $Addressbooks->[0]{path}); + $self->assert_str_equals('/dav/addressbooks/user/user1@example.com/Default/', + $Addressbooks->[0]{href}); + $self->assert_num_equals(0, $Addressbooks->[0]{isReadOnly}); - $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); - $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.com/Shared', $Addressbooks->[1]{path}); - $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.com/Shared/', $Addressbooks->[1]{href}); - $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); + $self->assert_str_equals('Shared Address Book', $Addressbooks->[1]{name}); + $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.com/Shared', + $Addressbooks->[1]{path}); + $self->assert_str_equals('/dav/addressbooks/zzzz/user2@example.com/Shared/', + $Addressbooks->[1]{href}); + $self->assert_num_equals(1, $Addressbooks->[1]{isReadOnly}); } diff --git a/cassandane/tiny-tests/Carddav/sync_collection b/cassandane/tiny-tests/Carddav/sync_collection index 809fa988a0..976d92f475 100644 --- a/cassandane/tiny-tests/Carddav/sync_collection +++ b/cassandane/tiny-tests/Carddav/sync_collection @@ -2,20 +2,19 @@ use Cassandane::Tiny; sub test_sync_collection - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; + my $CardDAV = $self->{carddav}; - my $homeset = "/dav/addressbooks/user/cassandane"; - my $bookId = "Default"; + my $homeset = "/dav/addressbooks/user/cassandane"; + my $bookId = "Default"; - my $uid1 = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; - my $uid2 = "addr1\@example.com"; - my $uid3 = "addr2\@example.com"; + my $uid1 = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; + my $uid2 = "addr1\@example.com"; + my $uid3 = "addr2\@example.com"; - my $vcard1 = Net::CardDAVTalk::VCard->new_fromstring(<new_fromstring(<new_fromstring(<new_fromstring(<new_fromstring(<new_fromstring(<NewContact($bookId, $vcard1); - my $href2 = $CardDAV->NewContact($bookId, $vcard2); + my $href1 = $CardDAV->NewContact($bookId, $vcard1); + my $href2 = $CardDAV->NewContact($bookId, $vcard2); - my ($adds, $removes, $errors, $syncToken) = - $CardDAV->SyncContactLinks($bookId); + my ($adds, $removes, $errors, $syncToken) + = $CardDAV->SyncContactLinks($bookId); - $self->assert_equals(scalar %$adds, 2); - $self->assert_not_null($adds->{"$homeset/$href1"}); - $self->assert_not_null($adds->{"$homeset/$href2"}); - $self->assert_deep_equals($removes, []); - $self->assert_deep_equals($errors, []); + $self->assert_equals(scalar %$adds, 2); + $self->assert_not_null($adds->{"$homeset/$href1"}); + $self->assert_not_null($adds->{"$homeset/$href2"}); + $self->assert_deep_equals($removes, []); + $self->assert_deep_equals($errors, []); - $CardDAV->DeleteContact("$homeset/$href1"); + $CardDAV->DeleteContact("$homeset/$href1"); - my $href3 = $CardDAV->NewContact($bookId, $vcard3); + my $href3 = $CardDAV->NewContact($bookId, $vcard3); - ($adds, $removes, $errors, $syncToken) = - $CardDAV->SyncContactLinks($bookId, syncToken => $syncToken); + ($adds, $removes, $errors, $syncToken) + = $CardDAV->SyncContactLinks($bookId, syncToken => $syncToken); - $self->assert_equals(scalar %$adds, 1); - $self->assert_not_null($adds->{"$homeset/$href3"}); - $self->assert_equals(scalar @$removes, 1); - $self->assert_str_equals("$homeset/$href1", $removes->[0]); - $self->assert_deep_equals($errors, []); + $self->assert_equals(scalar %$adds, 1); + $self->assert_not_null($adds->{"$homeset/$href3"}); + $self->assert_equals(scalar @$removes, 1); + $self->assert_str_equals("$homeset/$href1", $removes->[0]); + $self->assert_deep_equals($errors, []); - ($adds, $removes, $errors, $syncToken) = - $CardDAV->SyncContactLinks($bookId, syncToken => $syncToken); + ($adds, $removes, $errors, $syncToken) + = $CardDAV->SyncContactLinks($bookId, syncToken => $syncToken); - $self->assert_deep_equals($adds, {}); - $self->assert_deep_equals($removes, []); - $self->assert_deep_equals($errors, []); + $self->assert_deep_equals($adds, {}); + $self->assert_deep_equals($removes, []); + $self->assert_deep_equals($errors, []); } diff --git a/cassandane/tiny-tests/Carddav/too_large b/cassandane/tiny-tests/Carddav/too_large index c05ba02dbb..cb6911e607 100644 --- a/cassandane/tiny-tests/Carddav/too_large +++ b/cassandane/tiny-tests/Carddav/too_large @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_too_large - :needs_component_httpd :min_version_3_5 -{ - my ($self) = @_; + : needs_component_httpd : min_version_3_5 { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); - my $href = "$Id/bar.vcf"; + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); + my $href = "$Id/bar.vcf"; - my $notes = ('x') x 100000; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard') }; - my $Err = $@; - $self->assert_matches(qr/max-resource-size/, $Err); + # vcard should be rejected + eval { + $CardDAV->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + }; + my $Err = $@; + $self->assert_matches(qr/max-resource-size/, $Err); } diff --git a/cassandane/tiny-tests/Carddav/tspecial_resource_name b/cassandane/tiny-tests/Carddav/tspecial_resource_name index 73ddeae4f9..97c4cf8f52 100644 --- a/cassandane/tiny-tests/Carddav/tspecial_resource_name +++ b/cassandane/tiny-tests/Carddav/tspecial_resource_name @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_tspecial_resource_name - :needs_component_httpd -{ - my ($self) = @_; + : needs_component_httpd { + my ($self) = @_; - my $carddav = $self->{carddav}; - my $card = <{carddav}; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $carddav->Request('GET', $href); - $self->assert_matches(qr/\r\nUID:123456789\r\n/, $res->{content}); + my $href = "Default/()<>@,;:\"[]?=.vcf"; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + my $res = $carddav->Request('GET', $href); + $self->assert_matches(qr/\r\nUID:123456789\r\n/, $res->{content}); } diff --git a/cassandane/tiny-tests/Carddav/version_ignore_whitespace b/cassandane/tiny-tests/Carddav/version_ignore_whitespace index f19e6701f0..6665ff0e85 100644 --- a/cassandane/tiny-tests/Carddav/version_ignore_whitespace +++ b/cassandane/tiny-tests/Carddav/version_ignore_whitespace @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_version_ignore_whitespace - :min_version_3_3 :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_3 : needs_component_httpd { + my ($self) = @_; - my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - $self->assert_not_null($Id); - $self->assert_str_equals($Id, 'foo'); - my $href = "$Id/bar.vcf"; + my $CardDAV = $self->{carddav}; + my $Id = $CardDAV->NewAddressBook('foo'); + $self->assert_not_null($Id); + $self->assert_str_equals($Id, 'foo'); + my $href = "$Id/bar.vcf"; - my $card = <new_fromstring($card); - my $path = $CardDAV->NewContact($Id, $VCard); - my $res = $CardDAV->GetContact($path); - $self->assert_str_equals($res->{properties}{version}[0]{value}, '3.0'); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($card); + my $path = $CardDAV->NewContact($Id, $VCard); + my $res = $CardDAV->GetContact($path); + $self->assert_str_equals($res->{properties}{version}[0]{value}, '3.0'); } diff --git a/cassandane/tiny-tests/FastMail/ajaxui_jmapcontacts_contactgroup_set b/cassandane/tiny-tests/FastMail/ajaxui_jmapcontacts_contactgroup_set index c4d200d6ff..224e8fc0bc 100644 --- a/cassandane/tiny-tests/FastMail/ajaxui_jmapcontacts_contactgroup_set +++ b/cassandane/tiny-tests/FastMail/ajaxui_jmapcontacts_contactgroup_set @@ -2,96 +2,104 @@ use Cassandane::Tiny; sub test_ajaxui_jmapcontacts_contactgroup_set - :min_version_3_1 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); + my $jmap = $self->{jmap}; + my $service = $self->{instance}->get_service("http"); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.masteruser"); - $admintalk->setacl("user.masteruser", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.masteruser", masteruser => 'lrswipkxtecdn'); - $admintalk->create("user.masteruser.#addressbooks.Default", ['TYPE', 'ADDRESSBOOK']); - $admintalk->create("user.masteruser.#addressbooks.Shared", ['TYPE', 'ADDRESSBOOK']); - $admintalk->setacl("user.masteruser.#addressbooks.Default", "masteruser" => 'lrswipkxtecdn') or die; - $admintalk->setacl("user.masteruser.#addressbooks.Shared", "masteruser" => 'lrswipkxtecdn') or die; - $admintalk->setacl("user.masteruser.#addressbooks.Shared", "cassandane" => 'lrswipkxtecdn') or die; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user.masteruser"); + $admintalk->setacl("user.masteruser", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.masteruser", masteruser => 'lrswipkxtecdn'); + $admintalk->create("user.masteruser.#addressbooks.Default", + [ 'TYPE', 'ADDRESSBOOK' ]); + $admintalk->create("user.masteruser.#addressbooks.Shared", + [ 'TYPE', 'ADDRESSBOOK' ]); + $admintalk->setacl("user.masteruser.#addressbooks.Default", + "masteruser" => 'lrswipkxtecdn') + or die; + $admintalk->setacl("user.masteruser.#addressbooks.Shared", + "masteruser" => 'lrswipkxtecdn') + or die; + $admintalk->setacl("user.masteruser.#addressbooks.Shared", + "cassandane" => 'lrswipkxtecdn') + or die; - my $mastertalk = Net::CardDAVTalk->new( - user => "masteruser", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $mastertalk = Net::CardDAVTalk->new( + user => "masteruser", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $res; + my $res; - xlog $self, "create contact group"; - $res = $self->_fmjmap_ok('ContactGroup/set', - accountId => 'cassandane', - create => { - "k2519" => { - name => "personal group", - addressbookId => 'Default', - contactIds => [], - otherAccountContactIds => { - masteruser => [], - }, - }, + xlog $self, "create contact group"; + $res = $self->_fmjmap_ok( + 'ContactGroup/set', + accountId => 'cassandane', + create => { + "k2519" => { + name => "personal group", + addressbookId => 'Default', + contactIds => [], + otherAccountContactIds => { + masteruser => [], }, - update => {}, - destroy => [], - ); - my $groupid = $res->{created}{"k2519"}{id}; - $self->assert_not_null($groupid); + }, + }, + update => {}, + destroy => [], + ); + my $groupid = $res->{created}{"k2519"}{id}; + $self->assert_not_null($groupid); - $res = $self->_fmjmap_ok('ContactGroup/get', - ids => [$groupid], - ); + $res = $self->_fmjmap_ok('ContactGroup/get', ids => [$groupid],); - $self->assert_num_equals(1, scalar @{$res->{list}}); - # check the rest? + $self->assert_num_equals(1, scalar @{ $res->{list} }); + # check the rest? - xlog $self, "create contact group"; - $res = $self->_fmjmap_ok('ContactGroup/set', - accountId => 'masteruser', - create => { - "k2520" => { - name => "shared group", - addressbookId => 'Shared', - contactIds => [], - otherAccountContactIds => {}, - }, - }, - update => {}, - destroy => [], - ); - my $sgroupid = $res->{created}{"k2520"}{id}; - $self->assert_not_null($sgroupid); + xlog $self, "create contact group"; + $res = $self->_fmjmap_ok( + 'ContactGroup/set', + accountId => 'masteruser', + create => { + "k2520" => { + name => "shared group", + addressbookId => 'Shared', + contactIds => [], + otherAccountContactIds => {}, + }, + }, + update => {}, + destroy => [], + ); + my $sgroupid = $res->{created}{"k2520"}{id}; + $self->assert_not_null($sgroupid); - xlog $self, "create invalid shared contact group"; - $res = $self->_fmjmap_ok('ContactGroup/set', - accountId => 'masteruser', - create => { - "k2521" => { - name => "invalid group", - addressbookId => 'Default', - contactIds => [], - otherAccountContactIds => {}, - }, - }, - update => {}, - destroy => [], - ); + xlog $self, "create invalid shared contact group"; + $res = $self->_fmjmap_ok( + 'ContactGroup/set', + accountId => 'masteruser', + create => { + "k2521" => { + name => "invalid group", + addressbookId => 'Default', + contactIds => [], + otherAccountContactIds => {}, + }, + }, + update => {}, + destroy => [], + ); - $self->assert_not_null($res->{notCreated}{"k2521"}); - $self->assert_null($res->{created}{"k2521"}); + $self->assert_not_null($res->{notCreated}{"k2521"}); + $self->assert_null($res->{created}{"k2521"}); - # now let's create a contact and put it in the event... + # now let's create a contact and put it in the event... } diff --git a/cassandane/tiny-tests/FastMail/carddav_rewrite_v4card_on_get b/cassandane/tiny-tests/FastMail/carddav_rewrite_v4card_on_get index e182eea938..12246e39b8 100644 --- a/cassandane/tiny-tests/FastMail/carddav_rewrite_v4card_on_get +++ b/cassandane/tiny-tests/FastMail/carddav_rewrite_v4card_on_get @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_carddav_rewrite_v4card_on_get - :needs_component_httpd :needs_dependency_icalvcard -{ - my ($self) = @_; - my $CardDAV = $self->{carddav}; + : needs_component_httpd : needs_dependency_icalvcard { + my ($self) = @_; + my $CardDAV = $self->{carddav}; - my $Id = $CardDAV->NewAddressBook('foo'); - my $href = "$Id/test.vcf"; - my $uid = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; - my $image = "R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + my $Id = $CardDAV->NewAddressBook('foo'); + my $href = "$Id/test.vcf"; + my $uid = "3b678b69-ca41-461e-b2c7-f96b9fe48d68"; + my $image = "R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; - # The UID and PHOTO property values of this version 4.0 - # vCard are bogus. The UID property value should either - # be a valid URI or it should have the VALUE=TEXT - # parameter set. The PHOTO property value must be a URI - # but instead it uses version 3.0 encoding for embedding - # data. - # - # This test asserts that we accept such data but rewrite on GET. - # - # The alternatives are: - # 1. Reject on PUT. This is problematic, as we might have - # been the ones writing that bogus data in the first place: - # https://github.com/cyrusimap/cyrus-imapd/commit/b8a879ccf22d52d336662d506d3a14ddf341b60b - # - # 2. Rewrite on PUT. This looks to be the preferrable - # solution in the long run, but it will require us to - # check how clients deal with us rewriting the UID value - # on PUT. +# The UID and PHOTO property values of this version 4.0 +# vCard are bogus. The UID property value should either +# be a valid URI or it should have the VALUE=TEXT +# parameter set. The PHOTO property value must be a URI +# but instead it uses version 3.0 encoding for embedding +# data. +# +# This test asserts that we accept such data but rewrite on GET. +# +# The alternatives are: +# 1. Reject on PUT. This is problematic, as we might have +# been the ones writing that bogus data in the first place: +# https://github.com/cyrusimap/cyrus-imapd/commit/b8a879ccf22d52d336662d506d3a14ddf341b60b +# +# 2. Rewrite on PUT. This looks to be the preferrable +# solution in the long run, but it will require us to +# check how clients deal with us rewriting the UID value +# on PUT. - my $card = < 'text/vcard; version=4.0', - 'Authorization' => $CardDAV->auth_header(), - ); + my %Headers = ( + 'Content-Type' => 'text/vcard; version=4.0', + 'Authorization' => $CardDAV->auth_header(), + ); - xlog $self, "PUT vCard v4 with v3 values"; - my $Response = $CardDAV->{ua}->request('PUT', $CardDAV->request_url($href), { - content => $card, - headers => \%Headers, - }); - $self->assert_num_equals(201, $Response->{status}); - $self->assert_not_null($Response->{headers}{etag}); + xlog $self, "PUT vCard v4 with v3 values"; + my $Response = $CardDAV->{ua}->request( + 'PUT', + $CardDAV->request_url($href), + { + content => $card, + headers => \%Headers, + } + ); + $self->assert_num_equals(201, $Response->{status}); + $self->assert_not_null($Response->{headers}{etag}); - xlog $self, "GET as vCard v4"; - my $response = $CardDAV->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - my $newcard = $response->{content}; - $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/PHOTO:data:image\/gif;base64,$image/, $newcard); + xlog $self, "GET as vCard v4"; + my $response = $CardDAV->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + my $newcard = $response->{content}; + $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties + $self->assert_matches(qr/PHOTO:data:image\/gif;base64,$image/, $newcard); - xlog $self, "GET as vCard v3"; - $response = $CardDAV->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=3.0'); - $newcard = $response->{content}; - $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/PHOTO;ENCODING=[bB];TYPE=GIF:$image/, $newcard); + xlog $self, "GET as vCard v3"; + $response = $CardDAV->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=3.0'); + $newcard = $response->{content}; + $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties + $self->assert_matches(qr/PHOTO;ENCODING=[bB];TYPE=GIF:$image/, $newcard); - xlog $self, "GET without explicit version in Accept header"; - $response = $CardDAV->Request('GET', $href, '', - 'Accept' => 'text/vcard'); - $newcard = $response->{content}; - $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/VERSION:3.0/, $newcard); - $self->assert_matches(qr/PHOTO;ENCODING=[bB];TYPE=GIF:$image/, $newcard); + xlog $self, "GET without explicit version in Accept header"; + $response = $CardDAV->Request('GET', $href, '', 'Accept' => 'text/vcard'); + $newcard = $response->{content}; + $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties + $self->assert_matches(qr/VERSION:3.0/, $newcard); + $self->assert_matches(qr/PHOTO;ENCODING=[bB];TYPE=GIF:$image/, $newcard); - xlog $self, "GET without Accept header"; - $response = $CardDAV->Request('GET', $href, ''); - $newcard = $response->{content}; - $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/VERSION:3.0/, $newcard); - $self->assert_matches(qr/PHOTO;ENCODING=[bB];TYPE=GIF:$image/, $newcard); + xlog $self, "GET without Accept header"; + $response = $CardDAV->Request('GET', $href, ''); + $newcard = $response->{content}; + $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties + $self->assert_matches(qr/VERSION:3.0/, $newcard); + $self->assert_matches(qr/PHOTO;ENCODING=[bB];TYPE=GIF:$image/, $newcard); } diff --git a/cassandane/tiny-tests/FastMail/create_inherit_color b/cassandane/tiny-tests/FastMail/create_inherit_color index 767e3b30ad..e3fbca60df 100644 --- a/cassandane/tiny-tests/FastMail/create_inherit_color +++ b/cassandane/tiny-tests/FastMail/create_inherit_color @@ -2,38 +2,41 @@ use Cassandane::Tiny; sub test_create_inherit_color - :min_version_3_9 :AltNameSpace :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : AltNameSpace : needs_component_jmap { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "Create mailbox with color"; - my $res = $self->_fmjmap_ok('Mailbox/set', - accountId => 'cassandane', - create => { - 1 => { - parentId => JSON::null, - name => 'foo', - color => "coral", - }, - }, - update => {}, - destroy => [], - ); - $self->assert_not_null($res->{created}{1}); + xlog $self, "Create mailbox with color"; + my $res = $self->_fmjmap_ok( + 'Mailbox/set', + accountId => 'cassandane', + create => { + 1 => { + parentId => JSON::null, + name => 'foo', + color => "coral", + }, + }, + update => {}, + destroy => [], + ); + $self->assert_not_null($res->{created}{1}); - my $folder = "foo.bar"; - my $entry = "/shared/vendor/cmu/cyrus-imapd/color"; - my $color = "coral"; + my $folder = "foo.bar"; + my $entry = "/shared/vendor/cmu/cyrus-imapd/color"; + my $color = "coral"; - xlog $self, "Create child mailbox"; - $imaptalk->create($folder); + xlog $self, "Create child mailbox"; + $imaptalk->create($folder); - xlog $self, "Check the child has the same color"; - $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $entry => $color } - }, $res); + xlog $self, "Check the child has the same color"; + $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $folder => { $entry => $color } + }, + $res + ); } diff --git a/cassandane/tiny-tests/FastMail/cyr_expire_delete_findpaths_legacy b/cassandane/tiny-tests/FastMail/cyr_expire_delete_findpaths_legacy index 16bee0406a..3f759d60a3 100644 --- a/cassandane/tiny-tests/FastMail/cyr_expire_delete_findpaths_legacy +++ b/cassandane/tiny-tests/FastMail/cyr_expire_delete_findpaths_legacy @@ -2,71 +2,71 @@ use Cassandane::Tiny; sub test_cyr_expire_delete_findpaths_legacy - :DelayedDelete :min_version_3_5 :MailboxLegacyDirs - :needs_component_httpd -{ - my ($self) = @_; + : DelayedDelete : min_version_3_5 : MailboxLegacyDirs + : needs_component_httpd { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser"; - my $subfolder = "$inbox.foo"; + my $inbox = "user.magicuser"; + my $subfolder = "$inbox.foo"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "Delete $subfolder"; - $admintalk->unselect(); - $admintalk->delete($subfolder) - or $self->fail("Cannot delete folder $subfolder: $@"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + xlog $self, "Delete $subfolder"; + $admintalk->unselect(); + $admintalk->delete($subfolder) + or $self->fail("Cannot delete folder $subfolder: $@"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - xlog $self, "Ensure we can't select $subfolder anymore"; - $admintalk->select($subfolder); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $admintalk->get_last_error()); + xlog $self, "Ensure we can't select $subfolder anymore"; + $admintalk->select($subfolder); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, + $admintalk->get_last_error()); - my ($datapath) = $self->{instance}->folder_to_deleted_directories($subfolder); - $self->assert_not_null($datapath); + my ($datapath) = $self->{instance}->folder_to_deleted_directories($subfolder); + $self->assert_not_null($datapath); - xlog $self, "Run cyr_expire -D now."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0' ); + xlog $self, "Run cyr_expire -D now."; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0'); - # the folder should not exist now! - $self->assert_not_file_test($datapath, "-d"); + # the folder should not exist now! + $self->assert_not_file_test($datapath, "-d"); - # Delete the entire user! - $admintalk->delete($inbox); + # Delete the entire user! + $admintalk->delete($inbox); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/user/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/user/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "DELETED files exists"; - $self->assert(scalar grep { m{/DELETED/} } @files); - xlog $self, "no non-deleted paths"; - $self->assert(not scalar grep { not m{/DELETED/} } @files); + xlog $self, "DELETED files exists"; + $self->assert(scalar grep { m{/DELETED/} } @files); + xlog $self, "no non-deleted paths"; + $self->assert(not scalar grep { not m{/DELETED/} } @files); - xlog $self, "Run cyr_expire -D now."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0' ); + xlog $self, "Run cyr_expire -D now."; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0'); - open(FH, "-|", "find", $basedir); - @files = grep { m{/user/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/user/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no DELETED files exists"; - $self->assert(not scalar grep { m{/DELETED/} } @files); - xlog $self, "no non-deleted paths"; - $self->assert(not scalar grep { not m{/DELETED/} } @files); + xlog $self, "no DELETED files exists"; + $self->assert(not scalar grep { m{/DELETED/} } @files); + xlog $self, "no non-deleted paths"; + $self->assert(not scalar grep { not m{/DELETED/} } @files); } diff --git a/cassandane/tiny-tests/FastMail/cyr_expire_delete_findpaths_nolegacy b/cassandane/tiny-tests/FastMail/cyr_expire_delete_findpaths_nolegacy index 1b6424934b..866610c51c 100644 --- a/cassandane/tiny-tests/FastMail/cyr_expire_delete_findpaths_nolegacy +++ b/cassandane/tiny-tests/FastMail/cyr_expire_delete_findpaths_nolegacy @@ -2,75 +2,75 @@ use Cassandane::Tiny; sub test_cyr_expire_delete_findpaths_nolegacy - :DelayedDelete :min_version_3_5 :NoMailboxLegacyDirs - :needs_component_httpd -{ - my ($self) = @_; + : DelayedDelete : min_version_3_5 : NoMailboxLegacyDirs + : needs_component_httpd { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser"; - my $subfolder = "$inbox.foo"; + my $inbox = "user.magicuser"; + my $subfolder = "$inbox.foo"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $admintalk->status($inbox, ['mailboxid']); - my $inboxid = $res->{mailboxid}[0]; - $res = $admintalk->status($subfolder, ['mailboxid']); - my $subid = $res->{mailboxid}[0]; + my $res = $admintalk->status($inbox, ['mailboxid']); + my $inboxid = $res->{mailboxid}[0]; + $res = $admintalk->status($subfolder, ['mailboxid']); + my $subid = $res->{mailboxid}[0]; - xlog $self, "Delete $subfolder"; - $admintalk->unselect(); - $admintalk->delete($subfolder) - or $self->fail("Cannot delete folder $subfolder: $@"); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + xlog $self, "Delete $subfolder"; + $admintalk->unselect(); + $admintalk->delete($subfolder) + or $self->fail("Cannot delete folder $subfolder: $@"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - xlog $self, "Ensure we can't select $subfolder anymore"; - $admintalk->select($subfolder); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/Mailbox does not exist/i, $admintalk->get_last_error()); + xlog $self, "Ensure we can't select $subfolder anymore"; + $admintalk->select($subfolder); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/Mailbox does not exist/i, + $admintalk->get_last_error()); - my ($datapath) = $self->{instance}->folder_to_deleted_directories($subfolder); - $self->assert_not_null($datapath); + my ($datapath) = $self->{instance}->folder_to_deleted_directories($subfolder); + $self->assert_not_null($datapath); - xlog $self, "Run cyr_expire -D now."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0' ); + xlog $self, "Run cyr_expire -D now."; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0'); - # the folder should not exist now! - $self->assert_not_file_test($datapath, "-d"); + # the folder should not exist now! + $self->assert_not_file_test($datapath, "-d"); - # Delete the entire user! - $admintalk->delete($inbox); + # Delete the entire user! + $admintalk->delete($inbox); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/uuid/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/uuid/} } ; + close(FH); - xlog $self, "files for the inbox still exist"; - $self->assert(scalar grep { m{$inboxid} } @files); - xlog $self, "no files left for subfolder"; - $self->assert(not scalar grep { m{$subid} } @files); + xlog $self, "files for the inbox still exist"; + $self->assert(scalar grep { m{$inboxid} } @files); + xlog $self, "no files left for subfolder"; + $self->assert(not scalar grep { m{$subid} } @files); - xlog $self, "Run cyr_expire -D now."; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0' ); + xlog $self, "Run cyr_expire -D now."; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0'); - open(FH, "-|", "find", $basedir); - @files = grep { m{/uuid/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/uuid/} } ; + close(FH); - use Data::Dumper; - xlog $self, "no files for the inbox still exist" . Dumper(\@files, $inboxid);; - $self->assert(not scalar grep { m{$inboxid} } @files); + use Data::Dumper; + xlog $self, "no files for the inbox still exist" . Dumper(\@files, $inboxid); + $self->assert(not scalar grep { m{$inboxid} } @files); } diff --git a/cassandane/tiny-tests/FastMail/imap_list_notes b/cassandane/tiny-tests/FastMail/imap_list_notes index 19dc69b69a..9fcf441d67 100644 --- a/cassandane/tiny-tests/FastMail/imap_list_notes +++ b/cassandane/tiny-tests/FastMail/imap_list_notes @@ -2,84 +2,34 @@ use Cassandane::Tiny; sub test_imap_list_notes - :min_version_3_1 :needs_component_sieve - :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve + : needs_component_httpd { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.Foo") || die; - $imaptalk->create("INBOX.Foo.Hi") || die; - $imaptalk->create("INBOX.A") || die; - $imaptalk->create("INBOX.Junk", "(USE (\\Junk))"); - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); - $imaptalk->create("INBOX.Important", "(USE (\\Important))"); - $imaptalk->create("INBOX.Notes", "(USE (\\XNotes))"); + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.Foo") || die; + $imaptalk->create("INBOX.Foo.Hi") || die; + $imaptalk->create("INBOX.A") || die; + $imaptalk->create("INBOX.Junk", "(USE (\\Junk))"); + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))"); + $imaptalk->create("INBOX.Important", "(USE (\\Important))"); + $imaptalk->create("INBOX.Notes", "(USE (\\XNotes))"); - my $data = $imaptalk->list('', '*'); - $self->assert_deep_equals([ - [ - [ - '\\HasChildren', - ], - '.', - 'INBOX', - ], - [ - [ - '\\HasNoChildren', - ], - '.', - 'INBOX.A', - ], - [ - [ - '\\HasChildren', - ], - '.', - 'INBOX.Foo', - ], - [ - [ - '\\HasNoChildren', - ], - '.', - 'INBOX.Foo.Hi', - ], - [ - [ - '\\HasNoChildren', - '\\Important', - ], - '.', - 'INBOX.Important', - ], - [ - [ - '\\HasNoChildren', - '\\Junk', - ], - '.', - 'INBOX.Junk', - ], - [ - [ - '\\HasNoChildren', - '\\XNotes', - ], - '.', - 'INBOX.Notes', - ], - [ - [ - '\\HasNoChildren', - '\\Trash', - ], - '.', - 'INBOX.Trash', - ], -], $data); + my $data = $imaptalk->list('', '*'); + $self->assert_deep_equals( + [ + [ [ '\\HasChildren', ], '.', 'INBOX', ], + [ [ '\\HasNoChildren', ], '.', 'INBOX.A', ], + [ [ '\\HasChildren', ], '.', 'INBOX.Foo', ], + [ [ '\\HasNoChildren', ], '.', 'INBOX.Foo.Hi', ], + [ [ '\\HasNoChildren', '\\Important', ], '.', 'INBOX.Important', ], + [ [ '\\HasNoChildren', '\\Junk', ], '.', 'INBOX.Junk', ], + [ [ '\\HasNoChildren', '\\XNotes', ], '.', 'INBOX.Notes', ], + [ [ '\\HasNoChildren', '\\Trash', ], '.', 'INBOX.Trash', ], + ], + $data + ); } diff --git a/cassandane/tiny-tests/FastMail/issue_LP52545479 b/cassandane/tiny-tests/FastMail/issue_LP52545479 index bb0d026ae2..cce4402d29 100644 --- a/cassandane/tiny-tests/FastMail/issue_LP52545479 +++ b/cassandane/tiny-tests/FastMail/issue_LP52545479 @@ -2,94 +2,126 @@ use Cassandane::Tiny; sub test_issue_LP52545479 - :min_version_3_1 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendar1 => { - name => 'calendar1', - color => 'coral', - sortOrder => 1, - isVisible => JSON::true, - } - }, - }, 'R1'], - ], [ $self->default_using ]); - my $calendarId = $res->[0][1]{created}{calendar1}{id}; - $self->assert_not_null($calendarId); + my $res = $jmap->CallMethods( + [ + [ + 'Calendar/set', + { + create => { + calendar1 => { + name => 'calendar1', + color => 'coral', + sortOrder => 1, + isVisible => JSON::true, + } + }, + }, + 'R1' + ], + ], + [ $self->default_using ] + ); + my $calendarId = $res->[0][1]{created}{calendar1}{id}; + $self->assert_not_null($calendarId); - $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - firstName => "firstName", - lastName => "lastName", - notes => "x" x 1024 - } + $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + create => { + contact1 => { + firstName => "firstName", + lastName => "lastName", + notes => "x" x 1024 + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + $calendarId => JSON::true, + }, + uid => '58ADE31-custom-UID', + title => 'event1', + start => '2015-11-07T09:00:00', + duration => 'PT5M', + sequence => 42, + timeZone => 'Etc/UTC', + showWithoutTime => JSON::false, + locale => 'en', + description => 'x' x 1024, + freeBusyStatus => 'busy', + privacy => 'secret', + participants => undef, + alerts => undef, } - }, 'R1'], - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - $calendarId => JSON::true, - }, - uid => '58ADE31-custom-UID', - title => 'event1', - start => '2015-11-07T09:00:00', - duration => 'PT5M', - sequence => 42, - timeZone => 'Etc/UTC', - showWithoutTime => JSON::false, - locale => 'en', - description => 'x' x 1024, - freeBusyStatus => 'busy', - privacy => 'secret', - participants => undef, - alerts => undef, - } - }, - }, 'R2'], - ], [ $self->default_using ]); - my $contactId1 = $res->[0][1]{created}{contact1}{id}; - $self->assert_not_null($contactId1); - my $eventId1 = $res->[1][1]{created}{event1}{id}; - $self->assert_not_null($eventId1); + }, + }, + 'R2' + ], + ], + [ $self->default_using ] + ); + my $contactId1 = $res->[0][1]{created}{contact1}{id}; + $self->assert_not_null($contactId1); + my $eventId1 = $res->[1][1]{created}{event1}{id}; + $self->assert_not_null($eventId1); - my $res_annot_storage = 'ANNOTATION-STORAGE'; - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj < 3 || ($maj == 3 && $min < 9)) { - $res_annot_storage = 'X-ANNOTATION-STORAGE'; - } + my $res_annot_storage = 'ANNOTATION-STORAGE'; + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj < 3 || ($maj == 3 && $min < 9)) { + $res_annot_storage = 'X-ANNOTATION-STORAGE'; + } - $self->_set_quotaroot('user.cassandane'); - $self->_set_quotalimits(storage => 1, - $res_annot_storage => 1); # that's 1024 bytes + $self->_set_quotaroot('user.cassandane'); + $self->_set_quotalimits( + storage => 1, + $res_annot_storage => 1 + ); # that's 1024 bytes - $res = $jmap->CallMethods([ - ['Contact/set', { - update => { - $contactId1 => { - lastName => "updatedLastName", - } - } - }, 'R1'], - ['CalendarEvent/set', { - update => { - $eventId1 => { - description => "y" x 2048, - } - } - }, 'R2'], - ], [ $self->default_using ]); - $self->assert_str_equals('overQuota', $res->[0][1]{notUpdated}{$contactId1}{type}); - $self->assert(not exists $res->[0][1]{updated}{$contactId1}); - $self->assert_str_equals('overQuota', $res->[1][1]{notUpdated}{$eventId1}{type}); - $self->assert(not exists $res->[1][1]{updated}{$eventId1}); + $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + update => { + $contactId1 => { + lastName => "updatedLastName", + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + update => { + $eventId1 => { + description => "y" x 2048, + } + } + }, + 'R2' + ], + ], + [ $self->default_using ] + ); + $self->assert_str_equals('overQuota', + $res->[0][1]{notUpdated}{$contactId1}{type}); + $self->assert(not exists $res->[0][1]{updated}{$contactId1}); + $self->assert_str_equals('overQuota', + $res->[1][1]{notUpdated}{$eventId1}{type}); + $self->assert(not exists $res->[1][1]{updated}{$eventId1}); } diff --git a/cassandane/tiny-tests/FastMail/mailbox_case_difference b/cassandane/tiny-tests/FastMail/mailbox_case_difference index b662d4ce30..f9b9984113 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_case_difference +++ b/cassandane/tiny-tests/FastMail/mailbox_case_difference @@ -2,42 +2,44 @@ use Cassandane::Tiny; sub test_mailbox_case_difference - :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # we need the mail extensions for isSeenShared - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need the mail extensions for isSeenShared + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.Foo.Hi") || die; - $imaptalk->create("INBOX.A") || die; + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.Foo.Hi") || die; + $imaptalk->create("INBOX.A") || die; - xlog $self, "fetch mailboxes"; - my $res = $jmap->Call('Mailbox/get', {}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->{list}}; + xlog $self, "fetch mailboxes"; + my $res = $jmap->Call('Mailbox/get', {}); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->{list} }; - xlog $self, "move INBOX.A to INBOX.Foo.B"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "move INBOX.A to INBOX.Foo.B"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{A} => { - name => "Hi", + name => "Hi", parentId => $mboxids{Foo}, }, $mboxids{Hi} => { name => "HI", } } - }); + } + ); - $self->assert_null($res->{notUpdated}); - $self->assert(exists $res->{updated}{$mboxids{A}}); - $self->assert(exists $res->{updated}{$mboxids{Hi}}); + $self->assert_null($res->{notUpdated}); + $self->assert(exists $res->{updated}{ $mboxids{A} }); + $self->assert(exists $res->{updated}{ $mboxids{Hi} }); } diff --git a/cassandane/tiny-tests/FastMail/mailbox_query b/cassandane/tiny-tests/FastMail/mailbox_query index ddb8a63567..c4d55ae0fa 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_query +++ b/cassandane/tiny-tests/FastMail/mailbox_query @@ -2,72 +2,83 @@ use Cassandane::Tiny; sub test_mailbox_query - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + 1 => { name => 'Sent', role => 'sent' }, + 2 => { name => 'Trash', role => 'junk' }, + 3 => { name => 'Foo' }, + 4 => { name => 'Bar', sortOrder => 30 }, + 5 => { name => 'Early', sortOrder => 2 }, + 6 => { name => 'Child', parentId => '#5' }, + 7 => { name => 'EarlyChild', parentId => '#5', sortOrder => 0 }, + }, + }, + 'a' + ], + [ + 'Mailbox/query', + { + sortAsTree => $JSON::true, + sort => [ { property => 'sortOrder' }, { property => 'name' } ], + filterAsTree => $JSON::true, + filter => { + operator => 'OR', + conditions => [ { role => 'inbox' }, { hasAnyRole => $JSON::false } ], + }, + }, + 'b' + ], + [ + 'Mailbox/get', + { + '#ids' => { + resultOf => 'b', + name => 'Mailbox/query', + path => '/ids', + }, + }, + 'c' + ], + ]); - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - 1 => { name => 'Sent', role => 'sent' }, - 2 => { name => 'Trash', role => 'junk' }, - 3 => { name => 'Foo' }, - 4 => { name => 'Bar', sortOrder => 30 }, - 5 => { name => 'Early', sortOrder => 2 }, - 6 => { name => 'Child', parentId => '#5' }, - 7 => { name => 'EarlyChild', parentId => '#5', sortOrder => 0 }, - }, - }, 'a'], - ['Mailbox/query', { - sortAsTree => $JSON::true, - sort => [{property => 'sortOrder'}, {property => 'name'}], - filterAsTree => $JSON::true, - filter => { - operator => 'OR', - conditions => [{role => 'inbox'}, {hasAnyRole => $JSON::false}], - }, - }, 'b'], - ['Mailbox/get', { - '#ids' => { - resultOf => 'b', - name => 'Mailbox/query', - path => '/ids', - }, - }, 'c'], - ]); + # default sort orders should have been set for Sent, Trash and Foo: - # default sort orders should have been set for Sent, Trash and Foo: + $self->assert_num_equals(5, $res->[0][1]{created}{1}{sortOrder}); + $self->assert_num_equals(6, $res->[0][1]{created}{2}{sortOrder}); + $self->assert_num_equals(10, $res->[0][1]{created}{3}{sortOrder}); + $self->assert_num_equals(10, $res->[0][1]{created}{6}{sortOrder}); - $self->assert_num_equals(5, $res->[0][1]{created}{1}{sortOrder}); - $self->assert_num_equals(6, $res->[0][1]{created}{2}{sortOrder}); - $self->assert_num_equals(10, $res->[0][1]{created}{3}{sortOrder}); - $self->assert_num_equals(10, $res->[0][1]{created}{6}{sortOrder}); + # sortOrder shouldn't be returned where it's been set explicitly + $self->assert_null($res->[0][1]{created}{4}{sortOrder}); + $self->assert_null($res->[0][1]{created}{5}{sortOrder}); + $self->assert_null($res->[0][1]{created}{7}{sortOrder}); - # sortOrder shouldn't be returned where it's been set explicitly - $self->assert_null($res->[0][1]{created}{4}{sortOrder}); - $self->assert_null($res->[0][1]{created}{5}{sortOrder}); - $self->assert_null($res->[0][1]{created}{7}{sortOrder}); + my %mailboxes = map { $_->{id} => $_ } @{ $res->[2][1]{list} }; - my %mailboxes = map { $_->{id} => $_ } @{$res->[2][1]{list}}; + my $list = $res->[1][1]{ids}; - my $list = $res->[1][1]{ids}; + # expected values for name and sortOrder + my @expected = ( + [ 'Inbox', 1 ], + [ 'Early', 2 ], + [ 'EarlyChild', 0 ], + [ 'Child', 10 ], + [ 'Foo', 10 ], + [ 'Bar', 30 ], + ); + $self->assert_num_equals(scalar @expected, scalar @$list); - # expected values for name and sortOrder - my @expected = ( - ['Inbox', 1], - ['Early', 2], - ['EarlyChild', 0], - ['Child', 10], - ['Foo', 10], - ['Bar', 30], - ); - $self->assert_num_equals(scalar @expected, scalar @$list); - - for (0..$#expected) { - $self->assert_str_equals($expected[$_][0], $mailboxes{$list->[$_]}{name}); - $self->assert_num_equals($expected[$_][1], $mailboxes{$list->[$_]}{sortOrder}); - } + for (0 .. $#expected) { + $self->assert_str_equals($expected[$_][0], $mailboxes{ $list->[$_] }{name}); + $self->assert_num_equals($expected[$_][1], + $mailboxes{ $list->[$_] }{sortOrder}); + } } diff --git a/cassandane/tiny-tests/FastMail/mailbox_rename_inside_deep b/cassandane/tiny-tests/FastMail/mailbox_rename_inside_deep index a82794ce20..fcf22e0dc6 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_rename_inside_deep +++ b/cassandane/tiny-tests/FastMail/mailbox_rename_inside_deep @@ -2,38 +2,41 @@ use Cassandane::Tiny; sub test_mailbox_rename_inside_deep - :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # we need the mail extensions for isSeenShared - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need the mail extensions for isSeenShared + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.A") || die; - $imaptalk->create("INBOX.A.B") || die; - $imaptalk->create("INBOX.A.B.C") || die; + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.A") || die; + $imaptalk->create("INBOX.A.B") || die; + $imaptalk->create("INBOX.A.B.C") || die; - xlog $self, "fetch mailboxes"; - my $res = $jmap->Call('Mailbox/get', {}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->{list}}; + xlog $self, "fetch mailboxes"; + my $res = $jmap->Call('Mailbox/get', {}); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->{list} }; - xlog $self, "move INBOX.A to be a child of INBOX.A.B.C"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "move INBOX.A to be a child of INBOX.A.B.C"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{A} => { parentId => $mboxids{C}, } } - }); + } + ); - # rejected due to being a child - $self->assert_str_equals("parentId", $res->{notUpdated}{$mboxids{A}}{properties}[0]); + # rejected due to being a child + $self->assert_str_equals("parentId", + $res->{notUpdated}{ $mboxids{A} }{properties}[0]); } diff --git a/cassandane/tiny-tests/FastMail/mailbox_rename_sub_inbox_both b/cassandane/tiny-tests/FastMail/mailbox_rename_sub_inbox_both index 86ee087f8f..b912896599 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_rename_sub_inbox_both +++ b/cassandane/tiny-tests/FastMail/mailbox_rename_sub_inbox_both @@ -2,43 +2,45 @@ use Cassandane::Tiny; sub test_mailbox_rename_sub_inbox_both - :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # we need the mail extensions for isSeenShared - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need the mail extensions for isSeenShared + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.INBOX.Child") || die; - $imaptalk->create("INBOX.Example.INBOX") || die; - $imaptalk->create("INBOX.Example.Other") || die; - $imaptalk->create("INBOX.Top") || die; + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.INBOX.Child") || die; + $imaptalk->create("INBOX.Example.INBOX") || die; + $imaptalk->create("INBOX.Example.Other") || die; + $imaptalk->create("INBOX.Top") || die; - xlog $self, "fetch mailboxes"; - my $res = $jmap->Call('Mailbox/get', {}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->{list}}; + xlog $self, "fetch mailboxes"; + my $res = $jmap->Call('Mailbox/get', {}); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->{list} }; - xlog $self, "move Example.INBOX to top level and rename at same time"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "move Example.INBOX to top level and rename at same time"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{INBOX} => { parentId => undef, - name => "INBOX1", + name => "INBOX1", } } - }); - $self->assert(exists $res->{updated}{$mboxids{INBOX}}); - $self->assert_null($res->{notUpdated}); - - # make sure we didn't create the deep tree! - $self->assert_syslog_does_not_match($self->{instance}, - qr/INBOX\.INBOX\.INBOX/); + } + ); + $self->assert(exists $res->{updated}{ $mboxids{INBOX} }); + $self->assert_null($res->{notUpdated}); + + # make sure we didn't create the deep tree! + $self->assert_syslog_does_not_match($self->{instance}, + qr/INBOX\.INBOX\.INBOX/); } diff --git a/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_both b/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_both index be85deb744..6f8f2c87d1 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_both +++ b/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_both @@ -2,48 +2,50 @@ use Cassandane::Tiny; sub test_mailbox_rename_to_clash_both - :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # we need the mail extensions for isSeenShared - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need the mail extensions for isSeenShared + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.Foo") || die; - $imaptalk->create("INBOX.Foo.A") || die; - $imaptalk->create("INBOX.Bar") || die; - $imaptalk->create("INBOX.Bar.B") || die; + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.Foo") || die; + $imaptalk->create("INBOX.Foo.A") || die; + $imaptalk->create("INBOX.Bar") || die; + $imaptalk->create("INBOX.Bar.B") || die; - xlog $self, "fetch mailboxes"; - my $res = $jmap->Call('Mailbox/get', {}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->{list}}; + xlog $self, "fetch mailboxes"; + my $res = $jmap->Call('Mailbox/get', {}); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->{list} }; - xlog $self, "move INBOX.Foo.A to INBOX.Bar.B"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "move INBOX.Foo.A to INBOX.Bar.B"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{A} => { parentId => $mboxids{Bar}, - name => "B", + name => "B", } } - }); + } + ); - # rejected due to name existing - $self->assert_str_equals("name", $res->{notUpdated}{$mboxids{A}}{properties}[0]); + # rejected due to name existing + $self->assert_str_equals("name", + $res->{notUpdated}{ $mboxids{A} }{properties}[0]); - $res = $jmap->Call('Mailbox/get', {}); - my %mboxids2 = map { $_->{name} => $_->{id} } @{$res->{list}}; - $self->assert_deep_equals(\%mboxids, \%mboxids2); + $res = $jmap->Call('Mailbox/get', {}); + my %mboxids2 = map { $_->{name} => $_->{id} } @{ $res->{list} }; + $self->assert_deep_equals(\%mboxids, \%mboxids2); - # there were no renames - $self->assert_syslog_does_not_match($self->{instance}, - qr/auditlog: rename/); + # there were no renames + $self->assert_syslog_does_not_match($self->{instance}, qr/auditlog: rename/); } diff --git a/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_name_only b/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_name_only index cfe98a29cb..95c6522b1f 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_name_only +++ b/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_name_only @@ -2,46 +2,47 @@ use Cassandane::Tiny; sub test_mailbox_rename_to_clash_name_only - :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # we need the mail extensions for isSeenShared - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need the mail extensions for isSeenShared + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.A") || die; - $imaptalk->create("INBOX.B") || die; + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.A") || die; + $imaptalk->create("INBOX.B") || die; - xlog $self, "fetch mailboxes"; - my $res = $jmap->Call('Mailbox/get', {}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->{list}}; + xlog $self, "fetch mailboxes"; + my $res = $jmap->Call('Mailbox/get', {}); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->{list} }; - xlog $self, "move INBOX.A to INBOX.B"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "move INBOX.A to INBOX.B"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{A} => { name => "B", } } - }); + } + ); - # rejected due to name existing - $self->assert_null($res->{updated}); - $self->assert_not_null($res->{notUpdated}{$mboxids{A}}); + # rejected due to name existing + $self->assert_null($res->{updated}); + $self->assert_not_null($res->{notUpdated}{ $mboxids{A} }); - $res = $jmap->Call('Mailbox/get', {}); - my %mboxids2 = map { $_->{name} => $_->{id} } @{$res->{list}}; - $self->assert_deep_equals(\%mboxids, \%mboxids2); + $res = $jmap->Call('Mailbox/get', {}); + my %mboxids2 = map { $_->{name} => $_->{id} } @{ $res->{list} }; + $self->assert_deep_equals(\%mboxids, \%mboxids2); - # there were no renames - $self->assert_syslog_does_not_match($self->{instance}, - qr/auditlog: rename/); + # there were no renames + $self->assert_syslog_does_not_match($self->{instance}, qr/auditlog: rename/); } diff --git a/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_name_only_deep b/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_name_only_deep index 4cf5c56af9..7bc21aea0b 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_name_only_deep +++ b/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_name_only_deep @@ -2,45 +2,46 @@ use Cassandane::Tiny; sub test_mailbox_rename_to_clash_name_only_deep - :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # we need the mail extensions for isSeenShared - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need the mail extensions for isSeenShared + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.A") || die; - $imaptalk->create("INBOX.A.B") || die; - $imaptalk->create("INBOX.C") || die; + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.A") || die; + $imaptalk->create("INBOX.A.B") || die; + $imaptalk->create("INBOX.C") || die; - xlog $self, "fetch mailboxes"; - my $res = $jmap->Call('Mailbox/get', {}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->{list}}; + xlog $self, "fetch mailboxes"; + my $res = $jmap->Call('Mailbox/get', {}); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->{list} }; - $imaptalk->create("INBOX.C.B") || die; + $imaptalk->create("INBOX.C.B") || die; - xlog $self, "move INBOX.A.B to INBOX.C.B"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "move INBOX.A.B to INBOX.C.B"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{B} => { parentId => $mboxids{C}, } } - }); + } + ); - # rejected due to name existing - $self->assert_null($res->{updated}); - $self->assert_not_null($res->{notUpdated}{$mboxids{B}}); + # rejected due to name existing + $self->assert_null($res->{updated}); + $self->assert_not_null($res->{notUpdated}{ $mboxids{B} }); - # there were no renames - $self->assert_syslog_does_not_match($self->{instance}, - qr/auditlog: rename/); + # there were no renames + $self->assert_syslog_does_not_match($self->{instance}, qr/auditlog: rename/); } diff --git a/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_parent_only b/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_parent_only index 26eb9a3781..baa04fba57 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_parent_only +++ b/cassandane/tiny-tests/FastMail/mailbox_rename_to_clash_parent_only @@ -2,44 +2,45 @@ use Cassandane::Tiny; sub test_mailbox_rename_to_clash_parent_only - :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # we need the mail extensions for isSeenShared - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need the mail extensions for isSeenShared + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.A") || die; - $imaptalk->create("INBOX.A.B") || die; + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.A") || die; + $imaptalk->create("INBOX.A.B") || die; - xlog $self, "fetch mailboxes"; - my $res = $jmap->Call('Mailbox/get', {}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->{list}}; + xlog $self, "fetch mailboxes"; + my $res = $jmap->Call('Mailbox/get', {}); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->{list} }; - $imaptalk->create("INBOX.B") || die; + $imaptalk->create("INBOX.B") || die; - xlog $self, "move INBOX.A.B to be a child of INBOX"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "move INBOX.A.B to be a child of INBOX"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{B} => { parentId => undef, } } - }); + } + ); - # rejected due to being a child - $self->assert_null($res->{updated}); - $self->assert_not_null($res->{notUpdated}{$mboxids{B}}); + # rejected due to being a child + $self->assert_null($res->{updated}); + $self->assert_not_null($res->{notUpdated}{ $mboxids{B} }); - # there were no renames - $self->assert_syslog_does_not_match($self->{instance}, - qr/auditlog: rename/); + # there were no renames + $self->assert_syslog_does_not_match($self->{instance}, qr/auditlog: rename/); } diff --git a/cassandane/tiny-tests/FastMail/mailbox_rename_to_inbox_sub b/cassandane/tiny-tests/FastMail/mailbox_rename_to_inbox_sub index 2900541320..2008892a31 100644 --- a/cassandane/tiny-tests/FastMail/mailbox_rename_to_inbox_sub +++ b/cassandane/tiny-tests/FastMail/mailbox_rename_to_inbox_sub @@ -2,83 +2,97 @@ use Cassandane::Tiny; sub test_mailbox_rename_to_inbox_sub - :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # we need the mail extensions for isSeenShared - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need the mail extensions for isSeenShared + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.INBOX.Child") || die; - $imaptalk->create("INBOX.Example.INBOX") || die; - $imaptalk->create("INBOX.Example.Other") || die; - $imaptalk->create("INBOX.Top") || die; + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.INBOX.Child") || die; + $imaptalk->create("INBOX.Example.INBOX") || die; + $imaptalk->create("INBOX.Example.Other") || die; + $imaptalk->create("INBOX.Top") || die; - xlog $self, "fetch mailboxes"; - my $res = $jmap->Call('Mailbox/get', {}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->{list}}; + xlog $self, "fetch mailboxes"; + my $res = $jmap->Call('Mailbox/get', {}); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->{list} }; - xlog $self, "fail move Example.INBOX to top level"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "fail move Example.INBOX to top level"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{INBOX} => { parentId => undef, } } - }); - $self->assert_null($res->{updated}); - $self->assert_str_equals("parentId", $res->{notUpdated}{$mboxids{INBOX}}{properties}[0]); + } + ); + $self->assert_null($res->{updated}); + $self->assert_str_equals("parentId", + $res->{notUpdated}{ $mboxids{INBOX} }{properties}[0]); - xlog $self, "fail move Top to inbox"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "fail move Top to inbox"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{Top} => { name => 'inbox', } } - }); - $self->assert_null($res->{updated}); - $self->assert_str_equals("name", $res->{notUpdated}{$mboxids{Top}}{properties}[0]); + } + ); + $self->assert_null($res->{updated}); + $self->assert_str_equals("name", + $res->{notUpdated}{ $mboxids{Top} }{properties}[0]); - xlog $self, "fail move Example.Other to InBox"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "fail move Example.Other to InBox"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{Other} => { - name => "InBox", + name => "InBox", parentId => undef, } } - }); - $self->assert_null($res->{updated}); - $self->assert_str_equals("name", $res->{notUpdated}{$mboxids{Other}}{properties}[0]); + } + ); + $self->assert_null($res->{updated}); + $self->assert_str_equals("name", + $res->{notUpdated}{ $mboxids{Other} }{properties}[0]); - # no updates YET! - $res = $jmap->Call('Mailbox/get', {}); - my %mboxids2 = map { $_->{name} => $_->{id} } @{$res->{list}}; - $self->assert_deep_equals(\%mboxids, \%mboxids2); + # no updates YET! + $res = $jmap->Call('Mailbox/get', {}); + my %mboxids2 = map { $_->{name} => $_->{id} } @{ $res->{list} }; + $self->assert_deep_equals(\%mboxids, \%mboxids2); - xlog $self, "Move Example.INBOX again to sub of Inbox (allowed)"; - $res = $jmap->Call('Mailbox/set', { + xlog $self, "Move Example.INBOX again to sub of Inbox (allowed)"; + $res = $jmap->Call( + 'Mailbox/set', + { update => { $mboxids{INBOX} => { - parentId => $mboxids{Inbox}, + parentId => $mboxids{Inbox}, isSeenShared => $JSON::true, } } - }); - # this will have content which is NULL, but it should exist - $self->assert(exists $res->{updated}{$mboxids{INBOX}}); - $self->assert_null($res->{notUpdated}); + } + ); + # this will have content which is NULL, but it should exist + $self->assert(exists $res->{updated}{ $mboxids{INBOX} }); + $self->assert_null($res->{notUpdated}); - # make sure we didn't create the deep tree! - $self->assert_syslog_does_not_match($self->{instance}, - qr/INBOX\.INBOX\.INBOX/); + # make sure we didn't create the deep tree! + $self->assert_syslog_does_not_match($self->{instance}, + qr/INBOX\.INBOX\.INBOX/); } diff --git a/cassandane/tiny-tests/FastMail/relocate_legacy_domain b/cassandane/tiny-tests/FastMail/relocate_legacy_domain index 55225bd64e..fd0901d028 100644 --- a/cassandane/tiny-tests/FastMail/relocate_legacy_domain +++ b/cassandane/tiny-tests/FastMail/relocate_legacy_domain @@ -2,43 +2,43 @@ use Cassandane::Tiny; sub test_relocate_legacy_domain - :DelayedDelete :min_version_3_5 :MailboxLegacyDirs - :needs_component_httpd -{ - my ($self) = @_; + : DelayedDelete : min_version_3_5 : MailboxLegacyDirs + : needs_component_httpd { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser\@example.com"; - my $subfolder = "user.magicuser.foo\@example.com"; + my $inbox = "user.magicuser\@example.com"; + my $subfolder = "user.magicuser.foo\@example.com"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, 'relocate_by_id', '-u' => "magicuser\@example.com" ); + $self->{instance}->run_command({ cyrus => 1 }, + 'relocate_by_id', '-u' => "magicuser\@example.com"); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); } diff --git a/cassandane/tiny-tests/FastMail/relocate_legacy_nodomain b/cassandane/tiny-tests/FastMail/relocate_legacy_nodomain index 25e0590c93..72c381ce3c 100644 --- a/cassandane/tiny-tests/FastMail/relocate_legacy_nodomain +++ b/cassandane/tiny-tests/FastMail/relocate_legacy_nodomain @@ -2,43 +2,43 @@ use Cassandane::Tiny; sub test_relocate_legacy_nodomain - :DelayedDelete :min_version_3_5 :MailboxLegacyDirs - :needs_component_httpd -{ - my ($self) = @_; + : DelayedDelete : min_version_3_5 : MailboxLegacyDirs + : needs_component_httpd { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser"; - my $subfolder = "user.magicuser.foo"; + my $inbox = "user.magicuser"; + my $subfolder = "user.magicuser.foo"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, 'relocate_by_id', '-u' => "magicuser" ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'relocate_by_id', '-u' => "magicuser"); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); } diff --git a/cassandane/tiny-tests/FastMail/relocate_legacy_nosearchdb b/cassandane/tiny-tests/FastMail/relocate_legacy_nosearchdb index 5672f9a2d0..a8e945ddf6 100644 --- a/cassandane/tiny-tests/FastMail/relocate_legacy_nosearchdb +++ b/cassandane/tiny-tests/FastMail/relocate_legacy_nosearchdb @@ -2,46 +2,46 @@ use Cassandane::Tiny; sub test_relocate_legacy_nosearchdb - :DelayedDelete :min_version_3_5 :MailboxLegacyDirs - :needs_component_httpd -{ - my ($self) = @_; + : DelayedDelete : min_version_3_5 : MailboxLegacyDirs + : needs_component_httpd { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser\@example.com"; - my $subfolder = "user.magicuser.foo\@example.com"; + my $inbox = "user.magicuser\@example.com"; + my $subfolder = "user.magicuser.foo\@example.com"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Don't create the search database! - # A user who's never been indexed should still relocate cleanly + # Don't create the search database! + # A user who's never been indexed should still relocate cleanly - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, 'relocate_by_id', '-u' => "magicuser\@example.com" ); + $self->{instance}->run_command({ cyrus => 1 }, + 'relocate_by_id', '-u' => "magicuser\@example.com"); - open(FH, "-|", "find", $basedir); - @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); - # Hopefully squatter still works! - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Hopefully squatter still works! + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); } diff --git a/cassandane/tiny-tests/FastMail/relocate_messages_still_exist b/cassandane/tiny-tests/FastMail/relocate_messages_still_exist index 7872684edb..b27f091f35 100644 --- a/cassandane/tiny-tests/FastMail/relocate_messages_still_exist +++ b/cassandane/tiny-tests/FastMail/relocate_messages_still_exist @@ -2,85 +2,90 @@ use Cassandane::Tiny; sub test_relocate_messages_still_exist - :DelayedDelete :min_version_3_5 :MailboxLegacyDirs - :needs_component_httpd -{ - my ($self) = @_; - - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); - - my $username = "magicuser\@example.com"; - - $admintalk->create("user.$username"); - $admintalk->setacl("user.$username", admin => 'lrswipkxtecdan'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - - xlog $self, "Connect as the new user"; - my $svc = $self->{instance}->get_service('imap'); - $self->{store} = $svc->create_store(username => $username, folder => 'INBOX'); - $self->{store}->set_fetch_attributes('uid'); - my $imaptalk = $self->{store}->get_client(); - - $self->make_message("Email 1") or die; - $self->make_message("Email 2") or die; - $self->make_message("Email xyzzy") or die; - - $imaptalk->create("INBOX.subfolder"); - $imaptalk->create("INBOX.subfolder2"); - - $self->{store}->set_folder("INBOX.subfolder"); - $self->make_message("Email xyzzy") or die; - - $imaptalk->list('', '*', 'return', [ "status", [ "messages", "uidvalidity", "highestmodseq", "mailboxid" ] ]); - my $prestatus = $imaptalk->get_response_code('status'); - - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); - - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); - - $self->{instance}->run_command({ cyrus => 1 }, 'relocate_by_id', '-u' => $username ); - - open(FH, "-|", "find", $basedir); - @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; - close(FH); - - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); - - $imaptalk = $self->{store}->get_client(); - - $imaptalk->select("INBOX"); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - my $exists = $imaptalk->get_response_code('exists'); - $self->assert_num_equals(3, $exists); - my $msgs = $imaptalk->search("fuzzy", ["subject", { Quote => "xyzzy" }]) || die; - $self->assert_num_equals(1, scalar @$msgs); - - $imaptalk->select("INBOX.subfolder"); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $exists = $imaptalk->get_response_code('exists'); - $self->assert_num_equals(1, $exists); - $msgs = $imaptalk->search("fuzzy", ["subject", { Quote => "xyzzy" }]) || die; - $self->assert_num_equals(1, scalar @$msgs); - - $imaptalk->select("INBOX.subfolder2"); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $exists = $imaptalk->get_response_code('exists'); - $self->assert_num_equals(0, $exists); - $msgs = $imaptalk->search("fuzzy", ["subject", { Quote => "xyzzy" }]) || die; - $self->assert_num_equals(0, scalar @$msgs); - - $imaptalk->list('', '*', 'return', [ "status", [ "messages", "uidvalidity", "highestmodseq", "mailboxid" ] ]); - my $poststatus = $imaptalk->get_response_code('status'); - - $self->assert_deep_equals($prestatus, $poststatus); + : DelayedDelete : min_version_3_5 : MailboxLegacyDirs + : needs_component_httpd { + my ($self) = @_; + + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); + + my $username = "magicuser\@example.com"; + + $admintalk->create("user.$username"); + $admintalk->setacl("user.$username", admin => 'lrswipkxtecdan'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + xlog $self, "Connect as the new user"; + my $svc = $self->{instance}->get_service('imap'); + $self->{store} = $svc->create_store(username => $username, folder => 'INBOX'); + $self->{store}->set_fetch_attributes('uid'); + my $imaptalk = $self->{store}->get_client(); + + $self->make_message("Email 1") or die; + $self->make_message("Email 2") or die; + $self->make_message("Email xyzzy") or die; + + $imaptalk->create("INBOX.subfolder"); + $imaptalk->create("INBOX.subfolder2"); + + $self->{store}->set_folder("INBOX.subfolder"); + $self->make_message("Email xyzzy") or die; + + $imaptalk->list('', '*', 'return', + [ "status", [ "messages", "uidvalidity", "highestmodseq", "mailboxid" ] ]); + my $prestatus = $imaptalk->get_response_code('status'); + + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); + + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'relocate_by_id', '-u' => $username); + + open(FH, "-|", "find", $basedir); + @files = grep { m{/magicuser/} and not m{/conf/lock/} } ; + close(FH); + + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); + + $imaptalk = $self->{store}->get_client(); + + $imaptalk->select("INBOX"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + my $exists = $imaptalk->get_response_code('exists'); + $self->assert_num_equals(3, $exists); + my $msgs = $imaptalk->search("fuzzy", [ "subject", { Quote => "xyzzy" } ]) + || die; + $self->assert_num_equals(1, scalar @$msgs); + + $imaptalk->select("INBOX.subfolder"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $exists = $imaptalk->get_response_code('exists'); + $self->assert_num_equals(1, $exists); + $msgs = $imaptalk->search("fuzzy", [ "subject", { Quote => "xyzzy" } ]) + || die; + $self->assert_num_equals(1, scalar @$msgs); + + $imaptalk->select("INBOX.subfolder2"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $exists = $imaptalk->get_response_code('exists'); + $self->assert_num_equals(0, $exists); + $msgs = $imaptalk->search("fuzzy", [ "subject", { Quote => "xyzzy" } ]) + || die; + $self->assert_num_equals(0, scalar @$msgs); + + $imaptalk->list('', '*', 'return', + [ "status", [ "messages", "uidvalidity", "highestmodseq", "mailboxid" ] ]); + my $poststatus = $imaptalk->get_response_code('status'); + + $self->assert_deep_equals($prestatus, $poststatus); } diff --git a/cassandane/tiny-tests/FastMail/rename_deepfolder_intermediates b/cassandane/tiny-tests/FastMail/rename_deepfolder_intermediates index c3cd05ed5b..7a71fbf2ba 100644 --- a/cassandane/tiny-tests/FastMail/rename_deepfolder_intermediates +++ b/cassandane/tiny-tests/FastMail/rename_deepfolder_intermediates @@ -2,137 +2,150 @@ use Cassandane::Tiny; sub test_rename_deepfolder_intermediates - :AllowMoves :Replication :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->setquota('user.cassandane', ['STORAGE', 500000]); - - my $rhttp = $self->{replica}->get_service('http'); - my $rjmap = Mail::JMAPTalk->new( - user => 'cassandane', - password => 'pass', - host => $rhttp->host(), - port => $rhttp->port(), - scheme => 'http', - url => '/jmap/', - ); - - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - - $self->_fmjmap_ok('Calendar/set', - create => { - "1" => { name => "A calendar" }, - }, - ); - - $self->_fmjmap_ok('Contact/set', - create => { - "1" => {firstName => "first", lastName => "last"}, - "2" => {firstName => "second", lastName => "last"}, - }, - ); - - $self->_fmjmap_ok('Mailbox/set', - create => { - "1" => { name => 'Archive', parentId => undef, role => 'archive' }, - "2" => { name => 'Drafts', parentId => undef, role => 'drafts' }, - "3" => { name => 'Junk', parentId => undef, role => 'junk' }, - "4" => { name => 'Sent', parentId => undef, role => 'sent' }, - "5" => { name => 'Trash', parentId => undef, role => 'trash' }, - "6" => { name => 'bar', parentId => undef, role => undef }, - "7" => { name => 'sub', parentId => "#6", role => undef }, - }, - ); - - xlog $self, "Create a folder with intermediates"; - $admintalk->create("user.cassandane.folderA.folderB.folderC"); - - my $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); - my %byname = map { $_->{name} => $_->{id} } @{$data->{list}}; - - xlog $self, "Test replication"; - # replicate and check initial state - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication('cassandane'); - unlink($synclogfname); - - $data = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); - my %byname_repl = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_repl); - - # n.b. run_replication dropped all our store connections... - $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->getsyslog(); - my $res = $admintalk->rename('user.cassandane.folderA', 'user.cassandane.folderZ'); - $self->assert(not $admintalk->get_last_error()); - - xlog $self, "Make sure we didn't create intermediates in the process!"; - my $syslog = join "\n", $self->{instance}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); - my %byname_new = map { $_->{name} => $_->{id} } @{$data->{list}}; - - # we renamed a folder! - $byname{folderZ} = delete $byname{folderA}; - - $self->assert_deep_equals(\%byname, \%byname_new); - - # replicate and check the renames - $self->{replica}->getsyslog(); - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $syslog = join "\n", $self->{replica}->getsyslog(); - - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - # check replication is clean - $self->check_replication('cassandane'); - - $data = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); - my %byname_newrepl = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_newrepl); - - # n.b. run_replication dropped all our store connections... - $admintalk = $self->{adminstore}->get_client(); - $admintalk->delete("user.cassandane"); - - xlog $self, "Make sure we didn't create intermediates in the process!"; - $syslog = join "\n", $self->{instance}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - xlog $self, "Make sure there are no files left with cassandane in the name"; - $self->assert_str_equals(q{}, join(q{ }, glob "$self->{instance}{basedir}/conf/user/c/cassandane.*")); - $self->assert_not_file_test("$self->{instance}{basedir}/data/c/user/cassandane", "-d"); - $self->assert_not_file_test("$self->{instance}{basedir}/conf/quota/c/user.cassandane", "-f"); - - # replicate and check the renames - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $syslog = join "\n", $self->{replica}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - xlog $self, "Make sure there are no files left with cassandane in the name on the replica"; - $self->assert_str_equals(q{}, join(q{ }, glob "$self->{replica}{basedir}/conf/user/c/cassandane.*")); - $self->assert_not_file_test("$self->{replica}{basedir}/data/c/user/cassandane", "-d"); - $self->assert_not_file_test("$self->{replica}{basedir}/conf/quota/c/user.cassandane", "-f"); - - xlog $self, "Now clean up all the deleted mailboxes"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0', '-a' ); + : AllowMoves : Replication : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->setquota('user.cassandane', [ 'STORAGE', 500000 ]); + + my $rhttp = $self->{replica}->get_service('http'); + my $rjmap = Mail::JMAPTalk->new( + user => 'cassandane', + password => 'pass', + host => $rhttp->host(), + port => $rhttp->port(), + scheme => 'http', + url => '/jmap/', + ); + + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + + $self->_fmjmap_ok( + 'Calendar/set', + create => { + "1" => { name => "A calendar" }, + }, + ); + + $self->_fmjmap_ok( + 'Contact/set', + create => { + "1" => { firstName => "first", lastName => "last" }, + "2" => { firstName => "second", lastName => "last" }, + }, + ); + + $self->_fmjmap_ok( + 'Mailbox/set', + create => { + "1" => { name => 'Archive', parentId => undef, role => 'archive' }, + "2" => { name => 'Drafts', parentId => undef, role => 'drafts' }, + "3" => { name => 'Junk', parentId => undef, role => 'junk' }, + "4" => { name => 'Sent', parentId => undef, role => 'sent' }, + "5" => { name => 'Trash', parentId => undef, role => 'trash' }, + "6" => { name => 'bar', parentId => undef, role => undef }, + "7" => { name => 'sub', parentId => "#6", role => undef }, + }, + ); + + xlog $self, "Create a folder with intermediates"; + $admintalk->create("user.cassandane.folderA.folderB.folderC"); + + my $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); + my %byname = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + xlog $self, "Test replication"; + # replicate and check initial state + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication('cassandane'); + unlink($synclogfname); + + $data + = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); + my %byname_repl = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_repl); + + # n.b. run_replication dropped all our store connections... + $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->getsyslog(); + my $res + = $admintalk->rename('user.cassandane.folderA', 'user.cassandane.folderZ'); + $self->assert(not $admintalk->get_last_error()); + + xlog $self, "Make sure we didn't create intermediates in the process!"; + my $syslog = join "\n", $self->{instance}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); + my %byname_new = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + # we renamed a folder! + $byname{folderZ} = delete $byname{folderA}; + + $self->assert_deep_equals(\%byname, \%byname_new); + + # replicate and check the renames + $self->{replica}->getsyslog(); + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $syslog = join "\n", $self->{replica}->getsyslog(); + + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + # check replication is clean + $self->check_replication('cassandane'); + + $data + = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); + my %byname_newrepl = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_newrepl); + + # n.b. run_replication dropped all our store connections... + $admintalk = $self->{adminstore}->get_client(); + $admintalk->delete("user.cassandane"); + + xlog $self, "Make sure we didn't create intermediates in the process!"; + $syslog = join "\n", $self->{instance}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + xlog $self, "Make sure there are no files left with cassandane in the name"; + $self->assert_str_equals(q{}, + join(q{ }, glob "$self->{instance}{basedir}/conf/user/c/cassandane.*")); + $self->assert_not_file_test( + "$self->{instance}{basedir}/data/c/user/cassandane", "-d"); + $self->assert_not_file_test( + "$self->{instance}{basedir}/conf/quota/c/user.cassandane", "-f"); + + # replicate and check the renames + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $syslog = join "\n", $self->{replica}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + xlog $self, + "Make sure there are no files left with cassandane in the name on the replica"; + $self->assert_str_equals(q{}, + join(q{ }, glob "$self->{replica}{basedir}/conf/user/c/cassandane.*")); + $self->assert_not_file_test( + "$self->{replica}{basedir}/data/c/user/cassandane", "-d"); + $self->assert_not_file_test( + "$self->{replica}{basedir}/conf/quota/c/user.cassandane", "-f"); + + xlog $self, "Now clean up all the deleted mailboxes"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0', '-a'); } diff --git a/cassandane/tiny-tests/FastMail/rename_deepfolder_intermediates_rightnow b/cassandane/tiny-tests/FastMail/rename_deepfolder_intermediates_rightnow index 02bfbc84db..6f89e662a2 100644 --- a/cassandane/tiny-tests/FastMail/rename_deepfolder_intermediates_rightnow +++ b/cassandane/tiny-tests/FastMail/rename_deepfolder_intermediates_rightnow @@ -2,132 +2,145 @@ use Cassandane::Tiny; sub test_rename_deepfolder_intermediates_rightnow - :AllowMoves :Replication :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions :RightNow -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - $admintalk->setquota('user.cassandane', ['STORAGE', 500000]); - - my $rhttp = $self->{replica}->get_service('http'); - my $rjmap = Mail::JMAPTalk->new( - user => 'cassandane', - password => 'pass', - host => $rhttp->host(), - port => $rhttp->port(), - scheme => 'http', - url => '/jmap/', - ); - - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - - $self->_fmjmap_ok('Calendar/set', - create => { - "1" => { name => "A calendar" }, - }, - ); - - $self->_fmjmap_ok('Contact/set', - create => { - "1" => {firstName => "first", lastName => "last"}, - "2" => {firstName => "second", lastName => "last"}, - }, - ); - - $self->_fmjmap_ok('Mailbox/set', - create => { - "1" => { name => 'Archive', parentId => undef, role => 'archive' }, - "2" => { name => 'Drafts', parentId => undef, role => 'drafts' }, - "3" => { name => 'Junk', parentId => undef, role => 'junk' }, - "4" => { name => 'Sent', parentId => undef, role => 'sent' }, - "5" => { name => 'Trash', parentId => undef, role => 'trash' }, - "6" => { name => 'bar', parentId => undef, role => undef }, - "7" => { name => 'sub', parentId => "#6", role => undef }, - }, - ); - - xlog $self, "Create a folder with intermediates"; - $admintalk->create("user.cassandane.folderA.folderB.folderC"); - - my $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); - my %byname = map { $_->{name} => $_->{id} } @{$data->{list}}; - - xlog $self, "Test replication"; - # replicate and check initial state - $self->check_replication('cassandane'); - - $data = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); - my %byname_repl = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_repl); - - # n.b. run_replication dropped all our store connections... - $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->getsyslog(); - my $res = $admintalk->rename('user.cassandane.folderA', 'user.cassandane.folderZ'); - $self->assert(not $admintalk->get_last_error()); - - xlog $self, "Make sure we didn't create intermediates in the process!"; - my $syslog = join "\n", $self->{instance}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); - my %byname_new = map { $_->{name} => $_->{id} } @{$data->{list}}; - - # we renamed a folder! - $byname{folderZ} = delete $byname{folderA}; - - $self->assert_deep_equals(\%byname, \%byname_new); - - # replicate and check the renames - $syslog = join "\n", $self->{replica}->getsyslog(); - - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - # check replication is clean - $self->check_replication('cassandane'); - - $data = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); - my %byname_newrepl = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_newrepl); - - # n.b. run_replication dropped all our store connections... - $admintalk = $self->{adminstore}->get_client(); - $admintalk->delete("user.cassandane"); - - xlog $self, "Make sure we didn't create intermediates in the process!"; - $syslog = join "\n", $self->{instance}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - xlog $self, "Make sure there are no files left with cassandane in the name"; - $self->assert_str_equals(q{}, join(q{ }, glob "$self->{instance}{basedir}/conf/user/c/cassandane.*")); - $self->assert_not_file_test("$self->{instance}{basedir}/data/c/user/cassandane", "-d"); - $self->assert_not_file_test("$self->{instance}{basedir}/conf/quota/c/user.cassandane", "-d"); - - # replicate and check the renames - $syslog = join "\n", $self->{replica}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - xlog $self, "Make sure there are no files left with cassandane in the on the replica"; - $self->assert_str_equals(q{}, join(q{ }, glob "$self->{replica}{basedir}/conf/user/c/cassandane.*")); - $self->assert_not_file_test("$self->{replica}{basedir}/data/c/user/cassandane", "-d"); - $self->assert_not_file_test("$self->{replica}{basedir}/conf/quota/c/user.cassandane", "-f"); - - xlog $self, "Now clean up all the deleted mailboxes"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0', '-a' ); + : AllowMoves : Replication : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions : RightNow { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + $admintalk->setquota('user.cassandane', [ 'STORAGE', 500000 ]); + + my $rhttp = $self->{replica}->get_service('http'); + my $rjmap = Mail::JMAPTalk->new( + user => 'cassandane', + password => 'pass', + host => $rhttp->host(), + port => $rhttp->port(), + scheme => 'http', + url => '/jmap/', + ); + + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + + $self->_fmjmap_ok( + 'Calendar/set', + create => { + "1" => { name => "A calendar" }, + }, + ); + + $self->_fmjmap_ok( + 'Contact/set', + create => { + "1" => { firstName => "first", lastName => "last" }, + "2" => { firstName => "second", lastName => "last" }, + }, + ); + + $self->_fmjmap_ok( + 'Mailbox/set', + create => { + "1" => { name => 'Archive', parentId => undef, role => 'archive' }, + "2" => { name => 'Drafts', parentId => undef, role => 'drafts' }, + "3" => { name => 'Junk', parentId => undef, role => 'junk' }, + "4" => { name => 'Sent', parentId => undef, role => 'sent' }, + "5" => { name => 'Trash', parentId => undef, role => 'trash' }, + "6" => { name => 'bar', parentId => undef, role => undef }, + "7" => { name => 'sub', parentId => "#6", role => undef }, + }, + ); + + xlog $self, "Create a folder with intermediates"; + $admintalk->create("user.cassandane.folderA.folderB.folderC"); + + my $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); + my %byname = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + xlog $self, "Test replication"; + # replicate and check initial state + $self->check_replication('cassandane'); + + $data + = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); + my %byname_repl = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_repl); + + # n.b. run_replication dropped all our store connections... + $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->getsyslog(); + my $res + = $admintalk->rename('user.cassandane.folderA', 'user.cassandane.folderZ'); + $self->assert(not $admintalk->get_last_error()); + + xlog $self, "Make sure we didn't create intermediates in the process!"; + my $syslog = join "\n", $self->{instance}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); + my %byname_new = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + # we renamed a folder! + $byname{folderZ} = delete $byname{folderA}; + + $self->assert_deep_equals(\%byname, \%byname_new); + + # replicate and check the renames + $syslog = join "\n", $self->{replica}->getsyslog(); + + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + # check replication is clean + $self->check_replication('cassandane'); + + $data + = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); + my %byname_newrepl = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_newrepl); + + # n.b. run_replication dropped all our store connections... + $admintalk = $self->{adminstore}->get_client(); + $admintalk->delete("user.cassandane"); + + xlog $self, "Make sure we didn't create intermediates in the process!"; + $syslog = join "\n", $self->{instance}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + xlog $self, "Make sure there are no files left with cassandane in the name"; + $self->assert_str_equals(q{}, + join(q{ }, glob "$self->{instance}{basedir}/conf/user/c/cassandane.*")); + $self->assert_not_file_test( + "$self->{instance}{basedir}/data/c/user/cassandane", "-d"); + $self->assert_not_file_test( + "$self->{instance}{basedir}/conf/quota/c/user.cassandane", "-d"); + + # replicate and check the renames + $syslog = join "\n", $self->{replica}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + xlog $self, + "Make sure there are no files left with cassandane in the on the replica"; + $self->assert_str_equals(q{}, + join(q{ }, glob "$self->{replica}{basedir}/conf/user/c/cassandane.*")); + $self->assert_not_file_test( + "$self->{replica}{basedir}/data/c/user/cassandane", "-d"); + $self->assert_not_file_test( + "$self->{replica}{basedir}/conf/quota/c/user.cassandane", "-f"); + + xlog $self, "Now clean up all the deleted mailboxes"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0', '-a'); } diff --git a/cassandane/tiny-tests/FastMail/rename_deepuser_standardfolders b/cassandane/tiny-tests/FastMail/rename_deepuser_standardfolders index c218f589af..3348232ec7 100644 --- a/cassandane/tiny-tests/FastMail/rename_deepuser_standardfolders +++ b/cassandane/tiny-tests/FastMail/rename_deepuser_standardfolders @@ -2,105 +2,109 @@ use Cassandane::Tiny; sub test_rename_deepuser_standardfolders - :AllowMoves :Replication :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my $rhttp = $self->{replica}->get_service('http'); - my $rjmap = Mail::JMAPTalk->new( - user => 'cassandane', - password => 'pass', - host => $rhttp->host(), - port => $rhttp->port(), - scheme => 'http', - url => '/jmap/', - ); - - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - - $self->_fmjmap_ok('Calendar/set', - create => { - "1" => { name => "A calendar" }, - }, - ); - - $self->_fmjmap_ok('Contact/set', - create => { - "1" => {firstName => "first", lastName => "last"}, - "2" => {firstName => "second", lastName => "last"}, - }, - ); - - $self->_fmjmap_ok('Mailbox/set', - create => { - "1" => { name => 'Archive', parentId => undef, role => 'archive' }, - "2" => { name => 'Drafts', parentId => undef, role => 'drafts' }, - "3" => { name => 'Junk', parentId => undef, role => 'junk' }, - "4" => { name => 'Sent', parentId => undef, role => 'sent' }, - "5" => { name => 'Trash', parentId => undef, role => 'trash' }, - "6" => { name => 'bar', parentId => undef, role => undef }, - "7" => { name => 'sub', parentId => "#6", role => undef }, - }, - ); - - xlog $self, "Create a folder with intermediates"; - $admintalk->create("user.cassandane.folderA.folderB.folderC"); - - my $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); - my %byname = map { $_->{name} => $_->{id} } @{$data->{list}}; - - xlog $self, "Test user rename"; - # replicate and check initial state - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication('cassandane'); - unlink($synclogfname); - - $data = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); - my %byname_repl = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_repl); - - # n.b. run_replication dropped all our store connections... - $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->getsyslog(); - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert(not $admintalk->get_last_error()); - - xlog $self, "Make sure we didn't create intermediates in the process!"; - my $syslog = join "\n", $self->{instance}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - $res = $admintalk->select("user.newuser.bar.sub"); - $self->assert(not $admintalk->get_last_error()); - - $self->{jmap}->{user} = 'newuser'; - $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); - my %byname_new = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_new); - - # replicate and check the renames - $self->{replica}->getsyslog(); - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $syslog = join "\n", $self->{replica}->getsyslog(); - - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - # check replication is clean - $self->check_replication('newuser'); - - $rjmap->{user} = 'newuser'; - $data = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); - my %byname_newrepl = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_newrepl); + : AllowMoves : Replication : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my $rhttp = $self->{replica}->get_service('http'); + my $rjmap = Mail::JMAPTalk->new( + user => 'cassandane', + password => 'pass', + host => $rhttp->host(), + port => $rhttp->port(), + scheme => 'http', + url => '/jmap/', + ); + + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + + $self->_fmjmap_ok( + 'Calendar/set', + create => { + "1" => { name => "A calendar" }, + }, + ); + + $self->_fmjmap_ok( + 'Contact/set', + create => { + "1" => { firstName => "first", lastName => "last" }, + "2" => { firstName => "second", lastName => "last" }, + }, + ); + + $self->_fmjmap_ok( + 'Mailbox/set', + create => { + "1" => { name => 'Archive', parentId => undef, role => 'archive' }, + "2" => { name => 'Drafts', parentId => undef, role => 'drafts' }, + "3" => { name => 'Junk', parentId => undef, role => 'junk' }, + "4" => { name => 'Sent', parentId => undef, role => 'sent' }, + "5" => { name => 'Trash', parentId => undef, role => 'trash' }, + "6" => { name => 'bar', parentId => undef, role => undef }, + "7" => { name => 'sub', parentId => "#6", role => undef }, + }, + ); + + xlog $self, "Create a folder with intermediates"; + $admintalk->create("user.cassandane.folderA.folderB.folderC"); + + my $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); + my %byname = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + xlog $self, "Test user rename"; + # replicate and check initial state + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication('cassandane'); + unlink($synclogfname); + + $data + = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); + my %byname_repl = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_repl); + + # n.b. run_replication dropped all our store connections... + $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->getsyslog(); + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert(not $admintalk->get_last_error()); + + xlog $self, "Make sure we didn't create intermediates in the process!"; + my $syslog = join "\n", $self->{instance}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + $res = $admintalk->select("user.newuser.bar.sub"); + $self->assert(not $admintalk->get_last_error()); + + $self->{jmap}->{user} = 'newuser'; + $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); + my %byname_new = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_new); + + # replicate and check the renames + $self->{replica}->getsyslog(); + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $syslog = join "\n", $self->{replica}->getsyslog(); + + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + # check replication is clean + $self->check_replication('newuser'); + + $rjmap->{user} = 'newuser'; + $data + = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); + my %byname_newrepl = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_newrepl); } diff --git a/cassandane/tiny-tests/FastMail/rename_deepuser_standardfolders_rightnow b/cassandane/tiny-tests/FastMail/rename_deepuser_standardfolders_rightnow index 433d2fd5ec..b714963b12 100644 --- a/cassandane/tiny-tests/FastMail/rename_deepuser_standardfolders_rightnow +++ b/cassandane/tiny-tests/FastMail/rename_deepuser_standardfolders_rightnow @@ -2,100 +2,104 @@ use Cassandane::Tiny; sub test_rename_deepuser_standardfolders_rightnow - :AllowMoves :Replication :min_version_3_3 :needs_component_sieve - :needs_component_jmap :JMAPExtensions :RightNow -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my $rhttp = $self->{replica}->get_service('http'); - my $rjmap = Mail::JMAPTalk->new( - user => 'cassandane', - password => 'pass', - host => $rhttp->host(), - port => $rhttp->port(), - scheme => 'http', - url => '/jmap/', - ); - - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - - $self->_fmjmap_ok('Calendar/set', - create => { - "1" => { name => "A calendar" }, - }, - ); - - $self->_fmjmap_ok('Contact/set', - create => { - "1" => {firstName => "first", lastName => "last"}, - "2" => {firstName => "second", lastName => "last"}, - }, - ); - - $self->_fmjmap_ok('Mailbox/set', - create => { - "1" => { name => 'Archive', parentId => undef, role => 'archive' }, - "2" => { name => 'Drafts', parentId => undef, role => 'drafts' }, - "3" => { name => 'Junk', parentId => undef, role => 'junk' }, - "4" => { name => 'Sent', parentId => undef, role => 'sent' }, - "5" => { name => 'Trash', parentId => undef, role => 'trash' }, - "6" => { name => 'bar', parentId => undef, role => undef }, - "7" => { name => 'sub', parentId => "#6", role => undef }, - }, - ); - - xlog $self, "Create a folder with intermediates"; - $admintalk->create("user.cassandane.folderA.folderB.folderC"); - - my $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); - my %byname = map { $_->{name} => $_->{id} } @{$data->{list}}; - - xlog $self, "Test user rename"; - # check initial state (replication has been running rightnow!) - $self->check_replication('cassandane'); - - $data = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); - my %byname_repl = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_repl); - - # n.b. run_replication dropped all our store connections... - $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->getsyslog(); - my $res = $admintalk->rename('user.cassandane', 'user.newuser'); - $self->assert(not $admintalk->get_last_error()); - - xlog $self, "Make sure we didn't create intermediates in the process!"; - my $syslog = join "\n", $self->{instance}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - $res = $admintalk->select("user.newuser.bar.sub"); - $self->assert(not $admintalk->get_last_error()); - - $self->{jmap}->{user} = 'newuser'; - $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); - my %byname_new = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_new); - - # check nothing got logged on the replica - $syslog = join "\n", $self->{replica}->getsyslog(); - $self->assert_does_not_match(qr/creating intermediate with children/, - $syslog); - $self->assert_does_not_match(qr/deleting intermediate with no children/, - $syslog); - - # check replication is clean - $self->check_replication('newuser'); - - $rjmap->{user} = 'newuser'; - $data = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); - my %byname_newrepl = map { $_->{name} => $_->{id} } @{$data->{list}}; - - $self->assert_deep_equals(\%byname, \%byname_newrepl); + : AllowMoves : Replication : min_version_3_3 : needs_component_sieve + : needs_component_jmap : JMAPExtensions : RightNow { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my $rhttp = $self->{replica}->get_service('http'); + my $rjmap = Mail::JMAPTalk->new( + user => 'cassandane', + password => 'pass', + host => $rhttp->host(), + port => $rhttp->port(), + scheme => 'http', + url => '/jmap/', + ); + + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + + $self->_fmjmap_ok( + 'Calendar/set', + create => { + "1" => { name => "A calendar" }, + }, + ); + + $self->_fmjmap_ok( + 'Contact/set', + create => { + "1" => { firstName => "first", lastName => "last" }, + "2" => { firstName => "second", lastName => "last" }, + }, + ); + + $self->_fmjmap_ok( + 'Mailbox/set', + create => { + "1" => { name => 'Archive', parentId => undef, role => 'archive' }, + "2" => { name => 'Drafts', parentId => undef, role => 'drafts' }, + "3" => { name => 'Junk', parentId => undef, role => 'junk' }, + "4" => { name => 'Sent', parentId => undef, role => 'sent' }, + "5" => { name => 'Trash', parentId => undef, role => 'trash' }, + "6" => { name => 'bar', parentId => undef, role => undef }, + "7" => { name => 'sub', parentId => "#6", role => undef }, + }, + ); + + xlog $self, "Create a folder with intermediates"; + $admintalk->create("user.cassandane.folderA.folderB.folderC"); + + my $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); + my %byname = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + xlog $self, "Test user rename"; + # check initial state (replication has been running rightnow!) + $self->check_replication('cassandane'); + + $data + = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); + my %byname_repl = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_repl); + + # n.b. run_replication dropped all our store connections... + $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->getsyslog(); + my $res = $admintalk->rename('user.cassandane', 'user.newuser'); + $self->assert(not $admintalk->get_last_error()); + + xlog $self, "Make sure we didn't create intermediates in the process!"; + my $syslog = join "\n", $self->{instance}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + $res = $admintalk->select("user.newuser.bar.sub"); + $self->assert(not $admintalk->get_last_error()); + + $self->{jmap}->{user} = 'newuser'; + $data = $self->_fmjmap_ok('Mailbox/get', properties => ['name']); + my %byname_new = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_new); + + # check nothing got logged on the replica + $syslog = join "\n", $self->{replica}->getsyslog(); + $self->assert_does_not_match(qr/creating intermediate with children/, + $syslog); + $self->assert_does_not_match(qr/deleting intermediate with no children/, + $syslog); + + # check replication is clean + $self->check_replication('newuser'); + + $rjmap->{user} = 'newuser'; + $data + = $self->_fmjmap_ok('Mailbox/get', jmap => $rjmap, properties => ['name']); + my %byname_newrepl = map { $_->{name} => $_->{id} } @{ $data->{list} }; + + $self->assert_deep_equals(\%byname, \%byname_newrepl); } diff --git a/cassandane/tiny-tests/FastMail/rename_quotaroot b/cassandane/tiny-tests/FastMail/rename_quotaroot index a05ac1df54..e3ca15431f 100644 --- a/cassandane/tiny-tests/FastMail/rename_quotaroot +++ b/cassandane/tiny-tests/FastMail/rename_quotaroot @@ -2,33 +2,33 @@ use Cassandane::Tiny; sub test_rename_quotaroot - :AllowMoves :Replication :min_version_3_2 - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : AllowMoves : Replication : min_version_3_2 + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user.newuser@example.com'); - $admintalk->setacl('user.newuser@example.com', 'admin' => 'lrswipkxtecdan'); - $admintalk->setacl('user.newuser@example.com', 'newuser@example.com' => 'lrswipkxtecdan'); - $admintalk->setquota('user.newuser@example.com', [storage => 3000000]); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user.newuser@example.com'); + $admintalk->setacl('user.newuser@example.com', 'admin' => 'lrswipkxtecdan'); + $admintalk->setacl('user.newuser@example.com', + 'newuser@example.com' => 'lrswipkxtecdan'); + $admintalk->setquota('user.newuser@example.com', [ storage => 3000000 ]); - my $newtalk = $self->{store}->get_client(username => 'newuser@example.com'); - $newtalk->create("INBOX.sub"); - $newtalk->create("INBOX.magic"); + my $newtalk = $self->{store}->get_client(username => 'newuser@example.com'); + $newtalk->create("INBOX.sub"); + $newtalk->create("INBOX.magic"); - $self->{adminstore}->set_folder('user.newuser.magic@example.com'); - $self->make_message("Message foo", store => $self->{adminstore}); + $self->{adminstore}->set_folder('user.newuser.magic@example.com'); + $self->make_message("Message foo", store => $self->{adminstore}); - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication('newuser@example.com'); - unlink($synclogfname); + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication('newuser@example.com'); + unlink($synclogfname); - $admintalk = $self->{adminstore}->get_client(); - $admintalk->rename('user.newuser@example.com', 'user.del@internal'); + $admintalk = $self->{adminstore}->get_client(); + $admintalk->rename('user.newuser@example.com', 'user.del@internal'); - $self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->check_replication('del@internal'); + $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->check_replication('del@internal'); } diff --git a/cassandane/tiny-tests/FastMail/search_deleted_folder b/cassandane/tiny-tests/FastMail/search_deleted_folder index af861efa87..b27bca82e9 100644 --- a/cassandane/tiny-tests/FastMail/search_deleted_folder +++ b/cassandane/tiny-tests/FastMail/search_deleted_folder @@ -2,50 +2,48 @@ use Cassandane::Tiny; sub test_search_deleted_folder - :DelayedDelete :min_version_3_5 :NoMailboxLegacyDirs :needs_component_jmap -{ - my ($self) = @_; - - my $talk = $self->{store}->get_client(); - - my $res = $self->_fmjmap_ok('Mailbox/get'); - my %m = map { $_->{name} => $_ } @{$res->{list}}; - my $inboxid = $m{"Inbox"}{id}; - $self->assert_not_null($inboxid); - - xlog $self, "Create the sub folders and emails"; - $talk->create("INBOX.sub"); - $talk->create("INBOX.extra"); - $self->make_message("Email abcd xyz hello 1") or die; - $self->{store}->set_folder("INBOX.sub"); - $self->make_message("Email abcd xyz hello 2") or die; - $self->{store}->set_folder("INBOX.extra"); - $self->make_message("Email abcd xyz hello 3") or die; - - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - $res = $self->_fmjmap_ok('Email/query', - filter => { text => "abcd", inMailboxOtherThan => [$inboxid] }, - ); - $self->assert_num_equals(2, scalar @{$res->{ids}}); - - xlog $self, "Delete the sub folder"; - $talk->delete("INBOX.sub"); - - xlog $self, "check that email can't be found"; - $res = $self->_fmjmap_ok('Email/query', - filter => { text => "xyz", inMailboxOtherThan => [$inboxid] }, - ); - $self->assert_num_equals(1, scalar @{$res->{ids}}); - - xlog $self, "use cyr_expire to clean up the deleted folder"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0', '-a' ); - - xlog $self, "check that email can't be found after folder deleted"; - $res = $self->_fmjmap_ok('Email/query', - filter => { text => "hello", inMailboxOtherThan => [$inboxid] }, - ); - $self->assert_num_equals(1, scalar @{$res->{ids}}); + : DelayedDelete : min_version_3_5 : NoMailboxLegacyDirs : + needs_component_jmap { + my ($self) = @_; + + my $talk = $self->{store}->get_client(); + + my $res = $self->_fmjmap_ok('Mailbox/get'); + my %m = map { $_->{name} => $_ } @{ $res->{list} }; + my $inboxid = $m{"Inbox"}{id}; + $self->assert_not_null($inboxid); + + xlog $self, "Create the sub folders and emails"; + $talk->create("INBOX.sub"); + $talk->create("INBOX.extra"); + $self->make_message("Email abcd xyz hello 1") or die; + $self->{store}->set_folder("INBOX.sub"); + $self->make_message("Email abcd xyz hello 2") or die; + $self->{store}->set_folder("INBOX.extra"); + $self->make_message("Email abcd xyz hello 3") or die; + + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + $res = $self->_fmjmap_ok('Email/query', + filter => { text => "abcd", inMailboxOtherThan => [$inboxid] },); + $self->assert_num_equals(2, scalar @{ $res->{ids} }); + + xlog $self, "Delete the sub folder"; + $talk->delete("INBOX.sub"); + + xlog $self, "check that email can't be found"; + $res = $self->_fmjmap_ok('Email/query', + filter => { text => "xyz", inMailboxOtherThan => [$inboxid] },); + $self->assert_num_equals(1, scalar @{ $res->{ids} }); + + xlog $self, "use cyr_expire to clean up the deleted folder"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'cyr_expire', '-D' => '0', '-a'); + + xlog $self, "check that email can't be found after folder deleted"; + $res = $self->_fmjmap_ok('Email/query', + filter => { text => "hello", inMailboxOtherThan => [$inboxid] },); + $self->assert_num_equals(1, scalar @{ $res->{ids} }); } diff --git a/cassandane/tiny-tests/FastMail/sync_reset_legacy b/cassandane/tiny-tests/FastMail/sync_reset_legacy index 27b760076c..494617dc1d 100644 --- a/cassandane/tiny-tests/FastMail/sync_reset_legacy +++ b/cassandane/tiny-tests/FastMail/sync_reset_legacy @@ -2,43 +2,43 @@ use Cassandane::Tiny; sub test_sync_reset_legacy - :DelayedDelete :min_version_3_5 :MailboxLegacyDirs - :needs_component_replication -{ - my ($self) = @_; + : DelayedDelete : min_version_3_5 : MailboxLegacyDirs + : needs_component_replication { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser"; - my $subfolder = "$inbox.foo"; + my $inbox = "user.magicuser"; + my $subfolder = "$inbox.foo"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/user/magicuser/} and not m{/conf/lock/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/user/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "files exist"; - $self->assert_not_equals(0, scalar @files); + xlog $self, "files exist"; + $self->assert_not_equals(0, scalar @files); - $self->{instance}->run_command({ cyrus => 1 }, 'sync_reset', '-f' => 'magicuser' ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'sync_reset', '-f' => 'magicuser'); - open(FH, "-|", "find", $basedir); - @files = grep { m{/user/magicuser/} and not m{/conf/lock/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/user/magicuser/} and not m{/conf/lock/} } ; + close(FH); - xlog $self, "no files left for this user"; - $self->assert_equals(0, scalar @files); + xlog $self, "no files left for this user"; + $self->assert_equals(0, scalar @files); } diff --git a/cassandane/tiny-tests/FastMail/sync_reset_nolegacy b/cassandane/tiny-tests/FastMail/sync_reset_nolegacy index bc11f48c66..045cf7186a 100644 --- a/cassandane/tiny-tests/FastMail/sync_reset_nolegacy +++ b/cassandane/tiny-tests/FastMail/sync_reset_nolegacy @@ -2,50 +2,50 @@ use Cassandane::Tiny; sub test_sync_reset_nolegacy - :DelayedDelete :min_version_3_5 :NoMailboxLegacyDirs - :needs_component_replication -{ - my ($self) = @_; + : DelayedDelete : min_version_3_5 : NoMailboxLegacyDirs + : needs_component_replication { + my ($self) = @_; - my $adminstore = $self->{adminstore}; - my $admintalk = $adminstore->get_client(); + my $adminstore = $self->{adminstore}; + my $admintalk = $adminstore->get_client(); - my $inbox = "user.magicuser"; - my $subfolder = "$inbox.foo"; + my $inbox = "user.magicuser"; + my $subfolder = "$inbox.foo"; - $admintalk->create($inbox); - $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); - $admintalk->create($subfolder); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create($inbox); + $admintalk->setacl($inbox, admin => 'lrswipkxtecdan'); + $admintalk->create($subfolder); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $adminstore->set_folder($subfolder); - $self->make_message("Email", store => $adminstore) or die; + $adminstore->set_folder($subfolder); + $self->make_message("Email", store => $adminstore) or die; - # Create the search database. - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + # Create the search database. + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $admintalk->status($inbox, ['mailboxid']); - my $inboxid = $res->{mailboxid}[0]; - $res = $admintalk->status($subfolder, ['mailboxid']); - my $subid = $res->{mailboxid}[0]; + my $res = $admintalk->status($inbox, ['mailboxid']); + my $inboxid = $res->{mailboxid}[0]; + $res = $admintalk->status($subfolder, ['mailboxid']); + my $subid = $res->{mailboxid}[0]; - my $basedir = $self->{instance}{basedir}; - open(FH, "-|", "find", $basedir); - my @files = grep { m{/uuid/} } ; - close(FH); + my $basedir = $self->{instance}{basedir}; + open(FH, "-|", "find", $basedir); + my @files = grep { m{/uuid/} } ; + close(FH); - xlog $self, "files exists"; - $self->assert(scalar grep { m{$inboxid} } @files); - $self->assert(scalar grep { m{$subid} } @files); + xlog $self, "files exists"; + $self->assert(scalar grep { m{$inboxid} } @files); + $self->assert(scalar grep { m{$subid} } @files); - $self->{instance}->run_command({ cyrus => 1 }, 'sync_reset', '-f' => 'magicuser' ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'sync_reset', '-f' => 'magicuser'); - open(FH, "-|", "find", $basedir); - @files = grep { m{/uuid/} } ; - close(FH); + open(FH, "-|", "find", $basedir); + @files = grep { m{/uuid/} } ; + close(FH); - xlog $self, "ensure there's no files left matching either uuid!"; - $self->assert(not scalar grep { m{$inboxid} } @files); - $self->assert(not scalar grep { m{$subid} } @files); + xlog $self, "ensure there's no files left matching either uuid!"; + $self->assert(not scalar grep { m{$inboxid} } @files); + $self->assert(not scalar grep { m{$subid} } @files); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_calendars_all b/cassandane/tiny-tests/JMAPBackup/restore_calendars_all index f44f688a68..85b9c464be 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_calendars_all +++ b/cassandane/tiny-tests/JMAPBackup/restore_calendars_all @@ -2,198 +2,226 @@ use Cassandane::Tiny; sub test_restore_calendars_all - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "create calendars"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - "1" => { - name => "foo", - color => "coral", - sortOrder => 1, - isVisible => \1 - }, - "2" => { - name => "bar", - color => "aqua", - sortOrder => 2, - isVisible => \1 - } - } - }, "R1"] - ]); - my $calid = $res->[0][1]{created}{"1"}{id}; - my $calid2 = $res->[0][1]{created}{"2"}{id}; - - xlog "send invitation as organizer"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - "calendarIds" => { - $calid => JSON::true, - }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT15M", - "replyTo" => { - imip => "mailto:cassandane\@example.com", - }, - "participants" => { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - "att" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:bugs@example.com', - }, - }, - }, - }, - "2" => { - "calendarIds" => { - $calid2 => JSON::true, - }, - "title" => "bar", - "description" => "bar's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2019-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT15M", - "replyTo" => { - imip => "mailto:cassandane\@example.com", - }, - "participants" => { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - "att" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:bugs@example.com', - }, - }, - }, - } - }}, "R1"] - ]); - my $id = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($id); - $self->assert(exists $res->[0][1]{created}{'2'}); - - my $mark = time(); - sleep 2; - - xlog "update an event title and delete a calendar"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $id => { 'title' => "foo2", 'sequence' => 1 }, + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "create calendars"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 1, + isVisible => \1 + }, + "2" => { + name => "bar", + color => "aqua", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ]); + my $calid = $res->[0][1]{created}{"1"}{id}; + my $calid2 = $res->[0][1]{created}{"2"}{id}; + + xlog "send invitation as organizer"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + "calendarIds" => { + $calid => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT15M", + "replyTo" => { + imip => "mailto:cassandane\@example.com", + }, + "participants" => { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, }, - }, 'R2'], - ['Calendar/set', { - destroy => ["$calid2"], - onDestroyRemoveEvents => JSON::true, - }, "R2.5"], - ['CalendarEvent/get', { - properties => ['title', 'sequence'], - }, 'R3'], - ]); - $self->assert(exists $res->[0][1]{updated}{$id}); - $self->assert_str_equals($calid2, $res->[1][1]{destroyed}[0]); - $self->assert_num_equals(1, scalar(@{$res->[2][1]{list}})); - $self->assert_str_equals('foo2', $res->[2][1]{list}[0]{title}); - - # clean notification cache - $self->{instance}->getnotify(); - - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; - - xlog "restore calendars prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreCalendars', { - undoPeriod => $period, - undoAll => JSON::true - }, "R4"], - ['CalendarEvent/get', { - properties => ['title', 'sequence', 'calendarIds'], - }, "R5"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreCalendars', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); - - $self->assert_str_equals('CalendarEvent/get', $res->[1][0]); - $self->assert_str_equals('R5', $res->[1][2]); - $self->assert_num_equals(2, scalar(@{$res->[1][1]{list}})); - - my @got = sort { $a->{title} cmp $b->{title} } @{$res->[1][1]{list}}; - $self->assert_str_equals('bar', $got[0]{title}); - $self->assert_str_equals('foo', $got[1]{title}); - $self->assert_num_equals(2, $got[1]{sequence}); - - xlog "check that the restored calendar has correct name and color"; - $res = $jmap->CallMethods([ - ['Calendar/get', { - ids => [(keys %{$got[0]{calendarIds}})[0]], - properties => ['name', 'color'], - }, "R5.5"] - ]); - $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); - $self->assert_str_equals('aqua', $res->[0][1]{list}[0]{color}); - - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); - - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; - - $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REQUEST"); - - xlog "try to restore calendar to before initial creation"; - $res = $jmap->CallMethods([ - ['Backup/restoreCalendars', { - undoPeriod => "P1D", - undoAll => JSON::true - }, "R6"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); - $self->assert_str_equals('R6', $res->[0][2]); + "att" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:bugs@example.com', + }, + }, + }, + }, + "2" => { + "calendarIds" => { + $calid2 => JSON::true, + }, + "title" => "bar", + "description" => "bar's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2019-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT15M", + "replyTo" => { + imip => "mailto:cassandane\@example.com", + }, + "participants" => { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + "att" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:bugs@example.com', + }, + }, + }, + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($id); + $self->assert(exists $res->[0][1]{created}{'2'}); + + my $mark = time(); + sleep 2; + + xlog "update an event title and delete a calendar"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $id => { 'title' => "foo2", 'sequence' => 1 }, + }, + }, + 'R2' + ], + [ + 'Calendar/set', + { + destroy => ["$calid2"], + onDestroyRemoveEvents => JSON::true, + }, + "R2.5" + ], + [ + 'CalendarEvent/get', + { + properties => [ 'title', 'sequence' ], + }, + 'R3' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + $self->assert_str_equals($calid2, $res->[1][1]{destroyed}[0]); + $self->assert_num_equals(1, scalar(@{ $res->[2][1]{list} })); + $self->assert_str_equals('foo2', $res->[2][1]{list}[0]{title}); + + # clean notification cache + $self->{instance}->getnotify(); + + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; + + xlog "restore calendars prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreCalendars', + { + undoPeriod => $period, + undoAll => JSON::true + }, + "R4" + ], + [ + 'CalendarEvent/get', + { + properties => [ 'title', 'sequence', 'calendarIds' ], + }, + "R5" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreCalendars', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); + + $self->assert_str_equals('CalendarEvent/get', $res->[1][0]); + $self->assert_str_equals('R5', $res->[1][2]); + $self->assert_num_equals(2, scalar(@{ $res->[1][1]{list} })); + + my @got = sort { $a->{title} cmp $b->{title} } @{ $res->[1][1]{list} }; + $self->assert_str_equals('bar', $got[0]{title}); + $self->assert_str_equals('foo', $got[1]{title}); + $self->assert_num_equals(2, $got[1]{sequence}); + + xlog "check that the restored calendar has correct name and color"; + $res = $jmap->CallMethods([ [ + 'Calendar/get', + { + ids => [ (keys %{ $got[0]{calendarIds} })[0] ], + properties => [ 'name', 'color' ], + }, + "R5.5" + ] ]); + $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); + $self->assert_str_equals('aqua', $res->[0][1]{list}[0]{color}); + + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); + + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; + + $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REQUEST"); + + xlog "try to restore calendar to before initial creation"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreCalendars', + { + undoPeriod => "P1D", + undoAll => JSON::true + }, + "R6" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + $self->assert_str_equals('R6', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_calendars_all_dryrun b/cassandane/tiny-tests/JMAPBackup/restore_calendars_all_dryrun index 8642faa04f..b60c24bf19 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_calendars_all_dryrun +++ b/cassandane/tiny-tests/JMAPBackup/restore_calendars_all_dryrun @@ -2,167 +2,191 @@ use Cassandane::Tiny; sub test_restore_calendars_all_dryrun - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "create calendars"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - "1" => { - name => "foo", - color => "coral", - sortOrder => 1, - isVisible => \1 - }, - "2" => { - name => "bar", - color => "aqua", - sortOrder => 2, - isVisible => \1 - } - } - }, "R1"] - ]); - my $calid = $res->[0][1]{created}{"1"}{id}; - my $calid2 = $res->[0][1]{created}{"2"}{id}; + xlog "create calendars"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 1, + isVisible => \1 + }, + "2" => { + name => "bar", + color => "aqua", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ]); + my $calid = $res->[0][1]{created}{"1"}{id}; + my $calid2 = $res->[0][1]{created}{"2"}{id}; - xlog "send invitation as organizer"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - "calendarIds" => { - $calid => JSON::true, - }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT15M", - "replyTo" => { - imip => "mailto:cassandane\@example.com", - }, - "participants" => { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - "att" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:bugs@example.com', - }, - }, - }, - }, - "2" => { - "calendarIds" => { - $calid2 => JSON::true, - }, - "title" => "bar", - "description" => "bar's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2019-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT15M", - "replyTo" => { - imip => "mailto:cassandane\@example.com", - }, - "participants" => { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - "att" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:bugs@example.com', - }, - }, - }, - } - }}, "R1"] - ]); - my $id = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($id); - $self->assert(exists $res->[0][1]{created}{'2'}); + xlog "send invitation as organizer"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + "calendarIds" => { + $calid => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT15M", + "replyTo" => { + imip => "mailto:cassandane\@example.com", + }, + "participants" => { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + "att" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:bugs@example.com', + }, + }, + }, + }, + "2" => { + "calendarIds" => { + $calid2 => JSON::true, + }, + "title" => "bar", + "description" => "bar's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2019-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT15M", + "replyTo" => { + imip => "mailto:cassandane\@example.com", + }, + "participants" => { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + "att" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:bugs@example.com', + }, + }, + }, + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($id); + $self->assert(exists $res->[0][1]{created}{'2'}); - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "update an event title and delete a calendar"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $id => { 'title' => "foo2", 'sequence' => 1 }, - }, - }, 'R2'], - ['Calendar/set', { - destroy => ["$calid2"], - onDestroyRemoveEvents => JSON::true, - }, "R2.5"], - ['CalendarEvent/get', { - properties => ['title', 'sequence'], - }, 'R3'], - ]); - $self->assert(exists $res->[0][1]{updated}{$id}); - $self->assert_str_equals($calid2, $res->[1][1]{destroyed}[0]); - $self->assert_num_equals(1, scalar(@{$res->[2][1]{list}})); - $self->assert_str_equals('foo2', $res->[2][1]{list}[0]{title}); + xlog "update an event title and delete a calendar"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $id => { 'title' => "foo2", 'sequence' => 1 }, + }, + }, + 'R2' + ], + [ + 'Calendar/set', + { + destroy => ["$calid2"], + onDestroyRemoveEvents => JSON::true, + }, + "R2.5" + ], + [ + 'CalendarEvent/get', + { + properties => [ 'title', 'sequence' ], + }, + 'R3' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + $self->assert_str_equals($calid2, $res->[1][1]{destroyed}[0]); + $self->assert_num_equals(1, scalar(@{ $res->[2][1]{list} })); + $self->assert_str_equals('foo2', $res->[2][1]{list}[0]{title}); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; - xlog "restore calendars prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreCalendars', { - performDryRun => JSON::true, - undoPeriod => $period, - undoAll => JSON::true - }, "R4"], - ['CalendarEvent/get', { - properties => ['title', 'sequence'], - }, "R5"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreCalendars', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); + xlog "restore calendars prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreCalendars', + { + performDryRun => JSON::true, + undoPeriod => $period, + undoAll => JSON::true + }, + "R4" + ], + [ + 'CalendarEvent/get', + { + properties => [ 'title', 'sequence' ], + }, + "R5" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreCalendars', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); - $self->assert_str_equals('CalendarEvent/get', $res->[1][0]); - $self->assert_str_equals('R5', $res->[1][2]); - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals('foo2', $res->[1][1]{list}[0]{title}); + $self->assert_str_equals('CalendarEvent/get', $res->[1][0]); + $self->assert_str_equals('R5', $res->[1][2]); + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals('foo2', $res->[1][1]{list}[0]{title}); - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($imip); + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_null($imip); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_calendars_batch_size_bug1 b/cassandane/tiny-tests/JMAPBackup/restore_calendars_batch_size_bug1 index f35aafff27..b966320268 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_calendars_batch_size_bug1 +++ b/cassandane/tiny-tests/JMAPBackup/restore_calendars_batch_size_bug1 @@ -2,94 +2,99 @@ use Cassandane::Tiny; sub test_restore_calendars_batch_size_bug1 - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - "1" => { - name => "foo" - } - } - }, "R1"] - ]); - my $calid = $res->[0][1]{created}{"1"}{id}; - - xlog "create a bunch of events"; - # two more than current Cyrus batch size (512) - my %events = (); - foreach my $n (1..514) { - $events{"$n"} = { - "calendarIds" => { - $calid => JSON::true, - }, - "title" => "foo", - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT15M" + xlog "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo" } - } + } + }, + "R1" + ] ]); + my $calid = $res->[0][1]{created}{"1"}{id}; + + xlog "create a bunch of events"; + # two more than current Cyrus batch size (512) + my %events = (); + foreach my $n (1 .. 514) { + $events{"$n"} = { + "calendarIds" => { + $calid => JSON::true, + }, + "title" => "foo", + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT15M" + }; + } - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - %events - }}, "R1"] - ]); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => {%events} + }, + "R1" + ] ]); - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "fetch the id of event 513"; - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'] - ]); - my $id513 = $res->[0][1]{ids}[512]; - $self->assert_not_null($id513); + xlog "fetch the id of event 513"; + $res = $jmap->CallMethods([ [ 'CalendarEvent/query', {}, 'R1' ] ]); + my $id513 = $res->[0][1]{ids}[512]; + $self->assert_not_null($id513); - xlog "delete event 513"; - # leaving first 512 events for batch 1 and #514 for batch 2 - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [$id513] - }, 'R2'] - ]); - $self->assert_str_equals($id513, $res->[0][1]{destroyed}[0]); + xlog "delete event 513"; + # leaving first 512 events for batch 1 and #514 for batch 2 + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + destroy => [$id513] + }, + 'R2' + ] ]); + $self->assert_str_equals($id513, $res->[0][1]{destroyed}[0]); - xlog $self, "expire 513 from disk"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '0d' ); + xlog $self, "expire 513 from disk"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '0d'); - xlog "delete calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - destroy => ["$calid"], - onDestroyRemoveEvents => JSON::true, - }, "R2."] - ]); - $self->assert_str_equals($calid, $res->[0][1]{destroyed}[0]); + xlog "delete calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + destroy => ["$calid"], + onDestroyRemoveEvents => JSON::true, + }, + "R2." + ] ]); + $self->assert_str_equals($calid, $res->[0][1]{destroyed}[0]); - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; - xlog "restore calendar"; - $res = $jmap->CallMethods([ - ['Backup/restoreCalendars', { - performDryRun => JSON::true, - undoPeriod => $period, - undoAll => JSON::true - }, "R4"] - ]); + xlog "restore calendar"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreCalendars', + { + performDryRun => JSON::true, + undoPeriod => $period, + undoAll => JSON::true + }, + "R4" + ] ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreCalendars', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(513, $res->[0][1]{numDestroysUndone}); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreCalendars', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(513, $res->[0][1]{numDestroysUndone}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_calendars_batch_size_bug2 b/cassandane/tiny-tests/JMAPBackup/restore_calendars_batch_size_bug2 index eed7849d78..20ad9fedbd 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_calendars_batch_size_bug2 +++ b/cassandane/tiny-tests/JMAPBackup/restore_calendars_batch_size_bug2 @@ -2,94 +2,99 @@ use Cassandane::Tiny; sub test_restore_calendars_batch_size_bug2 - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - "1" => { - name => "foo" - } - } - }, "R1"] - ]); - my $calid = $res->[0][1]{created}{"1"}{id}; - - xlog "create a bunch of events"; - # one more than current Cyrus batch size (512) - my %events = (); - foreach my $n (1..513) { - $events{"$n"} = { - "calendarIds" => { - $calid => JSON::true, - }, - "title" => "foo", - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT15M" + xlog "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo" } - } + } + }, + "R1" + ] ]); + my $calid = $res->[0][1]{created}{"1"}{id}; + + xlog "create a bunch of events"; + # one more than current Cyrus batch size (512) + my %events = (); + foreach my $n (1 .. 513) { + $events{"$n"} = { + "calendarIds" => { + $calid => JSON::true, + }, + "title" => "foo", + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT15M" + }; + } - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - %events - }}, "R1"] - ]); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => {%events} + }, + "R1" + ] ]); - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "fetch the id of event 513"; - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'] - ]); - my $id513 = $res->[0][1]{ids}[512]; - $self->assert_not_null($id513); + xlog "fetch the id of event 513"; + $res = $jmap->CallMethods([ [ 'CalendarEvent/query', {}, 'R1' ] ]); + my $id513 = $res->[0][1]{ids}[512]; + $self->assert_not_null($id513); - xlog "delete event 513"; - # leaving first 512 events for batch 1 and none for batch 2 - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [$id513] - }, 'R2'] - ]); - $self->assert_str_equals($id513, $res->[0][1]{destroyed}[0]); + xlog "delete event 513"; + # leaving first 512 events for batch 1 and none for batch 2 + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + destroy => [$id513] + }, + 'R2' + ] ]); + $self->assert_str_equals($id513, $res->[0][1]{destroyed}[0]); - xlog $self, "expire 513 from disk"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '0d' ); + xlog $self, "expire 513 from disk"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-X' => '0d'); - xlog "delete calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - destroy => ["$calid"], - onDestroyRemoveEvents => JSON::true, - }, "R2."] - ]); - $self->assert_str_equals($calid, $res->[0][1]{destroyed}[0]); + xlog "delete calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + destroy => ["$calid"], + onDestroyRemoveEvents => JSON::true, + }, + "R2." + ] ]); + $self->assert_str_equals($calid, $res->[0][1]{destroyed}[0]); - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; - xlog "restore calendar"; - $res = $jmap->CallMethods([ - ['Backup/restoreCalendars', { - performDryRun => JSON::true, - undoPeriod => $period, - undoAll => JSON::true - }, "R4"] - ]); + xlog "restore calendar"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreCalendars', + { + performDryRun => JSON::true, + undoPeriod => $period, + undoAll => JSON::true + }, + "R4" + ] ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreCalendars', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(512, $res->[0][1]{numDestroysUndone}); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreCalendars', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(512, $res->[0][1]{numDestroysUndone}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_contacts b/cassandane/tiny-tests/JMAPBackup/restore_contacts index 4864e1e88c..91c662a6c4 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_contacts +++ b/cassandane/tiny-tests/JMAPBackup/restore_contacts @@ -2,160 +2,202 @@ use Cassandane::Tiny; sub test_restore_contacts - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', {create => { - "a" => {firstName => "a", lastName => "a"}, - "b" => {firstName => "b", lastName => "b"}, - "c" => {firstName => "c", lastName => "c"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $contactA = $res->[0][1]{created}{"a"}{id}; - my $contactB = $res->[0][1]{created}{"b"}{id}; - my $contactC = $res->[0][1]{created}{"c"}{id}; - - xlog "destroy contact C"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$contactC] - }, "R1.5"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1.5', $res->[0][2]); - - xlog "dry-run restore contacts prior to most recent changes"; - $res = $jmap->CallMethods([['Backup/restoreContacts', { - undoPeriod => "P1D", - performDryRun => JSON::true, - undoAll => JSON::false - }, "R1.7"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); - $self->assert_str_equals('R1.7', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); - - my $mark = time(); - sleep 2; - - xlog "destroy contact A, update contact B, create contact D"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$contactA], - update => {$contactB => {firstName => "B"}}, - create => {"d" => {firstName => "d", lastName => "d"}} - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - my $contactD = $res->[0][1]{created}{"d"}{id}; - - xlog "destroy contact D, create contact E"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$contactD], - create => { - "e" => {firstName => "e", lastName => "e"} - } - }, "R4"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - my $contactE = $res->[0][1]{created}{"e"}{id}; - my $state = $res->[0][1]{newState}; - - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; - - xlog "restore contacts prior to most recent changes"; - $res = $jmap->CallMethods([['Backup/restoreContacts', { - undoPeriod => $period, - undoAll => JSON::false - }, "R5"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); - $self->assert_str_equals('R5', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(2, $res->[0][1]{numDestroysUndone}); - - xlog "get restored contacts"; - $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['firstName', 'lastName'], - }, "R6"], - ['ContactGroup/get', {}, "R6.1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/get', $res->[0][0]); - $self->assert_str_equals('R6', $res->[0][2]); - - my @got = sort { $a->{firstName} cmp $b->{firstName} } @{$res->[0][1]{list}}; - $self->assert_num_equals(4, scalar @got); - $self->assert_str_equals('B', $got[0]{firstName}); - $self->assert_str_equals('a', $got[1]{firstName}); - $self->assert_str_equals('d', $got[2]{firstName}); - $self->assert_str_equals('e', $got[3]{firstName}); - - $self->assert_str_equals('ContactGroup/get', $res->[1][0]); - $self->assert_str_equals('R6.1', $res->[1][2]); - $self->assert_num_equals(2, scalar @{$res->[1][1]{list}[0]{contactIds}}); - - my %contactIds = map { $_ => 1 } @{$res->[1][1]{list}[0]{contactIds}}; - $self->assert_not_null($contactIds{$contactA}); - $self->assert_not_null($contactIds{$contactD}); - - xlog "get contact updates"; - $res = $jmap->CallMethods([ - ['Contact/changes', { - sinceState => $state - }, "R6.5"], - ['ContactGroup/changes', { - sinceState => $state - }, "R6.6"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/changes', $res->[0][0]); - $self->assert_str_equals('R6.5', $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - - %contactIds = map { $_ => 1 } @{$res->[0][1]{created}}; - $self->assert_not_null($contactIds{$contactA}); - $self->assert_not_null($contactIds{$contactD}); - - $self->assert_str_equals('ContactGroup/changes', $res->[1][0]); - $self->assert_str_equals('R6.6', $res->[1][2]); - $self->assert_str_equals($state, $res->[1][1]{oldState}); - $self->assert_str_not_equals($state, $res->[1][1]{newState}); - $self->assert_equals(JSON::false, $res->[1][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{destroyed}}); - $state = $res->[1][1]{newState}; - - $diff = time() - $mark; - $period = "PT" . $diff . "S"; - - xlog "try to re-restore contacts prior to most recent changes"; - $res = $jmap->CallMethods([['Backup/restoreContacts', { - undoPeriod => $period, - performDryRun => JSON::true, - undoAll => JSON::false - }, "R7"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); - $self->assert_str_equals('R7', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numDestroysUndone}); + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "a" => { firstName => "a", lastName => "a" }, + "b" => { firstName => "b", lastName => "b" }, + "c" => { firstName => "c", lastName => "c" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $contactA = $res->[0][1]{created}{"a"}{id}; + my $contactB = $res->[0][1]{created}{"b"}{id}; + my $contactC = $res->[0][1]{created}{"c"}{id}; + + xlog "destroy contact C"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$contactC] + }, + "R1.5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1.5', $res->[0][2]); + + xlog "dry-run restore contacts prior to most recent changes"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreContacts', + { + undoPeriod => "P1D", + performDryRun => JSON::true, + undoAll => JSON::false + }, + "R1.7" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); + $self->assert_str_equals('R1.7', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); + + my $mark = time(); + sleep 2; + + xlog "destroy contact A, update contact B, create contact D"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$contactA], + update => { $contactB => { firstName => "B" } }, + create => { "d" => { firstName => "d", lastName => "d" } } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + my $contactD = $res->[0][1]{created}{"d"}{id}; + + xlog "destroy contact D, create contact E"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$contactD], + create => { + "e" => { firstName => "e", lastName => "e" } + } + }, + "R4" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + my $contactE = $res->[0][1]{created}{"e"}{id}; + my $state = $res->[0][1]{newState}; + + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; + + xlog "restore contacts prior to most recent changes"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreContacts', + { + undoPeriod => $period, + undoAll => JSON::false + }, + "R5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); + $self->assert_str_equals('R5', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(2, $res->[0][1]{numDestroysUndone}); + + xlog "get restored contacts"; + $res = $jmap->CallMethods([ + [ + 'Contact/get', + { + properties => [ 'firstName', 'lastName' ], + }, + "R6" + ], + [ 'ContactGroup/get', {}, "R6.1" ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/get', $res->[0][0]); + $self->assert_str_equals('R6', $res->[0][2]); + + my @got + = sort { $a->{firstName} cmp $b->{firstName} } @{ $res->[0][1]{list} }; + $self->assert_num_equals(4, scalar @got); + $self->assert_str_equals('B', $got[0]{firstName}); + $self->assert_str_equals('a', $got[1]{firstName}); + $self->assert_str_equals('d', $got[2]{firstName}); + $self->assert_str_equals('e', $got[3]{firstName}); + + $self->assert_str_equals('ContactGroup/get', $res->[1][0]); + $self->assert_str_equals('R6.1', $res->[1][2]); + $self->assert_num_equals(2, scalar @{ $res->[1][1]{list}[0]{contactIds} }); + + my %contactIds = map { $_ => 1 } @{ $res->[1][1]{list}[0]{contactIds} }; + $self->assert_not_null($contactIds{$contactA}); + $self->assert_not_null($contactIds{$contactD}); + + xlog "get contact updates"; + $res = $jmap->CallMethods([ + [ + 'Contact/changes', + { + sinceState => $state + }, + "R6.5" + ], + [ + 'ContactGroup/changes', + { + sinceState => $state + }, + "R6.6" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/changes', $res->[0][0]); + $self->assert_str_equals('R6.5', $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + + %contactIds = map { $_ => 1 } @{ $res->[0][1]{created} }; + $self->assert_not_null($contactIds{$contactA}); + $self->assert_not_null($contactIds{$contactD}); + + $self->assert_str_equals('ContactGroup/changes', $res->[1][0]); + $self->assert_str_equals('R6.6', $res->[1][2]); + $self->assert_str_equals($state, $res->[1][1]{oldState}); + $self->assert_str_not_equals($state, $res->[1][1]{newState}); + $self->assert_equals(JSON::false, $res->[1][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{destroyed} }); + $state = $res->[1][1]{newState}; + + $diff = time() - $mark; + $period = "PT" . $diff . "S"; + + xlog "try to re-restore contacts prior to most recent changes"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreContacts', + { + undoPeriod => $period, + performDryRun => JSON::true, + undoAll => JSON::false + }, + "R7" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); + $self->assert_str_equals('R7', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numDestroysUndone}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_contacts_all b/cassandane/tiny-tests/JMAPBackup/restore_contacts_all index 95694a2d36..0cba1be0ee 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_contacts_all +++ b/cassandane/tiny-tests/JMAPBackup/restore_contacts_all @@ -2,192 +2,242 @@ use Cassandane::Tiny; sub test_restore_contacts_all - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - my $start = time(); - sleep 2; - - xlog "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', {create => { - "a" => {firstName => "a", lastName => "a"}, - "b" => {firstName => "b", lastName => "b"}, - "c" => {firstName => "c", lastName => "c"}, - "d" => {firstName => "d", lastName => "d"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $contactA = $res->[0][1]{created}{"a"}{id}; - my $contactB = $res->[0][1]{created}{"b"}{id}; - my $contactC = $res->[0][1]{created}{"c"}{id}; - my $contactD = $res->[0][1]{created}{"d"}{id}; - - xlog "destroy contact A, update contact B"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$contactA], - update => {$contactB => {firstName => "B"}} - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - - xlog "get contacts"; - $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['firstName', 'lastName'], - }, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/get', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - - my @expect = sort { $a->{firstName} cmp $b->{firstName} } @{$res->[0][1]{list}}; - - my $mark = time(); - sleep 2; - - xlog "destroy contact C, update contacts B and D, create contact E"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$contactC], - update => { - $contactB => {lastName => "B"}, - $contactD => {lastName => "D"}, - }, - create => { - "e" => {firstName => "e", lastName => "e"} - } - }, "R4"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - my $contactE = $res->[0][1]{created}{"e"}{id}; - my $state = $res->[0][1]{newState}; - - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; - - xlog "restore contacts prior to most recent changes"; - $res = $jmap->CallMethods([['Backup/restoreContacts', { - undoPeriod => $period, - undoAll => JSON::true - }, "R5"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); - $self->assert_str_equals('R5', $res->[0][2]); - $self->assert_num_equals(1, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(2, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); - - xlog "get restored contacts"; - $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['firstName', 'lastName'], - }, "R6"], - ['ContactGroup/get', {}, "R6.1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/get', $res->[0][0]); - $self->assert_str_equals('R6', $res->[0][2]); - - my @got = sort { $a->{firstName} cmp $b->{firstName} } @{$res->[0][1]{list}}; - $self->assert_num_equals(scalar @expect, scalar @got); - $self->assert_deep_equals(\@expect, \@got); - - $self->assert_str_equals('ContactGroup/get', $res->[1][0]); - $self->assert_str_equals('R6.1', $res->[1][2]); - $self->assert_str_equals($contactC, $res->[1][1]{list}[0]{contactIds}[0]); - - xlog "get contact updates"; - $res = $jmap->CallMethods([ - ['Contact/changes', { - sinceState => $state - }, "R6.5"], - ['ContactGroup/changes', { - sinceState => $state - }, "R6.6"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/changes', $res->[0][0]); - $self->assert_str_equals('R6.5', $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals($contactC, $res->[0][1]{created}[0]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($contactE, $res->[0][1]{destroyed}[0]); - - $self->assert_str_equals('ContactGroup/changes', $res->[1][0]); - $self->assert_str_equals('R6.6', $res->[1][2]); - $self->assert_str_equals($state, $res->[1][1]{oldState}); - $self->assert_str_not_equals($state, $res->[1][1]{newState}); - $self->assert_equals(JSON::false, $res->[1][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{destroyed}}); - $state = $res->[1][1]{newState}; - - $diff = time() - $start; - $period = "PT" . $diff . "S"; - - xlog "restore contacts to before initial creation"; - $res = $jmap->CallMethods([['Backup/restoreContacts', { - undoPeriod => $period, - undoAll => JSON::true - }, "R7"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); - $self->assert_str_equals('R7', $res->[0][2]); - $self->assert_num_equals(4, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numDestroysUndone}); - - xlog "get restored contacts"; - $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['firstName', 'lastName'], - }, "R8"], - ['ContactGroup/get', {}, "R8.1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/get', $res->[0][0]); - $self->assert_str_equals('R8', $res->[0][2]); - $self->assert_deep_equals([], $res->[0][1]{list}); - - $self->assert_str_equals('ContactGroup/get', $res->[1][0]); - $self->assert_str_equals('R8.1', $res->[1][2]); - $self->assert_deep_equals([], $res->[1][1]{list}); - - xlog "get contact updates"; - $res = $jmap->CallMethods([ - ['Contact/changes', { - sinceState => $state - }, "R8.5"], - ['ContactGroup/changes', { - sinceState => $state - }, "R8.6"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/changes', $res->[0][0]); - $self->assert_str_equals('R8.5', $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(3, scalar @{$res->[0][1]{destroyed}}); - - $self->assert_str_equals('ContactGroup/changes', $res->[1][0]); - $self->assert_str_equals('R8.6', $res->[1][2]); - $self->assert_str_equals($state, $res->[1][1]{oldState}); - $self->assert_str_not_equals($state, $res->[1][1]{newState}); - $self->assert_equals(JSON::false, $res->[1][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{updated}}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{destroyed}}); - $state = $res->[1][1]{newState}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + my $start = time(); + sleep 2; + + xlog "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "a" => { firstName => "a", lastName => "a" }, + "b" => { firstName => "b", lastName => "b" }, + "c" => { firstName => "c", lastName => "c" }, + "d" => { firstName => "d", lastName => "d" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $contactA = $res->[0][1]{created}{"a"}{id}; + my $contactB = $res->[0][1]{created}{"b"}{id}; + my $contactC = $res->[0][1]{created}{"c"}{id}; + my $contactD = $res->[0][1]{created}{"d"}{id}; + + xlog "destroy contact A, update contact B"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$contactA], + update => { $contactB => { firstName => "B" } } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + + xlog "get contacts"; + $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => [ 'firstName', 'lastName' ], + }, + "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/get', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + + my @expect + = sort { $a->{firstName} cmp $b->{firstName} } @{ $res->[0][1]{list} }; + + my $mark = time(); + sleep 2; + + xlog "destroy contact C, update contacts B and D, create contact E"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$contactC], + update => { + $contactB => { lastName => "B" }, + $contactD => { lastName => "D" }, + }, + create => { + "e" => { firstName => "e", lastName => "e" } + } + }, + "R4" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + my $contactE = $res->[0][1]{created}{"e"}{id}; + my $state = $res->[0][1]{newState}; + + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; + + xlog "restore contacts prior to most recent changes"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreContacts', + { + undoPeriod => $period, + undoAll => JSON::true + }, + "R5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); + $self->assert_str_equals('R5', $res->[0][2]); + $self->assert_num_equals(1, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(2, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); + + xlog "get restored contacts"; + $res = $jmap->CallMethods([ + [ + 'Contact/get', + { + properties => [ 'firstName', 'lastName' ], + }, + "R6" + ], + [ 'ContactGroup/get', {}, "R6.1" ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/get', $res->[0][0]); + $self->assert_str_equals('R6', $res->[0][2]); + + my @got + = sort { $a->{firstName} cmp $b->{firstName} } @{ $res->[0][1]{list} }; + $self->assert_num_equals(scalar @expect, scalar @got); + $self->assert_deep_equals(\@expect, \@got); + + $self->assert_str_equals('ContactGroup/get', $res->[1][0]); + $self->assert_str_equals('R6.1', $res->[1][2]); + $self->assert_str_equals($contactC, $res->[1][1]{list}[0]{contactIds}[0]); + + xlog "get contact updates"; + $res = $jmap->CallMethods([ + [ + 'Contact/changes', + { + sinceState => $state + }, + "R6.5" + ], + [ + 'ContactGroup/changes', + { + sinceState => $state + }, + "R6.6" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/changes', $res->[0][0]); + $self->assert_str_equals('R6.5', $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals($contactC, $res->[0][1]{created}[0]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($contactE, $res->[0][1]{destroyed}[0]); + + $self->assert_str_equals('ContactGroup/changes', $res->[1][0]); + $self->assert_str_equals('R6.6', $res->[1][2]); + $self->assert_str_equals($state, $res->[1][1]{oldState}); + $self->assert_str_not_equals($state, $res->[1][1]{newState}); + $self->assert_equals(JSON::false, $res->[1][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{destroyed} }); + $state = $res->[1][1]{newState}; + + $diff = time() - $start; + $period = "PT" . $diff . "S"; + + xlog "restore contacts to before initial creation"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreContacts', + { + undoPeriod => $period, + undoAll => JSON::true + }, + "R7" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); + $self->assert_str_equals('R7', $res->[0][2]); + $self->assert_num_equals(4, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numDestroysUndone}); + + xlog "get restored contacts"; + $res = $jmap->CallMethods([ + [ + 'Contact/get', + { + properties => [ 'firstName', 'lastName' ], + }, + "R8" + ], + [ 'ContactGroup/get', {}, "R8.1" ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/get', $res->[0][0]); + $self->assert_str_equals('R8', $res->[0][2]); + $self->assert_deep_equals([], $res->[0][1]{list}); + + $self->assert_str_equals('ContactGroup/get', $res->[1][0]); + $self->assert_str_equals('R8.1', $res->[1][2]); + $self->assert_deep_equals([], $res->[1][1]{list}); + + xlog "get contact updates"; + $res = $jmap->CallMethods([ + [ + 'Contact/changes', + { + sinceState => $state + }, + "R8.5" + ], + [ + 'ContactGroup/changes', + { + sinceState => $state + }, + "R8.6" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/changes', $res->[0][0]); + $self->assert_str_equals('R8.5', $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{destroyed} }); + + $self->assert_str_equals('ContactGroup/changes', $res->[1][0]); + $self->assert_str_equals('R8.6', $res->[1][2]); + $self->assert_str_equals($state, $res->[1][1]{oldState}); + $self->assert_str_not_equals($state, $res->[1][1]{newState}); + $self->assert_equals(JSON::false, $res->[1][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{updated} }); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{destroyed} }); + $state = $res->[1][1]{newState}; } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_contacts_all_dryrun b/cassandane/tiny-tests/JMAPBackup/restore_contacts_all_dryrun index d60a03049f..b60ca8148d 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_contacts_all_dryrun +++ b/cassandane/tiny-tests/JMAPBackup/restore_contacts_all_dryrun @@ -2,108 +2,137 @@ use Cassandane::Tiny; sub test_restore_contacts_all_dryrun - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', {create => { - "a" => {firstName => "a", lastName => "a"}, - "b" => {firstName => "b", lastName => "b"}, - "c" => {firstName => "c", lastName => "c"}, - "d" => {firstName => "d", lastName => "d"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $contactA = $res->[0][1]{created}{"a"}{id}; - my $contactB = $res->[0][1]{created}{"b"}{id}; - my $contactC = $res->[0][1]{created}{"c"}{id}; - my $contactD = $res->[0][1]{created}{"d"}{id}; + xlog "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "a" => { firstName => "a", lastName => "a" }, + "b" => { firstName => "b", lastName => "b" }, + "c" => { firstName => "c", lastName => "c" }, + "d" => { firstName => "d", lastName => "d" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $contactA = $res->[0][1]{created}{"a"}{id}; + my $contactB = $res->[0][1]{created}{"b"}{id}; + my $contactC = $res->[0][1]{created}{"c"}{id}; + my $contactD = $res->[0][1]{created}{"d"}{id}; - xlog "destroy contact A, update contact B"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$contactA], - update => {$contactB => {firstName => "B"}} - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); + xlog "destroy contact A, update contact B"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$contactA], + update => { $contactB => { firstName => "B" } } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); - xlog "get contacts"; - $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['firstName', 'lastName'], - }, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/get', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); + xlog "get contacts"; + $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => [ 'firstName', 'lastName' ], + }, + "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/get', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); - my @expect = sort { $a->{firstName} cmp $b->{firstName} } @{$res->[0][1]{list}}; + my @expect + = sort { $a->{firstName} cmp $b->{firstName} } @{ $res->[0][1]{list} }; - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "destroy contact C, update contacts B and D, create contact E"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$contactC], - update => { - $contactB => {lastName => "B"}, - $contactD => {lastName => "D"}, - }, - create => { - "e" => {firstName => "e", lastName => "e"} - } - }, "R4"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - my $contactE = $res->[0][1]{created}{"e"}{id}; - my $state = $res->[0][1]{newState}; + xlog "destroy contact C, update contacts B and D, create contact E"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$contactC], + update => { + $contactB => { lastName => "B" }, + $contactD => { lastName => "D" }, + }, + create => { + "e" => { firstName => "e", lastName => "e" } + } + }, + "R4" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + my $contactE = $res->[0][1]{created}{"e"}{id}; + my $state = $res->[0][1]{newState}; - $diff = time() - $mark; - $period = "PT" . $diff . "S"; + $diff = time() - $mark; + $period = "PT" . $diff . "S"; - xlog "restore contacts prior to most recent changes"; - $res = $jmap->CallMethods([['Backup/restoreContacts', { - performDryRun => JSON::true, - undoPeriod => $period, - undoAll => JSON::true - }, "R5"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); - $self->assert_str_equals('R5', $res->[0][2]); - $self->assert_num_equals(1, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(2, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); + xlog "restore contacts prior to most recent changes"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreContacts', + { + performDryRun => JSON::true, + undoPeriod => $period, + undoAll => JSON::true + }, + "R5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreContacts', $res->[0][0]); + $self->assert_str_equals('R5', $res->[0][2]); + $self->assert_num_equals(1, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(2, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); - xlog "get contact updates"; - $res = $jmap->CallMethods([ - ['Contact/changes', { - sinceState => $state - }, "R6.5"], - ['ContactGroup/changes', { - sinceState => $state - }, "R6.6"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/changes', $res->[0][0]); - $self->assert_str_equals('R6.5', $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); + xlog "get contact updates"; + $res = $jmap->CallMethods([ + [ + 'Contact/changes', + { + sinceState => $state + }, + "R6.5" + ], + [ + 'ContactGroup/changes', + { + sinceState => $state + }, + "R6.6" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/changes', $res->[0][0]); + $self->assert_str_equals('R6.5', $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); - $self->assert_str_equals('ContactGroup/changes', $res->[1][0]); - $self->assert_str_equals('R6.6', $res->[1][2]); - $self->assert_str_equals($state, $res->[1][1]{oldState}); - $self->assert_str_equals($state, $res->[1][1]{newState}); - $self->assert_equals(JSON::false, $res->[1][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{destroyed}}); + $self->assert_str_equals('ContactGroup/changes', $res->[1][0]); + $self->assert_str_equals('R6.6', $res->[1][2]); + $self->assert_str_equals($state, $res->[1][1]{oldState}); + $self->assert_str_equals($state, $res->[1][1]{newState}); + $self->assert_equals(JSON::false, $res->[1][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{destroyed} }); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_mail_draft_reply b/cassandane/tiny-tests/JMAPBackup/restore_mail_draft_reply index f7c80623f2..c3cdb44862 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_mail_draft_reply +++ b/cassandane/tiny-tests/JMAPBackup/restore_mail_draft_reply @@ -2,96 +2,128 @@ use Cassandane::Tiny; sub test_restore_mail_draft_reply - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my %exp; + my $jmap = $self->{jmap}; + my %exp; - xlog "create mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - '1' => { name => 'Drafts', parentId => undef } - } - }, "R1"] - ]); + xlog "create mailboxes"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + '1' => { name => 'Drafts', parentId => undef } + } + }, + "R1" + ] ]); - my $draftsId = $res->[0][1]{created}{1}{id}; + my $draftsId = $res->[0][1]{created}{1}{id}; - xlog $self, "generating email A"; - my $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -1)); - $exp{A} = $self->make_message("Email A", - date => $dt, body => "a"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + xlog $self, "generating email A"; + my $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -1)); + $exp{A} = $self->make_message( + "Email A", + date => $dt, + body => "a" + ); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - xlog $self, "generating email B referencing A"; - $dt = DateTime->now(); - $exp{B} = $self->make_message("Re: Email A", - references => [ $exp{A} ], - date => $dt, body => "b"); - $exp{B}->set_attributes(uid => 2, cid => $exp{A}->get_attribute('cid')); + xlog $self, "generating email B referencing A"; + $dt = DateTime->now(); + $exp{B} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "b" + ); + $exp{B}->set_attributes(uid => 2, cid => $exp{A}->get_attribute('cid')); - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { property => "receivedAt", - "isAscending" => $JSON::true }, - ], - }, "R1"]]); - my @ids = @{$res->[0][1]->{ids}}; - $self->assert_num_equals(2, scalar @ids); - my $idA = $ids[0]; - my $idB = $ids[1]; - - xlog $self, "update email B to be a draft"; - $res = $jmap->CallMethods([['Email/set', { - update => { $idB => { - 'keywords/$draft' => JSON::true } + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ + { + property => "receivedAt", + "isAscending" => $JSON::true }, - }, "R1"]]); + ], + }, + "R1" + ] ]); + my @ids = @{ $res->[0][1]->{ids} }; + $self->assert_num_equals(2, scalar @ids); + my $idA = $ids[0]; + my $idB = $ids[1]; + + xlog $self, "update email B to be a draft"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $idB => { + 'keywords/$draft' => JSON::true + } + }, + }, + "R1" + ] ]); - $self->assert(exists $res->[0][1]->{updated}{$idB}); + $self->assert(exists $res->[0][1]->{updated}{$idB}); - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "destroy 'draft' email"; - $res = $jmap->CallMethods([ - ['Email/set', { - destroy => ["$idB"] - }, "R6"], - ]); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{destroyed}})); - $self->assert_str_equals($idB, $res->[0][1]{destroyed}[0]); + xlog "destroy 'draft' email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + destroy => ["$idB"] + }, + "R6" + ], + ]); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{destroyed} })); + $self->assert_str_equals($idB, $res->[0][1]{destroyed}[0]); - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; - xlog "restore mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - verboseLogging => JSON::true, - restoreDrafts => JSON::true, - restoreNonDrafts => JSON::false, - undoPeriod => $period - }, "R7"], - ['Email/get', { - ids => ["$idB"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R8"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R7', $res->[0][2]); - $self->assert_num_equals(1, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); + xlog "restore mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + verboseLogging => JSON::true, + restoreDrafts => JSON::true, + restoreNonDrafts => JSON::false, + undoPeriod => $period + }, + "R7" + ], + [ + 'Email/get', + { + ids => ["$idB"], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R8" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R7', $res->[0][2]); + $self->assert_num_equals(1, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); - $self->assert_str_equals('Email/get', $res->[1][0]); - $self->assert_str_equals('R8', $res->[1][2]); - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals("$idB", $res->[1][1]{list}[0]{id}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{keywords}->{'$draft'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_str_equals('Email/get', $res->[1][0]); + $self->assert_str_equals('R8', $res->[1][2]); + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals("$idB", $res->[1][1]{list}[0]{id}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{keywords}->{'$draft'}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{keywords}->{'$restored'}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_mail_draft_sent b/cassandane/tiny-tests/JMAPBackup/restore_mail_draft_sent index 5bff0d021a..4b5fae3158 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_mail_draft_sent +++ b/cassandane/tiny-tests/JMAPBackup/restore_mail_draft_sent @@ -2,97 +2,117 @@ use Cassandane::Tiny; sub test_restore_mail_draft_sent - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "create mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - '1' => { name => 'Drafts', parentId => undef }, - '2' => { name => 'Sent', parentId => undef } - } - }, "R1"] - ]); + xlog "create mailboxes"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + '1' => { name => 'Drafts', parentId => undef }, + '2' => { name => 'Sent', parentId => undef } + } + }, + "R1" + ] ]); - my $draftsId = $res->[0][1]{created}{1}{id}; - my $sentId = $res->[0][1]{created}{2}{id}; + my $draftsId = $res->[0][1]{created}{1}{id}; + my $sentId = $res->[0][1]{created}{2}{id}; - xlog "create draft email"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - $draftsId => JSON::true - }, - keywords => { - '$draft' => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "email1" - } - }, - }, 'R2'] - ]); + xlog "create draft email"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + $draftsId => JSON::true + }, + keywords => { + '$draft' => JSON::true + }, + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "email1" + } + }, + }, + 'R2' + ] ]); - my $emailId = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId); + my $emailId = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId); - xlog "move email from Drafts to Sent"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "mailboxIds/$draftsId" => undef, - "mailboxIds/$sentId" => JSON::true, - 'keywords/$draft' => undef - } } - }, "R5"] - ]); + xlog "move email from Drafts to Sent"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$draftsId" => undef, + "mailboxIds/$sentId" => JSON::true, + 'keywords/$draft' => undef + } + } + }, + "R5" + ] ]); - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "destroy 'Sent' email"; - $res = $jmap->CallMethods([ - ['Email/set', { - destroy => ["$emailId"] - }, "R6"], - ]); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{destroyed}})); - $self->assert_str_equals($emailId, $res->[0][1]{destroyed}[0]); + xlog "destroy 'Sent' email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + destroy => ["$emailId"] + }, + "R6" + ], + ]); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{destroyed} })); + $self->assert_str_equals($emailId, $res->[0][1]{destroyed}[0]); - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; - xlog "restore mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - restoreDrafts => JSON::true, - restoreNonDrafts => JSON::true, - undoPeriod => $period - }, "R7"], - ['Email/get', { - ids => ["$emailId"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R8"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R7', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); + xlog "restore mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + restoreDrafts => JSON::true, + restoreNonDrafts => JSON::true, + undoPeriod => $period + }, + "R7" + ], + [ + 'Email/get', + { + ids => ["$emailId"], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R8" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R7', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); - $self->assert_str_equals('Email/get', $res->[1][0]); - $self->assert_str_equals('R8', $res->[1][2]); - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals("$emailId", $res->[1][1]{list}[0]{id}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$draft'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$sentId}); - $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$draftsId}); + $self->assert_str_equals('Email/get', $res->[1][0]); + $self->assert_str_equals('R8', $res->[1][2]); + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals("$emailId", $res->[1][1]{list}[0]{id}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$draft'}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$sentId}); + $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$draftsId}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_mail_exists b/cassandane/tiny-tests/JMAPBackup/restore_mail_exists index 76faffa43f..222f699267 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_mail_exists +++ b/cassandane/tiny-tests/JMAPBackup/restore_mail_exists @@ -2,95 +2,115 @@ use Cassandane::Tiny; sub test_restore_mail_exists - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "create email in Inbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - }, "R1"] - ]); + xlog "create email in Inbox"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{list}})); - my $inboxId = $res->[0][1]{list}[0]{id}; + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{list} })); + my $inboxId = $res->[0][1]{list}[0]{id}; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - $inboxId => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "email1" - } + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + $inboxId => JSON::true }, - }, 'R2'], - ['Email/get', { - ids => [ '#email1' ], - properties => ['receivedAt'] - }, "R3"] - ]); - my $emailId1 = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId1); - my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "email1" + } + }, + }, + 'R2' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => ['receivedAt'] + }, + "R3" + ] + ]); + my $emailId1 = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId1); + my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "create new mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "1" => { - name => "foo" - } - } - }, "R4"], - ]); - $self->assert_not_null($res); - my $fooId = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($fooId); + xlog "create new mailbox"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo" + } + } + }, + "R4" + ], + ]); + $self->assert_not_null($res); + my $fooId = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($fooId); - xlog "move email from Inbox to foo"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId1 => { - "mailboxIds/$inboxId" => undef, - "mailboxIds/$fooId" => JSON::true - } } - }, "R5"] - ]); + xlog "move email from Inbox to foo"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId1 => { + "mailboxIds/$inboxId" => undef, + "mailboxIds/$fooId" => JSON::true + } + } + }, + "R5" + ] ]); - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; - xlog "actually restore mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - undoPeriod => $period - }, "R7"], - ['Email/get', { - ids => ["$emailId1"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R9"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R7', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); + xlog "actually restore mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + undoPeriod => $period + }, + "R7" + ], + [ + 'Email/get', + { + ids => ["$emailId1"], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R9" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R7', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); - $self->assert_str_equals('Email/get', $res->[1][0]); - $self->assert_str_equals('R9', $res->[1][2]); - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals("$emailId1", $res->[1][1]{list}[0]{id}); - $self->assert_str_equals("$emailAt1", $res->[1][1]{list}[0]{receivedAt}); - $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$fooId}); - $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$inboxId}); + $self->assert_str_equals('Email/get', $res->[1][0]); + $self->assert_str_equals('R9', $res->[1][2]); + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals("$emailId1", $res->[1][1]{list}[0]{id}); + $self->assert_str_equals("$emailAt1", $res->[1][1]{list}[0]{receivedAt}); + $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$fooId}); + $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$inboxId}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_mail_full b/cassandane/tiny-tests/JMAPBackup/restore_mail_full index bdf1df0617..a5465b4b65 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_mail_full +++ b/cassandane/tiny-tests/JMAPBackup/restore_mail_full @@ -2,414 +2,514 @@ use Cassandane::Tiny; sub test_restore_mail_full - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "create mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "1" => { - name => "foo" - }, - "3" => { - name => "bar" - }, - "2" => { - name => "Drafts", - role => "Drafts" - } - } - }, "R1"], - ['Mailbox/get', { - }, "R2"] - ]); - $self->assert_not_null($res); - my $fooId = $res->[0][1]{created}{"1"}{id}; - my $barId = $res->[0][1]{created}{"3"}{id}; - my $draftsId = $res->[0][1]{created}{"2"}{id}; - $self->assert_not_null($fooId); - $self->assert_not_null($barId); - $self->assert_not_null($draftsId); - - $self->assert_num_equals(4, scalar(@{$res->[1][1]{list}})); - my %m = map { $_->{name} => $_ } @{$res->[1][1]{list}}; - my $inboxId = $m{"Inbox"}->{id}; - $self->assert_not_null($inboxId); - - xlog "create emails in Inbox"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - $inboxId => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "email1" - }, - email2 => { - mailboxIds => { - $inboxId => JSON::true, - $fooId => JSON::true - }, - from => [{ email => q{foo2@bar} }], - to => [{ email => q{bar2@foo} }], - subject => "email2" - }, - email3 => { - mailboxIds => { - $inboxId => JSON::true - }, - # explicity set this keyword to make sure it gets removed - keywords => { '$restored' => JSON::true }, - from => [{ email => q{foo3@bar} }], - to => [{ email => q{bar3@foo} }], - subject => "email3" - }, - email4 => { - mailboxIds => { - $fooId => JSON::true - }, - from => [{ email => q{foo4@bar} }], - to => [{ email => q{bar4@foo} }], - subject => "email4" - }, - email5 => { - mailboxIds => { - $fooId => JSON::true - }, - from => [{ email => q{foo5@bar} }], - to => [{ email => q{bar5@foo} }], - subject => "email5" - }, - email6 => { - mailboxIds => { - $inboxId => JSON::true - }, - from => [{ email => q{foo6@bar} }], - to => [{ email => q{bar6@foo} }], - subject => "email6" - } + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "create mailboxes"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo" + }, + "3" => { + name => "bar" + }, + "2" => { + name => "Drafts", + role => "Drafts" + } + } + }, + "R1" + ], + [ 'Mailbox/get', {}, "R2" ] + ]); + $self->assert_not_null($res); + my $fooId = $res->[0][1]{created}{"1"}{id}; + my $barId = $res->[0][1]{created}{"3"}{id}; + my $draftsId = $res->[0][1]{created}{"2"}{id}; + $self->assert_not_null($fooId); + $self->assert_not_null($barId); + $self->assert_not_null($draftsId); + + $self->assert_num_equals(4, scalar(@{ $res->[1][1]{list} })); + my %m = map { $_->{name} => $_ } @{ $res->[1][1]{list} }; + my $inboxId = $m{"Inbox"}->{id}; + $self->assert_not_null($inboxId); + + xlog "create emails in Inbox"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + $inboxId => JSON::true }, - }, 'R3'], - ['Email/get', { - ids => [ '#email1', '#email2', '#email3', '#email4', '#email5', '#email6' ], - properties => ['receivedAt'] - }, "R3.2"] - ]); - my $emailId1 = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId1); - my $emailId2 = $res->[0][1]{created}{email2}{id}; - $self->assert_not_null($emailId2); - my $emailId3 = $res->[0][1]{created}{email3}{id}; - $self->assert_not_null($emailId3); - my $emailId4 = $res->[0][1]{created}{email4}{id}; - $self->assert_not_null($emailId4); - my $emailId5 = $res->[0][1]{created}{email5}{id}; - $self->assert_not_null($emailId5); - my $emailId6 = $res->[0][1]{created}{email6}{id}; - $self->assert_not_null($emailId6); - - my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; - my $emailAt2 = $res->[1][1]{list}[1]{receivedAt}; - my $emailAt3 = $res->[1][1]{list}[2]{receivedAt}; - my $emailAt4 = $res->[1][1]{list}[3]{receivedAt}; - my $emailAt5 = $res->[1][1]{list}[4]{receivedAt}; - my $emailAt6 = $res->[1][1]{list}[5]{receivedAt}; - - xlog "create emails in Drafts"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - draft1 => { - mailboxIds => { - $draftsId => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "draft1", - keywords => { '$draft' => JSON::true }, - messageId => ['fake.123456789@local'], - }, - draft2 => { - mailboxIds => { - $draftsId => JSON::true - }, - from => [{ email => q{foo2@bar} }], - to => [{ email => q{bar2@foo} }], - subject => "draft2 (biggest)", - keywords => { '$draft' => JSON::true }, - messageId => ['fake.123456789@local'], - }, - draft3 => { - mailboxIds => { - $draftsId => JSON::true - }, - from => [{ email => q{foo3@bar} }], - to => [{ email => q{bar3@foo} }], - subject => "draft3 (bigger)", - keywords => { '$draft' => JSON::true }, - messageId => ['fake.123456789@local'], - }, + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "email1" + }, + email2 => { + mailboxIds => { + $inboxId => JSON::true, + $fooId => JSON::true }, - }, 'R3.5'], - ['Email/get', { - ids => [ '#draft1', '#draft2', '#draft3' ], - properties => ['receivedAt'] - }, "R3.7"] - ]); - my $draftId1 = $res->[0][1]{created}{draft1}{id}; - $self->assert_not_null($emailId1); - my $draftId2 = $res->[0][1]{created}{draft2}{id}; - $self->assert_not_null($emailId2); - my $draftId3 = $res->[0][1]{created}{draft3}{id}; - $self->assert_not_null($emailId3); - - my $draftAt1 = $res->[1][1]{list}[0]{receivedAt}; - my $draftAt2 = $res->[1][1]{list}[1]{receivedAt}; - my $draftAt3 = $res->[1][1]{list}[2]{receivedAt}; - - xlog "move email6 from Inbox to bar, delete email1 and email5"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId6 => { - "mailboxIds/$inboxId" => undef, - "mailboxIds/$barId" => JSON::true - } }, - destroy => ["$emailId1", "$emailId5"] - }, "R4"] - ]); - $self->assert_str_equals($emailId1, $res->[0][1]{destroyed}[0]); - - xlog "remove email2 from Inbox"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId2 => { "mailboxIds/$inboxId" => undef }} - }, "R4.5"] - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId2}); - - my $mark = time(); - sleep 2; - - xlog "destroy email2, all drafts, 'foo' and 'bar' mailboxes"; - $res = $jmap->CallMethods([ - ['Email/set', { - destroy => ["$emailId2", "$draftId1", "$draftId2", "$draftId3"] - }, "R5"], - ['Mailbox/set', { - destroy => ["$fooId", "$barId"], - onDestroyRemoveEmails => JSON::true - }, "R5.5"], - ]); - $self->assert_num_equals(4, scalar(@{$res->[0][1]{destroyed}})); - my @expect = sort ($emailId2, $draftId1, $draftId2, $draftId3); - my @got = sort @{$res->[0][1]{destroyed}}; - $self->assert_deep_equals(\@expect, \@got); - - $self->assert_num_equals(2, scalar @{$res->[1][1]{destroyed}}); - @expect = sort ($fooId, $barId); - @got = sort @{$res->[1][1]{destroyed}}; - $self->assert_deep_equals(\@expect, \@got); - - xlog "create a new 'bar' mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "1" => { - name => "bar" - } - } - }, "R5.7"], - ['Mailbox/get', { - }, "R5.8"], - ['Email/get', { - ids => ["$emailId1", "$emailId2", "$emailId3", "$emailId4", "$emailId5", "$emailId6", - "$draftId1", "$draftId2", "$draftId3"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R5.9"] - ]); - $self->assert_not_null($res); - my $newBarId = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($newBarId); - - @expect = sort ($emailId1, $emailId2, $emailId4, $emailId5, $emailId6, $draftId1, $draftId2, $draftId3); - @got = sort @{$res->[2][1]{notFound}}; - $self->assert_deep_equals(\@expect, \@got); - - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; - - xlog "perform a dry-run restoration of mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - performDryRun => JSON::true, - undoPeriod => $period - }, "R5.9.4"], - ['Email/get', { - ids => ["$emailId1", "$emailId2", "$emailId3", "$emailId4", "$emailId5", "$emailId6", - "$draftId1", "$draftId2", "$draftId3"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R5.9.5"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R5.9.4', $res->[0][2]); - $self->assert_num_equals(1, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(3, $res->[0][1]{numNonDraftsRestored}); - - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals("$emailId3", $res->[1][1]{list}[0]{id}); - $self->assert_str_equals("$emailAt3", $res->[1][1]{list}[0]{receivedAt}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$inboxId}); - - @expect = sort ($emailId1, $emailId2, $emailId4, $emailId5, $emailId6, $draftId1, $draftId2, $draftId3); - @got = sort @{$res->[1][1]{notFound}}; - $self->assert_deep_equals(\@expect, \@got); - - $diff = time() - $mark; - $period = "PT" . $diff . "S"; - - xlog "restore mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - restoreNonDrafts => JSON::false, - undoPeriod => $period - }, "R6"], - ['Email/get', { - ids => ["$emailId1", "$emailId2", "$emailId3", "$emailId4", "$emailId5", "$emailId6", - "$draftId1", "$draftId2", "$draftId3"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R6.2"], - ['Backup/restoreMail', { - restoreDrafts => JSON::false, - undoPeriod => $period - }, "R6.5"], - ['Mailbox/get', { - }, "R7"], - ['Email/get', { - ids => ["$emailId1", "$emailId2", "$emailId3", "$emailId4", "$emailId5", "$emailId6", - "$draftId1", "$draftId2", "$draftId3"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R8"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R6', $res->[0][2]); - $self->assert_num_equals(1, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); - - # - email3 should have $restored flag removed - # - draft1 should NOT be restored (smaller than draft2) - # - draft2 should be the only draft restored to mailbox 'Drafts' - # because it was the largest of those having the same Message-ID - # - draft3 should NOT be restored (smaller than draft2) - $self->assert_str_equals('Email/get', $res->[1][0]); - $self->assert_str_equals('R6.2', $res->[1][2]); - $self->assert_num_equals(2, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals("$emailId3", $res->[1][1]{list}[0]{id}); - $self->assert_str_equals("$emailAt3", $res->[1][1]{list}[0]{receivedAt}); - $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$inboxId}); - - $self->assert_str_equals("$draftId2", $res->[1][1]{list}[1]{id}); - $self->assert_str_equals("$draftAt2", $res->[1][1]{list}[1]{receivedAt}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[1]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[1]{mailboxIds}{$draftsId}); - - $self->assert_num_equals(7, scalar(@{$res->[1][1]{notFound}})); - @expect = sort ($emailId1, $emailId2, $emailId4, $emailId5, $emailId6, $draftId1, $draftId3); - @got = sort @{$res->[1][1]{notFound}}; - $self->assert_deep_equals(\@expect, \@got); - - $self->assert_str_equals('R6.5', $res->[2][2]); - $self->assert_num_equals(0, $res->[2][1]{numDraftsRestored}); - $self->assert_num_equals(3, $res->[2][1]{numNonDraftsRestored}); - - # - mailbox 'foo' should be recreated (will have a new id) - # - email1 should NOT be restored (destroyed prior to cutoff) - # - email2 should be restored to the server-recreated 'foo' mailbox ONLY - # (it was destroyed most recently) - # - email4 should be restored to the server-recreated 'foo' mailbox - # - email5 should NOT be restored (destroyed prior to cutoff) - # - email6 should be restored to the user-recreated 'bar' mailbox ONLY - # (it was destroyed most recently) - # - draft2 should have $restored flag removed - $self->assert_str_equals('Mailbox/get', $res->[3][0]); - $self->assert_str_equals('R7', $res->[3][2]); - $self->assert_num_equals(4, scalar(@{$res->[3][1]{list}})); - $self->assert_str_equals("bar", $res->[3][1]{list}[2]{name}); - $self->assert_str_equals($newBarId, $res->[3][1]{list}[2]{id}); - $self->assert_str_equals("foo", $res->[3][1]{list}[3]{name}); - my $newFooId = $res->[3][1]{list}[3]{id}; - - $self->assert_str_equals('Email/get', $res->[4][0]); - $self->assert_str_equals('R8', $res->[4][2]); - $self->assert_num_equals(5, scalar(@{$res->[4][1]{list}})); - $self->assert_str_equals("$emailId2", $res->[4][1]{list}[0]{id}); - $self->assert_str_equals("$emailAt2", $res->[4][1]{list}[0]{receivedAt}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[0]{mailboxIds}{$newFooId}); - $self->assert_null($res->[4][1]{list}[0]{mailboxIds}->{$inboxId}); - - $self->assert_str_equals("$emailId3", $res->[4][1]{list}[1]{id}); - $self->assert_str_equals("$emailAt3", $res->[4][1]{list}[1]{receivedAt}); - $self->assert_null($res->[4][1]{list}[1]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[1]{mailboxIds}{$inboxId}); - - $self->assert_str_equals("$emailId4", $res->[4][1]{list}[2]{id}); - $self->assert_str_equals("$emailAt4", $res->[4][1]{list}[2]{receivedAt}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[2]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[2]{mailboxIds}{$newFooId}); - - $self->assert_str_equals("$emailId6", $res->[4][1]{list}[3]{id}); - $self->assert_str_equals("$emailAt6", $res->[4][1]{list}[3]{receivedAt}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[3]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[3]{mailboxIds}{$newBarId}); - $self->assert_null($res->[4][1]{list}[3]{mailboxIds}->{$inboxId}); - - $self->assert_str_equals("$draftId2", $res->[4][1]{list}[4]{id}); - $self->assert_str_equals("$draftAt2", $res->[4][1]{list}[4]{receivedAt}); - $self->assert_null($res->[4][1]{list}[4]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[4][1]{list}[4]{mailboxIds}{$draftsId}); - - $self->assert_num_equals(4, scalar(@{$res->[4][1]{notFound}})); - @expect = sort ($emailId1, $emailId5, $draftId1, $draftId3); - @got = sort @{$res->[4][1]{notFound}}; - $self->assert_deep_equals(\@expect, \@got); - - $diff = time() - $mark; - $period = "PT" . $diff . "S"; - - xlog "re-restore mailbox back to same point in time"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - undoPeriod => $period - }, "R9"], - ['Email/get', { - ids => ["$emailId1", "$emailId2", "$emailId3", "$emailId4", "$emailId5", "$emailId6", - "$draftId1", "$draftId2", "$draftId3"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R10"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R9', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); - - $self->assert_str_equals('Email/get', $res->[1][0]); - $self->assert_str_equals('R10', $res->[1][2]); - $self->assert_num_equals(5, scalar(@{$res->[1][1]{list}})); - - $self->assert_str_equals("$draftId2", $res->[1][1]{list}[4]{id}); - $self->assert_str_equals("$draftAt2", $res->[1][1]{list}[4]{receivedAt}); - $self->assert_null($res->[4][1]{list}[4]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[4]{mailboxIds}{$draftsId}); + from => [ { email => q{foo2@bar} } ], + to => [ { email => q{bar2@foo} } ], + subject => "email2" + }, + email3 => { + mailboxIds => { + $inboxId => JSON::true + }, + # explicity set this keyword to make sure it gets removed + keywords => { '$restored' => JSON::true }, + from => [ { email => q{foo3@bar} } ], + to => [ { email => q{bar3@foo} } ], + subject => "email3" + }, + email4 => { + mailboxIds => { + $fooId => JSON::true + }, + from => [ { email => q{foo4@bar} } ], + to => [ { email => q{bar4@foo} } ], + subject => "email4" + }, + email5 => { + mailboxIds => { + $fooId => JSON::true + }, + from => [ { email => q{foo5@bar} } ], + to => [ { email => q{bar5@foo} } ], + subject => "email5" + }, + email6 => { + mailboxIds => { + $inboxId => JSON::true + }, + from => [ { email => q{foo6@bar} } ], + to => [ { email => q{bar6@foo} } ], + subject => "email6" + } + }, + }, + 'R3' + ], + [ + 'Email/get', + { + ids => + [ '#email1', '#email2', '#email3', '#email4', '#email5', '#email6' ], + properties => ['receivedAt'] + }, + "R3.2" + ] + ]); + my $emailId1 = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId1); + my $emailId2 = $res->[0][1]{created}{email2}{id}; + $self->assert_not_null($emailId2); + my $emailId3 = $res->[0][1]{created}{email3}{id}; + $self->assert_not_null($emailId3); + my $emailId4 = $res->[0][1]{created}{email4}{id}; + $self->assert_not_null($emailId4); + my $emailId5 = $res->[0][1]{created}{email5}{id}; + $self->assert_not_null($emailId5); + my $emailId6 = $res->[0][1]{created}{email6}{id}; + $self->assert_not_null($emailId6); + + my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; + my $emailAt2 = $res->[1][1]{list}[1]{receivedAt}; + my $emailAt3 = $res->[1][1]{list}[2]{receivedAt}; + my $emailAt4 = $res->[1][1]{list}[3]{receivedAt}; + my $emailAt5 = $res->[1][1]{list}[4]{receivedAt}; + my $emailAt6 = $res->[1][1]{list}[5]{receivedAt}; + + xlog "create emails in Drafts"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + draft1 => { + mailboxIds => { + $draftsId => JSON::true + }, + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "draft1", + keywords => { '$draft' => JSON::true }, + messageId => ['fake.123456789@local'], + }, + draft2 => { + mailboxIds => { + $draftsId => JSON::true + }, + from => [ { email => q{foo2@bar} } ], + to => [ { email => q{bar2@foo} } ], + subject => "draft2 (biggest)", + keywords => { '$draft' => JSON::true }, + messageId => ['fake.123456789@local'], + }, + draft3 => { + mailboxIds => { + $draftsId => JSON::true + }, + from => [ { email => q{foo3@bar} } ], + to => [ { email => q{bar3@foo} } ], + subject => "draft3 (bigger)", + keywords => { '$draft' => JSON::true }, + messageId => ['fake.123456789@local'], + }, + }, + }, + 'R3.5' + ], + [ + 'Email/get', + { + ids => [ '#draft1', '#draft2', '#draft3' ], + properties => ['receivedAt'] + }, + "R3.7" + ] + ]); + my $draftId1 = $res->[0][1]{created}{draft1}{id}; + $self->assert_not_null($emailId1); + my $draftId2 = $res->[0][1]{created}{draft2}{id}; + $self->assert_not_null($emailId2); + my $draftId3 = $res->[0][1]{created}{draft3}{id}; + $self->assert_not_null($emailId3); + + my $draftAt1 = $res->[1][1]{list}[0]{receivedAt}; + my $draftAt2 = $res->[1][1]{list}[1]{receivedAt}; + my $draftAt3 = $res->[1][1]{list}[2]{receivedAt}; + + xlog "move email6 from Inbox to bar, delete email1 and email5"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId6 => { + "mailboxIds/$inboxId" => undef, + "mailboxIds/$barId" => JSON::true + } + }, + destroy => [ "$emailId1", "$emailId5" ] + }, + "R4" + ] ]); + $self->assert_str_equals($emailId1, $res->[0][1]{destroyed}[0]); + + xlog "remove email2 from Inbox"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { $emailId2 => { "mailboxIds/$inboxId" => undef } } + }, + "R4.5" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$emailId2}); + + my $mark = time(); + sleep 2; + + xlog "destroy email2, all drafts, 'foo' and 'bar' mailboxes"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + destroy => [ "$emailId2", "$draftId1", "$draftId2", "$draftId3" ] + }, + "R5" + ], + [ + 'Mailbox/set', + { + destroy => [ "$fooId", "$barId" ], + onDestroyRemoveEmails => JSON::true + }, + "R5.5" + ], + ]); + $self->assert_num_equals(4, scalar(@{ $res->[0][1]{destroyed} })); + my @expect = sort ($emailId2, $draftId1, $draftId2, $draftId3); + my @got = sort @{ $res->[0][1]{destroyed} }; + $self->assert_deep_equals(\@expect, \@got); + + $self->assert_num_equals(2, scalar @{ $res->[1][1]{destroyed} }); + @expect = sort ($fooId, $barId); + @got = sort @{ $res->[1][1]{destroyed} }; + $self->assert_deep_equals(\@expect, \@got); + + xlog "create a new 'bar' mailbox"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "bar" + } + } + }, + "R5.7" + ], + [ 'Mailbox/get', {}, "R5.8" ], + [ + 'Email/get', + { + ids => [ + "$emailId1", "$emailId2", "$emailId3", "$emailId4", + "$emailId5", "$emailId6", "$draftId1", "$draftId2", + "$draftId3" + ], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R5.9" + ] + ]); + $self->assert_not_null($res); + my $newBarId = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($newBarId); + + @expect = sort ($emailId1, $emailId2, $emailId4, $emailId5, + $emailId6, $draftId1, $draftId2, $draftId3); + @got = sort @{ $res->[2][1]{notFound} }; + $self->assert_deep_equals(\@expect, \@got); + + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; + + xlog "perform a dry-run restoration of mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + performDryRun => JSON::true, + undoPeriod => $period + }, + "R5.9.4" + ], + [ + 'Email/get', + { + ids => [ + "$emailId1", "$emailId2", "$emailId3", "$emailId4", + "$emailId5", "$emailId6", "$draftId1", "$draftId2", + "$draftId3" + ], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R5.9.5" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R5.9.4', $res->[0][2]); + $self->assert_num_equals(1, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(3, $res->[0][1]{numNonDraftsRestored}); + + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals("$emailId3", $res->[1][1]{list}[0]{id}); + $self->assert_str_equals("$emailAt3", $res->[1][1]{list}[0]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$inboxId}); + + @expect = sort ($emailId1, $emailId2, $emailId4, $emailId5, + $emailId6, $draftId1, $draftId2, $draftId3); + @got = sort @{ $res->[1][1]{notFound} }; + $self->assert_deep_equals(\@expect, \@got); + + $diff = time() - $mark; + $period = "PT" . $diff . "S"; + + xlog "restore mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + restoreNonDrafts => JSON::false, + undoPeriod => $period + }, + "R6" + ], + [ + 'Email/get', + { + ids => [ + "$emailId1", "$emailId2", "$emailId3", "$emailId4", + "$emailId5", "$emailId6", "$draftId1", "$draftId2", + "$draftId3" + ], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R6.2" + ], + [ + 'Backup/restoreMail', + { + restoreDrafts => JSON::false, + undoPeriod => $period + }, + "R6.5" + ], + [ 'Mailbox/get', {}, "R7" ], + [ + 'Email/get', + { + ids => [ + "$emailId1", "$emailId2", "$emailId3", "$emailId4", + "$emailId5", "$emailId6", "$draftId1", "$draftId2", + "$draftId3" + ], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R8" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R6', $res->[0][2]); + $self->assert_num_equals(1, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); + + # - email3 should have $restored flag removed + # - draft1 should NOT be restored (smaller than draft2) + # - draft2 should be the only draft restored to mailbox 'Drafts' + # because it was the largest of those having the same Message-ID + # - draft3 should NOT be restored (smaller than draft2) + $self->assert_str_equals('Email/get', $res->[1][0]); + $self->assert_str_equals('R6.2', $res->[1][2]); + $self->assert_num_equals(2, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals("$emailId3", $res->[1][1]{list}[0]{id}); + $self->assert_str_equals("$emailAt3", $res->[1][1]{list}[0]{receivedAt}); + $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$inboxId}); + + $self->assert_str_equals("$draftId2", $res->[1][1]{list}[1]{id}); + $self->assert_str_equals("$draftAt2", $res->[1][1]{list}[1]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[1]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[1]{mailboxIds}{$draftsId}); + + $self->assert_num_equals(7, scalar(@{ $res->[1][1]{notFound} })); + @expect = sort ($emailId1, $emailId2, $emailId4, $emailId5, + $emailId6, $draftId1, $draftId3); + @got = sort @{ $res->[1][1]{notFound} }; + $self->assert_deep_equals(\@expect, \@got); + + $self->assert_str_equals('R6.5', $res->[2][2]); + $self->assert_num_equals(0, $res->[2][1]{numDraftsRestored}); + $self->assert_num_equals(3, $res->[2][1]{numNonDraftsRestored}); + + # - mailbox 'foo' should be recreated (will have a new id) + # - email1 should NOT be restored (destroyed prior to cutoff) + # - email2 should be restored to the server-recreated 'foo' mailbox ONLY + # (it was destroyed most recently) + # - email4 should be restored to the server-recreated 'foo' mailbox + # - email5 should NOT be restored (destroyed prior to cutoff) + # - email6 should be restored to the user-recreated 'bar' mailbox ONLY + # (it was destroyed most recently) + # - draft2 should have $restored flag removed + $self->assert_str_equals('Mailbox/get', $res->[3][0]); + $self->assert_str_equals('R7', $res->[3][2]); + $self->assert_num_equals(4, scalar(@{ $res->[3][1]{list} })); + $self->assert_str_equals("bar", $res->[3][1]{list}[2]{name}); + $self->assert_str_equals($newBarId, $res->[3][1]{list}[2]{id}); + $self->assert_str_equals("foo", $res->[3][1]{list}[3]{name}); + my $newFooId = $res->[3][1]{list}[3]{id}; + + $self->assert_str_equals('Email/get', $res->[4][0]); + $self->assert_str_equals('R8', $res->[4][2]); + $self->assert_num_equals(5, scalar(@{ $res->[4][1]{list} })); + $self->assert_str_equals("$emailId2", $res->[4][1]{list}[0]{id}); + $self->assert_str_equals("$emailAt2", $res->[4][1]{list}[0]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[4][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[4][1]{list}[0]{mailboxIds}{$newFooId}); + $self->assert_null($res->[4][1]{list}[0]{mailboxIds}->{$inboxId}); + + $self->assert_str_equals("$emailId3", $res->[4][1]{list}[1]{id}); + $self->assert_str_equals("$emailAt3", $res->[4][1]{list}[1]{receivedAt}); + $self->assert_null($res->[4][1]{list}[1]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, $res->[4][1]{list}[1]{mailboxIds}{$inboxId}); + + $self->assert_str_equals("$emailId4", $res->[4][1]{list}[2]{id}); + $self->assert_str_equals("$emailAt4", $res->[4][1]{list}[2]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[4][1]{list}[2]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[4][1]{list}[2]{mailboxIds}{$newFooId}); + + $self->assert_str_equals("$emailId6", $res->[4][1]{list}[3]{id}); + $self->assert_str_equals("$emailAt6", $res->[4][1]{list}[3]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[4][1]{list}[3]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[4][1]{list}[3]{mailboxIds}{$newBarId}); + $self->assert_null($res->[4][1]{list}[3]{mailboxIds}->{$inboxId}); + + $self->assert_str_equals("$draftId2", $res->[4][1]{list}[4]{id}); + $self->assert_str_equals("$draftAt2", $res->[4][1]{list}[4]{receivedAt}); + $self->assert_null($res->[4][1]{list}[4]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[4][1]{list}[4]{mailboxIds}{$draftsId}); + + $self->assert_num_equals(4, scalar(@{ $res->[4][1]{notFound} })); + @expect = sort ($emailId1, $emailId5, $draftId1, $draftId3); + @got = sort @{ $res->[4][1]{notFound} }; + $self->assert_deep_equals(\@expect, \@got); + + $diff = time() - $mark; + $period = "PT" . $diff . "S"; + + xlog "re-restore mailbox back to same point in time"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + undoPeriod => $period + }, + "R9" + ], + [ + 'Email/get', + { + ids => [ + "$emailId1", "$emailId2", "$emailId3", "$emailId4", + "$emailId5", "$emailId6", "$draftId1", "$draftId2", + "$draftId3" + ], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R10" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R9', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); + + $self->assert_str_equals('Email/get', $res->[1][0]); + $self->assert_str_equals('R10', $res->[1][2]); + $self->assert_num_equals(5, scalar(@{ $res->[1][1]{list} })); + + $self->assert_str_equals("$draftId2", $res->[1][1]{list}[4]{id}); + $self->assert_str_equals("$draftAt2", $res->[1][1]{list}[4]{receivedAt}); + $self->assert_null($res->[4][1]{list}[4]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[4]{mailboxIds}{$draftsId}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_mail_simple b/cassandane/tiny-tests/JMAPBackup/restore_mail_simple index 6827e8eeff..8876ed1529 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_mail_simple +++ b/cassandane/tiny-tests/JMAPBackup/restore_mail_simple @@ -2,179 +2,220 @@ use Cassandane::Tiny; sub test_restore_mail_simple - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog "create email in Inbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - }, "R1"] - ]); - - $self->assert_num_equals(1, scalar(@{$res->[0][1]{list}})); - my $inboxId = $res->[0][1]{list}[0]{id}; - - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - $inboxId => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "email1" - } + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog "create email in Inbox"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{list} })); + my $inboxId = $res->[0][1]{list}[0]{id}; + + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + $inboxId => JSON::true }, - }, 'R2'], - ['Email/get', { - ids => [ '#email1' ], - properties => ['receivedAt'] - }, "R3"] - ]); - my $emailId1 = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId1); - my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; - - xlog "create new mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "1" => { - name => "foo" - } - } - }, "R4"], - ]); - $self->assert_not_null($res); - my $fooId = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($fooId); - - xlog "move email from Inbox to foo"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId1 => { - "mailboxIds/$inboxId" => undef, - "mailboxIds/$fooId" => JSON::true - } } - }, "R5"] - ]); - - my $mark = time(); - sleep 2; - - xlog "destroy 'foo' mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => ["$fooId"], - onDestroyRemoveEmails => JSON::true - }, "R6"], - ]); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{destroyed}})); - $self->assert_str_equals($fooId, $res->[0][1]{destroyed}[0]); - - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; - - xlog "perform a dry-run restoration of mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - performDryRun => JSON::true, - restoreDrafts => JSON::false, - restoreNonDrafts => JSON::true, - undoPeriod => "PT1H" - }, "R7.1"], - ['Mailbox/get', { - }, "R8.1"], - ['Email/get', { - ids => ["$emailId1"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R9.1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R7.1', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); - - $self->assert_str_equals('Mailbox/get', $res->[1][0]); - $self->assert_str_equals('R8.1', $res->[1][2]); - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals("Inbox", $res->[1][1]{list}[0]{name}); - - $self->assert_str_equals('Email/get', $res->[2][0]); - $self->assert_str_equals('R9.1', $res->[2][2]); - $self->assert_num_equals(0, scalar(@{$res->[2][1]{list}})); - $self->assert_str_equals("$emailId1", $res->[2][1]{notFound}[0]); - - $diff = time() - $mark; - $period = "PT" . $diff . "S"; - - xlog "actually restore mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - restoreDrafts => JSON::false, - restoreNonDrafts => JSON::true, - undoPeriod => $period - }, "R7"], - ['Mailbox/get', { - }, "R8"], - ['Email/get', { - ids => ["$emailId1"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R9"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R7', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); - - $self->assert_str_equals('Mailbox/get', $res->[1][0]); - $self->assert_str_equals('R8', $res->[1][2]); - $self->assert_num_equals(2, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals("foo", $res->[1][1]{list}[1]{name}); - my $newFooId = $res->[1][1]{list}[1]{id}; - - $self->assert_str_equals('Email/get', $res->[2][0]); - $self->assert_str_equals('R9', $res->[2][2]); - $self->assert_num_equals(1, scalar(@{$res->[2][1]{list}})); - $self->assert_str_equals("$emailId1", $res->[2][1]{list}[0]{id}); - $self->assert_str_equals("$emailAt1", $res->[2][1]{list}[0]{receivedAt}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{mailboxIds}{$newFooId}); - $self->assert_null($res->[2][1]{list}[0]{mailboxIds}->{$inboxId}); - - $diff = time() - $mark; - $period = "PT" . $diff . "S"; - - xlog "attempt to re-restore mailbox back to same point in time"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - restoreDrafts => JSON::false, - restoreNonDrafts => JSON::true, - undoPeriod => $period - }, "R10"], - ['Email/get', { - ids => ["$emailId1"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R11"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R10', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); - - $self->assert_str_equals('Email/get', $res->[1][0]); - $self->assert_str_equals('R11', $res->[1][2]); - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals("$emailId1", $res->[1][1]{list}[0]{id}); - $self->assert_str_equals("$emailAt1", $res->[1][1]{list}[0]{receivedAt}); - $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$newFooId}); - $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$inboxId}); + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "email1" + } + }, + }, + 'R2' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => ['receivedAt'] + }, + "R3" + ] + ]); + my $emailId1 = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId1); + my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; + + xlog "create new mailbox"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo" + } + } + }, + "R4" + ], + ]); + $self->assert_not_null($res); + my $fooId = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($fooId); + + xlog "move email from Inbox to foo"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId1 => { + "mailboxIds/$inboxId" => undef, + "mailboxIds/$fooId" => JSON::true + } + } + }, + "R5" + ] ]); + + my $mark = time(); + sleep 2; + + xlog "destroy 'foo' mailbox"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + destroy => ["$fooId"], + onDestroyRemoveEmails => JSON::true + }, + "R6" + ], + ]); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{destroyed} })); + $self->assert_str_equals($fooId, $res->[0][1]{destroyed}[0]); + + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; + + xlog "perform a dry-run restoration of mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + performDryRun => JSON::true, + restoreDrafts => JSON::false, + restoreNonDrafts => JSON::true, + undoPeriod => "PT1H" + }, + "R7.1" + ], + [ 'Mailbox/get', {}, "R8.1" ], + [ + 'Email/get', + { + ids => ["$emailId1"], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R9.1" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R7.1', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); + + $self->assert_str_equals('Mailbox/get', $res->[1][0]); + $self->assert_str_equals('R8.1', $res->[1][2]); + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals("Inbox", $res->[1][1]{list}[0]{name}); + + $self->assert_str_equals('Email/get', $res->[2][0]); + $self->assert_str_equals('R9.1', $res->[2][2]); + $self->assert_num_equals(0, scalar(@{ $res->[2][1]{list} })); + $self->assert_str_equals("$emailId1", $res->[2][1]{notFound}[0]); + + $diff = time() - $mark; + $period = "PT" . $diff . "S"; + + xlog "actually restore mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + restoreDrafts => JSON::false, + restoreNonDrafts => JSON::true, + undoPeriod => $period + }, + "R7" + ], + [ 'Mailbox/get', {}, "R8" ], + [ + 'Email/get', + { + ids => ["$emailId1"], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R9" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R7', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); + + $self->assert_str_equals('Mailbox/get', $res->[1][0]); + $self->assert_str_equals('R8', $res->[1][2]); + $self->assert_num_equals(2, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals("foo", $res->[1][1]{list}[1]{name}); + my $newFooId = $res->[1][1]{list}[1]{id}; + + $self->assert_str_equals('Email/get', $res->[2][0]); + $self->assert_str_equals('R9', $res->[2][2]); + $self->assert_num_equals(1, scalar(@{ $res->[2][1]{list} })); + $self->assert_str_equals("$emailId1", $res->[2][1]{list}[0]{id}); + $self->assert_str_equals("$emailAt1", $res->[2][1]{list}[0]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[2][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[2][1]{list}[0]{mailboxIds}{$newFooId}); + $self->assert_null($res->[2][1]{list}[0]{mailboxIds}->{$inboxId}); + + $diff = time() - $mark; + $period = "PT" . $diff . "S"; + + xlog "attempt to re-restore mailbox back to same point in time"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + restoreDrafts => JSON::false, + restoreNonDrafts => JSON::true, + undoPeriod => $period + }, + "R10" + ], + [ + 'Email/get', + { + ids => ["$emailId1"], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R11" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R10', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); + + $self->assert_str_equals('Email/get', $res->[1][0]); + $self->assert_str_equals('R11', $res->[1][2]); + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals("$emailId1", $res->[1][1]{list}[0]{id}); + $self->assert_str_equals("$emailAt1", $res->[1][1]{list}[0]{receivedAt}); + $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{mailboxIds}{$newFooId}); + $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$inboxId}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_mail_submailbox b/cassandane/tiny-tests/JMAPBackup/restore_mail_submailbox index 1cfbe2bf17..6a5bc9934c 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_mail_submailbox +++ b/cassandane/tiny-tests/JMAPBackup/restore_mail_submailbox @@ -2,106 +2,127 @@ use Cassandane::Tiny; sub test_restore_mail_submailbox - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $start = time(); - sleep 2; + my $start = time(); + sleep 2; - xlog "create mailbox tree"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - 'A' => { name => 'A', parentId => undef }, - 'B' => { name => 'B', parentId => '#A' }, - 'C' => { name => 'C', parentId => '#B' } - } - }, "R1"] - ]); + xlog "create mailbox tree"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + 'A' => { name => 'A', parentId => undef }, + 'B' => { name => 'B', parentId => '#A' }, + 'C' => { name => 'C', parentId => '#B' } + } + }, + "R1" + ] ]); - my $aId = $res->[0][1]{created}{A}{id}; - my $bId = $res->[0][1]{created}{B}{id}; - my $cId = $res->[0][1]{created}{C}{id}; + my $aId = $res->[0][1]{created}{A}{id}; + my $bId = $res->[0][1]{created}{B}{id}; + my $cId = $res->[0][1]{created}{C}{id}; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - $cId => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "email1" - } + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + $cId => JSON::true }, - }, 'R2'], - ['Email/get', { - ids => [ '#email1' ], - properties => ['receivedAt'] - }, "R3"] - ]); - my $emailId1 = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId1); - my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "email1" + } + }, + }, + 'R2' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => ['receivedAt'] + }, + "R3" + ] + ]); + my $emailId1 = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId1); + my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; - xlog "destroy 'C' mailbox and its ancestors"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => ["$cId", "$bId", "$aId"], - onDestroyRemoveEmails => JSON::true - }, "R6"], - ]); - $self->assert_num_equals(3, scalar(@{$res->[0][1]{destroyed}})); - $self->assert_str_equals($cId, $res->[0][1]{destroyed}[0]); - $self->assert_str_equals($bId, $res->[0][1]{destroyed}[1]); - $self->assert_str_equals($aId, $res->[0][1]{destroyed}[2]); + xlog "destroy 'C' mailbox and its ancestors"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + destroy => [ "$cId", "$bId", "$aId" ], + onDestroyRemoveEmails => JSON::true + }, + "R6" + ], + ]); + $self->assert_num_equals(3, scalar(@{ $res->[0][1]{destroyed} })); + $self->assert_str_equals($cId, $res->[0][1]{destroyed}[0]); + $self->assert_str_equals($bId, $res->[0][1]{destroyed}[1]); + $self->assert_str_equals($aId, $res->[0][1]{destroyed}[2]); - my $diff = time() - $start; - my $period = "PT" . $diff . "S"; + my $diff = time() - $start; + my $period = "PT" . $diff . "S"; - xlog "restore mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - undoPeriod => $period - }, "R7"], - ['Mailbox/get', { - }, "R8"], - ['Email/get', { - ids => ["$emailId1"], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R9"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); - $self->assert_str_equals('R7', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); + xlog "restore mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + undoPeriod => $period + }, + "R7" + ], + [ 'Mailbox/get', {}, "R8" ], + [ + 'Email/get', + { + ids => ["$emailId1"], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R9" + ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreMail', $res->[0][0]); + $self->assert_str_equals('R7', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); - # Make sure that the proper mailbox tree was reconstructed - $self->assert_str_equals('Mailbox/get', $res->[1][0]); - $self->assert_str_equals('R8', $res->[1][2]); - $self->assert_num_equals(4, scalar(@{$res->[1][1]{list}})); + # Make sure that the proper mailbox tree was reconstructed + $self->assert_str_equals('Mailbox/get', $res->[1][0]); + $self->assert_str_equals('R8', $res->[1][2]); + $self->assert_num_equals(4, scalar(@{ $res->[1][1]{list} })); - $self->assert_str_equals("A", $res->[1][1]{list}[1]{name}); - my $newAId = $res->[1][1]{list}[1]{id}; + $self->assert_str_equals("A", $res->[1][1]{list}[1]{name}); + my $newAId = $res->[1][1]{list}[1]{id}; - $self->assert_str_equals("B", $res->[1][1]{list}[2]{name}); - my $newBId = $res->[1][1]{list}[2]{id}; - $self->assert_str_equals("$newAId", $res->[1][1]{list}[2]{parentId}); + $self->assert_str_equals("B", $res->[1][1]{list}[2]{name}); + my $newBId = $res->[1][1]{list}[2]{id}; + $self->assert_str_equals("$newAId", $res->[1][1]{list}[2]{parentId}); - $self->assert_str_equals("C", $res->[1][1]{list}[3]{name}); - my $newCId = $res->[1][1]{list}[3]{id}; - $self->assert_str_equals("$newBId", $res->[1][1]{list}[3]{parentId}); + $self->assert_str_equals("C", $res->[1][1]{list}[3]{name}); + my $newCId = $res->[1][1]{list}[3]{id}; + $self->assert_str_equals("$newBId", $res->[1][1]{list}[3]{parentId}); - $self->assert_str_equals('Email/get', $res->[2][0]); - $self->assert_str_equals('R9', $res->[2][2]); - $self->assert_num_equals(1, scalar(@{$res->[2][1]{list}})); - $self->assert_str_equals("$emailId1", $res->[2][1]{list}[0]{id}); - $self->assert_str_equals("$emailAt1", $res->[2][1]{list}[0]{receivedAt}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{mailboxIds}{$newCId}); + $self->assert_str_equals('Email/get', $res->[2][0]); + $self->assert_str_equals('R9', $res->[2][2]); + $self->assert_num_equals(1, scalar(@{ $res->[2][1]{list} })); + $self->assert_str_equals("$emailId1", $res->[2][1]{list}[0]{id}); + $self->assert_str_equals("$emailAt1", $res->[2][1]{list}[0]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[2][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{mailboxIds}{$newCId}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_mail_twice b/cassandane/tiny-tests/JMAPBackup/restore_mail_twice index 77180f4ba7..d5a7485415 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_mail_twice +++ b/cassandane/tiny-tests/JMAPBackup/restore_mail_twice @@ -2,144 +2,192 @@ use Cassandane::Tiny; sub test_restore_mail_twice - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))") || die; - $imaptalk->create("INBOX.Notes") || die; - - xlog $self, "get mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $inbox = $m{"Inbox"}; - my $trash = $m{"Trash"}; - my $notes = $m{"Notes"}; - - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - $inbox->{id} => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "email1" - } + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))") || die; + $imaptalk->create("INBOX.Notes") || die; + + xlog $self, "get mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $inbox = $m{"Inbox"}; + my $trash = $m{"Trash"}; + my $notes = $m{"Notes"}; + + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + $inbox->{id} => JSON::true }, - }, 'R2'], - ['Email/get', { - ids => [ '#email1' ], - properties => ['receivedAt'] - }, "R3"] - ]); - my $emailId1 = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId1); - my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; - - xlog "move email from Inbox to Trash"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId1 => { - "mailboxIds/$inbox->{id}" => undef, - "mailboxIds/$trash->{id}" => JSON::true - } } - }, "R5"] - ]); - - # need a gap between move and destroy otherwise we will restore both copies - my $mark = time(); - sleep 2; - - xlog "destroy email1"; - $res = $jmap->CallMethods([ - ['Email/set', { - destroy => [$emailId1] - }, "R5"], - ]); - - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; - - xlog "restore mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - undoPeriod => $period - }, "R7"], - ['Email/get', { - ids => [$emailId1], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R9"] - ]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); - - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals($emailId1, $res->[1][1]{list}[0]{id}); - $self->assert_str_equals($emailAt1, $res->[1][1]{list}[0]{receivedAt}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$trash->{id}}); - $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$inbox->{id}}); - - $diff = time() - $mark; - $period = "PT" . $diff . "S"; - - xlog "try restoring mail again"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - undoPeriod => $period - }, "R7"], - ['Email/get', { - ids => [$emailId1], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R9"] - ]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); - - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals($emailId1, $res->[1][1]{list}[0]{id}); - $self->assert_str_equals($emailAt1, $res->[1][1]{list}[0]{receivedAt}); - $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$trash->{id}}); - $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$inbox->{id}}); - - # need a gap between destroys otherwise we will restore both copies - my $mark = time(); - sleep 2; - - xlog "destroy email1 again"; - $res = $jmap->CallMethods([ - ['Email/set', { - destroy => [$emailId1] - }, "R5"], - ]); - - $diff = time() - $mark; - $period = "PT" . $diff . "S"; - - xlog "restore mail prior to most recent changes"; - $res = $jmap->CallMethods([ - ['Backup/restoreMail', { - undoPeriod => $period - }, "R7"], - ['Email/get', { - ids => [$emailId1], - properties => ['subject', 'keywords', 'mailboxIds', 'receivedAt'] - }, "R9"] - ]); - $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); - $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); - - $self->assert_num_equals(1, scalar(@{$res->[1][1]{list}})); - $self->assert_str_equals($emailId1, $res->[1][1]{list}[0]{id}); - $self->assert_str_equals($emailAt1, $res->[1][1]{list}[0]{receivedAt}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{keywords}->{'$restored'}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$trash->{id}}); - $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{$inbox->{id}}); + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "email1" + } + }, + }, + 'R2' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => ['receivedAt'] + }, + "R3" + ] + ]); + my $emailId1 = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId1); + my $emailAt1 = $res->[1][1]{list}[0]{receivedAt}; + + xlog "move email from Inbox to Trash"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId1 => { + "mailboxIds/$inbox->{id}" => undef, + "mailboxIds/$trash->{id}" => JSON::true + } + } + }, + "R5" + ] ]); + + # need a gap between move and destroy otherwise we will restore both copies + my $mark = time(); + sleep 2; + + xlog "destroy email1"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + destroy => [$emailId1] + }, + "R5" + ], + ]); + + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; + + xlog "restore mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + undoPeriod => $period + }, + "R7" + ], + [ + 'Email/get', + { + ids => [$emailId1], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R9" + ] + ]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); + + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals($emailId1, $res->[1][1]{list}[0]{id}); + $self->assert_str_equals($emailAt1, $res->[1][1]{list}[0]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{mailboxIds}{ $trash->{id} }); + $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{ $inbox->{id} }); + + $diff = time() - $mark; + $period = "PT" . $diff . "S"; + + xlog "try restoring mail again"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + undoPeriod => $period + }, + "R7" + ], + [ + 'Email/get', + { + ids => [$emailId1], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R9" + ] + ]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(0, $res->[0][1]{numNonDraftsRestored}); + + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals($emailId1, $res->[1][1]{list}[0]{id}); + $self->assert_str_equals($emailAt1, $res->[1][1]{list}[0]{receivedAt}); + $self->assert_null($res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{mailboxIds}{ $trash->{id} }); + $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{ $inbox->{id} }); + + # need a gap between destroys otherwise we will restore both copies + my $mark = time(); + sleep 2; + + xlog "destroy email1 again"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + destroy => [$emailId1] + }, + "R5" + ], + ]); + + $diff = time() - $mark; + $period = "PT" . $diff . "S"; + + xlog "restore mail prior to most recent changes"; + $res = $jmap->CallMethods([ + [ + 'Backup/restoreMail', + { + undoPeriod => $period + }, + "R7" + ], + [ + 'Email/get', + { + ids => [$emailId1], + properties => [ 'subject', 'keywords', 'mailboxIds', 'receivedAt' ] + }, + "R9" + ] + ]); + $self->assert_num_equals(0, $res->[0][1]{numDraftsRestored}); + $self->assert_num_equals(1, $res->[0][1]{numNonDraftsRestored}); + + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{list} })); + $self->assert_str_equals($emailId1, $res->[1][1]{list}[0]{id}); + $self->assert_str_equals($emailAt1, $res->[1][1]{list}[0]{receivedAt}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{keywords}->{'$restored'}); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{mailboxIds}{ $trash->{id} }); + $self->assert_null($res->[1][1]{list}[0]{mailboxIds}->{ $inbox->{id} }); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_notes b/cassandane/tiny-tests/JMAPBackup/restore_notes index 0d78f50d4f..16e660d975 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_notes +++ b/cassandane/tiny-tests/JMAPBackup/restore_notes @@ -2,115 +2,137 @@ use Cassandane::Tiny; sub test_restore_notes - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # force creation of notes mailbox prior to creating notes - my $res = $jmap->CallMethods([ - ['Note/set', { - }, "R0"] - ]); + # force creation of notes mailbox prior to creating notes + my $res = $jmap->CallMethods([ [ 'Note/set', {}, "R0" ] ]); - xlog "create notes"; - $res = $jmap->CallMethods([['Note/set', {create => { - "a" => {title => "a"}, - "b" => {title => "b"}, - "c" => {title => "c"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $noteA = $res->[0][1]{created}{"a"}{id}; - my $noteB = $res->[0][1]{created}{"b"}{id}; - my $noteC = $res->[0][1]{created}{"c"}{id}; + xlog "create notes"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + create => { + "a" => { title => "a" }, + "b" => { title => "b" }, + "c" => { title => "c" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $noteA = $res->[0][1]{created}{"a"}{id}; + my $noteB = $res->[0][1]{created}{"b"}{id}; + my $noteC = $res->[0][1]{created}{"c"}{id}; - xlog "destroy note C"; - $res = $jmap->CallMethods([['Note/set', { - destroy => [$noteC] - }, "R1.5"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R1.5', $res->[0][2]); + xlog "destroy note C"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + destroy => [$noteC] + }, + "R1.5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R1.5', $res->[0][2]); - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "destroy note A, update note B, create note D"; - $res = $jmap->CallMethods([['Note/set', { - destroy => [$noteA], - update => {$noteB => {title => "B"}}, - create => {"d" => {title => "d"}} - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - my $noteD = $res->[0][1]{created}{"d"}{id}; + xlog "destroy note A, update note B, create note D"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + destroy => [$noteA], + update => { $noteB => { title => "B" } }, + create => { "d" => { title => "d" } } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + my $noteD = $res->[0][1]{created}{"d"}{id}; - xlog "destroy note D, create note E"; - $res = $jmap->CallMethods([['Note/set', { - destroy => [$noteD], - create => { - "e" => {title => "e"} - } - }, "R4"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - my $noteE = $res->[0][1]{created}{"e"}{id}; - my $state = $res->[0][1]{newState}; + xlog "destroy note D, create note E"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + destroy => [$noteD], + create => { + "e" => { title => "e" } + } + }, + "R4" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + my $noteE = $res->[0][1]{created}{"e"}{id}; + my $state = $res->[0][1]{newState}; - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; - xlog "restore notes prior to most recent changes"; - $res = $jmap->CallMethods([['Backup/restoreNotes', { - undoPeriod => $period, - undoAll => JSON::false - }, "R5"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreNotes', $res->[0][0]); - $self->assert_str_equals('R5', $res->[0][2]); - $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(2, $res->[0][1]{numDestroysUndone}); + xlog "restore notes prior to most recent changes"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreNotes', + { + undoPeriod => $period, + undoAll => JSON::false + }, + "R5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreNotes', $res->[0][0]); + $self->assert_str_equals('R5', $res->[0][2]); + $self->assert_num_equals(0, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(2, $res->[0][1]{numDestroysUndone}); - xlog "get restored notes"; - $res = $jmap->CallMethods([ - ['Note/get', { - properties => ['title', 'isFlagged'], - }, "R6"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/get', $res->[0][0]); - $self->assert_str_equals('R6', $res->[0][2]); + xlog "get restored notes"; + $res = $jmap->CallMethods([ [ + 'Note/get', + { + properties => [ 'title', 'isFlagged' ], + }, + "R6" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/get', $res->[0][0]); + $self->assert_str_equals('R6', $res->[0][2]); - my @got = sort { $a->{title} cmp $b->{title} } @{$res->[0][1]{list}}; - $self->assert_num_equals(4, scalar @got); - $self->assert_str_equals('B', $got[0]{title}); - $self->assert_str_equals('a', $got[1]{title}); - $self->assert_str_equals('d', $got[2]{title}); - $self->assert_str_equals('e', $got[3]{title}); + my @got = sort { $a->{title} cmp $b->{title} } @{ $res->[0][1]{list} }; + $self->assert_num_equals(4, scalar @got); + $self->assert_str_equals('B', $got[0]{title}); + $self->assert_str_equals('a', $got[1]{title}); + $self->assert_str_equals('d', $got[2]{title}); + $self->assert_str_equals('e', $got[3]{title}); - xlog "get note updates"; - $res = $jmap->CallMethods([ - ['Note/changes', { - sinceState => $state - }, "R8.5"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/changes', $res->[0][0]); - $self->assert_str_equals('R8.5', $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); + xlog "get note updates"; + $res = $jmap->CallMethods([ [ + 'Note/changes', + { + sinceState => $state + }, + "R8.5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/changes', $res->[0][0]); + $self->assert_str_equals('R8.5', $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); - my %noteIds = map { $_ => 1 } @{$res->[0][1]{created}}; - $self->assert_not_null($noteIds{$noteA}); - $self->assert_not_null($noteIds{$noteD}); + my %noteIds = map { $_ => 1 } @{ $res->[0][1]{created} }; + $self->assert_not_null($noteIds{$noteA}); + $self->assert_not_null($noteIds{$noteD}); } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_notes_all b/cassandane/tiny-tests/JMAPBackup/restore_notes_all index e6bb5d7b3c..8fb14e5280 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_notes_all +++ b/cassandane/tiny-tests/JMAPBackup/restore_notes_all @@ -2,164 +2,193 @@ use Cassandane::Tiny; sub test_restore_notes_all - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - # force creation of notes mailbox prior to creating notes - my $res = $jmap->CallMethods([ - ['Note/set', { - }, "R0"] - ]); - - my $start = time(); - sleep 2; - - xlog "create notes"; - $res = $jmap->CallMethods([['Note/set', {create => { - "a" => {title => "a"}, - "b" => {title => "b"}, - "c" => {title => "c"}, - "d" => {title => "d"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $noteA = $res->[0][1]{created}{"a"}{id}; - my $noteB = $res->[0][1]{created}{"b"}{id}; - my $noteC = $res->[0][1]{created}{"c"}{id}; - my $noteD = $res->[0][1]{created}{"d"}{id}; - - xlog "destroy note A, update note B"; - $res = $jmap->CallMethods([['Note/set', { - destroy => [$noteA], - update => {$noteB => {title => "B"}} - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - - xlog "get notes"; - $res = $jmap->CallMethods([ - ['Note/get', { - properties => ['title', 'isFlagged'], - }, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/get', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - - my @expect = sort { $a->{title} cmp $b->{title} } @{$res->[0][1]{list}}; - - my $mark = time(); - sleep 2; - - xlog "destroy note C, update notes B and D, create note E"; - $res = $jmap->CallMethods([['Note/set', { - destroy => [$noteC], - update => { - $noteB => {isFlagged => JSON::true}, - $noteD => {isFlagged => JSON::true}, - }, - create => { - "e" => {title => "e"} - } - }, "R4"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - my $noteE = $res->[0][1]{created}{"e"}{id}; - my $state = $res->[0][1]{newState}; - - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; - - xlog "restore notes prior to most recent changes"; - $res = $jmap->CallMethods([['Backup/restoreNotes', { - undoPeriod => $period, - undoAll => JSON::true - }, "R5"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreNotes', $res->[0][0]); - $self->assert_str_equals('R5', $res->[0][2]); - $self->assert_num_equals(1, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(2, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); - - xlog "get restored notes"; - $res = $jmap->CallMethods([ - ['Note/get', { - properties => ['title', 'isFlagged'], - }, "R6"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/get', $res->[0][0]); - $self->assert_str_equals('R6', $res->[0][2]); - - my @got = sort { $a->{title} cmp $b->{title} } @{$res->[0][1]{list}}; - $self->assert_num_equals(scalar @expect, scalar @got); - $self->assert_deep_equals(\@expect, \@got); - - xlog "get note updates"; - $res = $jmap->CallMethods([ - ['Note/changes', { - sinceState => $state - }, "R6.5"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/changes', $res->[0][0]); - $self->assert_str_equals('R6.5', $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals($noteC, $res->[0][1]{created}[0]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($noteE, $res->[0][1]{destroyed}[0]); - $state = $res->[0][1]{newState}; - - $diff = time() - $start; - $period = "PT" . $diff . "S"; - - xlog "restore notes to before initial creation"; - $res = $jmap->CallMethods([['Backup/restoreNotes', { - undoPeriod => $period, - undoAll => JSON::true - }, "R7"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreNotes', $res->[0][0]); - $self->assert_str_equals('R7', $res->[0][2]); - $self->assert_num_equals(3, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(0, $res->[0][1]{numDestroysUndone}); - - xlog "get restored notes"; - $res = $jmap->CallMethods([ - ['Note/get', { - properties => ['title', 'isFlagged'], - }, "R8"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/get', $res->[0][0]); - $self->assert_str_equals('R8', $res->[0][2]); - $self->assert_deep_equals([], $res->[0][1]{list}); - - xlog "get note updates"; - $res = $jmap->CallMethods([ - ['Note/changes', { - sinceState => $state - }, "R8.5"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/changes', $res->[0][0]); - $self->assert_str_equals('R8.5', $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(3, scalar @{$res->[0][1]{destroyed}}); - $state = $res->[0][1]{newState}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + # force creation of notes mailbox prior to creating notes + my $res = $jmap->CallMethods([ [ 'Note/set', {}, "R0" ] ]); + + my $start = time(); + sleep 2; + + xlog "create notes"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + create => { + "a" => { title => "a" }, + "b" => { title => "b" }, + "c" => { title => "c" }, + "d" => { title => "d" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $noteA = $res->[0][1]{created}{"a"}{id}; + my $noteB = $res->[0][1]{created}{"b"}{id}; + my $noteC = $res->[0][1]{created}{"c"}{id}; + my $noteD = $res->[0][1]{created}{"d"}{id}; + + xlog "destroy note A, update note B"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + destroy => [$noteA], + update => { $noteB => { title => "B" } } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + + xlog "get notes"; + $res = $jmap->CallMethods([ [ + 'Note/get', + { + properties => [ 'title', 'isFlagged' ], + }, + "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/get', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + + my @expect = sort { $a->{title} cmp $b->{title} } @{ $res->[0][1]{list} }; + + my $mark = time(); + sleep 2; + + xlog "destroy note C, update notes B and D, create note E"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + destroy => [$noteC], + update => { + $noteB => { isFlagged => JSON::true }, + $noteD => { isFlagged => JSON::true }, + }, + create => { + "e" => { title => "e" } + } + }, + "R4" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + my $noteE = $res->[0][1]{created}{"e"}{id}; + my $state = $res->[0][1]{newState}; + + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; + + xlog "restore notes prior to most recent changes"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreNotes', + { + undoPeriod => $period, + undoAll => JSON::true + }, + "R5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreNotes', $res->[0][0]); + $self->assert_str_equals('R5', $res->[0][2]); + $self->assert_num_equals(1, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(2, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); + + xlog "get restored notes"; + $res = $jmap->CallMethods([ [ + 'Note/get', + { + properties => [ 'title', 'isFlagged' ], + }, + "R6" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/get', $res->[0][0]); + $self->assert_str_equals('R6', $res->[0][2]); + + my @got = sort { $a->{title} cmp $b->{title} } @{ $res->[0][1]{list} }; + $self->assert_num_equals(scalar @expect, scalar @got); + $self->assert_deep_equals(\@expect, \@got); + + xlog "get note updates"; + $res = $jmap->CallMethods([ [ + 'Note/changes', + { + sinceState => $state + }, + "R6.5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/changes', $res->[0][0]); + $self->assert_str_equals('R6.5', $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals($noteC, $res->[0][1]{created}[0]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($noteE, $res->[0][1]{destroyed}[0]); + $state = $res->[0][1]{newState}; + + $diff = time() - $start; + $period = "PT" . $diff . "S"; + + xlog "restore notes to before initial creation"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreNotes', + { + undoPeriod => $period, + undoAll => JSON::true + }, + "R7" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreNotes', $res->[0][0]); + $self->assert_str_equals('R7', $res->[0][2]); + $self->assert_num_equals(3, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(0, $res->[0][1]{numDestroysUndone}); + + xlog "get restored notes"; + $res = $jmap->CallMethods([ [ + 'Note/get', + { + properties => [ 'title', 'isFlagged' ], + }, + "R8" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/get', $res->[0][0]); + $self->assert_str_equals('R8', $res->[0][2]); + $self->assert_deep_equals([], $res->[0][1]{list}); + + xlog "get note updates"; + $res = $jmap->CallMethods([ [ + 'Note/changes', + { + sinceState => $state + }, + "R8.5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/changes', $res->[0][0]); + $self->assert_str_equals('R8.5', $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{destroyed} }); + $state = $res->[0][1]{newState}; } diff --git a/cassandane/tiny-tests/JMAPBackup/restore_notes_all_dryrun b/cassandane/tiny-tests/JMAPBackup/restore_notes_all_dryrun index a2eef7163f..930e682bce 100644 --- a/cassandane/tiny-tests/JMAPBackup/restore_notes_all_dryrun +++ b/cassandane/tiny-tests/JMAPBackup/restore_notes_all_dryrun @@ -2,102 +2,121 @@ use Cassandane::Tiny; sub test_restore_notes_all_dryrun - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # force creation of notes mailbox prior to creating notes - my $res = $jmap->CallMethods([ - ['Note/set', { - }, "R0"] - ]); + # force creation of notes mailbox prior to creating notes + my $res = $jmap->CallMethods([ [ 'Note/set', {}, "R0" ] ]); - xlog "create notes"; - $res = $jmap->CallMethods([['Note/set', {create => { - "a" => {title => "a"}, - "b" => {title => "b"}, - "c" => {title => "c"}, - "d" => {title => "d"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $noteA = $res->[0][1]{created}{"a"}{id}; - my $noteB = $res->[0][1]{created}{"b"}{id}; - my $noteC = $res->[0][1]{created}{"c"}{id}; - my $noteD = $res->[0][1]{created}{"d"}{id}; + xlog "create notes"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + create => { + "a" => { title => "a" }, + "b" => { title => "b" }, + "c" => { title => "c" }, + "d" => { title => "d" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $noteA = $res->[0][1]{created}{"a"}{id}; + my $noteB = $res->[0][1]{created}{"b"}{id}; + my $noteC = $res->[0][1]{created}{"c"}{id}; + my $noteD = $res->[0][1]{created}{"d"}{id}; - xlog "destroy note A, update note B"; - $res = $jmap->CallMethods([['Note/set', { - destroy => [$noteA], - update => {$noteB => {title => "B"}} - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); + xlog "destroy note A, update note B"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + destroy => [$noteA], + update => { $noteB => { title => "B" } } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); - xlog "get notes"; - $res = $jmap->CallMethods([ - ['Note/get', { - properties => ['title', 'isFlagged'], - }, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/get', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); + xlog "get notes"; + $res = $jmap->CallMethods([ [ + 'Note/get', + { + properties => [ 'title', 'isFlagged' ], + }, + "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/get', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); - my @expect = sort { $a->{title} cmp $b->{title} } @{$res->[0][1]{list}}; + my @expect = sort { $a->{title} cmp $b->{title} } @{ $res->[0][1]{list} }; - my $mark = time(); - sleep 2; + my $mark = time(); + sleep 2; - xlog "destroy note C, update notes B and D, create note E"; - $res = $jmap->CallMethods([['Note/set', { - destroy => [$noteC], - update => { - $noteB => {isFlagged => JSON::true}, - $noteD => {isFlagged => JSON::true}, - }, - create => { - "e" => {title => "e"} - } - }, "R4"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/set', $res->[0][0]); - $self->assert_str_equals('R4', $res->[0][2]); - my $noteE = $res->[0][1]{created}{"e"}{id}; - my $state = $res->[0][1]{newState}; + xlog "destroy note C, update notes B and D, create note E"; + $res = $jmap->CallMethods([ [ + 'Note/set', + { + destroy => [$noteC], + update => { + $noteB => { isFlagged => JSON::true }, + $noteD => { isFlagged => JSON::true }, + }, + create => { + "e" => { title => "e" } + } + }, + "R4" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/set', $res->[0][0]); + $self->assert_str_equals('R4', $res->[0][2]); + my $noteE = $res->[0][1]{created}{"e"}{id}; + my $state = $res->[0][1]{newState}; - my $diff = time() - $mark; - my $period = "PT" . $diff . "S"; + my $diff = time() - $mark; + my $period = "PT" . $diff . "S"; - xlog "restore notes prior to most recent changes"; - $res = $jmap->CallMethods([['Backup/restoreNotes', { - performDryRun => JSON::true, - undoPeriod => $period, - undoAll => JSON::true - }, "R5"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Backup/restoreNotes', $res->[0][0]); - $self->assert_str_equals('R5', $res->[0][2]); - $self->assert_num_equals(1, $res->[0][1]{numCreatesUndone}); - $self->assert_num_equals(2, $res->[0][1]{numUpdatesUndone}); - $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); + xlog "restore notes prior to most recent changes"; + $res = $jmap->CallMethods([ [ + 'Backup/restoreNotes', + { + performDryRun => JSON::true, + undoPeriod => $period, + undoAll => JSON::true + }, + "R5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Backup/restoreNotes', $res->[0][0]); + $self->assert_str_equals('R5', $res->[0][2]); + $self->assert_num_equals(1, $res->[0][1]{numCreatesUndone}); + $self->assert_num_equals(2, $res->[0][1]{numUpdatesUndone}); + $self->assert_num_equals(1, $res->[0][1]{numDestroysUndone}); - xlog "get note updates"; - $res = $jmap->CallMethods([ - ['Note/changes', { - sinceState => $state - }, "R6.5"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Note/changes', $res->[0][0]); - $self->assert_str_equals('R6.5', $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); + xlog "get note updates"; + $res = $jmap->CallMethods([ [ + 'Note/changes', + { + sinceState => $state + }, + "R6.5" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Note/changes', $res->[0][0]); + $self->assert_str_equals('R6.5', $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); } diff --git a/cassandane/tiny-tests/JMAPCalendars/account_get_capabilities b/cassandane/tiny-tests/JMAPCalendars/account_get_capabilities index 634d2e2486..527fc9eee6 100644 --- a/cassandane/tiny-tests/JMAPCalendars/account_get_capabilities +++ b/cassandane/tiny-tests/JMAPCalendars/account_get_capabilities @@ -2,51 +2,53 @@ use Cassandane::Tiny; sub test_account_get_capabilities - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $http = $self->{instance}->get_service("http"); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $http = $self->{instance}->get_service("http"); + my $admintalk = $self->{adminstore}->get_client(); - xlog "Get session object"; + xlog "Get session object"; - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - my $session = eval { decode_json($RawResponse->{content}) }; - $self->assert_not_null($session); + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + my $session = eval { decode_json($RawResponse->{content}) }; + $self->assert_not_null($session); - my $capas = $session->{accounts}{cassandane}{accountCapabilities}{'urn:ietf:params:jmap:calendars'}; - $self->assert_not_null($capas); + my $capas = $session->{accounts}{cassandane}{accountCapabilities} + {'urn:ietf:params:jmap:calendars'}; + $self->assert_not_null($capas); - $self->assert_not_null($capas->{minDateTime}); - $self->assert_not_null($capas->{maxDateTime}); - $self->assert_not_null($capas->{maxExpandedQueryDuration}); - $self->assert(exists $capas->{maxParticipantsPerEvent}); - $self->assert_equals(JSON::true, $capas->{mayCreateCalendar}); - $self->assert_num_equals(1, $capas->{maxCalendarsPerEvent}); + $self->assert_not_null($capas->{minDateTime}); + $self->assert_not_null($capas->{maxDateTime}); + $self->assert_not_null($capas->{maxExpandedQueryDuration}); + $self->assert(exists $capas->{maxParticipantsPerEvent}); + $self->assert_equals(JSON::true, $capas->{mayCreateCalendar}); + $self->assert_num_equals(1, $capas->{maxCalendarsPerEvent}); - $capas = $session->{accounts}{cassandane}{accountCapabilities}{'urn:ietf:params:jmap:principals'}; - $self->assert_not_null($capas); - $self->assert_str_equals('cassandane', $capas->{currentUserPrincipalId}); - $self->assert_str_equals('cassandane', - $capas->{'urn:ietf:params:jmap:calendars'}{accountId}); - $self->assert_equals(JSON::true, - $capas->{'urn:ietf:params:jmap:calendars'}{mayGetAvailability}); - $self->assert_not_null($capas->{'urn:ietf:params:jmap:calendars'}{sendTo}); + $capas = $session->{accounts}{cassandane}{accountCapabilities} + {'urn:ietf:params:jmap:principals'}; + $self->assert_not_null($capas); + $self->assert_str_equals('cassandane', $capas->{currentUserPrincipalId}); + $self->assert_str_equals('cassandane', + $capas->{'urn:ietf:params:jmap:calendars'}{accountId}); + $self->assert_equals(JSON::true, + $capas->{'urn:ietf:params:jmap:calendars'}{mayGetAvailability}); + $self->assert_not_null($capas->{'urn:ietf:params:jmap:calendars'}{sendTo}); - $capas = $session->{accounts}{cassandane}{accountCapabilities}{'urn:ietf:params:jmap:principals:owner'}; - $self->assert_not_null($capas); - $self->assert_str_equals('cassandane', $capas->{accountIdForPrincipal}); - $self->assert_str_equals('cassandane', $capas->{principalId}); + $capas = $session->{accounts}{cassandane}{accountCapabilities} + {'urn:ietf:params:jmap:principals:owner'}; + $self->assert_not_null($capas); + $self->assert_str_equals('cassandane', $capas->{accountIdForPrincipal}); + $self->assert_str_equals('cassandane', $capas->{principalId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/account_get_shareesactas b/cassandane/tiny-tests/JMAPCalendars/account_get_shareesactas index e53927217a..fe93f8bcec 100644 --- a/cassandane/tiny-tests/JMAPCalendars/account_get_shareesactas +++ b/cassandane/tiny-tests/JMAPCalendars/account_get_shareesactas @@ -2,38 +2,38 @@ use Cassandane::Tiny; sub test_account_get_shareesactas - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $http = $self->{instance}->get_service("http"); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $http = $self->{instance}->get_service("http"); + my $admintalk = $self->{adminstore}->get_client(); - my $getCapas = sub { - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - my $session = eval { decode_json($RawResponse->{content}) }; - $self->assert_not_null($session); - return $session->{accounts}{cassandane}{accountCapabilities}{'urn:ietf:params:jmap:calendars'}; + my $getCapas = sub { + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + my $session = eval { decode_json($RawResponse->{content}) }; + $self->assert_not_null($session); + return $session->{accounts}{cassandane}{accountCapabilities} + {'urn:ietf:params:jmap:calendars'}; + }; - xlog "Sharees act as self"; - my $capas = $getCapas->(); - $self->assert_str_equals('self', $capas->{shareesActAs}); + xlog "Sharees act as self"; + my $capas = $getCapas->(); + $self->assert_str_equals('self', $capas->{shareesActAs}); - xlog "Sharees act as secretary"; + xlog "Sharees act as secretary"; - my $xml = < @@ -43,13 +43,15 @@ sub test_account_get_shareesactas EOF - $caldav->Request('PROPPATCH', "/dav/calendars/user/cassandane", $xml, - 'Content-Type' => 'text/xml'); + $caldav->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane", + $xml, 'Content-Type' => 'text/xml' + ); - $capas = $getCapas->(); - $self->assert_str_equals('secretary', $capas->{shareesActAs}); + $capas = $getCapas->(); + $self->assert_str_equals('secretary', $capas->{shareesActAs}); - $xml = < @@ -59,9 +61,11 @@ EOF EOF - $caldav->Request('PROPPATCH', "/dav/calendars/user/cassandane", $xml, - 'Content-Type' => 'text/xml'); + $caldav->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane", + $xml, 'Content-Type' => 'text/xml' + ); - $capas = $getCapas->(); - $self->assert_str_equals('self', $capas->{shareesActAs}); + $capas = $getCapas->(); + $self->assert_str_equals('self', $capas->{shareesActAs}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/admin_migrate39_defaultalerts b/cassandane/tiny-tests/JMAPCalendars/admin_migrate39_defaultalerts index 2c1b024550..5e936a914d 100644 --- a/cassandane/tiny-tests/JMAPCalendars/admin_migrate39_defaultalerts +++ b/cassandane/tiny-tests/JMAPCalendars/admin_migrate39_defaultalerts @@ -4,265 +4,259 @@ use Cassandane::Tiny; use Data::ICal; sub test_admin_migrate39_defaultalerts - :needs_component_jmap :min_version_3_9 :ReverseACLs :MagicPlus -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - - my @using = qw( - urn:ietf:params:jmap:core - urn:ietf:params:jmap:calendars - https://cyrusimap.org/ns/jmap/admin - https://cyrusimap.org/ns/jmap/calendars - https://cyrusimap.org/ns/jmap/debug - https://cyrusimap.org/ns/jmap/performance - ); - - my $http = $self->{instance}->get_service("http"); - my $adminJmap = Mail::JMAPTalk->new( - user => 'admin', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $adminJmap->DefaultUsing(\@using); + : needs_component_jmap : min_version_3_9 : ReverseACLs : MagicPlus { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + + my @using = qw( + urn:ietf:params:jmap:core + urn:ietf:params:jmap:calendars + https://cyrusimap.org/ns/jmap/admin + https://cyrusimap.org/ns/jmap/calendars + https://cyrusimap.org/ns/jmap/debug + https://cyrusimap.org/ns/jmap/performance + ); + + my $http = $self->{instance}->get_service("http"); + my $adminJmap = Mail::JMAPTalk->new( + user => 'admin', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $adminJmap->DefaultUsing(\@using); + + xlog $self, "Make sure regular user can't call Admin method"; + my $res = $jmap->CallMethods( + [ [ 'Admin/migrateCalendarDefaultAlarms', {}, 'R1' ], ], \@using); + $self->assert_str_equals('accountNotSupportedByMethod', $res->[0][1]{type}); + + my $state = { did_migrate => 0 }; + + $self->assert_calendars($state); + $self->assert_events($state); + $self->assert_shared_calendars($state); + + xlog $self, "Migrate default alarms"; + $res = $adminJmap->CallMethods([ + [ 'Admin/migrateCalendarDefaultAlarms', {}, 'R1' ], ]); + + $state->{did_migrate} = 1; + $state->{migrateResponse} = $res->[0][1]; + + $self->assert_calendars($state); + $self->assert_events($state); + $self->assert_shared_calendars($state); +} - xlog $self, "Make sure regular user can't call Admin method"; - my $res = $jmap->CallMethods([ - ['Admin/migrateCalendarDefaultAlarms', {}, 'R1'], - ], \@using); - $self->assert_str_equals('accountNotSupportedByMethod', - $res->[0][1]{type}); - - my $state = { - did_migrate => 0 - }; - - $self->assert_calendars($state); - $self->assert_events($state); - $self->assert_shared_calendars($state); - - xlog $self, "Migrate default alarms"; - $res = $adminJmap->CallMethods([ - ['Admin/migrateCalendarDefaultAlarms', { }, 'R1'], - ]); +sub assert_calendars { + my ($self, $state) = @_; - $state->{did_migrate} = 1; - $state->{migrateResponse} = $res->[0][1]; + my $caldav = $self->{caldav}; + my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; - $self->assert_calendars($state); - $self->assert_events($state); - $self->assert_shared_calendars($state); -} + if (not $state->{did_migrate}) { + xlog $self, "Create calendars with legacy CalDAV alarms"; + $state->{calendarA} = { id => $self->create_legacy_calendar('A'), }; + $state->{calendarB} = { id => $self->create_legacy_calendar('B'), }; -sub assert_calendars -{ - my ($self, $state) = @_; - - my $caldav = $self->{caldav}; - my $imap = $self->{store}->get_client(); - my $jmap = $self->{jmap}; - - if (not $state->{did_migrate}) { - xlog $self, "Create calendars with legacy CalDAV alarms"; - $state->{calendarA} = { - id => $self->create_legacy_calendar('A'), - }; - $state->{calendarB} = { - id => $self->create_legacy_calendar('B'), - }; - - xlog $self, "Set CalDAV alarms on calendar A and calendar home"; - # That's not a representative example but allows to assert - # that migration adds a UID and removes the X-APPLE property. - my $valarms = <set_caldav_datetime_alarms($state->{calendarA}{id}, $valarms); - $self->set_caldav_datetime_alarms(undef, $valarms); - $state->{calendarA}{caldavAlarms} = $valarms; - } - - xlog $self, "Assert calendar default alerts"; - - $res = $jmap->CallMethods([ - ['Calendar/get', { - properties => [ - 'defaultAlertsWithTime', - 'defaultAlertsWithoutTime', - ], - }, 'R1'], - ]); - my %calendars = map { $_->{id} => $_ } @{$res->[0][1]{list}}; - - $self->assert_null($calendars{ - Default}{defaultAlertsWithTime}); - $self->assert_null($calendars{ - Default}{defaultAlertsWithoutTime}); - - my $calendarADefaultAlerts = $calendars{$state->{ - calendarA}{id}}{defaultAlertsWithTime}; - $self->assert_num_equals(1, scalar keys %$calendarADefaultAlerts); - $self->assert_null($calendars{$state->{ - calendarA}{id}}{defaultAlertsWithoutTime}); - - $self->assert_null($calendars{$state->{ - calendarB}{id}}{defaultAlertsWithTime}); - $self->assert_null($calendars{$state->{ - calendarB}{id}}{defaultAlertsWithoutTime}); - - if (not $state->{did_migrate}) { - $state->{calendarA}{defaultAlert} = (values %$calendarADefaultAlerts)[0]; - } else { - # Did migrate calendars - $self->assert_deep_equals({ - $state->{calendarA}{id} => undef, - $state->{calendarB}{id} => undef, - Default => undef, - }, $state->{migrateResponse}{migrated}{cassandane}); - - # Migration rewrites default alert UID, if none was set - $self->assert_matches(qr/UID:[0-9A-Za-z-]+/, - $self->get_jmap_defaultalerts_annotation($state->{calendarA}{id})); - - # JMAP annotation is set on both calendars - $self->assert_not_null($self->get_jmap_defaultalerts_annotation( - $state->{calendarA}{id})); - - $self->assert_not_null($self->get_jmap_defaultalerts_annotation( - $state->{calendarB}{id})); - - # CalDAV default alarms annotation got removed - $self->assert_null($self->get_caldav_datetime_annotation( - $state->{calendarA}{id})); - - $self->assert_null($self->get_caldav_datetime_annotation( - $state->{calendarB}{id})); - - # CalDAV default alarms annotation is kept on calendar home - $self->assert_not_null($self->get_caldav_datetime_annotation(undef)); - } -} - -sub assert_events -{ - my ($self, $state) = @_; - - my $caldav = $self->{caldav}; - my $jmap = $self->{jmap}; - - if (not $state->{did_migrate}) { - # First, create events - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventA => { - calendarIds => { - $state->{calendarA}{id} => JSON::true, - }, - title => "eventA", - start => "2023-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, - }, - eventB1 => { - calendarIds => { - $state->{calendarB}{id} => JSON::true, - }, - title => "eventB1", - start => "2023-01-20T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, - }, - eventB2 => { - calendarIds => { - $state->{calendarB}{id} => JSON::true, - }, - title => "eventB2", - start => "2023-01-21T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::false, - }, - }, - }, 'R1'], - ]); - - $state->{eventA} = { - xhref => $res->[0][1]{created}{eventA}{'x-href'} - }; - $self->assert_not_null($state->{eventA}{xhref}); - - $state->{eventB1} = { - xhref => $res->[0][1]{created}{eventB1}{'x-href'} - }; - $self->assert_not_null($state->{eventB1}{xhref}); - - $state->{eventB2} = { - xhref => $res->[0][1]{created}{eventB2}{'x-href'} - }; - $self->assert_not_null($state->{eventB2}{xhref}); - } + $self->set_caldav_datetime_alarms($state->{calendarA}{id}, $valarms); + $self->set_caldav_datetime_alarms(undef, $valarms); + $state->{calendarA}{caldavAlarms} = $valarms; + } + + xlog $self, "Assert calendar default alerts"; + + $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime', ], + }, + 'R1' + ], + ]); + my %calendars = map { $_->{id} => $_ } @{ $res->[0][1]{list} }; + + $self->assert_null($calendars{Default}{defaultAlertsWithTime}); + $self->assert_null($calendars{Default}{defaultAlertsWithoutTime}); + + my $calendarADefaultAlerts + = $calendars{ $state->{calendarA}{id} }{defaultAlertsWithTime}; + $self->assert_num_equals(1, scalar keys %$calendarADefaultAlerts); + $self->assert_null( + $calendars{ $state->{calendarA}{id} }{defaultAlertsWithoutTime}); + + $self->assert_null( + $calendars{ $state->{calendarB}{id} }{defaultAlertsWithTime}); + $self->assert_null( + $calendars{ $state->{calendarB}{id} }{defaultAlertsWithoutTime}); + + if (not $state->{did_migrate}) { + $state->{calendarA}{defaultAlert} = (values %$calendarADefaultAlerts)[0]; + } else { + # Did migrate calendars + $self->assert_deep_equals( + { + $state->{calendarA}{id} => undef, + $state->{calendarB}{id} => undef, + Default => undef, + }, + $state->{migrateResponse}{migrated}{cassandane} + ); - my ($veventA, $etagA) = $self->get_vevent($caldav, $state->{eventA}{xhref}); - my @valarmsA = grep { $_->ical_entry_type() eq 'VALARM' } @{$veventA->entries()}; - $self->assert_num_equals(1, scalar @valarmsA); + # Migration rewrites default alert UID, if none was set + $self->assert_matches(qr/UID:[0-9A-Za-z-]+/, + $self->get_jmap_defaultalerts_annotation($state->{calendarA}{id})); - my ($veventB1, $etagB1) = $self->get_vevent($caldav, $state->{eventB1}{xhref}); - my @valarmsB1 = grep { $_->ical_entry_type() eq 'VALARM' } @{$veventB1->entries()}; - $self->assert_num_equals(0, scalar @valarmsB1); + # JMAP annotation is set on both calendars + $self->assert_not_null($self->get_jmap_defaultalerts_annotation( + $state->{calendarA}{id})); - my ($veventB2, $etagB2) = $self->get_vevent($caldav, $state->{eventB2}{xhref}); - my @valarmsB2 = grep { $_->ical_entry_type() eq 'VALARM' } @{$veventB2->entries()}; - $self->assert_num_equals(0, scalar @valarmsB2); + $self->assert_not_null($self->get_jmap_defaultalerts_annotation( + $state->{calendarB}{id})); - if (not $state->{did_migrate}) { - $state->{eventA}{etag} = $etagA; - $state->{eventB1}{etag} = $etagB1; - $state->{eventB2}{etag} = $etagB2; - } else { - # Event A ETag must have changed - $self->assert_str_not_equals($state->{eventA}{etag}, $etagA); + # CalDAV default alarms annotation got removed + $self->assert_null($self->get_caldav_datetime_annotation( + $state->{calendarA}{id})); - # Event B1 ETag must have changed - $self->assert_str_not_equals($state->{eventB1}{etag}, $etagB1); + $self->assert_null($self->get_caldav_datetime_annotation( + $state->{calendarB}{id})); - # Event B@ ETag must not have changed - $self->assert_str_equals($state->{eventB2}{etag}, $etagB2); - } + # CalDAV default alarms annotation is kept on calendar home + $self->assert_not_null($self->get_caldav_datetime_annotation(undef)); + } } -sub assert_shared_calendars -{ - my ($self, $state) = @_; +sub assert_events { + my ($self, $state) = @_; - my $eventUidA1 = '40d6fe3c-6a51-489e-823e-3ea22f427a3e'; - my $eventUidA2 = '00a90af9-8398-4074-94bf-6251a1ab9e70'; - my $eventUidB1 = '93c7831d-e246-4dda-ab3f-52acba6b9e3b'; - my $eventUidB2 = '379e7061-d20f-45e4-8366-52d45836a7fd'; + my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; - if (not $state->{did_migrate}) { - xlog $self, "Create sharee"; - ($self->{shareeJmap}, $self->{shareeCaldav}) = $self->create_user('sharee'); + if (not $state->{did_migrate}) { + # First, create events + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + eventA => { + calendarIds => { + $state->{calendarA}{id} => JSON::true, + }, + title => "eventA", + start => "2023-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + eventB1 => { + calendarIds => { + $state->{calendarB}{id} => JSON::true, + }, + title => "eventB1", + start => "2023-01-20T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + eventB2 => { + calendarIds => { + $state->{calendarB}{id} => JSON::true, + }, + title => "eventB2", + start => "2023-01-21T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::false, + }, + }, + }, + 'R1' + ], + ]); - xlog $self, "Share calendars"; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->setacl("user.cassandane.#calendars.$state->{calendarA}{id}", - sharee => 'lrswipkxtecdn') or die; - $admintalk->setacl("user.cassandane.#calendars.$state->{calendarB}{id}", - sharee => 'lrswipkxtecdn') or die; + $state->{eventA} = { xhref => $res->[0][1]{created}{eventA}{'x-href'} }; + $self->assert_not_null($state->{eventA}{xhref}); + + $state->{eventB1} = { xhref => $res->[0][1]{created}{eventB1}{'x-href'} }; + $self->assert_not_null($state->{eventB1}{xhref}); + + $state->{eventB2} = { xhref => $res->[0][1]{created}{eventB2}{'x-href'} }; + $self->assert_not_null($state->{eventB2}{xhref}); + } + + my ($veventA, $etagA) = $self->get_vevent($caldav, $state->{eventA}{xhref}); + my @valarmsA + = grep { $_->ical_entry_type() eq 'VALARM' } @{ $veventA->entries() }; + $self->assert_num_equals(1, scalar @valarmsA); + + my ($veventB1, $etagB1) + = $self->get_vevent($caldav, $state->{eventB1}{xhref}); + my @valarmsB1 + = grep { $_->ical_entry_type() eq 'VALARM' } @{ $veventB1->entries() }; + $self->assert_num_equals(0, scalar @valarmsB1); + + my ($veventB2, $etagB2) + = $self->get_vevent($caldav, $state->{eventB2}{xhref}); + my @valarmsB2 + = grep { $_->ical_entry_type() eq 'VALARM' } @{ $veventB2->entries() }; + $self->assert_num_equals(0, scalar @valarmsB2); + + if (not $state->{did_migrate}) { + $state->{eventA}{etag} = $etagA; + $state->{eventB1}{etag} = $etagB1; + $state->{eventB2}{etag} = $etagB2; + } else { + # Event A ETag must have changed + $self->assert_str_not_equals($state->{eventA}{etag}, $etagA); + + # Event B1 ETag must have changed + $self->assert_str_not_equals($state->{eventB1}{etag}, $etagB1); + + # Event B@ ETag must not have changed + $self->assert_str_equals($state->{eventB2}{etag}, $etagB2); + } +} - xlog $self, "Create sharee useDefaultAlerts=true in calendar A"; - my $ical = <{did_migrate}) { + xlog $self, "Create sharee"; + ($self->{shareeJmap}, $self->{shareeCaldav}) = $self->create_user('sharee'); + + xlog $self, "Share calendars"; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->setacl("user.cassandane.#calendars.$state->{calendarA}{id}", + sharee => 'lrswipkxtecdn') + or die; + $admintalk->setacl("user.cassandane.#calendars.$state->{calendarB}{id}", + sharee => 'lrswipkxtecdn') + or die; + + xlog $self, "Create sharee useDefaultAlerts=true in calendar A"; + my $ical = <{shareeCaldav}->Request('PUT', - "cassandane.$state->{calendarA}{id}/$eventUidA1.ics", - $ical, - 'Content-Type' => 'text/calendar', - 'X-Cyrus-rewrite-usedefaultalerts' => 'false', - ); - - xlog $self, "Create sharee per-user prop in calendar A"; - my $ical = <{shareeCaldav}->Request( + 'PUT', + "cassandane.$state->{calendarA}{id}/$eventUidA1.ics", + $ical, + 'Content-Type' => 'text/calendar', + 'X-Cyrus-rewrite-usedefaultalerts' => 'false', + ); + + xlog $self, "Create sharee per-user prop in calendar A"; + my $ical = <{shareeCaldav}->Request('PUT', - "cassandane.$state->{calendarA}{id}/$eventUidA2.ics", - $ical, - 'Content-Type' => 'text/calendar', - 'X-Cyrus-rewrite-usedefaultalerts' => 'false', - ); - - xlog $self, "Create sharee useDefaultAlerts=true in calendar B"; - $ical = <{shareeCaldav}->Request( + 'PUT', + "cassandane.$state->{calendarA}{id}/$eventUidA2.ics", + $ical, + 'Content-Type' => 'text/calendar', + 'X-Cyrus-rewrite-usedefaultalerts' => 'false', + ); + + xlog $self, "Create sharee useDefaultAlerts=true in calendar B"; + $ical = <{shareeCaldav}->Request('PUT', - "cassandane.$state->{calendarB}{id}/$eventUidB1.ics", - $ical, - 'Content-Type' => 'text/calendar', - 'X-Cyrus-rewrite-usedefaultalerts' => 'false', - ); - - xlog $self, "Create sharee per-user prop in calendar B"; - $ical = <{shareeCaldav}->Request( + 'PUT', + "cassandane.$state->{calendarB}{id}/$eventUidB1.ics", + $ical, + 'Content-Type' => 'text/calendar', + 'X-Cyrus-rewrite-usedefaultalerts' => 'false', + ); + + xlog $self, "Create sharee per-user prop in calendar B"; + $ical = <{shareeCaldav}->Request('PUT', - "cassandane.$state->{calendarB}{id}/$eventUidB2.ics", - $ical, - 'Content-Type' => 'text/calendar', - 'X-Cyrus-rewrite-usedefaultalerts' => 'false', - ); - } - - my $res = $self->{shareeJmap}->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - properties => ['useDefaultAlerts', 'alerts', 'color', 'uid'], - }, 'R1'], - ]); - my %eventsByUid = map { $_->{uid} => $_ } @{$res->[0][1]{list}}; - - my $eventA1 = $eventsByUid{$eventUidA1}; - $self->assert_not_null($eventA1); - $self->assert_str_equals('blue', $eventA1->{color}); - - my $eventA2 = $eventsByUid{$eventUidA2}; - $self->assert_not_null($eventA2); - $self->assert_str_equals('red', $eventA2->{color}); - $self->assert_equals(JSON::false, $eventA2->{useDefaultAlerts}); - $self->assert_null($eventA2->{alerts}); - - my $eventB1 = $eventsByUid{$eventUidB1}; - $self->assert_not_null($eventB1); - $self->assert_null($eventB1->{color}); - - my $eventB2 = $eventsByUid{$eventUidB2}; - $self->assert_not_null($eventB2); - $self->assert_str_equals('brown', $eventB2->{color}); - $self->assert_equals(JSON::false, $eventB2->{useDefaultAlerts}); - $self->assert_null($eventB2->{alerts}); - - if (not $state->{did_migrate}) { - $self->assert_equals(JSON::true, $eventA1->{useDefaultAlerts}); - $self->assert_null($eventA1->{alerts}); - - $self->assert_equals(JSON::true, $eventB1->{useDefaultAlerts}); - $self->assert_null($eventB1->{alerts}); - } - else { - $self->assert_deep_equals({ - Default => undef, - }, $state->{migrateResponse}{migrated}{sharee}); + $self->{shareeCaldav}->Request( + 'PUT', + "cassandane.$state->{calendarB}{id}/$eventUidB2.ics", + $ical, + 'Content-Type' => 'text/calendar', + 'X-Cyrus-rewrite-usedefaultalerts' => 'false', + ); + } + + my $res = $self->{shareeJmap}->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + properties => [ 'useDefaultAlerts', 'alerts', 'color', 'uid' ], + }, + 'R1' + ], + ]); + my %eventsByUid = map { $_->{uid} => $_ } @{ $res->[0][1]{list} }; + + my $eventA1 = $eventsByUid{$eventUidA1}; + $self->assert_not_null($eventA1); + $self->assert_str_equals('blue', $eventA1->{color}); + + my $eventA2 = $eventsByUid{$eventUidA2}; + $self->assert_not_null($eventA2); + $self->assert_str_equals('red', $eventA2->{color}); + $self->assert_equals(JSON::false, $eventA2->{useDefaultAlerts}); + $self->assert_null($eventA2->{alerts}); + + my $eventB1 = $eventsByUid{$eventUidB1}; + $self->assert_not_null($eventB1); + $self->assert_null($eventB1->{color}); + + my $eventB2 = $eventsByUid{$eventUidB2}; + $self->assert_not_null($eventB2); + $self->assert_str_equals('brown', $eventB2->{color}); + $self->assert_equals(JSON::false, $eventB2->{useDefaultAlerts}); + $self->assert_null($eventB2->{alerts}); + + if (not $state->{did_migrate}) { + $self->assert_equals(JSON::true, $eventA1->{useDefaultAlerts}); + $self->assert_null($eventA1->{alerts}); + + $self->assert_equals(JSON::true, $eventB1->{useDefaultAlerts}); + $self->assert_null($eventB1->{alerts}); + } else { + $self->assert_deep_equals( + { + Default => undef, + }, + $state->{migrateResponse}{migrated}{sharee} + ); - $self->assert_equals(JSON::false, $eventA1->{useDefaultAlerts}); - $self->assert_not_null($eventA1->{alerts}); + $self->assert_equals(JSON::false, $eventA1->{useDefaultAlerts}); + $self->assert_not_null($eventA1->{alerts}); - $self->assert_equals(JSON::false, $eventB1->{useDefaultAlerts}); - $self->assert_null($eventB1->{alerts}); - } + $self->assert_equals(JSON::false, $eventB1->{useDefaultAlerts}); + $self->assert_null($eventB1->{alerts}); + } } # All the remaining functions just make the test code less gnarly -sub get_vevent -{ - my ($self, $caldav, $eventHref) = @_; - - xlog $self, "GET event"; - my %headers = ( - 'Content-Type' => 'text/calendar', - 'Authorization' => $caldav->auth_header(), - ); - my $res = $caldav->{ua}->request('GET', - $caldav->request_url($eventHref), { - headers => \%headers, - }); - $self->assert_str_equals('200', $res->{status}); - - my $vcalendar = Data::ICal->new(data => $res->{content}); - my @vevents = grep { $_->ical_entry_type() eq 'VEVENT' } @{$vcalendar->entries()}; - my $vevent = $vevents[0]; - $self->assert_not_null($vevent); - return ($vevent, $res->{headers}{etag}); +sub get_vevent { + my ($self, $caldav, $eventHref) = @_; + + xlog $self, "GET event"; + my %headers = ( + 'Content-Type' => 'text/calendar', + 'Authorization' => $caldav->auth_header(), + ); + my $res = $caldav->{ua}->request( + 'GET', + $caldav->request_url($eventHref), + { + headers => \%headers, + } + ); + $self->assert_str_equals('200', $res->{status}); + + my $vcalendar = Data::ICal->new(data => $res->{content}); + my @vevents + = grep { $_->ical_entry_type() eq 'VEVENT' } @{ $vcalendar->entries() }; + my $vevent = $vevents[0]; + $self->assert_not_null($vevent); + return ($vevent, $res->{headers}{etag}); } -sub get_jmap_defaultalerts_annotation -{ - my ($self, $calendarId) = @_; - my $imap = $self->{store}->get_client(); +sub get_jmap_defaultalerts_annotation { + my ($self, $calendarId) = @_; + my $imap = $self->{store}->get_client(); - my $mboxname = '#calendars'; - $mboxname .= ".$calendarId" if $calendarId; + my $mboxname = '#calendars'; + $mboxname .= ".$calendarId" if $calendarId; - my $res = $imap->getmetadata($mboxname, - '/private/vendor/cmu/cyrus-jmap/defaultalerts'); - return $res->{$mboxname}{ - '/private/vendor/cmu/cyrus-jmap/defaultalerts'}; + my $res = $imap->getmetadata($mboxname, + '/private/vendor/cmu/cyrus-jmap/defaultalerts'); + return $res->{$mboxname}{'/private/vendor/cmu/cyrus-jmap/defaultalerts'}; } -sub get_caldav_datetime_annotation -{ - my ($self, $calendarId) = @_; - my $imap = $self->{store}->get_client(); +sub get_caldav_datetime_annotation { + my ($self, $calendarId) = @_; + my $imap = $self->{store}->get_client(); - my $mboxname = '#calendars'; - $mboxname .= ".$calendarId" if $calendarId; + my $mboxname = '#calendars'; + $mboxname .= ".$calendarId" if $calendarId; - my $res = $imap->getmetadata($mboxname, - '/shared/vendor/cmu/cyrus-httpd/default-alarm-vevent-datetime'); - return $res->{$mboxname}{ - '/shared/vendor/cmu/cyrus-httpd/default-alarm-vevent-datetime'}; + my $res = $imap->getmetadata($mboxname, + '/shared/vendor/cmu/cyrus-httpd/default-alarm-vevent-datetime' + ); + return $res->{$mboxname}{ + '/shared/vendor/cmu/cyrus-httpd/default-alarm-vevent-datetime' + }; } +sub create_legacy_calendar { + my ($self, $name) = @_; + my $caldav = $self->{caldav}; + my $plusstore = $self->{instance}->get_service( + 'imap' + )->create_store(username => 'cassandane+dav'); + my $imap = $plusstore->get_client(); -sub create_legacy_calendar -{ - my ($self, $name) = @_; - my $caldav = $self->{caldav}; - my $plusstore = $self->{instance}->get_service('imap' - )->create_store(username => 'cassandane+dav'); - my $imap = $plusstore->get_client(); - - xlog $self, "Create calendar named $name"; - my $calendarId = $caldav->NewCalendar({name => $name}); - $self->assert_not_null($calendarId); + xlog $self, "Create calendar named $name"; + my $calendarId = $caldav->NewCalendar({ name => $name }); + $self->assert_not_null($calendarId); - xlog $self, "Remove JMAP default alert annotation"; - $imap->setmetadata("#calendars.$calendarId", - '/private/vendor/cmu/cyrus-jmap/defaultalerts', ''); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); + xlog $self, "Remove JMAP default alert annotation"; + $imap->setmetadata("#calendars.$calendarId", + '/private/vendor/cmu/cyrus-jmap/defaultalerts', ''); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); - return $calendarId; + return $calendarId; } -sub set_caldav_datetime_alarms -{ - my ($self, $calendarId, $valarms) = @_; - my $plusstore = $self->{instance}->get_service('imap' - )->create_store(username => 'cassandane+dav'); - my $imap = $plusstore->get_client(); - - my $mboxname = '#calendars'; - $mboxname .= ".$calendarId" if $calendarId; - - $imap->setmetadata($mboxname, - '/shared/vendor/cmu/cyrus-httpd/default-alarm-vevent-datetime', $valarms); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); +sub set_caldav_datetime_alarms { + my ($self, $calendarId, $valarms) = @_; + my $plusstore = $self->{instance}->get_service( + 'imap' + )->create_store(username => 'cassandane+dav'); + my $imap = $plusstore->get_client(); + + my $mboxname = '#calendars'; + $mboxname .= ".$calendarId" if $calendarId; + + $imap->setmetadata( + $mboxname, + '/shared/vendor/cmu/cyrus-httpd/default-alarm-vevent-datetime', + $valarms + ); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/JMAPCalendars/admin_rewrite_calendarevent_privacy b/cassandane/tiny-tests/JMAPCalendars/admin_rewrite_calendarevent_privacy index a4cc4c6a00..74bce19d70 100644 --- a/cassandane/tiny-tests/JMAPCalendars/admin_rewrite_calendarevent_privacy +++ b/cassandane/tiny-tests/JMAPCalendars/admin_rewrite_calendarevent_privacy @@ -4,163 +4,171 @@ use Cassandane::Tiny; use DBI; sub test_admin_rewrite_calendarevent_privacy - :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my @using = qw( + my @using = qw( urn:ietf:params:jmap:core urn:ietf:params:jmap:calendars https://cyrusimap.org/ns/jmap/admin https://cyrusimap.org/ns/jmap/calendars https://cyrusimap.org/ns/jmap/debug https://cyrusimap.org/ns/jmap/performance -); - - xlog $self, "Make sure regular user can't call Admin method"; - $res = $jmap->CallMethods([ - ['Admin/rewriteCalendarEventPrivacy', {}, 'R1'], - ], \@using); - $self->assert_str_equals('accountNotSupportedByMethod', - $res->[0][1]{type}); - - my $event1Uid = '40d11f36-245b-4a03-8034-df25f38f9f61'; - - xlog $self, "create two calendar events"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => $event1Uid, - title => 'event1', - start => '2020-01-01T09:00:00', - timeZone => 'Europe/Vienna', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - attendee1 => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee1@example.com', - }, - }, - }, + ); + + xlog $self, "Make sure regular user can't call Admin method"; + $res + = $jmap->CallMethods([ [ 'Admin/rewriteCalendarEventPrivacy', {}, 'R1' ], ], + \@using); + $self->assert_str_equals('accountNotSupportedByMethod', $res->[0][1]{type}); + + my $event1Uid = '40d11f36-245b-4a03-8034-df25f38f9f61'; + + xlog $self, "create two calendar events"; + my $res = $jmap->CallMethods( + [ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + uid => $event1Uid, + title => 'event1', + start => '2020-01-01T09:00:00', + timeZone => 'Europe/Vienna', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, }, - event2 => { - calendarIds => { - Default => JSON::true, - }, - uid => '6b18a778-5827-49b9-a2f1-4f67d7be2b6b', - title => 'event2', - start => '2020-02-02T09:00:00', - timeZone => 'Europe/Vienna', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - attendee1 => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee1@example.com', - }, - }, - }, + attendee1 => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee1@example.com', + }, }, - + }, }, - }, 'R1'], - ], \@using); - $self->assert_not_null($res->[0][1]{created}{event1}); - $self->assert_not_null($res->[0][1]{created}{event2}); - my $state = $res->[0][1]{newState}; - $self->assert_not_null($state); - - my $http = $self->{instance}->get_service("http"); - my $adminJmap = Mail::JMAPTalk->new( - user => 'admin', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $adminJmap->DefaultUsing(\@using); - - - my $dirs = ($self->{instance}->run_mbpath(-u => 'cassandane')); - my $db = DBI->connect("dbi:SQLite:dbname=$dirs->{user}{dav}","",""); - - xlog $self, "Make sure no calendar event is marked private"; - my $selectStmt = $db->prepare( - "SELECT rowid FROM ical_objs WHERE comp_flags >= 1024"); - $selectStmt->execute(); - $self->assert_null($selectStmt->fetch()); - - xlog $self, "Manually mark one calendar event private"; - my $updateStmt = $db->prepare( - "UPDATE ical_objs SET comp_flags = 1024 WHERE ical_uid = '$event1Uid'"); - $res = $updateStmt->execute(); - $self->assert_num_equals(1, $res); - - xlog $self, "Clear notifications"; - $self->{instance}->getnotify(); - - xlog $self, "Rewrite calendar event privacy as admin"; - $res = $adminJmap->CallMethods([ - ['Admin/rewriteCalendarEventPrivacy', {}, 'R1'], - ]); - $self->assert_num_equals(1, - scalar keys %{$res->[0][1]{rewritten}{cassandane}}); - $self->assert_null($res->[0][1]{notRewritten}); - - xlog $self, "Make sure no calendar event is marked private"; - $selectStmt->execute(); - $self->assert_null($selectStmt->fetch()); - - xlog $self, "Assert no iMIP notifications are sent"; - my $data = $self->{instance}->getnotify(); - $self->assert_num_equals(0, scalar grep { $_->{METHOD} eq 'imip' } @$data); - - xlog $self, "Assert new event message is sent to pusher"; - my @newevent = grep { - $_->{METHOD} eq 'pusher' and $_->{MESSAGE} =~ '{"event":"MessageNew"' - } @$data; - $self->assert_num_equals(1, scalar @newevent); - - xlog $self, "Assert CalendarEvent state changed"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [], - }, 'R1'] - ], \@using); - $self->assert_str_not_equals($state, $res->[0][1]{state}); + event2 => { + calendarIds => { + Default => JSON::true, + }, + uid => '6b18a778-5827-49b9-a2f1-4f67d7be2b6b', + title => 'event2', + start => '2020-02-02T09:00:00', + timeZone => 'Europe/Vienna', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + attendee1 => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee1@example.com', + }, + }, + }, + }, + + }, + }, + 'R1' + ], + ], + \@using + ); + $self->assert_not_null($res->[0][1]{created}{event1}); + $self->assert_not_null($res->[0][1]{created}{event2}); + my $state = $res->[0][1]{newState}; + $self->assert_not_null($state); + + my $http = $self->{instance}->get_service("http"); + my $adminJmap = Mail::JMAPTalk->new( + user => 'admin', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $adminJmap->DefaultUsing(\@using); + + my $dirs = ($self->{instance}->run_mbpath(-u => 'cassandane')); + my $db = DBI->connect("dbi:SQLite:dbname=$dirs->{user}{dav}", "", ""); + + xlog $self, "Make sure no calendar event is marked private"; + my $selectStmt + = $db->prepare("SELECT rowid FROM ical_objs WHERE comp_flags >= 1024"); + $selectStmt->execute(); + $self->assert_null($selectStmt->fetch()); + + xlog $self, "Manually mark one calendar event private"; + my $updateStmt = $db->prepare( + "UPDATE ical_objs SET comp_flags = 1024 WHERE ical_uid = '$event1Uid'"); + $res = $updateStmt->execute(); + $self->assert_num_equals(1, $res); + + xlog $self, "Clear notifications"; + $self->{instance}->getnotify(); + + xlog $self, "Rewrite calendar event privacy as admin"; + $res = $adminJmap->CallMethods([ + [ 'Admin/rewriteCalendarEventPrivacy', {}, 'R1' ], ]); + $self->assert_num_equals(1, + scalar keys %{ $res->[0][1]{rewritten}{cassandane} }); + $self->assert_null($res->[0][1]{notRewritten}); + + xlog $self, "Make sure no calendar event is marked private"; + $selectStmt->execute(); + $self->assert_null($selectStmt->fetch()); + + xlog $self, "Assert no iMIP notifications are sent"; + my $data = $self->{instance}->getnotify(); + $self->assert_num_equals(0, scalar grep { $_->{METHOD} eq 'imip' } @$data); + + xlog $self, "Assert new event message is sent to pusher"; + my @newevent = grep { + $_->{METHOD} eq 'pusher' and $_->{MESSAGE} =~ '{"event":"MessageNew"' + } @$data; + $self->assert_num_equals(1, scalar @newevent); + + xlog $self, "Assert CalendarEvent state changed"; + $res = $jmap->CallMethods( + [ [ + 'CalendarEvent/get', + { + ids => [], + }, + 'R1' + ] ], + \@using + ); + $self->assert_str_not_equals($state, $res->[0][1]{state}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_changes b/cassandane/tiny-tests/JMAPCalendars/calendar_changes index 619879b2e8..4eed7aab77 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_changes +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_changes @@ -2,110 +2,134 @@ use Cassandane::Tiny; sub test_calendar_changes - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { - "1" => { - name => "foo", - color => "coral", - sortOrder => 2, - isVisible => \1 - }, - "2" => { - name => "bar", - color => "aqua", - sortOrder => 3, - isVisible => \1 - } - }}, "R1"] - ]); - $self->assert_not_null($res); + xlog $self, "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1 + }, + "2" => { + name => "bar", + color => "aqua", + sortOrder => 3, + isVisible => \1 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; - my $state = $res->[0][1]{newState}; + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; + my $state = $res->[0][1]{newState}; - xlog $self, "get calendar updates without changes"; - $res = $jmap->CallMethods([['Calendar/changes', { - "sinceState" => $state - }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals(0, scalar @{$res->[0][1]{destroyed}}); + xlog $self, "get calendar updates without changes"; + $res = $jmap->CallMethods([ [ + 'Calendar/changes', + { + "sinceState" => $state + }, + "R1" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals(0, scalar @{ $res->[0][1]{destroyed} }); - xlog $self, "update name of calendar $id1, destroy calendar $id2"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - ifInState => $state, - update => {"$id1" => {name => "foo (upd)"}}, - destroy => [$id2] - }, "R1"] - ]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); + xlog $self, "update name of calendar $id1, destroy calendar $id2"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + ifInState => $state, + update => { "$id1" => { name => "foo (upd)" } }, + destroy => [$id2] + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); - xlog $self, "get calendar updates"; - $res = $jmap->CallMethods([['Calendar/changes', { - "sinceState" => $state - }, "R1"]]); - $self->assert_str_equals("Calendar/changes", $res->[0][0]); - $self->assert_str_equals("R1", $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{destroyed}[0]); - $state = $res->[0][1]{newState}; + xlog $self, "get calendar updates"; + $res = $jmap->CallMethods([ [ + 'Calendar/changes', + { + "sinceState" => $state + }, + "R1" + ] ]); + $self->assert_str_equals("Calendar/changes", $res->[0][0]); + $self->assert_str_equals("R1", $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{destroyed}[0]); + $state = $res->[0][1]{newState}; - xlog $self, "update color of calendar $id1"; - $res = $jmap->CallMethods([ - ['Calendar/set', { update => { $id1 => { color => "aqua" }}}, "R1" ] - ]); - $self->assert(exists $res->[0][1]{updated}{$id1}); + xlog $self, "update color of calendar $id1"; + $res = $jmap->CallMethods([ + [ 'Calendar/set', { update => { $id1 => { color => "aqua" } } }, "R1" ] + ]); + $self->assert(exists $res->[0][1]{updated}{$id1}); - xlog $self, "get calendar updates"; - $res = $jmap->CallMethods([['Calendar/changes', { - "sinceState" => $state - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $state = $res->[0][1]{newState}; + xlog $self, "get calendar updates"; + $res = $jmap->CallMethods([ [ + 'Calendar/changes', + { + "sinceState" => $state + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $state = $res->[0][1]{newState}; - xlog $self, "update sortOrder of calendar $id1"; - $res = $jmap->CallMethods([ - ['Calendar/set', { update => { $id1 => { sortOrder => 5 }}}, "R1" ] - ]); - $self->assert(exists $res->[0][1]{updated}{$id1}); + xlog $self, "update sortOrder of calendar $id1"; + $res = $jmap->CallMethods([ + [ 'Calendar/set', { update => { $id1 => { sortOrder => 5 } } }, "R1" ] ]); + $self->assert(exists $res->[0][1]{updated}{$id1}); - xlog $self, "get calendar updates"; - $res = $jmap->CallMethods([['Calendar/changes', { - "sinceState" => $state, - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $state = $res->[0][1]{newState}; + xlog $self, "get calendar updates"; + $res = $jmap->CallMethods([ [ + 'Calendar/changes', + { + "sinceState" => $state, + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $state = $res->[0][1]{newState}; - xlog $self, "get empty calendar updates"; - $res = $jmap->CallMethods([['Calendar/changes', { - "sinceState" => $state - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); + xlog $self, "get empty calendar updates"; + $res = $jmap->CallMethods([ [ + 'Calendar/changes', + { + "sinceState" => $state + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_changes_shared b/cassandane/tiny-tests/JMAPCalendars/calendar_changes_shared index e2e9c1a109..204f5ca10b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_changes_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_changes_shared @@ -2,226 +2,283 @@ use Cassandane::Tiny; sub test_calendar_changes_shared - :min_version_3_9 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_9 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - my $assert_changes = sub - { - my ($sinceState, $changes) = @_; + my $assert_changes = sub { + my ($sinceState, $changes) = @_; + + my $res = $jmap->CallMethods([ [ + 'Calendar/changes', + { + accountId => 'sharer', + sinceState => $sinceState, + }, + 'R1' + ] ]); + + $self->assert_deep_equals($changes->{created}, $res->[0][1]{created}); + $self->assert_deep_equals($changes->{updated}, $res->[0][1]{updated}); + $self->assert_deep_equals($changes->{destroyed}, $res->[0][1]{destroyed}); + $self->assert_str_equals($sinceState, $res->[0][1]{oldState}); + + return $res->[0][1]{newState}; + }; + + my $assert_calendars = sub { + my ($calendars) = @_; - my $res = $jmap->CallMethods([ - ['Calendar/changes', { - accountId => 'sharer', - sinceState => $sinceState, - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'Calendar/get', + { + accountId => 'sharer', + properties => ['id'], + }, + 'R1' + ] ]); - $self->assert_deep_equals($changes->{created}, $res->[0][1]{created}); - $self->assert_deep_equals($changes->{updated}, $res->[0][1]{updated}); - $self->assert_deep_equals($changes->{destroyed}, $res->[0][1]{destroyed}); - $self->assert_str_equals($sinceState, $res->[0][1]{oldState}); + my @wantCalendars = sort @{$calendars}; + my @haveCalendars = sort map { $_->{id} } @{ $res->[0][1]{list} }; + $self->assert_deep_equals(\@wantCalendars, \@haveCalendars); + }; - return $res->[0][1]{newState}; - }; + xlog $self, "Create sharer and share default calendar"; + my ($sharerJmap) = $self->create_user('sharer'); + $admin->setacl("user.sharer.#calendars.Default", cassandane => 'lrs'); - my $assert_calendars = sub + xlog $self, "Sharee gets initial calendar state"; + my $res = $jmap->CallMethods([ [ + 'Calendar/get', { - my ($calendars) = @_; - - my $res = $jmap->CallMethods([ - ['Calendar/get', { - accountId => 'sharer', - properties => ['id'], - }, 'R1'] - ]); - - my @wantCalendars = sort @{$calendars}; - my @haveCalendars = sort map { $_->{id} } @{$res->[0][1]{list}}; - $self->assert_deep_equals(\@wantCalendars, \@haveCalendars); - }; - - xlog $self, "Create sharer and share default calendar"; - my ($sharerJmap) = $self->create_user('sharer'); - $admin->setacl("user.sharer.#calendars.Default", cassandane => 'lrs'); - - xlog $self, "Sharee gets initial calendar state"; - my $res = $jmap->CallMethods([ - ['Calendar/get', { - accountId => 'sharer', - }, 'R1'] - ]); - my $state = $res->[0][1]{state}; - $self->assert_not_null($state); - $assert_calendars->(['Default']); - - xlog $self, "Sharer creates unshared calendars A and B"; - $res = $sharerJmap->CallMethods([ - ['Calendar/set', { - create => { - calA => { - name => 'A', - }, - calB => { - name => 'B', - }, - }, - }, 'R1'], - ]); - my $calendarA = $res->[0][1]{created}{calA}{id}; - $self->assert_not_null($calendarA); - my $calendarB = $res->[0][1]{created}{calB}{id}; - $self->assert_not_null($calendarB); - - $state = $assert_changes->($state, { - created => [], - updated => [], - destroyed => [] - }); - $assert_calendars->(['Default']); - - xlog $self, "Sharer creates and shares calendar C"; - $res = $sharerJmap->CallMethods([ - ['Calendar/set', { - create => { - calC => { - name => 'C', - shareWith => { - cassandane => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayRSVP => JSON::true, - }, - }, - }, - }, - }, 'R1'], - ]); - my $calendarC = $res->[0][1]{created}{calC}{id}; - $self->assert_not_null($calendarC); - - $state = $assert_changes->($state, { - created => [$calendarC], - updated => [], - destroyed => [] - }); - $assert_calendars->(['Default', $calendarC]); - - xlog $self, "Sharer shares calendar A"; - $res = $sharerJmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarA => { - shareWith => { - cassandane => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - }, - }, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarA}); - - $state = $assert_changes->($state, { - created => [], - updated => [$calendarA], # XXX this might better be in 'created' - destroyed => [] - }); - $assert_calendars->(['Default', $calendarA, $calendarC]); - - xlog $self, "Sharer shares calendar B with anyone"; - $admin->setacl("user.sharer.#calendars.$calendarB", anyone => 'lrs'); - - $state = $assert_changes->($state, { - created => [], - updated => [$calendarB], # XXX this might better be in 'created' - destroyed => [] - }); - $assert_calendars->(['Default', $calendarA, $calendarB, $calendarC]); - - xlog $self, "Sharee gets write rights on calendar C"; - $res = $sharerJmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarC => { - 'shareWith/cassandane/mayWriteAll' => JSON::true, - }, - }, - }, 'R1'], - ]); - - $state = $assert_changes->($state, { - created => [], - updated => [$calendarC], - destroyed => [] - }); - $assert_calendars->(['Default', $calendarA, $calendarB, $calendarC]); - - xlog $self, "Sharee looses write rights on calendar C"; - $res = $sharerJmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarC => { - 'shareWith/cassandane/mayWriteAll' => JSON::false, - }, + accountId => 'sharer', + }, + 'R1' + ] ]); + my $state = $res->[0][1]{state}; + $self->assert_not_null($state); + $assert_calendars->(['Default']); + + xlog $self, "Sharer creates unshared calendars A and B"; + $res = $sharerJmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + calA => { + name => 'A', + }, + calB => { + name => 'B', + }, + }, + }, + 'R1' + ], + ]); + my $calendarA = $res->[0][1]{created}{calA}{id}; + $self->assert_not_null($calendarA); + my $calendarB = $res->[0][1]{created}{calB}{id}; + $self->assert_not_null($calendarB); + + $state = $assert_changes->( + $state, + { + created => [], + updated => [], + destroyed => [] + } + ); + $assert_calendars->(['Default']); + + xlog $self, "Sharer creates and shares calendar C"; + $res = $sharerJmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + calC => { + name => 'C', + shareWith => { + cassandane => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayRSVP => JSON::true, + }, }, - }, 'R1'], - ]); - - $state = $assert_changes->($state, { - created => [], - updated => [$calendarC], - destroyed => [] - }); - $assert_calendars->(['Default', $calendarA, $calendarB, $calendarC]); - - xlog $self, "Sharer unshares calendar C"; - $res = $sharerJmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarC => { - 'shareWith/cassandane' => undef, - }, + }, + }, + }, + 'R1' + ], + ]); + my $calendarC = $res->[0][1]{created}{calC}{id}; + $self->assert_not_null($calendarC); + + $state = $assert_changes->( + $state, + { + created => [$calendarC], + updated => [], + destroyed => [] + } + ); + $assert_calendars->([ 'Default', $calendarC ]); + + xlog $self, "Sharer shares calendar A"; + $res = $sharerJmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarA => { + shareWith => { + cassandane => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + }, }, - }, 'R1'], - ]); - $assert_calendars->(['Default', $calendarA, $calendarB]); - - $state = $assert_changes->($state, { - created => [], - updated => [], - destroyed => [$calendarC] - }); - - xlog $self, "Sharer unshares calendar B for anyone"; - $admin->setacl("user.sharer.#calendars.$calendarB", anyone => ''); - - $state = $assert_changes->($state, { - created => [], - updated => [], - destroyed => [$calendarB] - }); - $assert_calendars->(['Default', $calendarA]); - - xlog $self, "Sharer destroys calendar A"; - $res = $sharerJmap->CallMethods([ - ['Calendar/set', { - destroy => [$calendarA], - }, 'R1'], - ]); - $self->assert_deep_equals([$calendarA], $res->[0][1]{destroyed}); - - $state = $assert_changes->($state, { - created => [], - updated => [], - destroyed => [$calendarA] - }); - $assert_calendars->(['Default']); -} + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarA}); + + $state = $assert_changes->( + $state, + { + created => [], + updated => [$calendarA], # XXX this might better be in 'created' + destroyed => [] + } + ); + $assert_calendars->([ 'Default', $calendarA, $calendarC ]); + xlog $self, "Sharer shares calendar B with anyone"; + $admin->setacl("user.sharer.#calendars.$calendarB", anyone => 'lrs'); + + $state = $assert_changes->( + $state, + { + created => [], + updated => [$calendarB], # XXX this might better be in 'created' + destroyed => [] + } + ); + $assert_calendars->([ 'Default', $calendarA, $calendarB, $calendarC ]); + + xlog $self, "Sharee gets write rights on calendar C"; + $res = $sharerJmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarC => { + 'shareWith/cassandane/mayWriteAll' => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + + $state = $assert_changes->( + $state, + { + created => [], + updated => [$calendarC], + destroyed => [] + } + ); + $assert_calendars->([ 'Default', $calendarA, $calendarB, $calendarC ]); + + xlog $self, "Sharee looses write rights on calendar C"; + $res = $sharerJmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarC => { + 'shareWith/cassandane/mayWriteAll' => JSON::false, + }, + }, + }, + 'R1' + ], + ]); + + $state = $assert_changes->( + $state, + { + created => [], + updated => [$calendarC], + destroyed => [] + } + ); + $assert_calendars->([ 'Default', $calendarA, $calendarB, $calendarC ]); + + xlog $self, "Sharer unshares calendar C"; + $res = $sharerJmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarC => { + 'shareWith/cassandane' => undef, + }, + }, + }, + 'R1' + ], + ]); + $assert_calendars->([ 'Default', $calendarA, $calendarB ]); + + $state = $assert_changes->( + $state, + { + created => [], + updated => [], + destroyed => [$calendarC] + } + ); + + xlog $self, "Sharer unshares calendar B for anyone"; + $admin->setacl("user.sharer.#calendars.$calendarB", anyone => ''); + + $state = $assert_changes->( + $state, + { + created => [], + updated => [], + destroyed => [$calendarB] + } + ); + $assert_calendars->([ 'Default', $calendarA ]); + + xlog $self, "Sharer destroys calendar A"; + $res = $sharerJmap->CallMethods([ + [ + 'Calendar/set', + { + destroy => [$calendarA], + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$calendarA], $res->[0][1]{destroyed}); + + $state = $assert_changes->( + $state, + { + created => [], + updated => [], + destroyed => [$calendarA] + } + ); + $assert_calendars->(['Default']); +} diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_copy_defaultalerts_mkcalendar b/cassandane/tiny-tests/JMAPCalendars/calendar_copy_defaultalerts_mkcalendar index 03783c1bcf..864b3a911c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_copy_defaultalerts_mkcalendar +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_copy_defaultalerts_mkcalendar @@ -2,134 +2,145 @@ use Cassandane::Tiny; sub test_calendar_copy_defaultalerts_mkcalendar - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "No default alerts are set on default calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/get', { - ids => ['Default'], - properties => [ - 'defaultAlertsWithTime', - 'defaultAlertsWithoutTime', - ], - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithoutTime}); + xlog $self, "No default alerts are set on default calendar"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + ids => ['Default'], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime', ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithoutTime}); - xlog $self, "Create calendar test1 over CalDAV"; - $res = $caldav->Request('MKCALENDAR', "/dav/calendars/user/cassandane/test1"); + xlog $self, "Create calendar test1 over CalDAV"; + $res = $caldav->Request('MKCALENDAR', "/dav/calendars/user/cassandane/test1"); - xlog $self, "New calendar does not have default alerts"; - $res = $jmap->CallMethods([ - ['Calendar/get', { - ids => ['test1'], - properties => [ - 'defaultAlertsWithTime', - 'defaultAlertsWithoutTime', - ], - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithoutTime}); + xlog $self, "New calendar does not have default alerts"; + $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + ids => ['test1'], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime', ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithoutTime}); - xlog $self, "Set default alarms on test1"; - my $defaultAlertsWithTime1 = { - 'e905cd3a-fdb7-413a-b7fa-1cd9daad501d' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT15M', - }, - action => 'display', - } - }; - my $defaultAlertsWithoutTime1 = { - '04c2bcfa-c35c-410c-83f5-27fba35257b3' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - } - }; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - test1 => { - defaultAlertsWithTime => $defaultAlertsWithTime1, - defaultAlertsWithoutTime => $defaultAlertsWithoutTime1, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{test1}); + xlog $self, "Set default alarms on test1"; + my $defaultAlertsWithTime1 = { + 'e905cd3a-fdb7-413a-b7fa-1cd9daad501d' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT15M', + }, + action => 'display', + } + }; + my $defaultAlertsWithoutTime1 = { + '04c2bcfa-c35c-410c-83f5-27fba35257b3' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'display', + } + }; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + test1 => { + defaultAlertsWithTime => $defaultAlertsWithTime1, + defaultAlertsWithoutTime => $defaultAlertsWithoutTime1, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{test1}); - xlog $self, "Create calendar test2 over CalDAV"; - $res = $caldav->Request('MKCALENDAR', "/dav/calendars/user/cassandane/test2"); + xlog $self, "Create calendar test2 over CalDAV"; + $res = $caldav->Request('MKCALENDAR', "/dav/calendars/user/cassandane/test2"); - xlog $self, "New calendar inherits default alerts from test1"; - $res = $jmap->CallMethods([ - ['Calendar/get', { - ids => ['test2'], - properties => [ - 'defaultAlertsWithTime', - 'defaultAlertsWithoutTime', - ], - }, 'R1'], - ]); - $self->assert_deep_equals($defaultAlertsWithTime1, - $res->[0][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_deep_equals($defaultAlertsWithoutTime1, - $res->[0][1]{list}[0]{defaultAlertsWithoutTime}); + xlog $self, "New calendar inherits default alerts from test1"; + $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + ids => ['test2'], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime', ], + }, + 'R1' + ], + ]); + $self->assert_deep_equals($defaultAlertsWithTime1, + $res->[0][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_deep_equals($defaultAlertsWithoutTime1, + $res->[0][1]{list}[0]{defaultAlertsWithoutTime}); - xlog $self, "Set default alarms with time on Default alert"; - my $defaultAlertsWithTime2 = { - 'e905cd3a-fdb7-413a-b7fa-1cd9daad501d' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT30M', - }, - action => 'display', - } - }; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => $defaultAlertsWithTime2, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + xlog $self, "Set default alarms with time on Default alert"; + my $defaultAlertsWithTime2 = { + 'e905cd3a-fdb7-413a-b7fa-1cd9daad501d' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT30M', + }, + action => 'display', + } + }; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => $defaultAlertsWithTime2, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog $self, "Create calendar test3 over CalDAV"; - $res = $caldav->Request('MKCALENDAR', "/dav/calendars/user/cassandane/test3"); + xlog $self, "Create calendar test3 over CalDAV"; + $res = $caldav->Request('MKCALENDAR', "/dav/calendars/user/cassandane/test3"); - xlog $self, "New calendar inherits default alerts from Default"; - $res = $jmap->CallMethods([ - ['Calendar/get', { - ids => ['test3'], - properties => [ - 'defaultAlertsWithTime', - 'defaultAlertsWithoutTime', - ], - }, 'R1'], - ]); - $self->assert_deep_equals($defaultAlertsWithTime2, - $res->[0][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithoutTime}); + xlog $self, "New calendar inherits default alerts from Default"; + $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + ids => ['test3'], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime', ], + }, + 'R1' + ], + ]); + $self->assert_deep_equals($defaultAlertsWithTime2, + $res->[0][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithoutTime}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_defaultalerts_synctoken b/cassandane/tiny-tests/JMAPCalendars/calendar_defaultalerts_synctoken index c1d7027c86..e67e96609b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_defaultalerts_synctoken +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_defaultalerts_synctoken @@ -2,114 +2,126 @@ use Cassandane::Tiny; sub test_calendar_defaultalerts_synctoken - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - xlog "Set default alerts on calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - xlog "Create events with and without default alerts"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - offset => "-PT10M", - }, - }, - }, + xlog "Set default alerts on calendar"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, - 2 => { - uid => 'eventuid2local', - calendarIds => { - Default => JSON::true, - }, - title => "event2", - start => "2020-01-21T13:00:00", - duration => "PT1H", - timeZone => "Europe/Vienna", - useDefaultAlerts => JSON::true, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + + xlog "Create events with and without default alerts"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + offset => "-PT10M", }, + }, }, - }, 'R1'], - ]); - my $event1Uid = $res->[0][1]{created}{1}{uid}; - $self->assert_not_null($event1Uid); - my $event2Uid = $res->[0][1]{created}{2}{uid}; - $self->assert_not_null($event2Uid); + }, + 2 => { + uid => 'eventuid2local', + calendarIds => { + Default => JSON::true, + }, + title => "event2", + start => "2020-01-21T13:00:00", + duration => "PT1H", + timeZone => "Europe/Vienna", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + my $event1Uid = $res->[0][1]{created}{1}{uid}; + $self->assert_not_null($event1Uid); + my $event2Uid = $res->[0][1]{created}{2}{uid}; + $self->assert_not_null($event2Uid); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + ]; - xlog "Fetch sync token"; - my $Cal = $CalDAV->GetCalendar('Default'); - my $syncToken = $Cal->{syncToken}; - $self->assert_not_null($syncToken); + xlog "Fetch sync token"; + my $Cal = $CalDAV->GetCalendar('Default'); + my $syncToken = $Cal->{syncToken}; + $self->assert_not_null($syncToken); - xlog "Update default alerts on calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert2 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT15M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + xlog "Update default alerts on calendar"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert2 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT15M', + }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Sync CalDAV changes"; - my ($adds, $removes, $errors) = $CalDAV->SyncEvents('Default', syncToken => $syncToken); + xlog "Sync CalDAV changes"; + my ($adds, $removes, $errors) + = $CalDAV->SyncEvents('Default', syncToken => $syncToken); - $self->assert_num_equals(1, scalar @{$adds}); - $self->assert_str_equals($adds->[0]{uid}, $event2Uid); - $self->assert_deep_equals($removes, []); - $self->assert_deep_equals($errors, []); + $self->assert_num_equals(1, scalar @{$adds}); + $self->assert_str_equals($adds->[0]{uid}, $event2Uid); + $self->assert_deep_equals($removes, []); + $self->assert_deep_equals($errors, []); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_defaultalerts_synctoken_shared b/cassandane/tiny-tests/JMAPCalendars/calendar_defaultalerts_synctoken_shared index 678378a8c0..5107604ffe 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_defaultalerts_synctoken_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_defaultalerts_synctoken_shared @@ -2,155 +2,182 @@ use Cassandane::Tiny; sub test_calendar_defaultalerts_synctoken_shared - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - xlog "Create other user and share calendar"; - my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user("other"); - $admintalk->setacl("user.cassandane.#calendars.Default", "other", "lrsiwntex") or die; - my $service = $self->{instance}->get_service("http"); - my $otherJMAP = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); + xlog "Create other user and share calendar"; + my $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->create_user("other"); + $admintalk->setacl("user.cassandane.#calendars.Default", "other", "lrsiwntex") + or die; + my $service = $self->{instance}->get_service("http"); + my $otherJMAP = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); - xlog "Set default alerts on calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - xlog "Create events without default alerts"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - offset => "-PT10M", - }, - }, - }, + xlog "Set default alerts on calendar"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, - 2 => { - uid => 'eventuid2local', - calendarIds => { - Default => JSON::true, - }, - title => "event2", - start => "2020-01-21T13:00:00", - duration => "PT1H", - timeZone => "Europe/Vienna", - useDefaultAlerts => JSON::true, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + + xlog "Create events without default alerts"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + offset => "-PT10M", }, + }, + }, + }, + 2 => { + uid => 'eventuid2local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - my $event1Uid = $res->[0][1]{created}{1}{uid}; - $self->assert_not_null($event1Uid); - my $event2Uid = $res->[0][1]{created}{2}{uid}; - $self->assert_not_null($event2Uid); - my $event2Id = $res->[0][1]{created}{2}{id}; - $self->assert_not_null($event2Id); + title => "event2", + start => "2020-01-21T13:00:00", + duration => "PT1H", + timeZone => "Europe/Vienna", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + my $event1Uid = $res->[0][1]{created}{1}{uid}; + $self->assert_not_null($event1Uid); + my $event2Uid = $res->[0][1]{created}{2}{uid}; + $self->assert_not_null($event2Uid); + my $event2Id = $res->[0][1]{created}{2}{id}; + $self->assert_not_null($event2Id); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + ]; - xlog "Set useDefaultAlerts to force per-user data split"; - $res = $otherJMAP->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $event2Id => { - color => 'green', - useDefaultAlerts => JSON::true, - }, + xlog "Set useDefaultAlerts to force per-user data split"; + $res = $otherJMAP->CallMethods( + [ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $event2Id => { + color => 'green', + useDefaultAlerts => JSON::true, }, - }, 'R1'], - ], $using); - $self->assert(exists $res->[0][1]{updated}{$event2Id}); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $event2Id => { - color => 'blue', - useDefaultAlerts => JSON::true, - }, + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert(exists $res->[0][1]{updated}{$event2Id}); + $res = $jmap->CallMethods( + [ + [ + 'CalendarEvent/set', + { + update => { + $event2Id => { + color => 'blue', + useDefaultAlerts => JSON::true, }, - }, 'R1'], - ], $using); - $self->assert(exists $res->[0][1]{updated}{$event2Id}); + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert(exists $res->[0][1]{updated}{$event2Id}); - xlog "Fetch sync token"; - my $Cal = $CalDAV->GetCalendar('Default'); - my $syncToken = $Cal->{syncToken}; - $self->assert_not_null($syncToken); + xlog "Fetch sync token"; + my $Cal = $CalDAV->GetCalendar('Default'); + my $syncToken = $Cal->{syncToken}; + $self->assert_not_null($syncToken); - xlog "Update default alerts on calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert2 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT15M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + xlog "Update default alerts on calendar"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert2 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT15M', + }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Sync CalDAV changes"; - my ($adds, $removes, $errors) = $CalDAV->SyncEvents('Default', syncToken => $syncToken); + xlog "Sync CalDAV changes"; + my ($adds, $removes, $errors) + = $CalDAV->SyncEvents('Default', syncToken => $syncToken); - $self->assert_num_equals(1, scalar @{$adds}); - $self->assert_str_equals($adds->[0]{uid}, $event2Uid); - $self->assert_deep_equals($removes, []); - $self->assert_deep_equals($errors, []); + $self->assert_num_equals(1, scalar @{$adds}); + $self->assert_str_equals($adds->[0]{uid}, $event2Uid); + $self->assert_deep_equals($removes, []); + $self->assert_deep_equals($errors, []); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_get b/cassandane/tiny-tests/JMAPCalendars/calendar_get index e74df730f4..f415b43ece 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_get +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_get @@ -2,45 +2,46 @@ use Cassandane::Tiny; sub test_calendar_get - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $id = $caldav->NewCalendar({ name => "calname", color => "aqua"}); - my $unknownId = "foo"; + my $id = $caldav->NewCalendar({ name => "calname", color => "aqua" }); + my $unknownId = "foo"; - xlog $self, "get existing calendar"; - my $res = $jmap->CallMethods([['Calendar/get', {ids => [$id]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Calendar/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{list}})); - $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals('aqua', $res->[0][1]{list}[0]{color}); + xlog $self, "get existing calendar"; + my $res = $jmap->CallMethods([ [ 'Calendar/get', { ids => [$id] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Calendar/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{list} })); + $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals('aqua', $res->[0][1]{list}[0]{color}); - xlog $self, "get existing calendar with select properties"; - $res = $jmap->CallMethods([['Calendar/get', { ids => [$id], properties => ["name"] }, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{list}})); - $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals("calname", $res->[0][1]{list}[0]{name}); - $self->assert_null($res->[0][1]{list}[0]{color}); + xlog $self, "get existing calendar with select properties"; + $res = $jmap->CallMethods( + [ [ 'Calendar/get', { ids => [$id], properties => ["name"] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{list} })); + $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals("calname", $res->[0][1]{list}[0]{name}); + $self->assert_null($res->[0][1]{list}[0]{color}); - xlog $self, "get unknown calendar"; - $res = $jmap->CallMethods([['Calendar/get', {ids => [$unknownId]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(0, scalar(@{$res->[0][1]{list}})); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{notFound}})); - $self->assert_str_equals($unknownId, $res->[0][1]{notFound}[0]); + xlog $self, "get unknown calendar"; + $res + = $jmap->CallMethods([ [ 'Calendar/get', { ids => [$unknownId] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(0, scalar(@{ $res->[0][1]{list} })); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{notFound} })); + $self->assert_str_equals($unknownId, $res->[0][1]{notFound}[0]); - xlog $self, "get all calendars"; - $res = $jmap->CallMethods([['Calendar/get', {ids => undef}, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(2, scalar(@{$res->[0][1]{list}})); - $res = $jmap->CallMethods([['Calendar/get', {}, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(2, scalar(@{$res->[0][1]{list}})); + xlog $self, "get all calendars"; + $res = $jmap->CallMethods([ [ 'Calendar/get', { ids => undef }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(2, scalar(@{ $res->[0][1]{list} })); + $res = $jmap->CallMethods([ [ 'Calendar/get', {}, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(2, scalar(@{ $res->[0][1]{list} })); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_get_default b/cassandane/tiny-tests/JMAPCalendars/calendar_get_default index b99456f4a9..c8a498b0df 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_get_default +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_get_default @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_calendar_get_default - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # XXX - A previous CalDAV test might have created the default - # calendar already. To make this test self-sufficient, we need - # to create a test user just for this test. How? - xlog $self, "get default calendar"; - my $res = $jmap->CallMethods([['Calendar/get', {ids => ["Default"]}, "R1"]]); - $self->assert_str_equals("Default", $res->[0][1]{list}[0]{id}); + # XXX - A previous CalDAV test might have created the default + # calendar already. To make this test self-sufficient, we need + # to create a test user just for this test. How? + xlog $self, "get default calendar"; + my $res + = $jmap->CallMethods([ [ 'Calendar/get', { ids => ["Default"] }, "R1" ] ]); + $self->assert_str_equals("Default", $res->[0][1]{list}[0]{id}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_get_freebusy_only b/cassandane/tiny-tests/JMAPCalendars/calendar_get_freebusy_only index 7b7dfdf938..edb6c0274f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_get_freebusy_only +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_get_freebusy_only @@ -2,55 +2,66 @@ use Cassandane::Tiny; sub test_calendar_get_freebusy_only - :min_version_3_5 :needs_component_jmap :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap : JMAPExtensions : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create other user"; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user.other'); - $admintalk->setacl('user.other', admin => 'lrswipkxtecdan') or die; - $admintalk->setacl('user.other', other => 'lrswipkxtecdn') or die; + xlog $self, "create other user"; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user.other'); + $admintalk->setacl('user.other', admin => 'lrswipkxtecdan') or die; + $admintalk->setacl('user.other', other => 'lrswipkxtecdn') or die; - my $service = $self->{instance}->get_service("http"); - my $otherJmap = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $otherJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + my $service = $self->{instance}->get_service("http"); + my $otherJmap = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $otherJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $otherJmap->CallMethods([ - ['Calendar/get', { - properties => ['id'], - }, 'R1'], - ]); - $admintalk->setacl('user.other.#calendars.Default', cassandane => 'l9') or die; + my $res = $otherJmap->CallMethods([ + [ + 'Calendar/get', + { + properties => ['id'], + }, + 'R1' + ], + ]); + $admintalk->setacl('user.other.#calendars.Default', cassandane => 'l9') + or die; - $res = $jmap->ua->get($jmap->uri(), { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }); - $self->assert_str_equals('200', $res->{status}); - my $session = eval { decode_json($res->{content}) }; - my $capabilities = $session->{accounts}{other}{accountCapabilities}; - $self->assert_not_null($capabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); + $res = $jmap->ua->get( + $jmap->uri(), + { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + } + ); + $self->assert_str_equals('200', $res->{status}); + my $session = eval { decode_json($res->{content}) }; + my $capabilities = $session->{accounts}{other}{accountCapabilities}; + $self->assert_not_null( + $capabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); - $res = $jmap->CallMethods([ - ['Calendar/get', { - accountId => 'other', - properties => ['id'], - }, 'R1'], - ]); - $self->assert_deep_equals([], $res->[0][1]{list}); + $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + accountId => 'other', + properties => ['id'], + }, + 'R1' + ], + ]); + $self->assert_deep_equals([], $res->[0][1]{list}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_get_shared b/cassandane/tiny-tests/JMAPCalendars/calendar_get_shared index abd127a4d1..d8ce293399 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_get_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_get_shared @@ -2,68 +2,80 @@ use Cassandane::Tiny; sub test_calendar_get_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "create calendar"; - my $CalendarId = $mantalk->NewCalendar({name => 'Manifold Calendar'}); - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + my $CalendarId = $mantalk->NewCalendar({ name => 'Manifold Calendar' }); + $self->assert_not_null($CalendarId); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId", "cassandane" => 'lr') or die; + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId", + "cassandane" => 'lr') + or die; - xlog $self, "get calendar"; - my $res = $jmap->CallMethods([['Calendar/get', {accountId => 'manifold'}, "R1"]]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert_str_equals("Manifold Calendar", $res->[0][1]{list}[0]->{name}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]->{myRights}{mayWriteAll}); - my $id = $res->[0][1]{list}[0]->{id}; + xlog $self, "get calendar"; + my $res = $jmap->CallMethods( + [ [ 'Calendar/get', { accountId => 'manifold' }, "R1" ] ]); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert_str_equals("Manifold Calendar", $res->[0][1]{list}[0]->{name}); + $self->assert_equals(JSON::true, + $res->[0][1]{list}[0]->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::false, + $res->[0][1]{list}[0]->{myRights}{mayWriteAll}); + my $id = $res->[0][1]{list}[0]->{id}; - xlog $self, "refetch calendar"; - $res = $jmap->CallMethods([['Calendar/get', {accountId => 'manifold', ids => [$id]}, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{list}[0]->{id}); + xlog $self, "refetch calendar"; + $res = $jmap->CallMethods( + [ [ 'Calendar/get', { accountId => 'manifold', ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{list}[0]->{id}); - xlog $self, "create another shared calendar"; - my $CalendarId2 = $mantalk->NewCalendar({name => 'Manifold Calendar 2'}); - $self->assert_not_null($CalendarId2); - $admintalk->setacl("user.manifold.#calendars.$CalendarId2", "cassandane" => 'lr') or die; + xlog $self, "create another shared calendar"; + my $CalendarId2 = $mantalk->NewCalendar({ name => 'Manifold Calendar 2' }); + $self->assert_not_null($CalendarId2); + $admintalk->setacl("user.manifold.#calendars.$CalendarId2", + "cassandane" => 'lr') + or die; - xlog $self, "remove access rights to calendar"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId", "cassandane" => '') or die; + xlog $self, "remove access rights to calendar"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId", "cassandane" => '') + or die; - xlog $self, "refetch calendar (should fail)"; - $res = $jmap->CallMethods([['Calendar/get', {accountId => 'manifold', ids => [$id]}, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); + xlog $self, "refetch calendar (should fail)"; + $res = $jmap->CallMethods( + [ [ 'Calendar/get', { accountId => 'manifold', ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); - xlog $self, "remove access rights to all shared calendars"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId2", "cassandane" => '') or die; + xlog $self, "remove access rights to all shared calendars"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId2", + "cassandane" => '') + or die; - xlog $self, "refetch calendar (should fail)"; - $res = $jmap->CallMethods([['Calendar/get', {accountId => 'manifold', ids => [$id]}, "R1"]]); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); + xlog $self, "refetch calendar (should fail)"; + $res = $jmap->CallMethods( + [ [ 'Calendar/get', { accountId => 'manifold', ids => [$id] }, "R1" ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set b/cassandane/tiny-tests/JMAPCalendars/calendar_set index 37666cb725..be8814a20c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set @@ -2,62 +2,74 @@ use Cassandane::Tiny; sub test_calendar_set - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", - color => "coral", - sortOrder => 2, - isVisible => \1 - }}}, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Calendar/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{created}); - - my $id = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get calendar $id"; - $res = $jmap->CallMethods([['Calendar/get', {ids => [$id]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{list}})); - $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals('foo', $res->[0][1]{list}[0]{name}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isVisible}); - - xlog $self, "update calendar $id"; - $res = $jmap->CallMethods([ - ['Calendar/set', {update => {"$id" => { - name => "bar", - isVisible => \0 - }}}, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert(exists $res->[0][1]{updated}{$id}); - - xlog $self, "get calendar $id"; - $res = $jmap->CallMethods([['Calendar/get', {ids => [$id]}, "R1"]]); - $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isVisible}); - - xlog $self, "destroy calendar $id"; - $res = $jmap->CallMethods([['Calendar/set', {destroy => ["$id"]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{destroyed}); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); - - xlog $self, "get calendar $id"; - $res = $jmap->CallMethods([['Calendar/get', {ids => [$id]}, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Calendar/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{created}); + + my $id = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get calendar $id"; + $res = $jmap->CallMethods([ [ 'Calendar/get', { ids => [$id] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{list} })); + $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals('foo', $res->[0][1]{list}[0]{name}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isVisible}); + + xlog $self, "update calendar $id"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + update => { + "$id" => { + name => "bar", + isVisible => \0 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert(exists $res->[0][1]{updated}{$id}); + + xlog $self, "get calendar $id"; + $res = $jmap->CallMethods([ [ 'Calendar/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isVisible}); + + xlog $self, "destroy calendar $id"; + $res + = $jmap->CallMethods([ [ 'Calendar/set', { destroy => ["$id"] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{destroyed}); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + + xlog $self, "get calendar $id"; + $res = $jmap->CallMethods([ [ 'Calendar/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_badname b/cassandane/tiny-tests/JMAPCalendars/calendar_set_badname index 7472b56bad..031193b292 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_badname +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_badname @@ -2,25 +2,33 @@ use Cassandane::Tiny; sub test_calendar_set_badname - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create calendar with excessively long name"; - # Exceed the maximum allowed 256 byte length by 1. - my $badname = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt risus quis urna aliquam sollicitudin. Pellentesque aliquet nisl ut neque viverra pellentesque. Donec tincidunt eros at ante malesuada porta. Nam sapien arcu, vehicula non posuere."; + xlog $self, "create calendar with excessively long name"; + # Exceed the maximum allowed 256 byte length by 1. + my $badname + = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt risus quis urna aliquam sollicitudin. Pellentesque aliquet nisl ut neque viverra pellentesque. Donec tincidunt eros at ante malesuada porta. Nam sapien arcu, vehicula non posuere."; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => $badname, color => "aqua", - sortOrder => 1, isVisible => \1 - }}}, "R1"] - ]); - $self->assert_not_null($res); - my $errType = $res->[0][1]{notCreated}{"1"}{type}; - my $errProp = $res->[0][1]{notCreated}{"1"}{properties}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals(["name"], $errProp); + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => $badname, + color => "aqua", + sortOrder => 1, + isVisible => \1 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + my $errType = $res->[0][1]{notCreated}{"1"}{type}; + my $errProp = $res->[0][1]{notCreated}{"1"}{properties}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(["name"], $errProp); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_defaultalerts b/cassandane/tiny-tests/JMAPCalendars/calendar_set_defaultalerts index 3782730c7c..ad12f28e87 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_defaultalerts +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_defaultalerts @@ -3,161 +3,200 @@ use Cassandane::Tiny; use Data::UUID; sub test_calendar_set_defaultalerts - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - my $alert1Id = '589c1b45-ca59-4072-90fb-93c41491e484'; - my $alert2Id = '899fd3e7-c0a0-442d-a04f-725c58728afb'; + my $alert1Id = '589c1b45-ca59-4072-90fb-93c41491e484'; + my $alert2Id = '899fd3e7-c0a0-442d-a04f-725c58728afb'; - my $defaultAlertsWithTime = { - $alert1Id => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT1H', - }, - action => 'email', - }, - $alert2Id => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - }, - }; + my $defaultAlertsWithTime = { + $alert1Id => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT1H', + }, + action => 'email', + }, + $alert2Id => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'display', + }, + }; - my $alert3Id = '2905eb80-48af-4e0f-85cc-de58155a2152'; + my $alert3Id = '2905eb80-48af-4e0f-85cc-de58155a2152'; - my $defaultAlertsWithoutTime = { - $alert3Id => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - }, - }; + my $defaultAlertsWithoutTime = { + $alert3Id => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'display', + }, + }; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - 1 => { - name => 'test', - color => 'blue', - defaultAlertsWithTime => $defaultAlertsWithTime, - defaultAlertsWithoutTime => $defaultAlertsWithoutTime, - } - } - }, 'R1'], - ['Calendar/get', { - ids => ['#1'], - properties => ['defaultAlertsWithTime', 'defaultAlertsWithoutTime'], - }, 'R2'] - ]); - my $calendarId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($calendarId); - $self->assert_deep_equals($defaultAlertsWithTime, - $res->[1][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_deep_equals($defaultAlertsWithoutTime, - $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + 1 => { + name => 'test', + color => 'blue', + defaultAlertsWithTime => $defaultAlertsWithTime, + defaultAlertsWithoutTime => $defaultAlertsWithoutTime, + } + } + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => ['#1'], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime' ], + }, + 'R2' + ] + ]); + my $calendarId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($calendarId); + $self->assert_deep_equals($defaultAlertsWithTime, + $res->[1][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_deep_equals($defaultAlertsWithoutTime, + $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); - my $alert4Id = '5e7b49d3-fcef-484f-8d31-f9fb178ebc65'; - my $alert4 = { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT30M', - }, - action => 'display', - }; + my $alert4Id = '5e7b49d3-fcef-484f-8d31-f9fb178ebc65'; + my $alert4 = { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT30M', + }, + action => 'display', + }; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - "defaultAlertsWithTime/$alert1Id" => undef, - "defaultAlertsWithTime/$alert4Id" => $alert4, - } - } - }, 'R1'], - ['Calendar/get', { - ids => [$calendarId], - properties => ['defaultAlertsWithTime', 'defaultAlertsWithoutTime'], - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarId}); + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + "defaultAlertsWithTime/$alert1Id" => undef, + "defaultAlertsWithTime/$alert4Id" => $alert4, + } + } + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => [$calendarId], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime' ], + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarId}); - delete $defaultAlertsWithTime->{$alert1Id}; - $defaultAlertsWithTime->{$alert4Id} = $alert4; - $self->assert_deep_equals($defaultAlertsWithTime, - $res->[1][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_deep_equals($defaultAlertsWithoutTime, - $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); + delete $defaultAlertsWithTime->{$alert1Id}; + $defaultAlertsWithTime->{$alert4Id} = $alert4; + $self->assert_deep_equals($defaultAlertsWithTime, + $res->[1][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_deep_equals($defaultAlertsWithoutTime, + $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - "defaultAlertsWithoutTime/$alert3Id/trigger/offset" => '-PT5M', - } - } - }, 'R1'], - ['Calendar/get', { - ids => [$calendarId], - properties => ['defaultAlertsWithTime', 'defaultAlertsWithoutTime'], - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarId}); + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + "defaultAlertsWithoutTime/$alert3Id/trigger/offset" => '-PT5M', + } + } + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => [$calendarId], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime' ], + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarId}); - $defaultAlertsWithoutTime->{$alert3Id}{trigger}{offset} = '-PT5M'; - $self->assert_deep_equals($defaultAlertsWithTime, - $res->[1][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_deep_equals($defaultAlertsWithoutTime, - $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); + $defaultAlertsWithoutTime->{$alert3Id}{trigger}{offset} = '-PT5M'; + $self->assert_deep_equals($defaultAlertsWithTime, + $res->[1][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_deep_equals($defaultAlertsWithoutTime, + $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - defaultAlertsWithTime => undef, - } - } - }, 'R1'], - ['Calendar/get', { - ids => [$calendarId], - properties => ['defaultAlertsWithTime', 'defaultAlertsWithoutTime'], - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarId}); - $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_deep_equals($defaultAlertsWithoutTime, - $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + defaultAlertsWithTime => undef, + } + } + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => [$calendarId], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime' ], + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarId}); + $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_deep_equals($defaultAlertsWithoutTime, + $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - defaultAlertsWithoutTime => undef, - } - } - }, 'R1'], - ['Calendar/get', { - ids => [$calendarId], - properties => ['defaultAlertsWithTime', 'defaultAlertsWithoutTime'], - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarId}); - $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithTime}); - $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithoutTime}); + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + defaultAlertsWithoutTime => undef, + } + } + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => [$calendarId], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime' ], + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarId}); + $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithoutTime}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_defaultalerts_shared b/cassandane/tiny-tests/JMAPCalendars/calendar_set_defaultalerts_shared index 6d87b839a0..15302e2089 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_defaultalerts_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_defaultalerts_shared @@ -3,374 +3,398 @@ use Cassandane::Tiny; use Data::UUID; sub test_calendar_set_defaultalerts_shared - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - - my $t = $self->create_test; - - my $ownerJmap = $t->{owner}{jmap}; - my $shareeJmap = $t->{sharee}{jmap}; - - $self->assert_shared_defaultalerts($t); - - xlog $self, "Owner sets default alarms"; - - my $alertWithTimeOwnerId = '4c08cb1d-60e0-46e0-9cc1-9622b7a820ed'; - $t->{owner}->{defaultAlertsWithTime} = { - $alertWithTimeOwnerId => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'email', + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + + my $t = $self->create_test; + + my $ownerJmap = $t->{owner}{jmap}; + my $shareeJmap = $t->{sharee}{jmap}; + + $self->assert_shared_defaultalerts($t); + + xlog $self, "Owner sets default alarms"; + + my $alertWithTimeOwnerId = '4c08cb1d-60e0-46e0-9cc1-9622b7a820ed'; + $t->{owner}->{defaultAlertsWithTime} = { + $alertWithTimeOwnerId => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'email', + }, + }; + + my $alertWithoutTimeOwnerId = '3f8c29c3-d305-4c19-adb6-57cc3308918c'; + $t->{owner}->{defaultAlertsWithoutTime} = { + $alertWithoutTimeOwnerId => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'display', + }, + }; + $ownerJmap->CallMethods([ + [ + 'Calendar/set', + { + accountId => 'owner', + update => { + Default => { + defaultAlertsWithTime => $t->{owner}->{defaultAlertsWithTime}, + defaultAlertsWithoutTime => $t->{owner}->{defaultAlertsWithoutTime}, + }, }, - }; - - my $alertWithoutTimeOwnerId = '3f8c29c3-d305-4c19-adb6-57cc3308918c'; - $t->{owner}->{defaultAlertsWithoutTime} = { - $alertWithoutTimeOwnerId => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', + }, + 'R1' + ], + ]); + + $self->assert_shared_defaultalerts($t); + + xlog $self, 'Sharee sets default alarms'; + my $alertWithTimeShareeId = 'b61e5b53-8ea2-46f4-949d-7b49734ba4d3'; + $t->{sharee}->{defaultAlertsWithTime} = { + $alertWithTimeShareeId => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'email', + }, + }; + my $alertWithoutTimeShareeId = '97d7c889-272f-4ce3-8d21-4a32b17ecece'; + $t->{sharee}->{defaultAlertsWithoutTime} = { + $alertWithoutTimeShareeId => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'display', + }, + }; + $shareeJmap->CallMethods([ + [ + 'Calendar/set', + { + accountId => 'owner', + update => { + Default => { + defaultAlertsWithTime => $t->{sharee}->{defaultAlertsWithTime}, + defaultAlertsWithoutTime => + $t->{sharee}->{defaultAlertsWithoutTime}, + }, }, - }; - $ownerJmap->CallMethods([ - ['Calendar/set', { - accountId => 'owner', - update => { - Default => { - defaultAlertsWithTime => - $t->{owner}->{defaultAlertsWithTime}, - defaultAlertsWithoutTime => - $t->{owner}->{defaultAlertsWithoutTime}, - }, - }, - }, 'R1'], - ]); - - $self->assert_shared_defaultalerts($t); - - xlog $self, 'Sharee sets default alarms'; - my $alertWithTimeShareeId = 'b61e5b53-8ea2-46f4-949d-7b49734ba4d3'; - $t->{sharee}->{defaultAlertsWithTime} = { - $alertWithTimeShareeId => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'email', + }, + 'R1' + ], + ]); + + $self->assert_shared_defaultalerts($t); + + xlog $self, 'Owner removes default alarms'; + $t->{owner}->{defaultAlertsWithTime} = undef; + $t->{owner}->{defaultAlertsWithoutTime} = undef; + $ownerJmap->CallMethods([ + [ + 'Calendar/set', + { + accountId => 'owner', + update => { + Default => { + defaultAlertsWithTime => $t->{owner}->{defaultAlertsWithTime}, + defaultAlertsWithoutTime => $t->{owner}->{defaultAlertsWithoutTime}, + }, }, - }; - my $alertWithoutTimeShareeId = '97d7c889-272f-4ce3-8d21-4a32b17ecece'; - $t->{sharee}->{defaultAlertsWithoutTime} = { - $alertWithoutTimeShareeId => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', + }, + 'R1' + ], + ]); + + $self->assert_shared_defaultalerts($t); + + xlog $self, 'Sharee removes default alarms'; + $t->{sharee}->{defaultAlertsWithTime} = undef; + $t->{sharee}->{defaultAlertsWithoutTime} = undef; + $shareeJmap->CallMethods([ + [ + 'Calendar/set', + { + accountId => 'owner', + update => { + Default => { + defaultAlertsWithTime => $t->{sharee}->{defaultAlertsWithTime}, + defaultAlertsWithoutTime => + $t->{sharee}->{defaultAlertsWithoutTime}, + }, }, - }; - $shareeJmap->CallMethods([ - ['Calendar/set', { - accountId => 'owner', - update => { - Default => { - defaultAlertsWithTime => - $t->{sharee}->{defaultAlertsWithTime}, - defaultAlertsWithoutTime => - $t->{sharee}->{defaultAlertsWithoutTime}, - }, - }, - }, 'R1'], - ]); - - $self->assert_shared_defaultalerts($t); - - xlog $self, 'Owner removes default alarms'; - $t->{owner}->{defaultAlertsWithTime} = undef; - $t->{owner}->{defaultAlertsWithoutTime} = undef; - $ownerJmap->CallMethods([ - ['Calendar/set', { - accountId => 'owner', - update => { - Default => { - defaultAlertsWithTime => - $t->{owner}->{defaultAlertsWithTime}, - defaultAlertsWithoutTime => - $t->{owner}->{defaultAlertsWithoutTime}, - }, - }, - }, 'R1'], - ]); - - $self->assert_shared_defaultalerts($t); - - xlog $self, 'Sharee removes default alarms'; - $t->{sharee}->{defaultAlertsWithTime} = undef; - $t->{sharee}->{defaultAlertsWithoutTime} = undef; - $shareeJmap->CallMethods([ - ['Calendar/set', { - accountId => 'owner', - update => { - Default => { - defaultAlertsWithTime => - $t->{sharee}->{defaultAlertsWithTime}, - defaultAlertsWithoutTime => - $t->{sharee}->{defaultAlertsWithoutTime}, - }, - }, - }, 'R1'], - ]); + }, + 'R1' + ], + ]); - $self->assert_shared_defaultalerts($t); + $self->assert_shared_defaultalerts($t); } sub _can_match { - my $event = shift; - my $want = shift; + my $event = shift; + my $want = shift; - # I wrote a really good one of these for Caldav, but this will do for now - foreach my $key (keys %$want) { - return 0 if not exists $event->{$key}; - return 0 if $event->{$key} ne $want->{$key}; - } + # I wrote a really good one of these for Caldav, but this will do for now + foreach my $key (keys %$want) { + return 0 if not exists $event->{$key}; + return 0 if $event->{$key} ne $want->{$key}; + } - return 1; + return 1; } sub assert_alarm_notifs { - my $self = shift; - my @want = @_; - # pick first calendar alarm from notifications - my $data = $self->{instance}->getnotify(); - if ($self->{replica}) { - my $more = $self->{replica}->getnotify(); - push @$data, @$more; - } - my @events; - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @events, $e; - } - } + my $self = shift; + my @want = @_; + # pick first calendar alarm from notifications + my $data = $self->{instance}->getnotify(); + if ($self->{replica}) { + my $more = $self->{replica}->getnotify(); + push @$data, @$more; + } + my @events; + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @events, $e; + } } - - my @left; - while (my $event = shift @events) { - my $found = 0; - my @newwant; - foreach my $data (@want) { - if (not $found and _can_match($event, $data)) { - $found = 1; - } - else { - push @newwant, $data; - } - } - if (not $found) { - push @left, $event; - } - @want = @newwant; + } + + my @left; + while (my $event = shift @events) { + my $found = 0; + my @newwant; + foreach my $data (@want) { + if (not $found and _can_match($event, $data)) { + $found = 1; + } else { + push @newwant, $data; + } } - - if (@want or @left) { - my $dump = Data::Dumper->Dump([\@want, \@left], [qw(want left)]); - $self->assert_equals(0, scalar @want, - "expected events were not received:\n$dump"); - $self->assert_equals(0, scalar @left, - "unexpected extra events were received:\n$dump"); + if (not $found) { + push @left, $event; } + @want = @newwant; + } + + if (@want or @left) { + my $dump = Data::Dumper->Dump([ \@want, \@left ], [qw(want left)]); + $self->assert_equals(0, scalar @want, + "expected events were not received:\n$dump"); + $self->assert_equals(0, scalar @left, + "unexpected extra events were received:\n$dump"); + } } -sub assert_shared_defaultalerts -{ - my ($self, $t) = @_; - - for my $who (qw/owner sharee/) { - my $jmap = $t->{$who}->{jmap}; - - xlog $self, "Assert alarms for $who"; - my $res = $jmap->CallMethods([ - ['Calendar/get', { - accountId => 'owner', - properties => [ - 'defaultAlertsWithTime', - 'defaultAlertsWithoutTime', - ], - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{list}[0]); - - my $defaultAlertsWithTime = - $t->{$who}->{defaultAlertsWithTime}; - if ($defaultAlertsWithTime) { - $self->assert_deep_equals($defaultAlertsWithTime, - $res->[0][1]{list}[0]{defaultAlertsWithTime}); - } else { - $self->assert_null( - $res->[0][1]{list}[0]{defaultAlertsWithTime}); - } +sub assert_shared_defaultalerts { + my ($self, $t) = @_; - my $defaultAlertsWithoutTime = - $t->{$who}->{defaultAlertsWithoutTime}; - if ($defaultAlertsWithoutTime) { - $self->assert_deep_equals($defaultAlertsWithoutTime, - $res->[0][1]{list}[0]{defaultAlertsWithoutTime}); - } else { - $self->assert_null( - $res->[0][1]{list}[0]{defaultAlertsWithoutTime}); - } + for my $who (qw/owner sharee/) { + my $jmap = $t->{$who}->{jmap}; - xlog 'Assert default alarms in CalDAV GET'; - my $caldav = $t->{$who}->{caldav}; - my $xhref = $t->{$who}->{xhref}; - - $res = $caldav->Request('GET', $xhref); - if ($defaultAlertsWithTime) { - $self->assert_matches(qr/BEGIN:VALARM/, $res->{content}); - my $uid = (values %{$defaultAlertsWithTime})->{uid}; - $self->assert_matches(qr/UID:$uid/, $res->{content}); - } else { - $self->assert(not $res->{content} =~ qr/BEGIN:VALARM/); - } + xlog $self, "Assert alarms for $who"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + accountId => 'owner', + properties => + [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime', ], + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{list}[0]); + + my $defaultAlertsWithTime = $t->{$who}->{defaultAlertsWithTime}; + if ($defaultAlertsWithTime) { + $self->assert_deep_equals($defaultAlertsWithTime, + $res->[0][1]{list}[0]{defaultAlertsWithTime}); + } else { + $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithTime}); } - xlog 'Assert calalarmd alarms'; - my @alarms; - for my $who (qw/owner sharee/) { - my $defaultAlertsWithTime = - $t->{$who}->{defaultAlertsWithTime}; - - if ($defaultAlertsWithTime) { - my $alertid = (keys %{$defaultAlertsWithTime})[0]; - my $event_start = $t->{start}->strftime('%Y%m%dT%H%M%S'); - push (@alarms, { - start => $event_start, alertId => $alertid, userId => $who - }); + my $defaultAlertsWithoutTime = $t->{$who}->{defaultAlertsWithoutTime}; + if ($defaultAlertsWithoutTime) { + $self->assert_deep_equals($defaultAlertsWithoutTime, + $res->[0][1]{list}[0]{defaultAlertsWithoutTime}); + } else { + $self->assert_null($res->[0][1]{list}[0]{defaultAlertsWithoutTime}); + } + + xlog 'Assert default alarms in CalDAV GET'; + my $caldav = $t->{$who}->{caldav}; + my $xhref = $t->{$who}->{xhref}; + + $res = $caldav->Request('GET', $xhref); + if ($defaultAlertsWithTime) { + $self->assert_matches(qr/BEGIN:VALARM/, $res->{content}); + my $uid = (values %{$defaultAlertsWithTime})->{uid}; + $self->assert_matches(qr/UID:$uid/, $res->{content}); + } else { + $self->assert(not $res->{content} =~ qr/BEGIN:VALARM/); + } + } + + xlog 'Assert calalarmd alarms'; + my @alarms; + for my $who (qw/owner sharee/) { + my $defaultAlertsWithTime = $t->{$who}->{defaultAlertsWithTime}; + + if ($defaultAlertsWithTime) { + my $alertid = (keys %{$defaultAlertsWithTime})[0]; + my $event_start = $t->{start}->strftime('%Y%m%dT%H%M%S'); + push( + @alarms, + { + start => $event_start, + alertId => $alertid, + userId => $who } + ); } - $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $t->{now}->epoch() ); - $self->assert_alarm_notifs(@alarms); - - xlog 'Move clock on week forward'; - $t->{now}->add(DateTime::Duration->new(days =>7)); - $t->{start}->add(DateTime::Duration->new(days =>7)); + } + $self->{instance}->getnotify(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $t->{now}->epoch()); + $self->assert_alarm_notifs(@alarms); + + xlog 'Move clock on week forward'; + $t->{now}->add(DateTime::Duration->new(days => 7)); + $t->{start}->add(DateTime::Duration->new(days => 7)); } -sub create_test -{ - my ($self) = @_; - - my ($ownerJmap, $ownerCaldav) = $self->create_user('owner'); - my ($shareeJmap, $shareeCaldav) = $self->create_user('sharee'); - - my $now = DateTime->now(); - $now->set_time_zone('Etc/UTC'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); - - # define the event to start in a few seconds - my $start = $now->clone(); - $start->add(DateTime::Duration->new(seconds => 2)); - - xlog $self, 'Create event and share calendar with sharee'; - my $res = $ownerJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'owner', - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'ed326178-43dc-474d-a496-6cba057c9afe', - title => 'test', - start => $start->strftime('%Y-%m-%dT%H:%M:%S'), - timeZone => 'Europe/Vienna', - duration => 'PT15M', - recurrenceRules => [{ - '@type' => 'RecurrenceRule', - frequency => 'weekly', - }], - useDefaultAlerts => JSON::true, - }, +sub create_test { + my ($self) = @_; + + my ($ownerJmap, $ownerCaldav) = $self->create_user('owner'); + my ($shareeJmap, $shareeCaldav) = $self->create_user('sharee'); + + my $now = DateTime->now(); + $now->set_time_zone('Etc/UTC'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); + + # define the event to start in a few seconds + my $start = $now->clone(); + $start->add(DateTime::Duration->new(seconds => 2)); + + xlog $self, 'Create event and share calendar with sharee'; + my $res = $ownerJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'owner', + create => { + event1 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['Calendar/set', { - accountId => 'owner', - update => { - Default => { - shareWith => { - 'sharee' => { - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayRSVP => JSON::true, - }, - }, - }, - }, - }, 'R2'], - ]); - - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); - my $ownerHref = $res->[0][1]{created}{event1}{'x-href'}; - $self->assert_not_null($ownerHref); - $self->assert(exists $res->[1][1]{updated}{Default}); - - xlog $self, 'Sharee sets useDefaultAlerts=true'; - my $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'owner', - update => { - $eventId => { - useDefaultAlerts => JSON::true, - }, + uid => 'ed326178-43dc-474d-a496-6cba057c9afe', + title => 'test', + start => $start->strftime('%Y-%m-%dT%H:%M:%S'), + timeZone => 'Europe/Vienna', + duration => 'PT15M', + recurrenceRules => [ { + '@type' => 'RecurrenceRule', + frequency => 'weekly', + } ], + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'Calendar/set', + { + accountId => 'owner', + update => { + Default => { + shareWith => { + 'sharee' => { + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayRSVP => JSON::true, + }, }, - }, 'R1'], - ['CalendarEvent/get', { - accountId => 'owner', - ids => [$eventId], - properties => ['x-href'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - my $shareeHref = $res->[1][1]{list}[0]{'x-href'}; - $self->assert_not_null($shareeHref); - - return { - now => $now, - start => $start, - owner => { - jmap => $ownerJmap, - caldav => $ownerCaldav, - defaultAlertsWithTime => undef, - defaultAlertsWithoutTime => undef, - xhref => $ownerHref, + }, }, - sharee => { - jmap => $shareeJmap, - caldav => $shareeCaldav, - defaultAlertsWithTime => undef, - defaultAlertsWithoutTime => undef, - xhref => $shareeHref, + }, + 'R2' + ], + ]); + + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); + my $ownerHref = $res->[0][1]{created}{event1}{'x-href'}; + $self->assert_not_null($ownerHref); + $self->assert(exists $res->[1][1]{updated}{Default}); + + xlog $self, 'Sharee sets useDefaultAlerts=true'; + my $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'owner', + update => { + $eventId => { + useDefaultAlerts => JSON::true, + }, }, - }; + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + accountId => 'owner', + ids => [$eventId], + properties => ['x-href'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + my $shareeHref = $res->[1][1]{list}[0]{'x-href'}; + $self->assert_not_null($shareeHref); + + return { + now => $now, + start => $start, + owner => { + jmap => $ownerJmap, + caldav => $ownerCaldav, + defaultAlertsWithTime => undef, + defaultAlertsWithoutTime => undef, + xhref => $ownerHref, + }, + sharee => { + jmap => $shareeJmap, + caldav => $shareeCaldav, + defaultAlertsWithTime => undef, + defaultAlertsWithoutTime => undef, + xhref => $shareeHref, + }, + }; } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_destroy_events b/cassandane/tiny-tests/JMAPCalendars/calendar_set_destroy_events index 43ef2701ee..fd4d27982a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_destroy_events +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_destroy_events @@ -2,63 +2,86 @@ use Cassandane::Tiny; sub test_calendar_set_destroy_events - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - xlog "Create calendar and event"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - 1 => { - name => 'test', - }, + xlog "Create calendar and event"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + 1 => { + name => 'test', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + 2 => { + uid => 'eventuid1local', + calendarIds => { + '#1' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/set', { - create => { - 2 => { - uid => 'eventuid1local', - calendarIds => { - '#1' => JSON::true, - }, - title => "event1", - start => "2020-03-30T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - }, - }, - }, 'R2'], - ]); - my $calendarId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($calendarId); - my $eventId = $res->[1][1]{created}{2}{id}; - $self->assert_not_null($eventId); + title => "event1", + start => "2020-03-30T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + }, + }, + }, + 'R2' + ], + ]); + my $calendarId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($calendarId); + my $eventId = $res->[1][1]{created}{2}{id}; + $self->assert_not_null($eventId); - xlog "Destroy calendar (with and without onDestroyEvents)"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - destroy => [$calendarId], - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['id'], - }, 'R2'], - ['Calendar/set', { - destroy => [$calendarId], - onDestroyRemoveEvents => JSON::true, - }, 'R3'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['id'], - }, 'R2'], - ]); - $self->assert_str_equals('calendarHasEvents', - $res->[0][1]{notDestroyed}{$calendarId}{type}); - $self->assert_str_equals($eventId, $res->[1][1]{list}[0]{id}); - $self->assert_deep_equals([$calendarId], $res->[2][1]{destroyed}); - $self->assert_deep_equals([$eventId], $res->[3][1]{notFound}); + xlog "Destroy calendar (with and without onDestroyEvents)"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + destroy => [$calendarId], + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['id'], + }, + 'R2' + ], + [ + 'Calendar/set', + { + destroy => [$calendarId], + onDestroyRemoveEvents => JSON::true, + }, + 'R3' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['id'], + }, + 'R2' + ], + ]); + $self->assert_str_equals('calendarHasEvents', + $res->[0][1]{notDestroyed}{$calendarId}{type}); + $self->assert_str_equals($eventId, $res->[1][1]{list}[0]{id}); + $self->assert_deep_equals([$calendarId], $res->[2][1]{destroyed}); + $self->assert_deep_equals([$eventId], $res->[3][1]{notFound}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_destroyspecials b/cassandane/tiny-tests/JMAPCalendars/calendar_set_destroyspecials index 3cf46c0669..a3096498e1 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_destroyspecials +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_destroyspecials @@ -2,37 +2,34 @@ use Cassandane::Tiny; sub test_calendar_set_destroyspecials - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my @specialIds = ["Inbox", "Outbox", "Default", "Attachments"]; + my @specialIds = [ "Inbox", "Outbox", "Default", "Attachments" ]; - xlog $self, "destroy special calendars"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { destroy => @specialIds }, "R1"] - ]); - $self->assert_not_null($res); + xlog $self, "destroy special calendars"; + my $res = $jmap->CallMethods([ + [ 'Calendar/set', { destroy => @specialIds }, "R1" ] ]); + $self->assert_not_null($res); - my $errType; + my $errType; - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min >= 5)) { - # Default calendar may be destroyed from 3.5+ - $self->assert_deep_equals(['Default'], $res->[0][1]{destroyed}); - } - else { - # but previously, this was forbidden - $errType = $res->[0][1]{notDestroyed}{"Default"}{type}; - $self->assert_str_equals("isDefault", $errType); - } + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min >= 5)) { + # Default calendar may be destroyed from 3.5+ + $self->assert_deep_equals(['Default'], $res->[0][1]{destroyed}); + } else { + # but previously, this was forbidden + $errType = $res->[0][1]{notDestroyed}{"Default"}{type}; + $self->assert_str_equals("isDefault", $errType); + } - $errType = $res->[0][1]{notDestroyed}{"Inbox"}{type}; - $self->assert_str_equals("notFound", $errType); - $errType = $res->[0][1]{notDestroyed}{"Outbox"}{type}; - $self->assert_str_equals("notFound", $errType); - $errType = $res->[0][1]{notDestroyed}{"Attachments"}{type}; - $self->assert_str_equals("notFound", $errType); + $errType = $res->[0][1]{notDestroyed}{"Inbox"}{type}; + $self->assert_str_equals("notFound", $errType); + $errType = $res->[0][1]{notDestroyed}{"Outbox"}{type}; + $self->assert_str_equals("notFound", $errType); + $errType = $res->[0][1]{notDestroyed}{"Attachments"}{type}; + $self->assert_str_equals("notFound", $errType); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_error b/cassandane/tiny-tests/JMAPCalendars/calendar_set_error index 89f22d16cb..8975ac1e82 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_error +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_error @@ -2,81 +2,113 @@ use Cassandane::Tiny; sub test_calendar_set_error - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create calendar with missing mandatory attributes"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => {}}}, "R1"] - ]); - $self->assert_not_null($res); - my $errType = $res->[0][1]{notCreated}{"1"}{type}; - my $errProp = $res->[0][1]{notCreated}{"1"}{properties}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals([ "name" ], $errProp); + xlog $self, "create calendar with missing mandatory attributes"; + my $res = $jmap->CallMethods([ + [ 'Calendar/set', { create => { "1" => {} } }, "R1" ] ]); + $self->assert_not_null($res); + my $errType = $res->[0][1]{notCreated}{"1"}{type}; + my $errProp = $res->[0][1]{notCreated}{"1"}{properties}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(["name"], $errProp); - xlog $self, "create calendar with invalid optional attributes"; - $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", color => "coral", - sortOrder => 2, isVisible => \1, - myRights => { - mayReadFreeBusy => \0, mayReadItems => \0, - mayAddItems => \0, mayModifyItems => \0, - mayRemoveItems => \0, mayRename => \0, - mayDelete => \0 - } - }}}, "R1"] - ]); - $errType = $res->[0][1]{notCreated}{"1"}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals(['myRights'], $res->[0][1]{notCreated}{"1"}{properties}); + xlog $self, "create calendar with invalid optional attributes"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1, + myRights => { + mayReadFreeBusy => \0, + mayReadItems => \0, + mayAddItems => \0, + mayModifyItems => \0, + mayRemoveItems => \0, + mayRename => \0, + mayDelete => \0 + } + } + } + }, + "R1" + ] ]); + $errType = $res->[0][1]{notCreated}{"1"}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(['myRights'], + $res->[0][1]{notCreated}{"1"}{properties}); - xlog $self, "update unknown calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { update => { "unknown" => { - name => "foo" - }}}, "R1"] - ]); - $errType = $res->[0][1]{notUpdated}{"unknown"}{type}; - $self->assert_str_equals("notFound", $errType); + xlog $self, "update unknown calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + update => { + "unknown" => { + name => "foo" + } + } + }, + "R1" + ] ]); + $errType = $res->[0][1]{notUpdated}{"unknown"}{type}; + $self->assert_str_equals("notFound", $errType); - xlog $self, "create calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", - sortOrder => 2, - isVisible => \1 - }}}, "R1"] - ]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "update calendar with immutable optional attributes"; - $res = $jmap->CallMethods([ - ['Calendar/set', { update => { $id => { - myRights => { - mayReadFreeBusy => \0, mayReadItems => \0, - mayAddItems => \0, mayModifyItems => \0, - mayRemoveItems => \0, mayRename => \0, - mayDelete => \0 - } - }}}, "R1"] - ]); - $errType = $res->[0][1]{notUpdated}{$id}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals(['myRights'], $res->[0][1]{notUpdated}{$id}{properties}); + xlog $self, "update calendar with immutable optional attributes"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + update => { + $id => { + myRights => { + mayReadFreeBusy => \0, + mayReadItems => \0, + mayAddItems => \0, + mayModifyItems => \0, + mayRemoveItems => \0, + mayRename => \0, + mayDelete => \0 + } + } + } + }, + "R1" + ] ]); + $errType = $res->[0][1]{notUpdated}{$id}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(['myRights'], + $res->[0][1]{notUpdated}{$id}{properties}); - xlog $self, "destroy unknown calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', {destroy => ["unknown"]}, "R1"] - ]); - $errType = $res->[0][1]{notDestroyed}{"unknown"}{type}; - $self->assert_str_equals("notFound", $errType); + xlog $self, "destroy unknown calendar"; + $res = $jmap->CallMethods([ + [ 'Calendar/set', { destroy => ["unknown"] }, "R1" ] ]); + $errType = $res->[0][1]{notDestroyed}{"unknown"}{type}; + $self->assert_str_equals("notFound", $errType); - xlog $self, "destroy calendar $id"; - $res = $jmap->CallMethods([['Calendar/set', {destroy => ["$id"]}, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy calendar $id"; + $res + = $jmap->CallMethods([ [ 'Calendar/set', { destroy => ["$id"] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_inherit_defaultalerts b/cassandane/tiny-tests/JMAPCalendars/calendar_set_inherit_defaultalerts index 2321664597..1181fd4e65 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_inherit_defaultalerts +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_inherit_defaultalerts @@ -3,83 +3,98 @@ use Cassandane::Tiny; use Data::UUID; sub test_calendar_set_inherit_defaultalerts - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $alert1Id = '589c1b45-ca59-4072-90fb-93c41491e484'; + my $alert1Id = '589c1b45-ca59-4072-90fb-93c41491e484'; - my $defaultAlertsWithTime = { - $alert1Id => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT1H', - }, - action => 'email', - }, - }; + my $defaultAlertsWithTime = { + $alert1Id => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT1H', + }, + action => 'email', + }, + }; - my $alert2Id = '899fd3e7-c0a0-442d-a04f-725c58728afb'; + my $alert2Id = '899fd3e7-c0a0-442d-a04f-725c58728afb'; - my $defaultAlertsWithoutTime = { - $alert2Id => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - }, - }; + my $defaultAlertsWithoutTime = { + $alert2Id => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'display', + }, + }; - xlog $self, "Create calendar1 with default alerts with time"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendar1 => { - name => 'calendar1', - defaultAlertsWithTime => $defaultAlertsWithTime, - } - } - }, 'R1'], - ['Calendar/get', { - ids => ['#calendar1'], - properties => ['defaultAlertsWithTime', 'defaultAlertsWithoutTime'], - }, 'R2'] - ]); + xlog $self, "Create calendar1 with default alerts with time"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + calendar1 => { + name => 'calendar1', + defaultAlertsWithTime => $defaultAlertsWithTime, + } + } + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => ['#calendar1'], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime' ], + }, + 'R2' + ] + ]); - $self->assert_deep_equals($defaultAlertsWithTime, - $res->[1][1]{list}[0]{defaultAlertsWithTime}); + $self->assert_deep_equals($defaultAlertsWithTime, + $res->[1][1]{list}[0]{defaultAlertsWithTime}); - xlog $self, "Assert no default alerts without time were inherited"; - $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithoutTime}); + xlog $self, "Assert no default alerts without time were inherited"; + $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithoutTime}); - xlog $self, "Create calendar2 with default alerts without time"; + xlog $self, "Create calendar2 with default alerts without time"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendar2 => { - name => 'calendar2', - defaultAlertsWithoutTime => $defaultAlertsWithoutTime, - } - } - }, 'R1'], - ['Calendar/get', { - ids => ['#calendar2'], - properties => ['defaultAlertsWithTime', 'defaultAlertsWithoutTime'], - }, 'R2'] - ]); + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + calendar2 => { + name => 'calendar2', + defaultAlertsWithoutTime => $defaultAlertsWithoutTime, + } + } + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => ['#calendar2'], + properties => [ 'defaultAlertsWithTime', 'defaultAlertsWithoutTime' ], + }, + 'R2' + ] + ]); - $self->assert_deep_equals($defaultAlertsWithoutTime, - $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); + $self->assert_deep_equals($defaultAlertsWithoutTime, + $res->[1][1]{list}[0]{defaultAlertsWithoutTime}); - xlog $self, "Assert no default alerts with time were inherited"; - $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithTime}); + xlog $self, "Assert no default alerts with time were inherited"; + $self->assert_null($res->[1][1]{list}[0]{defaultAlertsWithTime}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_issubscribed b/cassandane/tiny-tests/JMAPCalendars/calendar_set_issubscribed index 1898b8b77c..a07fb5dc41 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_issubscribed +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_issubscribed @@ -2,45 +2,60 @@ use Cassandane::Tiny; sub test_calendar_set_issubscribed - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Create calendar - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - '1' => { - name => 'A', - color => 'blue', - } - }, - }, 'R1'], - ['Calendar/get', { - ids => ['#1'], - properties => ['isSubscribed'] - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{created}{1}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); - my $id = $res->[0][1]{created}{"1"}{id}; + # Create calendar + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + '1' => { + name => 'A', + color => 'blue', + } + }, + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => ['#1'], + properties => ['isSubscribed'] + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{created}{1}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); + my $id = $res->[0][1]{created}{"1"}{id}; - # Can't unsubscribe own calendars - $res = $jmap->CallMethods([ - ['Calendar/set', - { update => { - $id => { - isSubscribed => JSON::false, - } - } - }, "R1"], - ['Calendar/get', { - ids => [$id], - properties => ['isSubscribed'] - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{notUpdated}{$id}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); + # Can't unsubscribe own calendars + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $id => { + isSubscribed => JSON::false, + } + } + }, + "R1" + ], + [ + 'Calendar/get', + { + ids => [$id], + properties => ['isSubscribed'] + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$id}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_issubscribed_shared b/cassandane/tiny-tests/JMAPCalendars/calendar_set_issubscribed_shared index ba15b16ce0..7b6e63ceed 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_issubscribed_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_issubscribed_shared @@ -2,58 +2,70 @@ use Cassandane::Tiny; sub test_calendar_set_issubscribed_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.other"); + xlog $self, "create shared account"; + $admintalk->create("user.other"); - $admintalk->setacl("user.other", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.other", other => 'lrswipkxtecdn'); + $admintalk->setacl("user.other", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.other", other => 'lrswipkxtecdn'); - xlog $self, "create and share default calendar"; - my $othertalk = Net::CalDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - $admintalk->setacl('user.other.#calendars.Default', "cassandane" => 'lr') or die; + xlog $self, "create and share default calendar"; + my $othertalk = Net::CalDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + $admintalk->setacl('user.other.#calendars.Default', "cassandane" => 'lr') + or die; - # Get calendar - my $res = $jmap->CallMethods([ - ['Calendar/get', { - accountId => 'other', - properties => ['isSubscribed'] - }, 'R!'], - ]); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isSubscribed}); - my $id = $res->[0][1]{list}[0]{id}; + # Get calendar + my $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + accountId => 'other', + properties => ['isSubscribed'] + }, + 'R!' + ], + ]); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isSubscribed}); + my $id = $res->[0][1]{list}[0]{id}; - # Toggle isSubscribed on read-only shared calendar - $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'other', - update => { - $id => { - isSubscribed => JSON::true, - } - } - }, "R1"], - ['Calendar/get', { - accountId => 'other', - ids => [$id], - properties => ['isSubscribed'] - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$id}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); + # Toggle isSubscribed on read-only shared calendar + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + accountId => 'other', + update => { + $id => { + isSubscribed => JSON::true, + } + } + }, + "R1" + ], + [ + 'Calendar/get', + { + accountId => 'other', + ids => [$id], + properties => ['isSubscribed'] + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_shared b/cassandane/tiny-tests/JMAPCalendars/calendar_set_shared index 4eedda8ee0..1c9a5ba8df 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_shared @@ -2,91 +2,122 @@ use Cassandane::Tiny; sub test_calendar_set_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); + my $service = $self->{instance}->get_service("http"); + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - # Call CalDAV once to create manifold's calendar home #calendars - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + # Call CalDAV once to create manifold's calendar home #calendars + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "share calendar home read-only to user"; - $admintalk->setacl("user.manifold.#calendars", cassandane => 'lr') or die; + xlog $self, "share calendar home read-only to user"; + $admintalk->setacl("user.manifold.#calendars", cassandane => 'lr') or die; - xlog $self, "create calendar (should fail)"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'manifold', - create => { "1" => { - name => "foo", - color => "coral", - sortOrder => 2, - isVisible => \1 - }}}, "R1"] - ]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert_str_equals("accountReadOnly", $res->[0][1]{notCreated}{1}{type}); + xlog $self, "create calendar (should fail)"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => 'manifold', + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert_str_equals("accountReadOnly", + $res->[0][1]{notCreated}{1}{type}); - xlog $self, "share calendar home read-writable to user"; - $admintalk->setacl("user.manifold.#calendars", cassandane => 'lrswipkxtecdn') or die; + xlog $self, "share calendar home read-writable to user"; + $admintalk->setacl("user.manifold.#calendars", cassandane => 'lrswipkxtecdn') + or die; - xlog $self, "create calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'manifold', - create => { "1" => { - name => "foo", - color => "coral", - sortOrder => 2, - isVisible => \1 - }}}, "R1"] - ]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - my $CalendarId = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($CalendarId); + xlog $self, "create calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => 'manifold', + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + my $CalendarId = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($CalendarId); - xlog $self, "share calendar read-only to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId", "cassandane" => 'lr') or die; + xlog $self, "share calendar read-only to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId", + "cassandane" => 'lr') + or die; - xlog $self, "update calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'manifold', - update => {$CalendarId => { - name => "bar", - isVisible => \0 - }}}, "R1"] - ]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert(exists $res->[0][1]{updated}{$CalendarId}); + xlog $self, "update calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => 'manifold', + update => { + $CalendarId => { + name => "bar", + isVisible => \0 + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert(exists $res->[0][1]{updated}{$CalendarId}); - xlog $self, "destroy calendar $CalendarId (should fail)"; - $res = $jmap->CallMethods([['Calendar/set', {accountId => 'manifold', destroy => [$CalendarId]}, "R1"]]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert_str_equals("accountReadOnly", $res->[0][1]{notDestroyed}{$CalendarId}{type}); + xlog $self, "destroy calendar $CalendarId (should fail)"; + $res = $jmap->CallMethods( + [ [ + 'Calendar/set', { accountId => 'manifold', destroy => [$CalendarId] }, + "R1" + ] ] + ); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert_str_equals("accountReadOnly", + $res->[0][1]{notDestroyed}{$CalendarId}{type}); - xlog $self, "share read-writable to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId", "cassandane" => 'lrswipkxtecdn') or die; + xlog $self, "share read-writable to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId", + "cassandane" => 'lrswipkxtecdn') + or die; - xlog $self, "destroy calendar $CalendarId"; - $res = $jmap->CallMethods([['Calendar/set', {accountId => 'manifold', destroy => [$CalendarId]}, "R1"]]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert_str_equals($CalendarId, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy calendar $CalendarId"; + $res = $jmap->CallMethods( + [ [ + 'Calendar/set', { accountId => 'manifold', destroy => [$CalendarId] }, + "R1" + ] ] + ); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert_str_equals($CalendarId, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_shared_sort_order b/cassandane/tiny-tests/JMAPCalendars/calendar_set_shared_sort_order index 673092f208..a32151a2d9 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_shared_sort_order +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_shared_sort_order @@ -2,63 +2,68 @@ use Cassandane::Tiny; sub test_calendar_set_shared_sort_order - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - my ($maj, $min) = Cassandane::Instance->get_version(); + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + my ($maj, $min) = Cassandane::Instance->get_version(); - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "create calendars"; - my $CalendarId1 = $mantalk->NewCalendar({name => 'Manifold Calendar1'}); - $self->assert_not_null($CalendarId1); - my $CalendarId2 = $mantalk->NewCalendar({name => 'Manifold Calendar2'}); - $self->assert_not_null($CalendarId2); + xlog $self, "create calendars"; + my $CalendarId1 = $mantalk->NewCalendar({ name => 'Manifold Calendar1' }); + $self->assert_not_null($CalendarId1); + my $CalendarId2 = $mantalk->NewCalendar({ name => 'Manifold Calendar2' }); + $self->assert_not_null($CalendarId2); - xlog $self, "share $CalendarId1 and $CalendarId2 read-only to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId1", "cassandane" => 'lrw') or die; - $admintalk->setacl("user.manifold.#calendars.$CalendarId2", "cassandane" => 'lrw') or die; + xlog $self, "share $CalendarId1 and $CalendarId2 read-only to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId1", + "cassandane" => 'lrw') + or die; + $admintalk->setacl("user.manifold.#calendars.$CalendarId2", + "cassandane" => 'lrw') + or die; - xlog $self, "Verify shared account is NOT isReadOnly"; - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - $self->assert_str_equals('200', $RawResponse->{status}); - my $session = eval { decode_json($RawResponse->{content}) }; - $self->assert_equals(JSON::false, $session->{accounts}{manifold}{isReadOnly}); + xlog $self, "Verify shared account is NOT isReadOnly"; + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + $self->assert_str_equals('200', $RawResponse->{status}); + my $session = eval { decode_json($RawResponse->{content}) }; + $self->assert_equals(JSON::false, $session->{accounts}{manifold}{isReadOnly}); - xlog $self, "Set sortOrder on a calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'manifold', - update => { - $CalendarId1 => { - sortOrder => 2 - } - } - }, "R1"] - ]); - $self->assert(exists $res->[0][1]{updated}{$CalendarId1}); + xlog $self, "Set sortOrder on a calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => 'manifold', + update => { + $CalendarId1 => { + sortOrder => 2 + } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$CalendarId1}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_sharewith b/cassandane/tiny-tests/JMAPCalendars/calendar_set_sharewith index 148e157909..fad667f1ea 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_sharewith +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_sharewith @@ -2,212 +2,232 @@ use Cassandane::Tiny; sub test_calendar_set_sharewith - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - # need to version-gate jmap features that aren't in 3.5... - my ($maj, $min) = Cassandane::Instance->get_version(); - - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); - - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.master"); - - my $mastalk = Net::CalDAVTalk->new( - user => "master", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.master", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.master", master => 'lrswipkxtecdn'); - - xlog $self, "create calendar"; - my $CalendarId = $mastalk->NewCalendar({name => 'Shared Calendar'}); - $self->assert_not_null($CalendarId); - - xlog $self, "share to user with permission to share"; - $admintalk->setacl("user.master.#calendars.$CalendarId", "cassandane" => 'lrswipkxtecdan9') or die; - - xlog $self, "create third account"; - $admintalk->create("user.manifold"); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - - xlog $self, "and a forth"; - $admintalk->create("user.paraphrase"); - - $admintalk->setacl("user.paraphrase", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.paraphrase", paraphrase => 'lrswipkxtecdn'); - - # Call CalDAV once to create manifold's calendar home #calendars - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - # Call CalDAV once to create paraphrase's calendar home #calendars - my $partalk = Net::CalDAVTalk->new( - user => "paraphrase", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "sharee gives third user access to shared calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'master', - update => { "$CalendarId" => { - "shareWith/manifold" => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - }, - "shareWith/paraphrase" => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayWriteAll => JSON::true, - }, - }}}, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Calendar/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "fetch invites"; - my ($adds) = $mantalk->SyncEventLinks("/dav/notifications/user/manifold"); - $self->assert_equals(1, scalar %$adds); - ($adds) = $partalk->SyncEventLinks("/dav/notifications/user/paraphrase"); - $self->assert_equals(1, scalar %$adds); - - xlog $self, "check ACL"; - my $acl = $admintalk->getacl("user.master.#calendars.$CalendarId"); - my %map = @$acl; - $self->assert_str_equals('lrswipkxtecdan9', $map{cassandane}); - $self->assert_str_equals('lrw59', $map{manifold}); - $self->assert_str_equals('lrswitedn79', $map{paraphrase}); - - xlog $self, "check Outbox ACL"; - $acl = $admintalk->getacl("user.master.#calendars.Outbox"); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + # need to version-gate jmap features that aren't in 3.5... + my ($maj, $min) = Cassandane::Instance->get_version(); + + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); + + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.master"); + + my $mastalk = Net::CalDAVTalk->new( + user => "master", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.master", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.master", master => 'lrswipkxtecdn'); + + xlog $self, "create calendar"; + my $CalendarId = $mastalk->NewCalendar({ name => 'Shared Calendar' }); + $self->assert_not_null($CalendarId); + + xlog $self, "share to user with permission to share"; + $admintalk->setacl("user.master.#calendars.$CalendarId", + "cassandane" => 'lrswipkxtecdan9') + or die; + + xlog $self, "create third account"; + $admintalk->create("user.manifold"); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + + xlog $self, "and a forth"; + $admintalk->create("user.paraphrase"); + + $admintalk->setacl("user.paraphrase", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.paraphrase", paraphrase => 'lrswipkxtecdn'); + + # Call CalDAV once to create manifold's calendar home #calendars + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + # Call CalDAV once to create paraphrase's calendar home #calendars + my $partalk = Net::CalDAVTalk->new( + user => "paraphrase", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "sharee gives third user access to shared calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => 'master', + update => { + "$CalendarId" => { + "shareWith/manifold" => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + }, + "shareWith/paraphrase" => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayWriteAll => JSON::true, + }, + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Calendar/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "fetch invites"; + my ($adds) = $mantalk->SyncEventLinks("/dav/notifications/user/manifold"); + $self->assert_equals(1, scalar %$adds); + ($adds) = $partalk->SyncEventLinks("/dav/notifications/user/paraphrase"); + $self->assert_equals(1, scalar %$adds); + + xlog $self, "check ACL"; + my $acl = $admintalk->getacl("user.master.#calendars.$CalendarId"); + my %map = @$acl; + $self->assert_str_equals('lrswipkxtecdan9', $map{cassandane}); + $self->assert_str_equals('lrw59', $map{manifold}); + $self->assert_str_equals('lrswitedn79', $map{paraphrase}); + + xlog $self, "check Outbox ACL"; + $acl = $admintalk->getacl("user.master.#calendars.Outbox"); + %map = @$acl; + $self->assert_null($map{manifold}) + ; # we don't create Outbox ACLs for read-only + $self->assert_str_equals('78', $map{paraphrase}); + + xlog $self, "check Principal ACL"; + $acl = $admintalk->getacl("user.master.#calendars"); + %map = @$acl; + # both users get ACLs on the Inbox + $self->assert_str_equals('lr', $map{manifold}); + $self->assert_str_equals('lr', $map{paraphrase}); + + my $Name = $mantalk->GetProps('/dav/principals/user/master', 'D:displayname'); + $self->assert_str_equals('master', $Name); + $Name = $partalk->GetProps('/dav/principals/user/master', 'D:displayname'); + $self->assert_str_equals('master', $Name); + + if ($maj > 3 || ($maj == 3 && $min >= 4)) { + xlog $self, "check ACL on JMAP upload folder"; + $acl = $admintalk->getacl("user.master.#jmap"); %map = @$acl; - $self->assert_null($map{manifold}); # we don't create Outbox ACLs for read-only - $self->assert_str_equals('78', $map{paraphrase}); - - xlog $self, "check Principal ACL"; - $acl = $admintalk->getacl("user.master.#calendars"); - %map = @$acl; - # both users get ACLs on the Inbox - $self->assert_str_equals('lr', $map{manifold}); - $self->assert_str_equals('lr', $map{paraphrase}); - - my $Name = $mantalk->GetProps('/dav/principals/user/master', 'D:displayname'); - $self->assert_str_equals('master', $Name); - $Name = $partalk->GetProps('/dav/principals/user/master', 'D:displayname'); - $self->assert_str_equals('master', $Name); - - if ($maj > 3 || ($maj == 3 && $min >= 4)) { - xlog $self, "check ACL on JMAP upload folder"; - $acl = $admintalk->getacl("user.master.#jmap"); - %map = @$acl; - $self->assert_str_equals('lrswitedn', $map{cassandane}); - $self->assert_str_equals('lrw', $map{manifold}); - $self->assert_str_equals('lrswitedn', $map{paraphrase}); - } - - xlog $self, "Clear initial syslog"; - $self->{instance}->getsyslog(); - - xlog $self, "Update sharewith just for manifold"; - $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'master', - update => { "$CalendarId" => { - "shareWith/manifold/mayWriteAll" => JSON::true, - }}}, "R1"] - ]); - - if ($self->{instance}->{have_syslog_replacement}) { - my @lines = $self->{instance}->getsyslog(); - $self->assert_matches(qr/manifold\.\#notifications/, "@lines"); - $self->assert((not grep { /paraphrase\.\#notifications/ } @lines), Data::Dumper::Dumper(\@lines)); - } - - if ($maj > 3 || ($maj == 3 && $min >= 4)) { - xlog $self, "check ACL on JMAP upload folder"; - $acl = $admintalk->getacl("user.master.#jmap"); - %map = @$acl; - $self->assert_str_equals('lrswitedn', $map{cassandane}); - $self->assert_str_equals('lrswitedn', $map{manifold}); - $self->assert_str_equals('lrswitedn', $map{paraphrase}); - } - - xlog $self, "Remove the access for paraphrase"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'master', - update => { "$CalendarId" => { - "shareWith/paraphrase" => undef, - }}}, "R1"] - ]); - - $self->assert_not_null($res); - $self->assert_str_equals('Calendar/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "check ACL"; - $acl = $admintalk->getacl("user.master.#calendars.$CalendarId"); - %map = @$acl; - $self->assert_str_equals('lrswipkxtecdan9', $map{cassandane}); - $self->assert_str_equals('lrswitedn579', $map{manifold}); - $self->assert_null($map{paraphrase}); - - xlog $self, "check Outbox ACL"; - $acl = $admintalk->getacl("user.master.#calendars.Outbox"); + $self->assert_str_equals('lrswitedn', $map{cassandane}); + $self->assert_str_equals('lrw', $map{manifold}); + $self->assert_str_equals('lrswitedn', $map{paraphrase}); + } + + xlog $self, "Clear initial syslog"; + $self->{instance}->getsyslog(); + + xlog $self, "Update sharewith just for manifold"; + $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => 'master', + update => { + "$CalendarId" => { + "shareWith/manifold/mayWriteAll" => JSON::true, + } + } + }, + "R1" + ] ]); + + if ($self->{instance}->{have_syslog_replacement}) { + my @lines = $self->{instance}->getsyslog(); + $self->assert_matches(qr/manifold\.\#notifications/, "@lines"); + $self->assert((not grep { /paraphrase\.\#notifications/ } @lines), + Data::Dumper::Dumper(\@lines)); + } + + if ($maj > 3 || ($maj == 3 && $min >= 4)) { + xlog $self, "check ACL on JMAP upload folder"; + $acl = $admintalk->getacl("user.master.#jmap"); %map = @$acl; - $self->assert_str_equals('78', $map{manifold}); - $self->assert_null($map{paraphrase}); - - xlog $self, "check Principal ACL"; - $acl = $admintalk->getacl("user.master.#calendars"); + $self->assert_str_equals('lrswitedn', $map{cassandane}); + $self->assert_str_equals('lrswitedn', $map{manifold}); + $self->assert_str_equals('lrswitedn', $map{paraphrase}); + } + + xlog $self, "Remove the access for paraphrase"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => 'master', + update => { + "$CalendarId" => { + "shareWith/paraphrase" => undef, + } + } + }, + "R1" + ] ]); + + $self->assert_not_null($res); + $self->assert_str_equals('Calendar/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "check ACL"; + $acl = $admintalk->getacl("user.master.#calendars.$CalendarId"); + %map = @$acl; + $self->assert_str_equals('lrswipkxtecdan9', $map{cassandane}); + $self->assert_str_equals('lrswitedn579', $map{manifold}); + $self->assert_null($map{paraphrase}); + + xlog $self, "check Outbox ACL"; + $acl = $admintalk->getacl("user.master.#calendars.Outbox"); + %map = @$acl; + $self->assert_str_equals('78', $map{manifold}); + $self->assert_null($map{paraphrase}); + + xlog $self, "check Principal ACL"; + $acl = $admintalk->getacl("user.master.#calendars"); + %map = @$acl; + # both users get ACLs on the Inbox + $self->assert_str_equals('lr', $map{manifold}); + $self->assert_null($map{paraphrase}); + + xlog $self, "Check propfind"; + $Name = eval { + $partalk->GetProps('/dav/principals/user/master', 'D:displayname'); + }; + my $error = $@; + $self->assert_null($Name); + $self->assert_matches(qr/403 Forbidden/, $error); + + if ($maj > 3 || ($maj == 3 && $min >= 4)) { + xlog $self, "check ACL on JMAP upload folder"; + $acl = $admintalk->getacl("user.master.#jmap"); %map = @$acl; - # both users get ACLs on the Inbox - $self->assert_str_equals('lr', $map{manifold}); + $self->assert_str_equals('lrswitedn', $map{cassandane}); + $self->assert_str_equals('lrswitedn', $map{manifold}); $self->assert_null($map{paraphrase}); - - xlog $self, "Check propfind"; - $Name = eval { $partalk->GetProps('/dav/principals/user/master', 'D:displayname') }; - my $error = $@; - $self->assert_null($Name); - $self->assert_matches(qr/403 Forbidden/, $error); - - if ($maj > 3 || ($maj == 3 && $min >= 4)) { - xlog $self, "check ACL on JMAP upload folder"; - $acl = $admintalk->getacl("user.master.#jmap"); - %map = @$acl; - $self->assert_str_equals('lrswitedn', $map{cassandane}); - $self->assert_str_equals('lrswitedn', $map{manifold}); - $self->assert_null($map{paraphrase}); - } + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_sharewith_acl b/cassandane/tiny-tests/JMAPCalendars/calendar_set_sharewith_acl index d54b5a1b97..1c89fa2a7b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_sharewith_acl +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_sharewith_acl @@ -2,115 +2,138 @@ use Cassandane::Tiny; sub test_calendar_set_sharewith_acl - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.aatester"); - $admin->create("user.zztester"); + $admin->create("user.aatester"); + $admin->create("user.zztester"); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - '1' => { - name => 'A', - } - }, - }, 'R1'], - ]); - my $calendarId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($calendarId); - - my @testCases = ({ - rights => { - mayReadFreeBusy => JSON::true, - }, - acl => '9', - }, { - rights => { - mayReadItems => JSON::true, - }, - acl => 'lrw', - }, { - rights => { - mayWriteAll => JSON::true, - }, - acl => 'switedn7', - wantRights => { - mayWriteAll => JSON::true, - mayWriteOwn => JSON::true, - mayUpdatePrivate => JSON::true, - mayRSVP => JSON::true, - }, - }, { - rights => { - mayWriteOwn => JSON::true, - }, - acl => 'w6', - }, { - rights => { - mayUpdatePrivate => JSON::true, - }, - acl => 'w5', - }, { - rights => { - mayRSVP => JSON::true, + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + '1' => { + name => 'A', + } }, - acl => 'w7', - }, { - rights => { - mayAdmin => JSON::true, - }, - acl => 'wa', - }, { - rights => { - mayDelete => JSON::true, - }, - acl => 'wxc', - }); + }, + 'R1' + ], + ]); + my $calendarId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($calendarId); - foreach(@testCases) { + my @testCases = ( + { + rights => { + mayReadFreeBusy => JSON::true, + }, + acl => '9', + }, + { + rights => { + mayReadItems => JSON::true, + }, + acl => 'lrw', + }, + { + rights => { + mayWriteAll => JSON::true, + }, + acl => 'switedn7', + wantRights => { + mayWriteAll => JSON::true, + mayWriteOwn => JSON::true, + mayUpdatePrivate => JSON::true, + mayRSVP => JSON::true, + }, + }, + { + rights => { + mayWriteOwn => JSON::true, + }, + acl => 'w6', + }, + { + rights => { + mayUpdatePrivate => JSON::true, + }, + acl => 'w5', + }, + { + rights => { + mayRSVP => JSON::true, + }, + acl => 'w7', + }, + { + rights => { + mayAdmin => JSON::true, + }, + acl => 'wa', + }, + { + rights => { + mayDelete => JSON::true, + }, + acl => 'wxc', + } + ); - xlog "Run test for acl $_->{acl}"; + foreach (@testCases) { - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - shareWith => { - aatester => $_->{rights}, - zztester => $_->{rights}, - }, - }, - }, - }, 'R1'], - ['Calendar/get', { - ids => [$calendarId], - properties => ['shareWith'], - }, 'R2'], - ]); + xlog "Run test for acl $_->{acl}"; - $_->{wantRights} ||= $_->{rights}; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + shareWith => { + aatester => $_->{rights}, + zztester => $_->{rights}, + }, + }, + }, + }, + 'R1' + ], + [ + 'Calendar/get', + { + ids => [$calendarId], + properties => ['shareWith'], + }, + 'R2' + ], + ]); - my %mergedrights = (( - mayReadFreeBusy => JSON::false, - mayReadItems => JSON::false, - mayWriteAll => JSON::false, - mayWriteOwn => JSON::false, - mayUpdatePrivate => JSON::false, - mayRSVP => JSON::false, - mayAdmin => JSON::false, - mayDelete => JSON::false, - ), %{$_->{wantRights}}); + $_->{wantRights} ||= $_->{rights}; - $self->assert_deep_equals(\%mergedrights, - $res->[1][1]{list}[0]{shareWith}{aatester}); - $self->assert_deep_equals(\%mergedrights, - $res->[1][1]{list}[0]{shareWith}{zztester}); - my %acl = @{$admin->getacl("user.cassandane.#calendars.$calendarId")}; - $self->assert_str_equals($_->{acl}, $acl{aatester}); - $self->assert_str_equals($_->{acl}, $acl{zztester}); - } + my %mergedrights = ( + ( + mayReadFreeBusy => JSON::false, + mayReadItems => JSON::false, + mayWriteAll => JSON::false, + mayWriteOwn => JSON::false, + mayUpdatePrivate => JSON::false, + mayRSVP => JSON::false, + mayAdmin => JSON::false, + mayDelete => JSON::false, + ), + %{ $_->{wantRights} } + ); + + $self->assert_deep_equals(\%mergedrights, + $res->[1][1]{list}[0]{shareWith}{aatester}); + $self->assert_deep_equals(\%mergedrights, + $res->[1][1]{list}[0]{shareWith}{zztester}); + my %acl = @{ $admin->getacl("user.cassandane.#calendars.$calendarId") }; + $self->assert_str_equals($_->{acl}, $acl{aatester}); + $self->assert_str_equals($_->{acl}, $acl{zztester}); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_state b/cassandane/tiny-tests/JMAPCalendars/calendar_set_state index 17dc7f7345..a8c5332e1a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_state +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_state @@ -2,102 +2,119 @@ use Cassandane::Tiny; sub test_calendar_set_state - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with invalid state token"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - ifInState => "badstate", - create => { "1" => { name => "foo" }} - }, "R1"] - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + xlog $self, "create with invalid state token"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + ifInState => "badstate", + create => { "1" => { name => "foo" } } + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "create with wrong state token"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - ifInState => "987654321", - create => { "1" => { name => "foo" }} - }, "R1"] - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + xlog $self, "create with wrong state token"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + ifInState => "987654321", + create => { "1" => { name => "foo" } } + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "create calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", - color => "coral", - sortOrder => 2, - isVisible => \1 - }}}, "R1"] - ]); - $self->assert_not_null($res); + xlog $self, "create calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); - my $id = $res->[0][1]{created}{"1"}{id}; - my $state = $res->[0][1]{newState}; + my $id = $res->[0][1]{created}{"1"}{id}; + my $state = $res->[0][1]{newState}; - xlog $self, "update calendar $id with current state"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - ifInState => $state, - update => {"$id" => {name => "bar"}} - }, "R1"] - ]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); + xlog $self, "update calendar $id with current state"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + ifInState => $state, + update => { "$id" => { name => "bar" } } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); - my $oldState = $state; - $state = $res->[0][1]{newState}; + my $oldState = $state; + $state = $res->[0][1]{newState}; - xlog $self, "setCalendar noops must keep state"; - $res = $jmap->CallMethods([ - ['Calendar/set', {}, "R1"], - ['Calendar/set', {}, "R2"], - ['Calendar/set', {}, "R3"] - ]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); + xlog $self, "setCalendar noops must keep state"; + $res = $jmap->CallMethods([ + [ 'Calendar/set', {}, "R1" ], + [ 'Calendar/set', {}, "R2" ], + [ 'Calendar/set', {}, "R3" ] + ]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); - xlog $self, "update calendar $id with expired state"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - ifInState => $oldState, - update => {"$id" => {name => "baz"}} - }, "R1"] - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals("stateMismatch", $res->[0][1]{type}); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "update calendar $id with expired state"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + ifInState => $oldState, + update => { "$id" => { name => "baz" } } + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals("stateMismatch", $res->[0][1]{type}); + $self->assert_str_equals('R1', $res->[0][2]); - xlog $self, "get calendar $id to make sure state didn't change"; - $res = $jmap->CallMethods([['Calendar/get', {ids => [$id]}, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]{state}); - $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); + xlog $self, "get calendar $id to make sure state didn't change"; + $res = $jmap->CallMethods([ [ 'Calendar/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]{state}); + $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); - xlog $self, "destroy calendar $id with expired state"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - ifInState => $oldState, - destroy => [$id] - }, "R1"] - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals("stateMismatch", $res->[0][1]{type}); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "destroy calendar $id with expired state"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + ifInState => $oldState, + destroy => [$id] + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals("stateMismatch", $res->[0][1]{type}); + $self->assert_str_equals('R1', $res->[0][2]); - xlog $self, "destroy calendar $id with current state"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - ifInState => $state, - destroy => [$id] - }, "R1"] - ]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy calendar $id with current state"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + ifInState => $state, + destroy => [$id] + }, + "R1" + ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_set_unknown_calendarright b/cassandane/tiny-tests/JMAPCalendars/calendar_set_unknown_calendarright index d43d337899..3a4c4e6d63 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_set_unknown_calendarright +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_set_unknown_calendarright @@ -2,31 +2,36 @@ use Cassandane::Tiny; sub test_calendar_set_unknown_calendarright - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - $self->create_user('sharee'); + $self->create_user('sharee'); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - sharee => { - unknownCalendarRight => JSON::true, - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + sharee => { + unknownCalendarRight => JSON::true, + }, }, - }, 'R1'], - ]); + }, + }, + }, + 'R1' + ], + ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{Default}{type}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{Default}{type}); - $self->assert_deep_equals(['shareWith/sharee/unknownCalendarRight'], - $res->[0][1]{notUpdated}{Default}{properties}); + $self->assert_deep_equals( + ['shareWith/sharee/unknownCalendarRight'], + $res->[0][1]{notUpdated}{Default}{properties} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendar_treat_as_mailbox b/cassandane/tiny-tests/JMAPCalendars/calendar_treat_as_mailbox index 6b7abd3580..611354f352 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendar_treat_as_mailbox +++ b/cassandane/tiny-tests/JMAPCalendars/calendar_treat_as_mailbox @@ -2,44 +2,50 @@ use Cassandane::Tiny; sub test_calendar_treat_as_mailbox - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", - color => "coral", - sortOrder => 2, - isVisible => \1 - }}}, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('Calendar/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{created}); + xlog $self, "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Calendar/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{created}); - my $id = $res->[0][1]{created}{"1"}{id}; + my $id = $res->[0][1]{created}{"1"}{id}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - 'urn:ietf:params:jmap:mail', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + 'urn:ietf:params:jmap:mail', + ]; - xlog $self, "rename as mailbox $id"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { update => { $id => { name => "foobar" } } }, "R1"] - ], $using); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_null($res->[0][1]{updated}); - $self->assert_not_null($res->[0][1]{notUpdated}); + xlog $self, "rename as mailbox $id"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/set', { update => { $id => { name => "foobar" } } }, "R1" ] ], + $using + ); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_null($res->[0][1]{updated}); + $self->assert_not_null($res->[0][1]{notUpdated}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification b/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification index 75c0409906..71cae721f9 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification +++ b/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification @@ -2,29 +2,28 @@ use Cassandane::Tiny; sub test_calendaralert_notification - :min_version_3_7 :needs_component_calalarmd :needs_component_jmap -{ - my ($self) = @_; - my $caldav = $self->{caldav}; - my $jmap = $self->{jmap}; - - my $calendarId = $caldav->NewCalendar({name => 'foo'}); - $self->assert_not_null($calendarId); - - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); - - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$calendarId/$uuid.ics"; - my $card = <{caldav}; + my $jmap = $self->{jmap}; + + my $calendarId = $caldav->NewCalendar({ name => 'foo' }); + $self->assert_not_null($calendarId); + + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); + + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; + my $href = "$calendarId/$uuid.ics"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog "Get calendar event alert ids"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['alerts'], - }, 'R1'], - ]); - - my %alerts = %{$res->[0][1]{list}[0]{alerts}}; - my %alertIds = map { $alerts{$_}{trigger}{offset} => $_ } keys %alerts; - $self->assert_num_equals(2, scalar keys %alertIds); - - # clean notification cache - $self->{instance}->getnotify(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); - - my $data = $self->{instance}->getnotify(); - my @events; - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @events, $e; - } - } + $caldav->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog "Get calendar event alert ids"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['alerts'], + }, + 'R1' + ], + ]); + + my %alerts = %{ $res->[0][1]{list}[0]{alerts} }; + my %alertIds = map { $alerts{$_}{trigger}{offset} => $_ } keys %alerts; + $self->assert_num_equals(2, scalar keys %alertIds); + + # clean notification cache + $self->{instance}->getnotify(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + + my $data = $self->{instance}->getnotify(); + my @events; + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @events, $e; + } } - - $self->assert_num_equals(1, scalar @events); - $self->assert_str_equals('cassandane', - $events[0]{userId}); # accountId - $self->assert_str_equals('574E2CD0-2D2A-4554-8B63-C7504481D3A9', - $events[0]{uid}); - $self->assert_str_equals(encode_eventid('574E2CD0-2D2A-4554-8B63-C7504481D3A9'), - $events[0]{calendarEventId}); - $self->assert_str_equals('', $events[0]{recurrenceId}); - $self->assert_str_equals($alertIds{'PT0S'}, $events[0]{alertId}); - - # clean notification cache - $self->{instance}->getnotify(); - - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 3660 ); - - $data = $self->{instance}->getnotify(); - @events = (); - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @events, $e; - } - } + } + + $self->assert_num_equals(1, scalar @events); + $self->assert_str_equals('cassandane', $events[0]{userId}); # accountId + $self->assert_str_equals('574E2CD0-2D2A-4554-8B63-C7504481D3A9', + $events[0]{uid}); + $self->assert_str_equals( + encode_eventid('574E2CD0-2D2A-4554-8B63-C7504481D3A9'), + $events[0]{calendarEventId}); + $self->assert_str_equals('', $events[0]{recurrenceId}); + $self->assert_str_equals($alertIds{'PT0S'}, $events[0]{alertId}); + + # clean notification cache + $self->{instance}->getnotify(); + + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 3660); + + $data = $self->{instance}->getnotify(); + @events = (); + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @events, $e; + } } - - $self->assert_num_equals(1, scalar @events); - $self->assert_str_equals('cassandane', - $events[0]{userId}); # accountId - $self->assert_str_equals('574E2CD0-2D2A-4554-8B63-C7504481D3A9', - $events[0]{uid}); - $self->assert_str_equals(encode_eventid('574E2CD0-2D2A-4554-8B63-C7504481D3A9'), - $events[0]{calendarEventId}); - $self->assert_str_equals('', $events[0]{recurrenceId}); - $self->assert_str_equals($alertIds{'PT1H'}, $events[0]{alertId}); + } + + $self->assert_num_equals(1, scalar @events); + $self->assert_str_equals('cassandane', $events[0]{userId}); # accountId + $self->assert_str_equals('574E2CD0-2D2A-4554-8B63-C7504481D3A9', + $events[0]{uid}); + $self->assert_str_equals( + encode_eventid('574E2CD0-2D2A-4554-8B63-C7504481D3A9'), + $events[0]{calendarEventId}); + $self->assert_str_equals('', $events[0]{recurrenceId}); + $self->assert_str_equals($alertIds{'PT1H'}, $events[0]{alertId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification_recurring b/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification_recurring index ec99cf19c2..733590b71a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification_recurring +++ b/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification_recurring @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_calendaralert_notification_recurring - :min_version_3_5 :needs_component_calalarmd :needs_component_jmap -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_calalarmd : needs_component_jmap { + my ($self) = @_; + my $caldav = $self->{caldav}; - my $calendarId = $caldav->NewCalendar({name => 'foo'}); - $self->assert_not_null($calendarId); + my $calendarId = $caldav->NewCalendar({ name => 'foo' }); + $self->assert_not_null($calendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start yesterday in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - $startdt->subtract(DateTime::Duration->new(days => 1)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + # define the event to start yesterday in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + $startdt->subtract(DateTime::Duration->new(days => 1)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - my $recurdt = $startdt->clone(); - $recurdt->add(DateTime::Duration->new(days => 1)); - my $recurid = $recurdt->strftime('%Y-%m-%dT%H:%M:%S'); + my $recurdt = $startdt->clone(); + $recurdt->add(DateTime::Duration->new(days => 1)); + my $recurid = $recurdt->strftime('%Y-%m-%dT%H:%M:%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$calendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - my $data = $self->{instance}->getnotify(); - my @events; - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @events, $e; - } - } + my $data = $self->{instance}->getnotify(); + my @events; + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @events, $e; + } } + } - $self->assert_num_equals(1, scalar @events); - $self->assert_str_equals('cassandane', - $events[0]{userId}); # accountId - $self->assert_str_equals('574E2CD0-2D2A-4554-8B63-C7504481D3A9', - $events[0]{uid}); - $self->assert_str_equals(encode_eventid('574E2CD0-2D2A-4554-8B63-C7504481D3A9'), - $events[0]{calendarEventId}); - $self->assert_str_equals($recurid, $events[0]{recurrenceId}); - $self->assert_str_equals('E157A1FC-06BB-4495-933E-4E99C79A8649', - $events[0]{alertId}); + $self->assert_num_equals(1, scalar @events); + $self->assert_str_equals('cassandane', $events[0]{userId}); # accountId + $self->assert_str_equals('574E2CD0-2D2A-4554-8B63-C7504481D3A9', + $events[0]{uid}); + $self->assert_str_equals( + encode_eventid('574E2CD0-2D2A-4554-8B63-C7504481D3A9'), + $events[0]{calendarEventId}); + $self->assert_str_equals($recurid, $events[0]{recurrenceId}); + $self->assert_str_equals('E157A1FC-06BB-4495-933E-4E99C79A8649', + $events[0]{alertId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification_standalone b/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification_standalone index 66a17cbf2d..f15eb51aa8 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification_standalone +++ b/cassandane/tiny-tests/JMAPCalendars/calendaralert_notification_standalone @@ -2,33 +2,32 @@ use Cassandane::Tiny; sub test_calendaralert_notification_standalone - :min_version_3_5 :needs_component_calalarmd :needs_component_jmap -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_calalarmd : needs_component_jmap { + my ($self) = @_; + my $caldav = $self->{caldav}; - my $calendarId = $caldav->NewCalendar({name => 'foo'}); - $self->assert_not_null($calendarId); + my $calendarId = $caldav->NewCalendar({ name => 'foo' }); + $self->assert_not_null($calendarId); - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $icalStart = $startdt->strftime('%Y%m%dT%H%M%S'); - my $icalRecurid = $startdt->strftime('%Y%m%dT%H%M%S'); - my $recurid = $startdt->strftime('%Y-%m-%dT%H:%M:%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $icalStart = $startdt->strftime('%Y%m%dT%H%M%S'); + my $icalRecurid = $startdt->strftime('%Y%m%dT%H%M%S'); + my $recurid = $startdt->strftime('%Y-%m-%dT%H:%M:%S'); - # set the trigger to notify us at the start of the event - my $trigger="PT0S"; + # set the trigger to notify us at the start of the event + my $trigger = "PT0S"; - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$calendarId/$uuid.ics"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - my $data = $self->{instance}->getnotify(); - my @events; - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @events, $e; - } - } + my $data = $self->{instance}->getnotify(); + my @events; + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @events, $e; + } } + } - $self->assert_num_equals(1, scalar @events); - $self->assert_str_equals('cassandane', - $events[0]{userId}); # accountId - $self->assert_str_equals('574E2CD0-2D2A-4554-8B63-C7504481D3A9', - $events[0]{uid}); - $self->assert_str_equals(encode_eventid('574E2CD0-2D2A-4554-8B63-C7504481D3A9', $icalRecurid), - $events[0]{calendarEventId}); - $self->assert_str_equals($recurid, $events[0]{recurrenceId}); - $self->assert_str_equals('E157A1FC-06BB-4495-933E-4E99C79A8649', - $events[0]{alertId}); + $self->assert_num_equals(1, scalar @events); + $self->assert_str_equals('cassandane', $events[0]{userId}); # accountId + $self->assert_str_equals('574E2CD0-2D2A-4554-8B63-C7504481D3A9', + $events[0]{uid}); + $self->assert_str_equals( + encode_eventid('574E2CD0-2D2A-4554-8B63-C7504481D3A9', $icalRecurid), + $events[0]{calendarEventId}); + $self->assert_str_equals($recurid, $events[0]{recurrenceId}); + $self->assert_str_equals('E157A1FC-06BB-4495-933E-4E99C79A8649', + $events[0]{alertId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_attendee_noorganizer b/cassandane/tiny-tests/JMAPCalendars/calendarevent_attendee_noorganizer index aec8a81a45..55c2fcac31 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_attendee_noorganizer +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_attendee_noorganizer @@ -2,79 +2,90 @@ use Cassandane::Tiny; sub test_calendarevent_attendee_noorganizer - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my ($eventId, $ical) = $self->icalfile('attendee_noorganizer'); - my $event = $self->putandget_vevent($eventId, $ical); - my $wantParticipants = { - '29deb29d758dbb27ffa3c39b499edd85b53dd33f' => { - '@type' => 'Participant', - 'sendTo' => { - 'imip' => 'mailto:attendee@local' - }, - 'roles' => { - 'attendee' => JSON::true - }, - 'participationStatus' => 'needs-action', - 'expectReply' => JSON::false, - } - }; - $self->assert_deep_equals($wantParticipants, $event->{participants}); - $self->assert_null($event->{replyTo}); - - xlog "Update event via JMAP"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - participants => $wantParticipants, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['participants', 'replyTo', 'x-href'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_deep_equals($wantParticipants, $res->[1][1]{list}[0]{participants}); - $self->assert_null($res->[1][1]{list}[0]{replyTo}); + xlog "Create event via CalDAV"; + my ($eventId, $ical) = $self->icalfile('attendee_noorganizer'); + my $event = $self->putandget_vevent($eventId, $ical); + my $wantParticipants = { + '29deb29d758dbb27ffa3c39b499edd85b53dd33f' => { + '@type' => 'Participant', + 'sendTo' => { + 'imip' => 'mailto:attendee@local' + }, + 'roles' => { + 'attendee' => JSON::true + }, + 'participationStatus' => 'needs-action', + 'expectReply' => JSON::false, + } + }; + $self->assert_deep_equals($wantParticipants, $event->{participants}); + $self->assert_null($event->{replyTo}); - my $xhref = $res->[1][1]{list}[0]{'x-href'}; - $self->assert_not_null($xhref); + xlog "Update event via JMAP"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + participants => $wantParticipants, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => [ 'participants', 'replyTo', 'x-href' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_deep_equals($wantParticipants, + $res->[1][1]{list}[0]{participants}); + $self->assert_null($res->[1][1]{list}[0]{replyTo}); - xlog "Validate no ORGANIZER got added"; - $res = $caldav->Request('GET', $xhref); - $self->assert(not($res->{content} =~ m/ORGANIZER/)); - $self->assert($res->{content} =~ m/ATTENDEE/); + my $xhref = $res->[1][1]{list}[0]{'x-href'}; + $self->assert_not_null($xhref); + xlog "Validate no ORGANIZER got added"; + $res = $caldav->Request('GET', $xhref); + $self->assert(not($res->{content} =~ m/ORGANIZER/)); + $self->assert($res->{content} =~ m/ATTENDEE/); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj < 3 || ($maj == 3 && $min < 7)) { - # versions 3.7 or higher are tested in calendarevent_set_replyto - xlog "Create event with no replyTo via JMAP (should fail)"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - calendarIds => { - 'Default' => JSON::true, - }, - title => "title", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT2H", - "timeZone" => "Europe/London", - participants => $wantParticipants, - }, - }, - }, 'R1'], - ]); - $self->assert_deep_equals(['replyTo', 'participants'], - $res->[0][1]{notCreated}{1}{properties}); - } + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj < 3 || ($maj == 3 && $min < 7)) { + # versions 3.7 or higher are tested in calendarevent_set_replyto + xlog "Create event with no replyTo via JMAP (should fail)"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + calendarIds => { + 'Default' => JSON::true, + }, + title => "title", + "start" => "2015-11-07T09:00:00", + "duration" => "PT2H", + "timeZone" => "Europe/London", + participants => $wantParticipants, + }, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals([ 'replyTo', 'participants' ], + $res->[0][1]{notCreated}{1}{properties}); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_blob_lookup b/cassandane/tiny-tests/JMAPCalendars/calendarevent_blob_lookup index f3ed8c5f7b..b33ee9d08d 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_blob_lookup +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_blob_lookup @@ -2,48 +2,54 @@ use Cassandane::Tiny; sub test_calendarevent_blob_lookup - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - title => 'event1', - start => '2020-01-01T09:00:00', - timeZone => 'Europe/Vienna', - duration => 'PT1H', - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); - my $blobId = $res->[0][1]{created}{event1}{blobId}; - $self->assert_not_null($blobId); + title => 'event1', + start => '2020-01-01T09:00:00', + timeZone => 'Europe/Vienna', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); + my $blobId = $res->[0][1]{created}{event1}{blobId}; + $self->assert_not_null($blobId); - $res = $jmap->CallMethods([ - ['Blob/lookup', { - typeNames => [ - 'CalendarEvent', - ], - ids => [$blobId], - }, 'R1'], - ], [ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/blob', - ]); - $self->assert_deep_equals([{ - id => $blobId, - matchedIds => { - CalendarEvent => [ - $eventId, - ], + $res = $jmap->CallMethods( + [ + [ + 'Blob/lookup', + { + typeNames => [ 'CalendarEvent', ], + ids => [$blobId], }, - }], $res->[0][1]{list}); + 'R1' + ], + ], + [ 'urn:ietf:params:jmap:core', 'https://cyrusimap.org/ns/jmap/blob', ] + ); + $self->assert_deep_equals( + [ { + id => $blobId, + matchedIds => { + CalendarEvent => [ $eventId, ], + }, + } ], + $res->[0][1]{list} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_blobid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_blobid index 40ea5e0630..d2cc10101b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_blobid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_blobid @@ -2,154 +2,177 @@ use Cassandane::Tiny; sub test_calendarevent_blobid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "create other user"; - - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.other"); - $admintalk->setacl("user.other", admin => 'lrswipkxtecdan') or die; - $admintalk->setacl("user.other", other => 'lrswipkxtecdn') or die; - - my $service = $self->{instance}->get_service("http"); - my $otherJmap = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $otherJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - - xlog $self, "create calendar event in other users calendar"; - - my $res = $otherJmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uid1', - title => "event1", - description => "", - freeBusyStatus => "busy", - start => "2019-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - action => "email", - }, - }, + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "create other user"; + + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user.other"); + $admintalk->setacl("user.other", admin => 'lrswipkxtecdan') or die; + $admintalk->setacl("user.other", other => 'lrswipkxtecdn') or die; + + my $service = $self->{instance}->get_service("http"); + my $otherJmap = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $otherJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); + + xlog $self, "create calendar event in other users calendar"; + + my $res = $otherJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uid1', + title => "event1", + description => "", + freeBusyStatus => "busy", + start => "2019-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", }, - } - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); - - xlog $self, "share calendar"; - - $admintalk->setacl("user.other.#calendars.Default", cassandane => 'lrswipkxtecdn') or die; - - xlog $self, "set per-user event data for cassandane user"; - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'other', - update => { - $eventId => { - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT10M", - }, - action => "email", - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog $self, "get event blobIds for cassandane and other user"; - - $res = $otherJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'other', - ids => [$eventId], - properties => ['blobId'], - }, 'R1'] - ]); - - # fetch a second time to make sure this works with a cached response - $res = $otherJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'other', - ids => [$eventId], - properties => ['blobId'], - }, 'R1'] - ]); - my $otherBlobId = $res->[0][1]{list}[0]{blobId}; - $self->assert_not_null($otherBlobId); - - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'other', - ids => [$eventId], - properties => ['blobId'], - }, 'R1'] - ]); - my $cassBlobId = $res->[0][1]{list}[0]{blobId}; - $self->assert_not_null($cassBlobId); - - xlog $self, "compare blob ids"; - - $self->assert_str_not_equals($otherBlobId, $cassBlobId); - - xlog $self, "download blob with userdata"; - - $res = $jmap->Download('other', $cassBlobId); - $self->assert_str_equals("BEGIN:VCALENDAR", substr($res->{content}, 0, 15)); - $self->assert_num_not_equals(-1, index($res->{content}, 'TRIGGER:-PT10M')); - - xlog $self, "update event"; - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'other', - update => { - $eventId => { - title => 'updatedTitle', - } - } - }, 'R1'], - ['CalendarEvent/get', { - accountId => 'other', - ids => [$eventId], - properties => ['blobId'], - }, 'R1'], - - ]); - $self->assert_str_equals($res->[0][1]{updated}{$eventId}{blobId}, - $res->[1][1]{list}[0]{blobId}); - $self->assert_str_not_equals($cassBlobId, $res->[1][1]{list}[0]{blobId}); + action => "email", + }, + }, + }, + } + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); + + xlog $self, "share calendar"; + + $admintalk->setacl("user.other.#calendars.Default", + cassandane => 'lrswipkxtecdn') + or die; + + xlog $self, "set per-user event data for cassandane user"; + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'other', + update => { + $eventId => { + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT10M", + }, + action => "email", + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog $self, "get event blobIds for cassandane and other user"; + + $res = $otherJmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => [$eventId], + properties => ['blobId'], + }, + 'R1' + ] ]); + + # fetch a second time to make sure this works with a cached response + $res = $otherJmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => [$eventId], + properties => ['blobId'], + }, + 'R1' + ] ]); + my $otherBlobId = $res->[0][1]{list}[0]{blobId}; + $self->assert_not_null($otherBlobId); + + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => [$eventId], + properties => ['blobId'], + }, + 'R1' + ] ]); + my $cassBlobId = $res->[0][1]{list}[0]{blobId}; + $self->assert_not_null($cassBlobId); + + xlog $self, "compare blob ids"; + + $self->assert_str_not_equals($otherBlobId, $cassBlobId); + + xlog $self, "download blob with userdata"; + + $res = $jmap->Download('other', $cassBlobId); + $self->assert_str_equals("BEGIN:VCALENDAR", substr($res->{content}, 0, 15)); + $self->assert_num_not_equals(-1, index($res->{content}, 'TRIGGER:-PT10M')); + + xlog $self, "update event"; + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'other', + update => { + $eventId => { + title => 'updatedTitle', + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => [$eventId], + properties => ['blobId'], + }, + 'R1' + ], + + ]); + $self->assert_str_equals($res->[0][1]{updated}{$eventId}{blobId}, + $res->[1][1]{list}[0]{blobId}); + $self->assert_str_not_equals($cassBlobId, $res->[1][1]{list}[0]{blobId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes b/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes index 52457ead28..b8c87bfdee 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes @@ -2,197 +2,242 @@ use Cassandane::Tiny; sub test_calendarevent_changes - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - - xlog $self, "create calendars A and B"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { - "1" => { - name => "A", color => "coral", sortOrder => 1, isVisible => JSON::true, - }, - "2" => { - name => "B", color => "blue", sortOrder => 1, isVisible => JSON::true - } - }}, "R1"] - ]); - my $calidA = $res->[0][1]{created}{"1"}{id}; - my $calidB = $res->[0][1]{created}{"2"}{id}; - my $state = $res->[0][1]{newState}; - - xlog $self, "create event #1 in calendar $calidA and event #2 in calendar $calidB"; - $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - $calidA => JSON::true, - }, - "title" => "1", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2015-10-06T00:00:00", - }, - "2" => { - calendarIds => { - $calidB => JSON::true, - }, - "title" => "2", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2015-10-06T00:00:00", - } - }}, "R1"]]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; - - xlog $self, "get calendar event updates"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { sinceState => $state }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $state = $res->[0][1]{newState}; - - xlog $self, "get zero calendar event updates"; - $res = $jmap->CallMethods([['CalendarEvent/changes', {sinceState => $state}, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $state = $res->[0][1]{newState}; - - xlog $self, "update event #1 and #2"; - $res = $jmap->CallMethods([['CalendarEvent/set', { update => { - $id1 => { - calendarIds => { - $calidA => JSON::true, - }, - "title" => "1(updated)", - }, - $id2 => { - calendarIds => { - $calidB => JSON::true, - }, - "title" => "2(updated)", - } - }}, "R1"]]); - $self->assert_num_equals(2, scalar keys %{$res->[0][1]{updated}}); - - xlog $self, "get exactly one update"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { - sinceState => $state, - maxChanges => 1 - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $state = $res->[0][1]{newState}; - - xlog $self, "get the final update"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { sinceState => $state }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $state = $res->[0][1]{newState}; - - xlog $self, "update event #1 and destroy #2"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - update => { - $id1 => { - calendarIds => { - $calidA => JSON::true, - }, - "title" => "1(updated)", - "description" => "", - }, - }, - destroy => [ $id2 ] - }, "R1"]]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{updated}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get calendar event updates"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { sinceState => $state }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{destroyed}[0]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $state = $res->[0][1]{newState}; - - xlog $self, "get zero calendar event updates"; - $res = $jmap->CallMethods([['CalendarEvent/changes', {sinceState => $state}, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $state = $res->[0][1]{newState}; - - xlog $self, "move event #1 from calendar $calidA to $calidB"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - update => { - $id1 => { - calendarIds => { - $calidB => JSON::true, - }, - }, - } - }, "R1"]]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{updated}}); - - xlog $self, "get calendar event updates"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { sinceState => $state }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $state = $res->[0][1]{newState}; - - xlog $self, "update and remove event #1"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - update => { - $id1 => { - calendarIds => { - $calidB => JSON::true, - }, - "title" => "1(goodbye)", - }, - }, - destroy => [ $id1 ] - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get calendar event updates"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { sinceState => $state }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $state = $res->[0][1]{newState}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + + xlog $self, "create calendars A and B"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "A", + color => "coral", + sortOrder => 1, + isVisible => JSON::true, + }, + "2" => { + name => "B", + color => "blue", + sortOrder => 1, + isVisible => JSON::true + } + } + }, + "R1" + ] ]); + my $calidA = $res->[0][1]{created}{"1"}{id}; + my $calidB = $res->[0][1]{created}{"2"}{id}; + my $state = $res->[0][1]{newState}; + + xlog $self, + "create event #1 in calendar $calidA and event #2 in calendar $calidB"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calidA => JSON::true, + }, + "title" => "1", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2015-10-06T00:00:00", + }, + "2" => { + calendarIds => { + $calidB => JSON::true, + }, + "title" => "2", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2015-10-06T00:00:00", + } + } + }, + "R1" + ] ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; + + xlog $self, "get calendar event updates"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $state = $res->[0][1]{newState}; + + xlog $self, "get zero calendar event updates"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $state = $res->[0][1]{newState}; + + xlog $self, "update event #1 and #2"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id1 => { + calendarIds => { + $calidA => JSON::true, + }, + "title" => "1(updated)", + }, + $id2 => { + calendarIds => { + $calidB => JSON::true, + }, + "title" => "2(updated)", + } + } + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar keys %{ $res->[0][1]{updated} }); + + xlog $self, "get exactly one update"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/changes', + { + sinceState => $state, + maxChanges => 1 + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $state = $res->[0][1]{newState}; + + xlog $self, "get the final update"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $state = $res->[0][1]{newState}; + + xlog $self, "update event #1 and destroy #2"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id1 => { + calendarIds => { + $calidA => JSON::true, + }, + "title" => "1(updated)", + "description" => "", + }, + }, + destroy => [$id2] + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{updated} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get calendar event updates"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{destroyed}[0]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $state = $res->[0][1]{newState}; + + xlog $self, "get zero calendar event updates"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $state = $res->[0][1]{newState}; + + xlog $self, "move event #1 from calendar $calidA to $calidB"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id1 => { + calendarIds => { + $calidB => JSON::true, + }, + }, + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{updated} }); + + xlog $self, "get calendar event updates"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $state = $res->[0][1]{newState}; + + xlog $self, "update and remove event #1"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id1 => { + calendarIds => { + $calidB => JSON::true, + }, + "title" => "1(goodbye)", + }, + }, + destroy => [$id1] + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get calendar event updates"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $state = $res->[0][1]{newState}; } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_add_override_keep_main_unchanged b/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_add_override_keep_main_unchanged index f16b72de2b..b8a6c23dbc 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_add_override_keep_main_unchanged +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_add_override_keep_main_unchanged @@ -2,14 +2,14 @@ use Cassandane::Tiny; sub test_calendarevent_changes_add_override_keep_main_unchanged - :min_version_3_7 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_7 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <CallMethods([ - ['CalendarEvent/get', { - }, 'R1'], - ]); - my $state = $res->[0][1]{state}; - $self->assert_not_null($state); + xlog $self, "Fetch CalendarEvent state"; + my $res = $jmap->CallMethods([ [ 'CalendarEvent/get', {}, 'R1' ], ]); + my $state = $res->[0][1]{state}; + $self->assert_not_null($state); - my $imip = <<'EOF'; + my $imip = <<'EOF'; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -59,28 +56,36 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite for recurring main event"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + xlog $self, "Deliver iMIP invite for recurring main event"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - xlog $self, "Query changes"; - $res = $jmap->CallMethods([ - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/changes', - path => '/created', - }, - }, 'R2'], - ]); - $state = $res->[0][1]{newState}; - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_not_null($res->[1][1]{list}[0]{recurrenceRules}); - $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); + xlog $self, "Query changes"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/changes', + path => '/created', + }, + }, + 'R2' + ], + ]); + $state = $res->[0][1]{newState}; + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_not_null($res->[1][1]{list}[0]{recurrenceRules}); + $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); - $imip = <<'EOF'; + $imip = <<'EOF'; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -114,23 +119,31 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP override without main event"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + xlog $self, "Deliver iMIP override without main event"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - xlog $self, "Query changes"; - $res = $jmap->CallMethods([ - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/changes', - path => '/updated', - }, - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_not_null($res->[1][1]{list}[0]{recurrenceRules}); - $self->assert_not_null($res->[1][1]{list}[0]{recurrenceOverrides}); + xlog $self, "Query changes"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/changes', + path => '/updated', + }, + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_not_null($res->[1][1]{list}[0]{recurrenceRules}); + $self->assert_not_null($res->[1][1]{list}[0]{recurrenceOverrides}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_ignore_specials b/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_ignore_specials index 1a2ebadec7..71763a1db6 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_ignore_specials +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_ignore_specials @@ -2,21 +2,26 @@ use Cassandane::Tiny; sub test_calendarevent_changes_ignore_specials - :needs_component_sieve :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_sieve : needs_component_httpd : needs_component_jmap : + min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [], - }, 'R1'], - ]); - my $state = $res->[0][1]{state}; - $self->assert_not_null($state); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [], + }, + 'R1' + ], + ]); + my $state = $res->[0][1]{state}; + $self->assert_not_null($state); - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -55,13 +60,17 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + xlog $self, "Deliver iMIP invite"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - $res = $jmap->CallMethods([ - ['CalendarEvent/changes', { - sinceState => $state - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/changes', + { + sinceState => $state + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_issue2558 b/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_issue2558 index 265b6210de..e3bb731ca9 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_issue2558 +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_changes_issue2558 @@ -2,21 +2,21 @@ use Cassandane::Tiny; sub test_calendarevent_changes_issue2558 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get calendar event updates with bad state"; - my $res = $jmap->CallMethods([['CalendarEvent/changes', { sinceState => 'nonsense' }, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "get calendar event updates with bad state"; + my $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => 'nonsense' }, "R1" ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + $self->assert_str_equals('R1', $res->[0][2]); - xlog $self, "get calendar event updates without state"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { }, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "get calendar event updates without state"; + $res = $jmap->CallMethods([ [ 'CalendarEvent/changes', {}, "R1" ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_copy b/cassandane/tiny-tests/JMAPCalendars/calendarevent_copy index 2c0b0b514e..c7c8b54bce 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_copy +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_copy @@ -2,95 +2,112 @@ use Cassandane::Tiny; sub test_calendarevent_copy - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared accounts"; - $admintalk->create("user.other"); + xlog $self, "create shared accounts"; + $admintalk->create("user.other"); - my $othercaldav = Net::CalDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $othercaldav = Net::CalDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $admintalk->setacl('user.other', admin => 'lrswipkxtecdan'); - $admintalk->setacl('user.other', other => 'lrswipkxtecdn'); + $admintalk->setacl('user.other', admin => 'lrswipkxtecdan'); + $admintalk->setacl('user.other', other => 'lrswipkxtecdn'); - xlog $self, "create source calendar"; - my $srcCalendarId = $caldav->NewCalendar({name => 'Source Calendar'}); - $self->assert_not_null($srcCalendarId); + xlog $self, "create source calendar"; + my $srcCalendarId = $caldav->NewCalendar({ name => 'Source Calendar' }); + $self->assert_not_null($srcCalendarId); - xlog $self, "create destination calendar"; - my $dstCalendarId = $othercaldav->NewCalendar({name => 'Destination Calendar'}); - $self->assert_not_null($dstCalendarId); + xlog $self, "create destination calendar"; + my $dstCalendarId + = $othercaldav->NewCalendar({ name => 'Destination Calendar' }); + $self->assert_not_null($dstCalendarId); - xlog $self, "share calendar"; - $admintalk->setacl("user.other.#calendars.$dstCalendarId", "cassandane" => 'lrswipkxtecdn') or die; + xlog $self, "share calendar"; + $admintalk->setacl("user.other.#calendars.$dstCalendarId", + "cassandane" => 'lrswipkxtecdn') + or die; - my $event = { - calendarIds => { - $srcCalendarId => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "participants" => undef, - "alerts"=> undef, - }; + my $event = { + calendarIds => { + $srcCalendarId => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "participants" => undef, + "alerts" => undef, + }; - xlog $self, "create event"; - my $res = $jmap->CallMethods([['CalendarEvent/set',{ - create => {"1" => $event}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - my $eventId = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create event"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { "1" => $event } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $eventId = $res->[0][1]{created}{"1"}{id}; - xlog $self, "copy event"; - $res = $jmap->CallMethods([['CalendarEvent/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 1 => { - id => $eventId, - calendarIds => { - $dstCalendarId => JSON::true, - }, - }, + xlog $self, "copy event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $eventId, + calendarIds => { + $dstCalendarId => JSON::true, + }, }, - onSuccessDestroyOriginal => JSON::true, + }, + onSuccessDestroyOriginal => JSON::true, }, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - my $copiedEventId = $res->[0][1]{created}{"1"}{id}; + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $copiedEventId = $res->[0][1]{created}{"1"}{id}; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'other', - ids => [$copiedEventId], - }, 'R1'], - ['CalendarEvent/get', { - accountId => undef, - ids => [$eventId], - }, 'R2'], - ]); - $self->assert_str_equals('foo', $res->[0][1]{list}[0]{title}); - $self->assert_str_equals($eventId, $res->[1][1]{notFound}[0]); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => [$copiedEventId], + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + accountId => undef, + ids => [$eventId], + }, + 'R2' + ], + ]); + $self->assert_str_equals('foo', $res->[0][1]{list}[0]{title}); + $self->assert_str_equals($eventId, $res->[1][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_copy_state b/cassandane/tiny-tests/JMAPCalendars/calendarevent_copy_state index 599e775ec3..5e37e83144 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_copy_state +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_copy_state @@ -2,116 +2,133 @@ use Cassandane::Tiny; sub test_calendarevent_copy_state - :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared accounts"; - $admintalk->create("user.other"); + xlog $self, "create shared accounts"; + $admintalk->create("user.other"); - my $othercaldav = Net::CalDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $othercaldav = Net::CalDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $admintalk->setacl('user.other', admin => 'lrswipkxtecdan'); - $admintalk->setacl('user.other', other => 'lrswipkxtecdn'); + $admintalk->setacl('user.other', admin => 'lrswipkxtecdan'); + $admintalk->setacl('user.other', other => 'lrswipkxtecdn'); - xlog $self, "create source calendar"; - my $srcCalendarId = $caldav->NewCalendar({name => 'Source Calendar'}); - $self->assert_not_null($srcCalendarId); + xlog $self, "create source calendar"; + my $srcCalendarId = $caldav->NewCalendar({ name => 'Source Calendar' }); + $self->assert_not_null($srcCalendarId); - xlog $self, "create destination calendar"; - my $dstCalendarId = $othercaldav->NewCalendar({name => 'Destination Calendar'}); - $self->assert_not_null($dstCalendarId); + xlog $self, "create destination calendar"; + my $dstCalendarId + = $othercaldav->NewCalendar({ name => 'Destination Calendar' }); + $self->assert_not_null($dstCalendarId); - xlog $self, "share calendar"; - $admintalk->setacl("user.other.#calendars.$dstCalendarId", "cassandane" => 'lrswipkxtecdn') or die; + xlog $self, "share calendar"; + $admintalk->setacl("user.other.#calendars.$dstCalendarId", + "cassandane" => 'lrswipkxtecdn') + or die; - my $event = { - calendarIds => { - $srcCalendarId => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "participants" => undef, - "alerts"=> undef, - }; + my $event = { + calendarIds => { + $srcCalendarId => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "participants" => undef, + "alerts" => undef, + }; - xlog $self, "create event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => {"1" => $event} - }, "R1"], - ['CalendarEvent/get', { - accountId => 'other', - ids => ['foo'], # Just fetching current state for 'other' - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}); - my $eventId = $res->[0][1]{created}{"1"}{id}; - my $fromState = $res->[0][1]->{newState}; - $self->assert_not_null($fromState); - my $state = $res->[1][1]->{state}; - $self->assert_not_null($state); + xlog $self, "create event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { "1" => $event } + }, + "R1" + ], + [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => ['foo'], # Just fetching current state for 'other' + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}); + my $eventId = $res->[0][1]{created}{"1"}{id}; + my $fromState = $res->[0][1]->{newState}; + $self->assert_not_null($fromState); + my $state = $res->[1][1]->{state}; + $self->assert_not_null($state); - xlog $self, "move event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - ifFromInState => $fromState, - ifInState => $state, - create => { - 1 => { - id => $eventId, - calendarIds => { - $dstCalendarId => JSON::true, - }, - }, + xlog $self, "move event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + ifFromInState => $fromState, + ifInState => $state, + create => { + 1 => { + id => $eventId, + calendarIds => { + $dstCalendarId => JSON::true, }, - onSuccessDestroyOriginal => JSON::true, - destroyFromIfInState => $fromState, - }, "R1"], - ['CalendarEvent/get', { - accountId => 'other', - ids => ['#1'], - properties => ['title'], - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}); - my $oldState = $res->[0][1]->{oldState}; - $self->assert_str_equals($oldState, $state); - my $newState = $res->[0][1]->{newState}; - $self->assert_not_null($newState); - $self->assert_str_equals('CalendarEvent/set', $res->[1][0]); - $self->assert_str_equals($eventId, $res->[1][1]{destroyed}[0]); - $self->assert_str_equals('foo', $res->[2][1]{list}[0]{title}); + }, + }, + onSuccessDestroyOriginal => JSON::true, + destroyFromIfInState => $fromState, + }, + "R1" + ], + [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => ['#1'], + properties => ['title'], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}); + my $oldState = $res->[0][1]->{oldState}; + $self->assert_str_equals($oldState, $state); + my $newState = $res->[0][1]->{newState}; + $self->assert_not_null($newState); + $self->assert_str_equals('CalendarEvent/set', $res->[1][0]); + $self->assert_str_equals($eventId, $res->[1][1]{destroyed}[0]); + $self->assert_str_equals('foo', $res->[2][1]{list}[0]{title}); - # Is the blobId downloadable? - my $blob = $jmap->Download({ accept => 'text/calendar' }, - 'other', - $res->[0][1]{created}{"1"}{blobId}); - $self->assert_str_equals('text/calendar; component=VEVENT', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_matches(qr/\r\nSUMMARY;LANGUAGE=en:foo\r\n/, $blob->{content}); + # Is the blobId downloadable? + my $blob = $jmap->Download({ accept => 'text/calendar' }, + 'other', $res->[0][1]{created}{"1"}{blobId}); + $self->assert_str_equals('text/calendar; component=VEVENT', + $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_matches(qr/\r\nSUMMARY;LANGUAGE=en:foo\r\n/, $blob->{content}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_debugblobid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_debugblobid index 6e242df730..9368297d1e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_debugblobid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_debugblobid @@ -2,148 +2,166 @@ use Cassandane::Tiny; sub test_calendarevent_debugblobid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "create other user"; - - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.other"); - $admintalk->setacl("user.other", admin => 'lrswipkxtecdan') or die; - $admintalk->setacl("user.other", other => 'lrswipkxtecdn') or die; - - my $service = $self->{instance}->get_service("http"); - my $otherJmap = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $otherJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - - xlog $self, "create calendar event in other users calendar"; - - my $res = $otherJmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uid1', - title => "event1", - description => "", - freeBusyStatus => "busy", - start => "2019-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - action => "email", - }, - }, + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "create other user"; + + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user.other"); + $admintalk->setacl("user.other", admin => 'lrswipkxtecdan') or die; + $admintalk->setacl("user.other", other => 'lrswipkxtecdn') or die; + + my $service = $self->{instance}->get_service("http"); + my $otherJmap = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $otherJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); + + xlog $self, "create calendar event in other users calendar"; + + my $res = $otherJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uid1', + title => "event1", + description => "", + freeBusyStatus => "busy", + start => "2019-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", }, - } - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); - - xlog $self, "share calendar"; - - $admintalk->setacl("user.other.#calendars.Default", cassandane => 'lrswipkxtecdn') or die; - - xlog $self, "set per-user event data for cassandane user"; - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'other', - update => { - $eventId => { - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT10M", - }, - action => "email", - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog $self, "get debugBlobId as regular user"; - - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/debug', - ]; - - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'other', - ids => [$eventId], - properties => ['debugBlobId'], - }, 'R1'] - ], $using); - my $debugBlobId = $res->[0][1]{list}[0]{debugBlobId}; - $self->assert_not_null($debugBlobId); - - xlog $self, "attempt to download debugBlob as non-admin (should fail)"; - - my $downloadUri = $jmap->downloaduri('other', $debugBlobId); - my %Headers = ( - 'Authorization' => $jmap->auth_header(), - ); - my $RawResponse = $jmap->ua->get($downloadUri, { headers => \%Headers }); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawResponse); - } - $self->assert_str_equals('404', $RawResponse->{status}); - - xlog $self, "get debugBlobId as admin user"; - - my $adminJmap = Mail::JMAPTalk->new( - user => 'admin', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $res = $adminJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'other', - ids => [$eventId], - properties => ['debugBlobId'], - }, 'R1'] - ], $using); - $debugBlobId = $res->[0][1]{list}[0]{debugBlobId}; - $self->assert_not_null($debugBlobId); - - xlog $self, "download debugBlob with userdata"; - - $res = $adminJmap->Download('other', $debugBlobId); - $self->assert_str_equals("multipart/mixed", substr($res->{headers}{'content-type'}, 0, 15)); - $self->assert_num_not_equals(-1, index($res->{content}, 'SUMMARY:event1')); + action => "email", + }, + }, + }, + } + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); + + xlog $self, "share calendar"; + + $admintalk->setacl("user.other.#calendars.Default", + cassandane => 'lrswipkxtecdn') + or die; + + xlog $self, "set per-user event data for cassandane user"; + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'other', + update => { + $eventId => { + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT10M", + }, + action => "email", + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog $self, "get debugBlobId as regular user"; + + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/debug', + ]; + + $res = $jmap->CallMethods( + [ [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => [$eventId], + properties => ['debugBlobId'], + }, + 'R1' + ] ], + $using + ); + my $debugBlobId = $res->[0][1]{list}[0]{debugBlobId}; + $self->assert_not_null($debugBlobId); + + xlog $self, "attempt to download debugBlob as non-admin (should fail)"; + + my $downloadUri = $jmap->downloaduri('other', $debugBlobId); + my %Headers = ('Authorization' => $jmap->auth_header(),); + my $RawResponse = $jmap->ua->get($downloadUri, { headers => \%Headers }); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawResponse); + } + $self->assert_str_equals('404', $RawResponse->{status}); + + xlog $self, "get debugBlobId as admin user"; + + my $adminJmap = Mail::JMAPTalk->new( + user => 'admin', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $res = $adminJmap->CallMethods( + [ [ + 'CalendarEvent/get', + { + accountId => 'other', + ids => [$eventId], + properties => ['debugBlobId'], + }, + 'R1' + ] ], + $using + ); + $debugBlobId = $res->[0][1]{list}[0]{debugBlobId}; + $self->assert_not_null($debugBlobId); + + xlog $self, "download debugBlob with userdata"; + + $res = $adminJmap->Download('other', $debugBlobId); + $self->assert_str_equals("multipart/mixed", + substr($res->{headers}{'content-type'}, 0, 15)); + $self->assert_num_not_equals(-1, index($res->{content}, 'SUMMARY:event1')); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_calalarmd b/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_calalarmd index 145a4586b9..eb8333e02b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_calalarmd +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_calalarmd @@ -2,48 +2,55 @@ use Cassandane::Tiny; sub test_calendarevent_defaultalerts_calalarmd - :min_version_3_9 :needs_component_jmap :needs_component_calalarmd -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $now = DateTime->now(); - $now->set_time_zone('Australia/Sydney'); - - xlog $self, "Create calendar without default alarms"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - 1 => { - name => 'test', - }, - } - }, 'R1'], - ]); - my $calendarId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($calendarId); - - xlog $self, "Remove any default alarms"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - defaultAlertsWithTime => undef, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarId}); - - xlog $self, "Create event that starts in an hour, ocurring daily"; - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(hours => 1)); - my $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; - my $href = "$calendarId/$uuid.ics"; - my $card = <{jmap}; + my $caldav = $self->{caldav}; + my $now = DateTime->now(); + $now->set_time_zone('Australia/Sydney'); + + xlog $self, "Create calendar without default alarms"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + 1 => { + name => 'test', + }, + } + }, + 'R1' + ], + ]); + my $calendarId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($calendarId); + + xlog $self, "Remove any default alarms"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + defaultAlertsWithTime => undef, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarId}); + + xlog $self, "Create event that starts in an hour, ocurring daily"; + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(hours => 1)); + my $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + my $uuid = "574E2CD0-2D2A-4554-8B63-C7504481D3A9"; + my $href = "$calendarId/$uuid.ics"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); - - xlog $self, "Assert that useDefaultAlerts got rewritten to false"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['useDefaultAlerts'], - }, 'R1'], - ]); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{useDefaultAlerts}); - my $eventId = $res->[0][1]{list}[0]{id}; - - # clean notification cache - $self->{instance}->getnotify(); - - xlog "Custom alarm triggers half an hour before event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800); - $self->assert_alarms({summary => 'Simple', start => $start}); - - xlog $self, "Set default alarms to trigger at start of event"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - }, - } + $caldav->Request('PUT', $href, $card, 'Content-Type' => 'text/calendar'); + + xlog $self, "Assert that useDefaultAlerts got rewritten to false"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['useDefaultAlerts'], + }, + 'R1' + ], + ]); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{useDefaultAlerts}); + my $eventId = $res->[0][1]{list}[0]{id}; + + # clean notification cache + $self->{instance}->getnotify(); + + xlog "Custom alarm triggers half an hour before event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800); + $self->assert_alarms({ summary => 'Simple', start => $start }); + + xlog $self, "Set default alarms to trigger at start of event"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', }, + action => 'display', + }, } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarId}); - - xlog $self, "Set useDefaultAlerts=true for event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - useDefaultAlerts => JSON::true, - } - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog $self, "Forward clock by one day"; - $now->add(DateTime::Duration->new(days => 1)); - $startdt->add(DateTime::Duration->new(days => 1)); - $start = $startdt->strftime('%Y%m%dT%H%M%S'); - $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - # clean notification cache - $self->{instance}->getnotify(); - - xlog "Custom alarm does not trigger half an hour before event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800); - $self->assert_alarms(); - - xlog "Default alarm triggers at start of event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch); - $self->assert_alarms({summary => 'Simple', start => $start}); - - xlog $self, "Update default alarms to trigger half an hour after event"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - defaultAlertsWithTime => { - alert2 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT30M', - }, - action => 'display', - }, - } + }, + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarId}); + + xlog $self, "Set useDefaultAlerts=true for event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + useDefaultAlerts => JSON::true, + } + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog $self, "Forward clock by one day"; + $now->add(DateTime::Duration->new(days => 1)); + $startdt->add(DateTime::Duration->new(days => 1)); + $start = $startdt->strftime('%Y%m%dT%H%M%S'); + $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + # clean notification cache + $self->{instance}->getnotify(); + + xlog "Custom alarm does not trigger half an hour before event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800); + $self->assert_alarms(); + + xlog "Default alarm triggers at start of event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch); + $self->assert_alarms({ summary => 'Simple', start => $start }); + + xlog $self, "Update default alarms to trigger half an hour after event"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + defaultAlertsWithTime => { + alert2 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT30M', }, + action => 'display', + }, } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarId}); - - xlog $self, "Forward clock by one day"; - $now->add(DateTime::Duration->new(days => 1)); - $startdt->add(DateTime::Duration->new(days => 1)); - $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - xlog "Custom alarm does not trigger half an hour before event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800); - $self->assert_alarms(); - - xlog "Former default alarm does not trigger at start of event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch); - $self->assert_alarms(); - - xlog "Current default alarm gets triggered half an hour after the event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch + 1800 ); - $self->assert_alarms({summary => 'Simple', start => $start}); - - xlog $self, "Forward clock by one day"; - $now->add(DateTime::Duration->new(days => 1)); - $startdt->add(DateTime::Duration->new(days => 1)); - $start = $startdt->strftime('%Y%m%dT%H%M%S'); - - xlog $self, "Remove default alarms again"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - $calendarId => { - defaultAlertsWithTime => undef, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$calendarId}); - - xlog "Custom alarm does not trigger half an hour before event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800); - $self->assert_alarms(); - - xlog "Former default alarm does not trigger at start of event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch); - $self->assert_alarms(); - - xlog "Former default alarm does not trigger half an hour after the event"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch + 1800 ); - $self->assert_alarms(); + }, + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarId}); + + xlog $self, "Forward clock by one day"; + $now->add(DateTime::Duration->new(days => 1)); + $startdt->add(DateTime::Duration->new(days => 1)); + $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + xlog "Custom alarm does not trigger half an hour before event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800); + $self->assert_alarms(); + + xlog "Former default alarm does not trigger at start of event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch); + $self->assert_alarms(); + + xlog "Current default alarm gets triggered half an hour after the event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch + 1800); + $self->assert_alarms({ summary => 'Simple', start => $start }); + + xlog $self, "Forward clock by one day"; + $now->add(DateTime::Duration->new(days => 1)); + $startdt->add(DateTime::Duration->new(days => 1)); + $start = $startdt->strftime('%Y%m%dT%H%M%S'); + + xlog $self, "Remove default alarms again"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + $calendarId => { + defaultAlertsWithTime => undef, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$calendarId}); + + xlog "Custom alarm does not trigger half an hour before event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch - 1800); + $self->assert_alarms(); + + xlog "Former default alarm does not trigger at start of event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch); + $self->assert_alarms(); + + xlog "Former default alarm does not trigger half an hour after the event"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $startdt->epoch + 1800); + $self->assert_alarms(); } sub _can_match { - my $event = shift; - my $want = shift; + my $event = shift; + my $want = shift; - # I wrote a really good one of these for Caldav, but this will do for now - foreach my $key (keys %$want) { - return 0 if not exists $event->{$key}; - return 0 if $event->{$key} ne $want->{$key}; - } + # I wrote a really good one of these for Caldav, but this will do for now + foreach my $key (keys %$want) { + return 0 if not exists $event->{$key}; + return 0 if $event->{$key} ne $want->{$key}; + } - return 1; + return 1; } - sub assert_alarms { - my $self = shift; - my @want = @_; - # pick first calendar alarm from notifications - my $data = $self->{instance}->getnotify(); - if ($self->{replica}) { - my $more = $self->{replica}->getnotify(); - push @$data, @$more; + my $self = shift; + my @want = @_; + # pick first calendar alarm from notifications + my $data = $self->{instance}->getnotify(); + if ($self->{replica}) { + my $more = $self->{replica}->getnotify(); + push @$data, @$more; + } + my @events; + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @events, $e; + } } - my @events; - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @events, $e; - } - } + } + + my @left; + while (my $event = shift @events) { + my $found = 0; + my @newwant; + foreach my $data (@want) { + if (not $found and _can_match($event, $data)) { + $found = 1; + } else { + push @newwant, $data; + } } - - my @left; - while (my $event = shift @events) { - my $found = 0; - my @newwant; - foreach my $data (@want) { - if (not $found and _can_match($event, $data)) { - $found = 1; - } - else { - push @newwant, $data; - } - } - if (not $found) { - push @left, $event; - } - @want = @newwant; - } - - if (@want or @left) { - my $dump = Data::Dumper->Dump([\@want, \@left], [qw(want left)]); - $self->assert_equals(0, scalar @want, - "expected events were not received:\n$dump"); - $self->assert_equals(0, scalar @left, - "unexpected extra events were received:\n$dump"); + if (not $found) { + push @left, $event; } + @want = @newwant; + } + + if (@want or @left) { + my $dump = Data::Dumper->Dump([ \@want, \@left ], [qw(want left)]); + $self->assert_equals(0, scalar @want, + "expected events were not received:\n$dump"); + $self->assert_equals(0, scalar @left, + "unexpected extra events were received:\n$dump"); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_etag_preserved_after_alarm b/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_etag_preserved_after_alarm index 7b36bdc155..057fce190a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_etag_preserved_after_alarm +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_etag_preserved_after_alarm @@ -2,101 +2,112 @@ use Cassandane::Tiny; sub test_calendarevent_defaultalerts_etag_preserved_after_alarm - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "Set default alarms"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - } - }, + xlog $self, "Set default alarms"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', }, + action => 'display', + } }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog $self, "Create event that starts in a few seconds"; - my $eventUid = '5f0dec98-8952-418e-91fa-159cb2ba28da'; - my $now = DateTime->now(); - $now->set_time_zone('Etc/UTC'); - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); + xlog $self, "Create event that starts in a few seconds"; + my $eventUid = '5f0dec98-8952-418e-91fa-159cb2ba28da'; + my $now = DateTime->now(); + $now->set_time_zone('Etc/UTC'); + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - uid => $eventUid, - calendarIds => { - 'Default' => JSON::true, - }, - title => "event1", - start => $startdt->strftime('%Y-%m-%dT%H:%M:%S'), - duration => "PT1H", - timeZone => "Etc/UTC", - useDefaultAlerts => JSON::true, - }, + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + uid => $eventUid, + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); + title => "event1", + start => $startdt->strftime('%Y-%m-%dT%H:%M:%S'), + duration => "PT1H", + timeZone => "Etc/UTC", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); - xlog $self, "Get event ETag before alarm fires"; - my %Headers; - if ($caldav->{user}) { - $Headers{'Authorization'} = $caldav->auth_header(); - } - $res = $caldav->{ua}->request('HEAD', - $caldav->request_url("Default/$eventUid.ics"), - { headers => \%Headers, }); + xlog $self, "Get event ETag before alarm fires"; + my %Headers; + if ($caldav->{user}) { + $Headers{'Authorization'} = $caldav->auth_header(); + } + $res = $caldav->{ua}->request( + 'HEAD', + $caldav->request_url("Default/$eventUid.ics"), + { headers => \%Headers, } + ); - my $etag = $res->{headers}{etag}; - $self->assert_not_null($etag); + my $etag = $res->{headers}{etag}; + $self->assert_not_null($etag); - xlog $self, "Run calalarmd"; - $self->{instance}->getnotify(); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60 ); - my $notifdata = $self->{instance}->getnotify(); - my @notifs; - foreach (@$notifdata) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @notifs, $e; - } - } + xlog $self, "Run calalarmd"; + $self->{instance}->getnotify(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + my $notifdata = $self->{instance}->getnotify(); + my @notifs; + foreach (@$notifdata) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @notifs, $e; + } } - $self->assert_num_equals(1, scalar @notifs); + } + $self->assert_num_equals(1, scalar @notifs); - xlog $self, "Get event ETag after alarm fired"; - my %Headers; - if ($caldav->{user}) { - $Headers{'Authorization'} = $caldav->auth_header(); - } - $res = $caldav->{ua}->request('HEAD', - $caldav->request_url("Default/$eventUid.ics"), - { headers => \%Headers, }); + xlog $self, "Get event ETag after alarm fired"; + my %Headers; + if ($caldav->{user}) { + $Headers{'Authorization'} = $caldav->auth_header(); + } + $res = $caldav->{ua}->request( + 'HEAD', + $caldav->request_url("Default/$eventUid.ics"), + { headers => \%Headers, } + ); - xlog $self, "Assert ETag has not changed"; - my $oldEtag = $etag; - my $newEtag = $res->{headers}{etag}; - $self->assert_str_equals($oldEtag, $newEtag); + xlog $self, "Assert ETag has not changed"; + my $oldEtag = $etag; + my $newEtag = $res->{headers}{etag}; + $self->assert_str_equals($oldEtag, $newEtag); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_imip b/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_imip index 1e64647e95..280104e968 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_imip +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_imip @@ -2,15 +2,16 @@ use Cassandane::Tiny; sub test_calendarevent_defaultalerts_imip - :needs_component_sieve :needs_component_httpd :needs_component_jmap :min_version_3_9 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my ($maj, $min) = Cassandane::Instance->get_version(); - my $uuid = new Data::UUID; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{jmap}; + my ($maj, $min) = Cassandane::Instance->get_version(); + my $uuid = new Data::UUID; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }; - - my $alertWithoutTimeId = 'baedd1d3-36d6-4d8f-986c-073c5e1f2f70'; - my $alertWithoutTime = { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - }; - - xlog 'Set default alerts on calendar'; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - $alertWithTimeId => $alertWithTime, - }, - defaultAlertsWithoutTime => { - $alertWithoutTimeId => $alertWithoutTime, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - my $imip = <<'EOF'; + ); + + my $alertWithTimeId = '8f335685-7e2a-49ee-bae3-ee4a5afd0a5e'; + my $alertWithTime = { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', + }, + action => 'display', + }; + + my $alertWithoutTimeId = 'baedd1d3-36d6-4d8f-986c-073c5e1f2f70'; + my $alertWithoutTime = { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', + }, + action => 'display', + }; + + xlog 'Set default alerts on calendar'; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + $alertWithTimeId => $alertWithTime, + }, + defaultAlertsWithoutTime => { + $alertWithoutTimeId => $alertWithoutTime, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + + my $imip = <<'EOF'; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -94,51 +99,59 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - - xlog $self, "Assert that useDefaultAlerts is set"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'alerts', 'useDefaultAlerts'] - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{useDefaultAlerts}); - $self->assert_deep_equals({ $alertWithTimeId => $alertWithTime }, - $res->[0][1]{list}[0]{alerts}); - - my $eventId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($eventId); - - my $customAlertId = '3b438031-621e-4e1c-b7eb-fe8c75cc2d6a'; - my $customAlert = { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT10M', - }, - action => 'display', - }; - - xlog "Set custom alert on event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - alerts => { - $customAlertId => $customAlert, - }, - useDefaultAlerts => JSON::false, - }, - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog "Update event via iTIP"; - $imip = <<'EOF'; + xlog $self, "Deliver iMIP invite"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + + xlog $self, "Assert that useDefaultAlerts is set"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'alerts', 'useDefaultAlerts' ] + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{useDefaultAlerts}); + $self->assert_deep_equals({ $alertWithTimeId => $alertWithTime }, + $res->[0][1]{list}[0]{alerts}); + + my $eventId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($eventId); + + my $customAlertId = '3b438031-621e-4e1c-b7eb-fe8c75cc2d6a'; + my $customAlert = { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT10M', + }, + action => 'display', + }; + + xlog "Set custom alert on event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + alerts => { + $customAlertId => $customAlert, + }, + useDefaultAlerts => JSON::false, + }, + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog "Update event via iTIP"; + $imip = <<'EOF'; Date: Thu, 23 Sep 2021 10:06:18 -0400 From: Sally Sender To: Cassandane @@ -171,17 +184,20 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP update"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'alerts', 'useDefaultAlerts'] - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_equals(JSON::false, - $res->[0][1]{list}[0]{useDefaultAlerts}); - $self->assert_deep_equals({ $customAlertId => $customAlert }, - $res->[0][1]{list}[0]{alerts}); + xlog $self, "Deliver iMIP update"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'alerts', 'useDefaultAlerts' ] + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{useDefaultAlerts}); + $self->assert_deep_equals({ $customAlertId => $customAlert }, + $res->[0][1]{list}[0]{alerts}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_updated_by_caldav_put b/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_updated_by_caldav_put index 4457278436..74fc87d0b0 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_updated_by_caldav_put +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_defaultalerts_updated_by_caldav_put @@ -3,421 +3,449 @@ use Cassandane::Tiny; use Data::ICal; sub test_calendarevent_defaultalerts_updated_by_caldav_put - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "Set default alerts on calendar"; - $self->{defaultAlertIds} = [ - '73aac5e1-e736-4c81-8b30-fb6ad5781f95', - 'a7ce891b-ae41-4fdb-a3d1-346d3889c90b' - ]; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - $self->{defaultAlertIds}[0] => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - $self->{defaultAlertIds}[1] => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT15M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - $self->assert_preserved_when_unchanged; - $self->assert_preserved_by_snooze_alarm; - $self->assert_preserved_by_apple_alarm; - - $self->assert_disabled_by_user_alarm; - $self->assert_disabled_by_removed_default_alarm; - $self->assert_disabled_by_no_alarms; - - $self->assert_not_disabled_if_xheader_set; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "Set default alerts on calendar"; + $self->{defaultAlertIds} = [ + '73aac5e1-e736-4c81-8b30-fb6ad5781f95', + 'a7ce891b-ae41-4fdb-a3d1-346d3889c90b' + ]; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + $self->{defaultAlertIds}[0] => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', + }, + action => 'display', + }, + $self->{defaultAlertIds}[1] => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT15M', + }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + + $self->assert_preserved_when_unchanged; + $self->assert_preserved_by_snooze_alarm; + $self->assert_preserved_by_apple_alarm; + + $self->assert_disabled_by_user_alarm; + $self->assert_disabled_by_removed_default_alarm; + $self->assert_disabled_by_no_alarms; + + $self->assert_not_disabled_if_xheader_set; } -sub assert_valarms -{ - my ($self, $vevent, %args) = @_; +sub assert_valarms { + my ($self, $vevent, %args) = @_; - my @props = @{$vevent->property('X-JMAP-USEDEFAULTALERTS')}; - if ($args{useDefaultAlerts}) { - $self->assert_not_null($props[0]); - $self->assert_str_equals('TRUE', $props[0]->value); - } elsif ($props) { - $self->assert_str_equals('FALSE', $props[0]->value); - } + my @props = @{ $vevent->property('X-JMAP-USEDEFAULTALERTS') }; + if ($args{useDefaultAlerts}) { + $self->assert_not_null($props[0]); + $self->assert_str_equals('TRUE', $props[0]->value); + } elsif ($props) { + $self->assert_str_equals('FALSE', $props[0]->value); + } - my @valarms = grep { $_->ical_entry_type eq 'VALARM' } @{$vevent->entries}; - my @gotUids = sort map { @{$_->property('UID')}[0]->value } @valarms; - my @wantUids = sort @{${args}{uids}}; + my @valarms = grep { $_->ical_entry_type eq 'VALARM' } @{ $vevent->entries }; + my @gotUids = sort map { @{ $_->property('UID') }[0]->value } @valarms; + my @wantUids = sort @{ ${args}{uids} }; - $self->assert_deep_equals(\@wantUids, \@gotUids); + $self->assert_deep_equals(\@wantUids, \@gotUids); } -sub caldav_get -{ - my ($self, $eventHref) = @_; - my $caldav = $self->{caldav}; - - xlog $self, "GET event"; - my %headers = ( - 'Content-Type' => 'text/calendar', - 'Authorization' => $caldav->auth_header, - ); - my $res = $caldav->{ua}->request('GET', - $caldav->request_url($eventHref), { - headers => \%headers, - }); - $self->assert_str_equals('200', $res->{status}); - - my $vcalendar = Data::ICal->new(data => $res->{content}); - my @vevents = grep { $_->ical_entry_type eq 'VEVENT' } @{$vcalendar->entries}; - my $vevent = $vevents[0]; - $self->assert_not_null($vevent); - return ($vcalendar, $vevent, $res->{headers}{etag}); +sub caldav_get { + my ($self, $eventHref) = @_; + my $caldav = $self->{caldav}; + + xlog $self, "GET event"; + my %headers = ( + 'Content-Type' => 'text/calendar', + 'Authorization' => $caldav->auth_header, + ); + my $res = $caldav->{ua}->request( + 'GET', + $caldav->request_url($eventHref), + { + headers => \%headers, + } + ); + $self->assert_str_equals('200', $res->{status}); + + my $vcalendar = Data::ICal->new(data => $res->{content}); + my @vevents + = grep { $_->ical_entry_type eq 'VEVENT' } @{ $vcalendar->entries }; + my $vevent = $vevents[0]; + $self->assert_not_null($vevent); + return ($vcalendar, $vevent, $res->{headers}{etag}); } -sub caldav_put -{ - my ($self, $href, $args) = @_; - my $caldav = $self->{caldav}; - - my %headers = ( - 'Content-Type' => 'text/calendar', - 'Authorization' => $caldav->auth_header - ); - @headers{ keys %{$args->{headers}} } = values %{$args->{headers}}; - my $res = $caldav->{ua}->request('PUT', - $caldav->request_url($href), { - headers => \%headers, - content => $args->{body}, - }); - - $self->assert_str_equals('204', $res->{status}); - return $res->{headers}{etag}; -} +sub caldav_put { + my ($self, $href, $args) = @_; + my $caldav = $self->{caldav}; + + my %headers = ( + 'Content-Type' => 'text/calendar', + 'Authorization' => $caldav->auth_header + ); + @headers{ keys %{ $args->{headers} } } = values %{ $args->{headers} }; + my $res = $caldav->{ua}->request( + 'PUT', + $caldav->request_url($href), + { + headers => \%headers, + content => $args->{body}, + } + ); + $self->assert_str_equals('204', $res->{status}); + return $res->{headers}{etag}; +} -sub create_jevent -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $ug = Data::UUID->new; - my $eventUid = $ug->create_str; - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - $eventUid => { - uid => $eventUid, - calendarIds => { - 'Default' => JSON::true, - }, - title => "event1", - start => '2023-02-17T15:10:00', - duration => "PT1H", - timeZone => "Etc/UTC", - useDefaultAlerts => JSON::true, - }, +sub create_jevent { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $ug = Data::UUID->new; + my $eventUid = $ug->create_str; + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + $eventUid => { + uid => $eventUid, + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{created}); - - my $eventHref = "Default/$eventUid.ics"; - - xlog "Rewrite unchanged iCalendar event"; - # We'll later change the iCalendar event data using - # the Data::ICal module. This library seems to sort - # iCalendar properties alphabetically when it - # serializes the event to iCalendar, so let's make - # sure the Cyrus on-disk representation matches. - # We need this to compare ETags later. - my ($vcalendar, $vevent) = $self->caldav_get($eventHref); - $self->caldav_put($eventHref, { body => $vcalendar->as_string }); - - return $eventHref; + title => "event1", + start => '2023-02-17T15:10:00', + duration => "PT1H", + timeZone => "Etc/UTC", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{created}); + + my $eventHref = "Default/$eventUid.ics"; + + xlog "Rewrite unchanged iCalendar event"; + # We'll later change the iCalendar event data using + # the Data::ICal module. This library seems to sort + # iCalendar properties alphabetically when it + # serializes the event to iCalendar, so let's make + # sure the Cyrus on-disk representation matches. + # We need this to compare ETags later. + my ($vcalendar, $vevent) = $self->caldav_get($eventHref); + $self->caldav_put($eventHref, { body => $vcalendar->as_string }); + + return $eventHref; } -sub assert_preserved_when_unchanged -{ - my ($self) = @_; - my $caldav = $self->{caldav}; - - xlog $self, "Assert useDefaultAlerts=true for no changes"; - - xlog $self, "Create JMAP event with default alerts"; - my $eventHref = $self->create_jevent; - - xlog $self, "Assert alarms via CalDAV"; - my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); - - xlog $self, "PUT back unchanged event"; - my $putetag = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); - $self->assert_null($putetag); - - xlog $self, "Assert alarms via CalDAV"; - ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); - $self->assert_str_equals($getetag1, $getetag2); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); +sub assert_preserved_when_unchanged { + my ($self) = @_; + my $caldav = $self->{caldav}; + + xlog $self, "Assert useDefaultAlerts=true for no changes"; + + xlog $self, "Create JMAP event with default alerts"; + my $eventHref = $self->create_jevent; + + xlog $self, "Assert alarms via CalDAV"; + my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); + + xlog $self, "PUT back unchanged event"; + my $putetag + = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); + $self->assert_null($putetag); + + xlog $self, "Assert alarms via CalDAV"; + ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); + $self->assert_str_equals($getetag1, $getetag2); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); } -sub assert_preserved_by_snooze_alarm -{ - my ($self) = @_; - my $caldav = $self->{caldav}; - - xlog $self, "Assert useDefaultAlerts=true for snooze alarm"; - - my $alarm = Data::ICal::Entry::Alarm::Display->new; - my $alarmUid = (Data::UUID->new)->create_str; - - # We actually should also acknowledge the default alarm - $alarm->add_properties( - description => 'useralarm', - trigger => '-PT15M', - 'related-to' => $self->{defaultAlertIds}[0], - uid => $alarmUid, - ); - - xlog $self, "Create JMAP event with default alerts"; - my $eventHref = $self->create_jevent; - - xlog $self, "Assert alarms via CalDAV"; - my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); - - xlog $self, "Add snooze alarm via CalDAV"; - $vevent->add_entry($alarm); - my $putetag = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); - $self->assert_null($putetag); - - xlog $self, "Assert alarms via CalDAV"; - ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); - my @wantUids = (($alarmUid), @{$self->{defaultAlertIds}}); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, uids => \@wantUids, - ); - $self->assert_str_not_equals($getetag1, $getetag2); +sub assert_preserved_by_snooze_alarm { + my ($self) = @_; + my $caldav = $self->{caldav}; + + xlog $self, "Assert useDefaultAlerts=true for snooze alarm"; + + my $alarm = Data::ICal::Entry::Alarm::Display->new; + my $alarmUid = (Data::UUID->new)->create_str; + + # We actually should also acknowledge the default alarm + $alarm->add_properties( + description => 'useralarm', + trigger => '-PT15M', + 'related-to' => $self->{defaultAlertIds}[0], + uid => $alarmUid, + ); + + xlog $self, "Create JMAP event with default alerts"; + my $eventHref = $self->create_jevent; + + xlog $self, "Assert alarms via CalDAV"; + my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); + + xlog $self, "Add snooze alarm via CalDAV"; + $vevent->add_entry($alarm); + my $putetag + = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); + $self->assert_null($putetag); + + xlog $self, "Assert alarms via CalDAV"; + ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); + my @wantUids = (($alarmUid), @{ $self->{defaultAlertIds} }); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => \@wantUids, + ); + $self->assert_str_not_equals($getetag1, $getetag2); } -sub assert_disabled_by_user_alarm -{ - my ($self) = @_; - my $caldav = $self->{caldav}; - - xlog $self, "Assert useDefaultAlerts=false for added user alarm"; - - my $alarm = Data::ICal::Entry::Alarm::Display->new; - my $alarmUid = (Data::UUID->new)->create_str; - $alarm->add_properties( - description => 'useralarm', - trigger => 'PT15M', - uid => $alarmUid, - ); - - xlog $self, "Create JMAP event with default alerts"; - my $eventHref = $self->create_jevent; - - xlog $self, "Assert alarms via CalDAV"; - my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); - - xlog $self, "Add user alarm via CalDAV"; - $vevent->add_entry($alarm); - my $putetag = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); - $self->assert_null($putetag); - - xlog $self, "Assert alarms via CalDAV"; - ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); - my @wantUids = (($alarmUid), @{$self->{defaultAlertIds}}); - $self->assert_valarms($vevent, - useDefaultAlerts => 0, uids => \@wantUids, - ); - $self->assert_str_not_equals($getetag1, $getetag2); +sub assert_disabled_by_user_alarm { + my ($self) = @_; + my $caldav = $self->{caldav}; + + xlog $self, "Assert useDefaultAlerts=false for added user alarm"; + + my $alarm = Data::ICal::Entry::Alarm::Display->new; + my $alarmUid = (Data::UUID->new)->create_str; + $alarm->add_properties( + description => 'useralarm', + trigger => 'PT15M', + uid => $alarmUid, + ); + + xlog $self, "Create JMAP event with default alerts"; + my $eventHref = $self->create_jevent; + + xlog $self, "Assert alarms via CalDAV"; + my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); + + xlog $self, "Add user alarm via CalDAV"; + $vevent->add_entry($alarm); + my $putetag + = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); + $self->assert_null($putetag); + + xlog $self, "Assert alarms via CalDAV"; + ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); + my @wantUids = (($alarmUid), @{ $self->{defaultAlertIds} }); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 0, + uids => \@wantUids, + ); + $self->assert_str_not_equals($getetag1, $getetag2); } -sub assert_preserved_by_apple_alarm -{ - my ($self) = @_; - my $caldav = $self->{caldav}; - - xlog $self, "Assert useDefaultAlerts=true for added Apple default alarm"; - - my $alarm = Data::ICal::Entry::Alarm::Display->new; - my $alarmUid = (Data::UUID->new)->create_str; - $alarm->add_properties( - description => 'applealarm', - trigger => '-PT15M', - uid => $alarmUid, - 'x-apple-default-alarm' => 'TRUE', - ); - - xlog $self, "Create JMAP event with default alerts"; - my $eventHref = $self->create_jevent; - - xlog $self, "Assert alarms via CalDAV"; - my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); - - xlog $self, "Add user alarm via CalDAV"; - $vevent->add_entry($alarm); - my $putetag = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); - $self->assert_null($putetag); - - xlog $self, "Assert alarms via CalDAV"; - ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); - my @wantUids = (($alarmUid), @{$self->{defaultAlertIds}}); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, uids => \@wantUids, - ); - $self->assert_str_not_equals($getetag1, $getetag2); +sub assert_preserved_by_apple_alarm { + my ($self) = @_; + my $caldav = $self->{caldav}; + + xlog $self, "Assert useDefaultAlerts=true for added Apple default alarm"; + + my $alarm = Data::ICal::Entry::Alarm::Display->new; + my $alarmUid = (Data::UUID->new)->create_str; + $alarm->add_properties( + description => 'applealarm', + trigger => '-PT15M', + uid => $alarmUid, + 'x-apple-default-alarm' => 'TRUE', + ); + + xlog $self, "Create JMAP event with default alerts"; + my $eventHref = $self->create_jevent; + + xlog $self, "Assert alarms via CalDAV"; + my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); + + xlog $self, "Add user alarm via CalDAV"; + $vevent->add_entry($alarm); + my $putetag + = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); + $self->assert_null($putetag); + + xlog $self, "Assert alarms via CalDAV"; + ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); + my @wantUids = (($alarmUid), @{ $self->{defaultAlertIds} }); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => \@wantUids, + ); + $self->assert_str_not_equals($getetag1, $getetag2); } -sub assert_disabled_by_removed_default_alarm -{ - my ($self) = @_; - my $caldav = $self->{caldav}; - - xlog $self, "Assert useDefaultAlerts=false for removed default alarm"; - - xlog $self, "Create JMAP event with default alerts"; - my $eventHref = $self->create_jevent; - - xlog $self, "Assert alarms via CalDAV"; - my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); - - xlog $self, "Remove one of two default alarms"; - splice(@{$vevent->entries}, 1); - my $keptAlarmUid = $vevent->entries->[0]->property('uid')->[0]->value; - my $putetag = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); - $self->assert_null($putetag); - - xlog $self, "Assert alarms via CalDAV"; - ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 0, uids => [ $keptAlarmUid ], - ); - $self->assert_str_not_equals($getetag1, $getetag2); +sub assert_disabled_by_removed_default_alarm { + my ($self) = @_; + my $caldav = $self->{caldav}; + + xlog $self, "Assert useDefaultAlerts=false for removed default alarm"; + + xlog $self, "Create JMAP event with default alerts"; + my $eventHref = $self->create_jevent; + + xlog $self, "Assert alarms via CalDAV"; + my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); + + xlog $self, "Remove one of two default alarms"; + splice(@{ $vevent->entries }, 1); + my $keptAlarmUid = $vevent->entries->[0]->property('uid')->[0]->value; + my $putetag + = $self->caldav_put($eventHref, { body => $vcalendar->as_string }); + $self->assert_null($putetag); + + xlog $self, "Assert alarms via CalDAV"; + ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 0, + uids => [$keptAlarmUid], + ); + $self->assert_str_not_equals($getetag1, $getetag2); } -sub assert_disabled_by_no_alarms -{ - my ($self) = @_; - my $caldav = $self->{caldav}; - - xlog $self, "Assert useDefaultAlerts=false for no alarms"; - - xlog $self, "Create JMAP event with default alerts"; - my $eventHref = $self->create_jevent; - - xlog $self, "Assert alarms via CalDAV"; - my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); - - xlog $self, "Remove all alarms from VEVENT"; - my $ical = $vcalendar->as_string; - $ical =~ s/BEGIN:VALARM.*END:VALARM\r\n//gms; - my $putetag = $self->caldav_put($eventHref, { body => $ical }); - $self->assert_null($putetag); - - xlog $self, "Assert alarms via CalDAV"; - ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 0, - uids => [ ], - ); - $self->assert_str_not_equals($getetag1, $getetag2); +sub assert_disabled_by_no_alarms { + my ($self) = @_; + my $caldav = $self->{caldav}; + + xlog $self, "Assert useDefaultAlerts=false for no alarms"; + + xlog $self, "Create JMAP event with default alerts"; + my $eventHref = $self->create_jevent; + + xlog $self, "Assert alarms via CalDAV"; + my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); + + xlog $self, "Remove all alarms from VEVENT"; + my $ical = $vcalendar->as_string; + $ical =~ s/BEGIN:VALARM.*END:VALARM\r\n//gms; + my $putetag = $self->caldav_put($eventHref, { body => $ical }); + $self->assert_null($putetag); + + xlog $self, "Assert alarms via CalDAV"; + ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 0, + uids => [], + ); + $self->assert_str_not_equals($getetag1, $getetag2); } -sub assert_not_disabled_if_xheader_set -{ - my ($self) = @_; - my $caldav = $self->{caldav}; - - xlog $self, "Assert useDefaultAlerts=true for user alarm if x-header is set"; - - my $alarm = Data::ICal::Entry::Alarm::Display->new; - my $alarmUid = (Data::UUID->new)->create_str; - $alarm->add_properties( - description => 'useralarm', - trigger => 'PT15M', - uid => $alarmUid, - ); - - xlog $self, "Create JMAP event with default alerts"; - my $eventHref = $self->create_jevent; - - xlog $self, "Assert alarms via CalDAV"; - my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); - - xlog $self, "Remove VALARMs from VEVENT and add new user VALARM"; - splice(@{$vevent->entries}); - $vevent->add_entry($alarm); - - xlog $self, "PUT via CalDAV"; - my $putetag = $self->caldav_put($eventHref, { - headers => { - 'X-Cyrus-rewrite-usedefaultalerts' => 'f', - }, - body => $vcalendar->as_string, - }); - $self->assert_null($putetag); - - xlog $self, "Assert alarms via CalDAV"; - ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); - $self->assert_valarms($vevent, - useDefaultAlerts => 1, - uids => $self->{defaultAlertIds}, - ); - $self->assert_str_not_equals($getetag1, $getetag2); +sub assert_not_disabled_if_xheader_set { + my ($self) = @_; + my $caldav = $self->{caldav}; + + xlog $self, "Assert useDefaultAlerts=true for user alarm if x-header is set"; + + my $alarm = Data::ICal::Entry::Alarm::Display->new; + my $alarmUid = (Data::UUID->new)->create_str; + $alarm->add_properties( + description => 'useralarm', + trigger => 'PT15M', + uid => $alarmUid, + ); + + xlog $self, "Create JMAP event with default alerts"; + my $eventHref = $self->create_jevent; + + xlog $self, "Assert alarms via CalDAV"; + my ($vcalendar, $vevent, $getetag1) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); + + xlog $self, "Remove VALARMs from VEVENT and add new user VALARM"; + splice(@{ $vevent->entries }); + $vevent->add_entry($alarm); + + xlog $self, "PUT via CalDAV"; + my $putetag = $self->caldav_put( + $eventHref, + { + headers => { + 'X-Cyrus-rewrite-usedefaultalerts' => 'f', + }, + body => $vcalendar->as_string, + } + ); + $self->assert_null($putetag); + + xlog $self, "Assert alarms via CalDAV"; + ($vcalendar, $vevent, $getetag2) = $self->caldav_get($eventHref); + $self->assert_valarms( + $vevent, + useDefaultAlerts => 1, + uids => $self->{defaultAlertIds}, + ); + $self->assert_str_not_equals($getetag1, $getetag2); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_encode_imip_uri b/cassandane/tiny-tests/JMAPCalendars/calendarevent_encode_imip_uri index 78c98d843b..1e7d23e5dd 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_encode_imip_uri +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_encode_imip_uri @@ -2,89 +2,97 @@ use Cassandane::Tiny; sub test_calendarevent_encode_imip_uri - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $uid = 'event1uid'; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $uid = 'event1uid'; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{instance}->getnotify(); + $self->{instance}->getnotify(); - xlog "Create event with percent-encoded participant uri"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => $uid, - title => 'event1', - start => '2020-01-01T09:00:00', - timeZone => 'Europe/Vienna', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - plusuri => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:plus%2Buri@example.com', - }, - expectReply => JSON::true, - participationStatus => 'needs-action', - }, - }, + xlog "Create event with percent-encoded participant uri"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + uid => $uid, + title => 'event1', + start => '2020-01-01T09:00:00', + timeZone => 'Europe/Vienna', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + plusuri => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:plus%2Buri@example.com', }, + expectReply => JSON::true, + participationStatus => 'needs-action', + }, }, - sendSchedulingMessages => JSON::true, - }, 'R1'], - ['CalendarEvent/get', { - properties => ['participants'], - }, 'R2'], - ]); + }, + }, + sendSchedulingMessages => JSON::true, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => ['participants'], + }, + 'R2' + ], + ]); - xlog "Assert the Participant uri is encoded"; - $self->assert_str_equals('mailto:plus%2Buri@example.com', - $res->[1][1]{list}[0]{participants}{plusuri}{sendTo}{imip}); + xlog "Assert the Participant uri is encoded"; + $self->assert_str_equals('mailto:plus%2Buri@example.com', + $res->[1][1]{list}[0]{participants}{plusuri}{sendTo}{imip}); - xlog "Assert the iCalendar data has the encoded URI"; - my $blobId = $res->[0][1]{created}{event1}{blobId}; - $res = $jmap->Download('cassandane', $blobId); - my $ical = $res->{content}; - $self->assert($ical =~ /mailto:plus%2Buri\@example\.com/g); + xlog "Assert the iCalendar data has the encoded URI"; + my $blobId = $res->[0][1]{created}{event1}{blobId}; + $res = $jmap->Download('cassandane', $blobId); + my $ical = $res->{content}; + $self->assert($ical =~ /mailto:plus%2Buri\@example\.com/g); - xlog "Assert the iMIP notification has the decoded recipient"; - my $data = $self->{instance}->getnotify(); - my ($imipnotif) = grep { $_->{METHOD} eq 'imip' } @$data; - my $payload = decode_json($imipnotif->{MESSAGE}); - $self->assert_str_equals('plus+uri@example.com', $payload->{recipient}); - my $expect_id = encode_eventid($uid); - $self->assert_str_equals($expect_id, $payload->{id}); - $self->assert_str_equals('REQUEST', $payload->{method}); + xlog "Assert the iMIP notification has the decoded recipient"; + my $data = $self->{instance}->getnotify(); + my ($imipnotif) = grep { $_->{METHOD} eq 'imip' } @$data; + my $payload = decode_json($imipnotif->{MESSAGE}); + $self->assert_str_equals('plus+uri@example.com', $payload->{recipient}); + my $expect_id = encode_eventid($uid); + $self->assert_str_equals($expect_id, $payload->{id}); + $self->assert_str_equals('REQUEST', $payload->{method}); - xlog "Assert the iTIP message has the encoded URI"; - my $itip = $payload->{ical}; - $self->assert($itip =~ /mailto:plus%2Buri\@example\.com/g); - $self->assert($itip =~ "METHOD:REQUEST"); + xlog "Assert the iTIP message has the encoded URI"; + my $itip = $payload->{ical}; + $self->assert($itip =~ /mailto:plus%2Buri\@example\.com/g); + $self->assert($itip =~ "METHOD:REQUEST"); - xlog "Deliver iTIP REPLY for participant"; - $itip =~ s/METHOD:REQUEST/METHOD:REPLY/g; - $itip =~ s/NEEDS-ACTION/ACCEPTED/g; + xlog "Deliver iTIP REPLY for participant"; + $itip =~ s/METHOD:REQUEST/METHOD:REPLY/g; + $itip =~ s/NEEDS-ACTION/ACCEPTED/g; - my $imip = <<"EOF"; + my $imip = <<"EOF"; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -94,16 +102,20 @@ X-Cassandane-Unique: 6de280c9-edff-4019-8ebd-cfebc73f8201 $itip EOF - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - xlog "Assert the participant status got updated"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['participants'], - }, 'R1'], - ]); - $self->assert_str_equals('mailto:plus%2Buri@example.com', - $res->[0][1]{list}[0]{participants}{plusuri}{sendTo}{imip}); - $self->assert_str_equals('accepted', - $res->[0][1]{list}[0]{participants}{plusuri}{participationStatus}); + xlog "Assert the participant status got updated"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['participants'], + }, + 'R1' + ], + ]); + $self->assert_str_equals('mailto:plus%2Buri@example.com', + $res->[0][1]{list}[0]{participants}{plusuri}{sendTo}{imip}); + $self->assert_str_equals('accepted', + $res->[0][1]{list}[0]{participants}{plusuri}{participationStatus}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_alerts b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_alerts index a38c021dbd..81d4b52312 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_alerts +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_alerts @@ -2,82 +2,81 @@ use Cassandane::Tiny; sub test_calendarevent_get_alerts - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my ($maj, $min) = Cassandane::Instance->get_version(); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my ($maj, $min) = Cassandane::Instance->get_version(); - my ($id, $ical) = $self->icalfile('alerts'); + my ($id, $ical) = $self->icalfile('alerts'); - my $alerts = { - '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-1' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - action => "email", - }, - '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-2' => { - '@type' => 'Alert', - trigger => { - '@type' => 'AbsoluteTrigger', - when => "2016-09-28T13:55:00Z", - }, - acknowledged => "2016-09-28T14:00:05Z", - action => "display", - }, + my $alerts = { + '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-1' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", + }, + action => "email", + }, + '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-2' => { + '@type' => 'Alert', + trigger => { + '@type' => 'AbsoluteTrigger', + when => "2016-09-28T13:55:00Z", + }, + acknowledged => "2016-09-28T14:00:05Z", + action => "display", + }, + '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "PT10M", + }, + action => "display", + }, + '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3-snoozed1' => { + '@type' => 'Alert', + trigger => { + '@type' => 'AbsoluteTrigger', + when => '2016-09-28T15:00:05Z', + }, + action => "display", + relatedTo => { '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "PT10M", - }, - action => "display", - }, - '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3-snoozed1' => { - '@type' => 'Alert', - trigger => { - '@type' => 'AbsoluteTrigger', - when => '2016-09-28T15:00:05Z', - }, - action => "display", - relatedTo => { - '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3' => { - '@type' => 'Relation', - relation => { - parent => JSON::true, - }, - } - }, - }, - '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3-snoozed2' => { - '@type' => 'Alert', - trigger => { - '@type' => 'AbsoluteTrigger', - when => '2016-09-28T15:00:05Z', - }, - action => "display", - relatedTo => { - '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3' => { - '@type' => 'Relation', - relation => {} - }, - }, - }, - '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-4' => { - '@type' => 'Alert', - trigger => { - '@type' => 'AbsoluteTrigger', - when => '1976-04-01T00:55:45Z', - }, - action => "display", + '@type' => 'Relation', + relation => { + parent => JSON::true, + }, + } + }, + }, + '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3-snoozed2' => { + '@type' => 'Alert', + trigger => { + '@type' => 'AbsoluteTrigger', + when => '2016-09-28T15:00:05Z', + }, + action => "display", + relatedTo => { + '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-3' => { + '@type' => 'Relation', + relation => {} }, - }; + }, + }, + '0CF835D0-CFEB-44AE-904A-C26AB62B73BB-4' => { + '@type' => 'Alert', + trigger => { + '@type' => 'AbsoluteTrigger', + when => '1976-04-01T00:55:45Z', + }, + action => "display", + }, + }; - my $event = $self->putandget_vevent($id, $ical); - $self->assert_str_equals(JSON::false, $event->{useDefaultAlerts}); - $self->assert_deep_equals($alerts, $event->{alerts}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_str_equals(JSON::false, $event->{useDefaultAlerts}); + $self->assert_deep_equals($alerts, $event->{alerts}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_attachbinary b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_attachbinary index e739caa7d7..518c0825f6 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_attachbinary +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_attachbinary @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_get_attachbinary - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + my $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -28,44 +27,54 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $rawIcal, - 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', 'Default/test.ics', $rawIcal, + 'Content-Type' => 'text/calendar'); - xlog "Fetch with Cyrus extension"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['links'], - }, 'R1'], - ]); - my $event = $res->[0][1]{list}[0]; - $self->assert_not_null($event); + xlog "Fetch with Cyrus extension"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['links'], + }, + 'R1' + ], + ]); + my $event = $res->[0][1]{list}[0]; + $self->assert_not_null($event); - my @links = values %{$event->{links}}; - $self->assert_num_equals(1, scalar @links); - $self->assert_null($links[0]{href}); - $self->assert_str_equals('text/plain', $links[0]{contentType}); - my $blobId = $links[0]{blobId}; - $self->assert_not_null($blobId); + my @links = values %{ $event->{links} }; + $self->assert_num_equals(1, scalar @links); + $self->assert_null($links[0]{href}); + $self->assert_str_equals('text/plain', $links[0]{contentType}); + my $blobId = $links[0]{blobId}; + $self->assert_not_null($blobId); - xlog "Fetch blob"; - $res = $jmap->Download('cassandane', $blobId); - $self->assert_str_equals("hello", $res->{content}); + xlog "Fetch blob"; + $res = $jmap->Download('cassandane', $blobId); + $self->assert_str_equals("hello", $res->{content}); - xlog "Fetch without Cyrus extension"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['links'], - }, 'R2'], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - ]); - $event = $res->[0][1]{list}[0]; - $self->assert_not_null($event); + xlog "Fetch without Cyrus extension"; + $res = $jmap->CallMethods( + [ + [ + 'CalendarEvent/get', + { + properties => ['links'], + }, + 'R2' + ], + ], + [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + ] + ); + $event = $res->[0][1]{list}[0]; + $self->assert_not_null($event); - @links = values %{$event->{links}}; - $self->assert_num_equals(1, scalar @links); - $self->assert_str_equals('data:text/plain;base64,aGVsbG8=', $links[0]{href}); - $self->assert_str_equals('text/plain', $links[0]{contentType}); + @links = values %{ $event->{links} }; + $self->assert_num_equals(1, scalar @links); + $self->assert_str_equals('data:text/plain;base64,aGVsbG8=', $links[0]{href}); + $self->assert_str_equals('text/plain', $links[0]{contentType}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_baseeventid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_baseeventid index 01c74b5d5e..aa96adc25a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_baseeventid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_baseeventid @@ -2,100 +2,117 @@ use Cassandane::Tiny; sub test_calendarevent_get_baseeventid - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $event1Uid = '1cf5da26-38e9-47ac-8449-04354ae3772d'; - my $event2Uid = '3e1356b8-5e55-4413-98c9-27da12271b99'; + my $jmap = $self->{jmap}; + my $event1Uid = '1cf5da26-38e9-47ac-8449-04354ae3772d'; + my $event2Uid = '3e1356b8-5e55-4413-98c9-27da12271b99'; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - 'Default' => JSON::true, - }, - uid => $event1Uid, - title => "event1", - start => "2023-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'daily', - count => 3, - }], - recurrenceOverrides => { - '2023-01-02T09:00:00' => { - start => '2023-01-02T12:00:00', - }, - }, - }, - event2 => { - calendarIds => { - 'Default' => JSON::true, - }, - uid => $event2Uid, - title => "event2", - start => "2023-01-01T01:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'daily', - count => 3, - }], - }, - } - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event1', '#event2',], - properties => ['baseEventId'], - }, 'R2'], - ]); - my $event1Id = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($event1Id); - $self->assert_null($res->[1][1]{list}[0]{baseEventId}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + 'Default' => JSON::true, + }, + uid => $event1Uid, + title => "event1", + start => "2023-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'daily', + count => 3, + } ], + recurrenceOverrides => { + '2023-01-02T09:00:00' => { + start => '2023-01-02T12:00:00', + }, + }, + }, + event2 => { + calendarIds => { + 'Default' => JSON::true, + }, + uid => $event2Uid, + title => "event2", + start => "2023-01-01T01:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'daily', + count => 3, + } ], + }, + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [ '#event1', '#event2', ], + properties => ['baseEventId'], + }, + 'R2' + ], + ]); + my $event1Id = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($event1Id); + $self->assert_null($res->[1][1]{list}[0]{baseEventId}); - my $event2Id = $res->[0][1]{created}{event2}{id}; - $self->assert_not_null($event2Id); - $self->assert_null($res->[1][1]{list}[0]{baseEventId}); + my $event2Id = $res->[0][1]{created}{event2}{id}; + $self->assert_not_null($event2Id); + $self->assert_null($res->[1][1]{list}[0]{baseEventId}); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - filter => { - after => '2023-01-01T01:00:00', - before => '2023-01-03T00:00:00', - }, - sort => [{ - property => 'start', - }], - expandRecurrences => JSON::true, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => ['baseEventId', 'utcStart',], - }, 'R2'], - ]); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/query', + { + filter => { + after => '2023-01-01T01:00:00', + before => '2023-01-03T00:00:00', + }, + sort => [ { + property => 'start', + } ], + expandRecurrences => JSON::true, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => [ 'baseEventId', 'utcStart', ], + }, + 'R2' + ], + ]); - $self->assert_deep_equals([ - encode_eventid($event1Uid, '20230101T090000'), - encode_eventid($event2Uid, '20230102T010000'), - encode_eventid($event1Uid, '20230102T090000'), - ], $res->[0][1]{ids}); + $self->assert_deep_equals( + [ + encode_eventid($event1Uid, '20230101T090000'), + encode_eventid($event2Uid, '20230102T010000'), + encode_eventid($event1Uid, '20230102T090000'), + ], + $res->[0][1]{ids} + ); - my @events = sort { - $a->{utcStart} cmp $b->{utcStart} - } @{$res->[1][1]{list}}; + my @events + = sort { $a->{utcStart} cmp $b->{utcStart} } @{ $res->[1][1]{list} }; - $self->assert_str_equals($event1Id, $events[0]{baseEventId}); - $self->assert_str_equals($event2Id, $events[1]{baseEventId}); - $self->assert_str_equals($event1Id, $events[2]{baseEventId}); + $self->assert_str_equals($event1Id, $events[0]{baseEventId}); + $self->assert_str_equals($event2Id, $events[1]{baseEventId}); + $self->assert_str_equals($event1Id, $events[2]{baseEventId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_color b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_color index aaec196675..7cb95d419f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_color +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_color @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_get_color - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('color'); + my ($id, $ical) = $self->icalfile('color'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals("red", $event->{color}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals("red", $event->{color}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_color_categories b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_color_categories index e41a55a68c..50fafd32b2 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_color_categories +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_color_categories @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_get_color_categories - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('color-categories'); + my ($id, $ical) = $self->icalfile('color-categories'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals("red", $event->{color}); - $self->assert_null($event->{keywords}{red}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals("red", $event->{color}); + $self->assert_null($event->{keywords}{red}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_description b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_description index cac0815e0a..27d3559db5 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_description +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_description @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_get_description - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('description'); + my ($id, $ical) = $self->icalfile('description'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals("Hello, world!", $event->{description}); - $self->assert_str_equals("text/plain", $event->{descriptionContentType}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals("Hello, world!", $event->{description}); + $self->assert_str_equals("text/plain", $event->{descriptionContentType}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_duplicate_recurrence_ids b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_duplicate_recurrence_ids index 7f6a4806b7..449e07d4ec 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_duplicate_recurrence_ids +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_duplicate_recurrence_ids @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_get_duplicate_recurrence_ids - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <<'EOF'; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -37,16 +36,20 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $ical, - 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', 'Default/test.ics', $ical, + 'Content-Type' => 'text/calendar'); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['title', 'recurrenceId'] - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('instance1', $res->[0][1]{list}[0]{title}); - $self->assert_str_equals('2021-01-01T06:00:00', - $res->[0][1]{list}[0]{recurrenceId}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'title', 'recurrenceId' ] + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('instance1', $res->[0][1]{list}[0]{title}); + $self->assert_str_equals('2021-01-01T06:00:00', + $res->[0][1]{list}[0]{recurrenceId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_empty_apple_location b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_empty_apple_location index 05e63695ee..43d63134ba 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_empty_apple_location +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_empty_apple_location @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_get_empty_apple_location - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['locations'], - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_null($res->[0][1]{list}[0]{locations}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['locations'], + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_null($res->[0][1]{list}[0]{locations}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_emptyprops b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_emptyprops index f751938e3b..6e0f0fc89c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_emptyprops +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_emptyprops @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_calendarevent_get_emptyprops - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create VEVENT with empty string properties"; + xlog "Create VEVENT with empty string properties"; - my $ical = <Request('PUT', '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); - - xlog "Make sure CalendarEvent/get returns it"; - - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - }, 'R2'], - ]); - - my $event = $res->[1][1]{list}[0]; - $self->assert_not_null($event); - - xlog "Make sure CalendarEvent/set{update} handles it"; - - # This triggers a specific empty-string related - # bug that only surfaces during update. - $event->{links} = { - links1 => { - href => 'https://example.com/2c505abe', + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); + + xlog "Make sure CalendarEvent/get returns it"; + + my $res = $jmap->CallMethods([ + [ 'CalendarEvent/query', {}, 'R1' ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' }, - }; - - delete($event->{blobId}); - delete($event->{debugBlobId}); - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $event->{id} => $event, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ $event->{id} ], - }, 'R2'], - ]); - - $self->assert(exists $res->[0][1]{updated}{$event->{id}}); - $self->assert_not_null($res->[1][1]{list}[0]{id}); - - xlog "Make sure CalendarEvent/set{create} handles it"; - - $event->{links} = undef; - $event->{uid} = '113a2c25-5458-48ce-9c35-29eb957a4631'; - delete($event->{id}); - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event2 => $event, - }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event2}{id}; - $self->assert_not_null($eventId); - - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [ $eventId ], - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{list}[0]{id}); + }, + 'R2' + ], + ]); + + my $event = $res->[1][1]{list}[0]; + $self->assert_not_null($event); + + xlog "Make sure CalendarEvent/set{update} handles it"; + + # This triggers a specific empty-string related + # bug that only surfaces during update. + $event->{links} = { + links1 => { + href => 'https://example.com/2c505abe', + }, + }; + + delete($event->{blobId}); + delete($event->{debugBlobId}); + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $event->{id} => $event, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [ $event->{id} ], + }, + 'R2' + ], + ]); + + $self->assert(exists $res->[0][1]{updated}{ $event->{id} }); + $self->assert_not_null($res->[1][1]{list}[0]{id}); + + xlog "Make sure CalendarEvent/set{create} handles it"; + + $event->{links} = undef; + $event->{uid} = '113a2c25-5458-48ce-9c35-29eb957a4631'; + delete($event->{id}); + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event2 => $event, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event2}{id}; + $self->assert_not_null($eventId); + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [$eventId], + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{list}[0]{id}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_endtimezone b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_endtimezone index 49ebaa5121..e0e20880f2 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_endtimezone +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_endtimezone @@ -2,21 +2,20 @@ use Cassandane::Tiny; sub test_calendarevent_get_endtimezone - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('endtimezone'); + my ($id, $ical) = $self->icalfile('endtimezone'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals("2016-09-28T13:00:00", $event->{start}); - $self->assert_str_equals("Europe/London", $event->{timeZone}); - $self->assert_str_equals("PT1H", $event->{duration}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals("2016-09-28T13:00:00", $event->{start}); + $self->assert_str_equals("Europe/London", $event->{timeZone}); + $self->assert_str_equals("PT1H", $event->{duration}); - my @locations = values %{$event->{locations}}; - $self->assert_num_equals(1, scalar @locations); - $self->assert_str_equals("Europe/Vienna", $locations[0]{timeZone}); - $self->assert_str_equals("end", $locations[0]{relativeTo}); + my @locations = values %{ $event->{locations} }; + $self->assert_num_equals(1, scalar @locations); + $self->assert_str_equals("Europe/Vienna", $locations[0]{timeZone}); + $self->assert_str_equals("end", $locations[0]{relativeTo}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_expandrecurrences_date b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_expandrecurrences_date index 24de90f165..a56033ec90 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_expandrecurrences_date +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_expandrecurrences_date @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_get_expandrecurrences_date - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <Request('PUT', '/dav/calendars/user/cassandane/Default/123456789.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', + '/dav/calendars/user/cassandane/Default/123456789.ics', + $ical, 'Content-Type' => 'text/calendar'); - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - filter => { - after => '2020-04-21T14:00:00', - before => '2020-04-22T13:59:59', - }, - expandRecurrences => JSON::true, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids', - }, - properties => ['start'], - }, 'R2'], - ]); - $self->assert_str_equals('2020-04-22T00:00:00', $res->[1][1]{list}[0]{start}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/query', + { + filter => { + after => '2020-04-21T14:00:00', + before => '2020-04-22T13:59:59', + }, + expandRecurrences => JSON::true, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids', + }, + properties => ['start'], + }, + 'R2' + ], + ]); + $self->assert_str_equals('2020-04-22T00:00:00', $res->[1][1]{list}[0]{start}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_floatingtzid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_floatingtzid index 954ef1507a..eb182964d4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_floatingtzid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_floatingtzid @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_calendarevent_get_floatingtzid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('floatingtzid'); + my ($id, $ical) = $self->icalfile('floatingtzid'); - # As seen in the wild: A floating DTSTART and a DTEND with TZID. + # As seen in the wild: A floating DTSTART and a DTEND with TZID. - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals("2019-03-10T11:15:00", $event->{start}); - $self->assert_str_equals("Europe/Amsterdam", $event->{timeZone}); - $self->assert_str_equals("PT1H45M", $event->{duration}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals("2019-03-10T11:15:00", $event->{start}); + $self->assert_str_equals("Europe/Amsterdam", $event->{timeZone}); + $self->assert_str_equals("PT1H45M", $event->{duration}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ignore_dead_standalone_instance b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ignore_dead_standalone_instance index d9753887ae..51239f6604 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ignore_dead_standalone_instance +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ignore_dead_standalone_instance @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_get_ignore_dead_standalone_instance - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <<'EOF'; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN @@ -28,18 +27,23 @@ UID:889i-uid1@example.com END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['title', 'recurrenceOverrides'], - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'title', 'recurrenceOverrides' ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); - $ical = <<'EOF'; + $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN @@ -85,18 +89,26 @@ UID:889i-uid1@example.com END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['title', 'recurrenceOverrides'], - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('Party', $res->[0][1]{list}[0]{title}); - $self->assert_num_equals(2, scalar keys %{$res->[0][1]{list}[0]{recurrenceOverrides}}); - $self->assert_not_null($res->[0][1]{list}[0]{recurrenceOverrides}{'1996-09-19T14:30:00'}); - $self->assert_not_null($res->[0][1]{list}[0]{recurrenceOverrides}{'1996-09-23T14:30:00'}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'title', 'recurrenceOverrides' ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('Party', $res->[0][1]{list}[0]{title}); + $self->assert_num_equals(2, + scalar keys %{ $res->[0][1]{list}[0]{recurrenceOverrides} }); + $self->assert_not_null( + $res->[0][1]{list}[0]{recurrenceOverrides}{'1996-09-19T14:30:00'}); + $self->assert_not_null( + $res->[0][1]{list}[0]{recurrenceOverrides}{'1996-09-23T14:30:00'}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ignore_embedded_ianatz b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ignore_embedded_ianatz index fbda44efe0..85d3ef0545 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ignore_embedded_ianatz +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ignore_embedded_ianatz @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_calendarevent_get_ignore_embedded_ianatz - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog "Create VEVENT with bogus IANA VTIMEZONE"; - my $ical = <<'EOF'; + xlog "Create VEVENT with bogus IANA VTIMEZONE"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//foo//bar//EN @@ -40,44 +39,56 @@ SUMMARY:test END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $ical, - 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', 'Default/test.ics', $ical, + 'Content-Type' => 'text/calendar'); - xlog "Assert start and duration"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['start', 'duration', 'timeZone'], - }, 'R1'], - ]); + xlog "Assert start and duration"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'start', 'duration', 'timeZone' ], + }, + 'R1' + ], + ]); - my $eventId = $res->[0][1]{list}[0]{id}; - $self->assert_str_equals('2021-03-28T01:00:00', $res->[0][1]{list}[0]{start}); - $self->assert_str_equals('PT2H', $res->[0][1]{list}[0]{duration}); - $self->assert_str_equals('Europe/Vienna', $res->[0][1]{list}[0]{timeZone}); + my $eventId = $res->[0][1]{list}[0]{id}; + $self->assert_str_equals('2021-03-28T01:00:00', $res->[0][1]{list}[0]{start}); + $self->assert_str_equals('PT2H', $res->[0][1]{list}[0]{duration}); + $self->assert_str_equals('Europe/Vienna', $res->[0][1]{list}[0]{timeZone}); - xlog "Assert timerange query"; - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - filter => { - after => '2021-03-27T23:00:00', - before => '2021-03-28T02:00:00' - }, - }, 'R1'], - ['CalendarEvent/query', { - filter => { - after => '2021-03-28T02:00:00', - before => '2021-03-28T23:00:00' - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$eventId], $res->[0][1]{ids}); - $self->assert_deep_equals([], $res->[1][1]{ids}); + xlog "Assert timerange query"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/query', + { + filter => { + after => '2021-03-27T23:00:00', + before => '2021-03-28T02:00:00' + }, + }, + 'R1' + ], + [ + 'CalendarEvent/query', + { + filter => { + after => '2021-03-28T02:00:00', + before => '2021-03-28T23:00:00' + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$eventId], $res->[0][1]{ids}); + $self->assert_deep_equals([], $res->[1][1]{ids}); - my @notifs = grep($_->{CLASS} eq 'IMIP', @{$self->{instance}->getnotify()}); - $self->assert_num_equals(1, scalar @notifs); - my $message = decode_json($notifs[0]->{MESSAGE}); - my $event = $message->{patch}; - $self->assert_str_equals('2021-03-28T01:00:00', $event->{start}); - $self->assert_str_equals('PT2H', $event->{duration}); - $self->assert_str_equals('Europe/Vienna', $event->{timeZone}); + my @notifs = grep($_->{CLASS} eq 'IMIP', @{ $self->{instance}->getnotify() }); + $self->assert_num_equals(1, scalar @notifs); + my $message = decode_json($notifs[0]->{MESSAGE}); + my $event = $message->{patch}; + $self->assert_str_equals('2021-03-28T01:00:00', $event->{start}); + $self->assert_str_equals('PT2H', $event->{duration}); + $self->assert_str_equals('Europe/Vienna', $event->{timeZone}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_isorigin b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_isorigin index 80b8535914..3c09102c7f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_isorigin +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_isorigin @@ -2,112 +2,131 @@ use Cassandane::Tiny; sub test_calendarevent_get_isorigin - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Create events"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventNoReplyTo => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - title => 'eventNoReplyTo', - start => '2021-01-01T15:30:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - }, - eventIsOrganizer => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - title => 'eventIsOrganizer', - start => '2021-01-01T15:30:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - participant1 => { - sendTo => { - imip => 'mailto:someone@example.com', - }, - }, - }, + xlog "Create events"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + eventNoReplyTo => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + title => 'eventNoReplyTo', + start => '2021-01-01T15:30:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + eventIsOrganizer => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + title => 'eventIsOrganizer', + start => '2021-01-01T15:30:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + participant1 => { + sendTo => { + imip => 'mailto:someone@example.com', }, - eventIsInvitee => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - title => 'eventIsInvitee', - start => '2021-01-01T15:30:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - replyTo => { - imip => 'mailto:someone@example.com', - }, - participants => { - participant1 => { - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - }, + }, + }, + }, + eventIsInvitee => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + title => 'eventIsInvitee', + start => '2021-01-01T15:30:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + replyTo => { + imip => 'mailto:someone@example.com', + }, + participants => { + participant1 => { + sendTo => { + imip => 'mailto:cassandane@example.com', }, + }, }, - }, 'R1'], - ['CalendarEvent/get', { - properties => ['isOrigin'], - }, 'R2'], - ]); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => ['isOrigin'], + }, + 'R2' + ], + ]); - my $eventNoReplyToId = $res->[0][1]{created}{eventNoReplyTo}{id}; - $self->assert_not_null($eventNoReplyToId); - my $eventIsOrganizerId = $res->[0][1]{created}{eventIsOrganizer}{id}; - $self->assert_not_null($eventIsOrganizerId); - my $eventIsInviteeId = $res->[0][1]{created}{eventIsInvitee}{id}; - $self->assert_not_null($eventIsInviteeId); + my $eventNoReplyToId = $res->[0][1]{created}{eventNoReplyTo}{id}; + $self->assert_not_null($eventNoReplyToId); + my $eventIsOrganizerId = $res->[0][1]{created}{eventIsOrganizer}{id}; + $self->assert_not_null($eventIsOrganizerId); + my $eventIsInviteeId = $res->[0][1]{created}{eventIsInvitee}{id}; + $self->assert_not_null($eventIsInviteeId); - $self->assert_equals(JSON::true, $res->[0][1]{created}{eventNoReplyTo}{isOrigin}); - $self->assert_equals(JSON::true, $res->[0][1]{created}{eventIsOrganizer}{isOrigin}); - $self->assert_equals(JSON::false, $res->[0][1]{created}{eventIsInvitee}{isOrigin}); + $self->assert_equals(JSON::true, + $res->[0][1]{created}{eventNoReplyTo}{isOrigin}); + $self->assert_equals(JSON::true, + $res->[0][1]{created}{eventIsOrganizer}{isOrigin}); + $self->assert_equals(JSON::false, + $res->[0][1]{created}{eventIsInvitee}{isOrigin}); - my %events = map { $_->{id} => $_ } @{$res->[1][1]{list}}; - $self->assert_equals(JSON::true, $events{$eventNoReplyToId}{isOrigin}); - $self->assert_equals(JSON::true, $events{$eventIsOrganizerId}{isOrigin}); - $self->assert_equals(JSON::false, $events{$eventIsInviteeId}{isOrigin}); + my %events = map { $_->{id} => $_ } @{ $res->[1][1]{list} }; + $self->assert_equals(JSON::true, $events{$eventNoReplyToId}{isOrigin}); + $self->assert_equals(JSON::true, $events{$eventIsOrganizerId}{isOrigin}); + $self->assert_equals(JSON::false, $events{$eventIsInviteeId}{isOrigin}); - xlog "Add scheduling to formerly unscheduled event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventNoReplyToId => { - replyTo => { - imip => 'mailto:someone@example.com', - }, - participants => { - participant1 => { - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - }, + xlog "Add scheduling to formerly unscheduled event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventNoReplyToId => { + replyTo => { + imip => 'mailto:someone@example.com', + }, + participants => { + participant1 => { + sendTo => { + imip => 'mailto:cassandane@example.com', }, + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ $eventNoReplyToId ], - properties => ['isOrigin'], - }, 'R2'], - ]); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventNoReplyToId], + properties => ['isOrigin'], + }, + 'R2' + ], + ]); - $self->assert_equals(JSON::false, $res->[0][1]{updated}{$eventNoReplyToId}{isOrigin}); - $self->assert_equals(JSON::false, $res->[1][1]{list}[0]{isOrigin}); + $self->assert_equals(JSON::false, + $res->[0][1]{updated}{$eventNoReplyToId}{isOrigin}); + $self->assert_equals(JSON::false, $res->[1][1]{list}[0]{isOrigin}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_keywords b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_keywords index a37e8e8017..95dfb0f60f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_keywords +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_keywords @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_calendarevent_get_keywords - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('keywords'); + my ($id, $ical) = $self->icalfile('keywords'); - my $event = $self->putandget_vevent($id, $ical); - my $keywords = { - 'foo' => JSON::true, - 'bar' => JSON::true, - 'baz' => JSON::true, - }; - $self->assert_deep_equals($keywords, $event->{keywords}); + my $event = $self->putandget_vevent($id, $ical); + my $keywords = { + 'foo' => JSON::true, + 'bar' => JSON::true, + 'baz' => JSON::true, + }; + $self->assert_deep_equals($keywords, $event->{keywords}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_links b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_links index 08b0380896..f7c7820589 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_links +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_links @@ -2,39 +2,38 @@ use Cassandane::Tiny; sub test_calendarevent_get_links - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('links'); - my $uri = "http://jmap.io/spec.html#calendar-events"; + my ($id, $ical) = $self->icalfile('links'); + my $uri = "http://jmap.io/spec.html#calendar-events"; - my $links = { - 'fad3249914b09ede1558fa01004f4f8149559591' => { - '@type' => 'Link', - href => "http://jmap.io/spec.html#calendar-events", - contentType => "text/html", - size => 4480, - title => "the spec", - rel => "enclosure", - cid => '123456789asd', - }, - '113fa6c507397df199a18d1371be615577f9117f' => { - '@type' => 'Link', - href => "http://example.com/some.url", - }, - 'describedby-attach' => { - '@type' => 'Link', - href => "http://describedby/attach", - rel => "describedby", - }, - 'describedby-url' => { - '@type' => 'Link', - href => "http://describedby/url", - rel => 'describedby', - } - }; + my $links = { + 'fad3249914b09ede1558fa01004f4f8149559591' => { + '@type' => 'Link', + href => "http://jmap.io/spec.html#calendar-events", + contentType => "text/html", + size => 4480, + title => "the spec", + rel => "enclosure", + cid => '123456789asd', + }, + '113fa6c507397df199a18d1371be615577f9117f' => { + '@type' => 'Link', + href => "http://example.com/some.url", + }, + 'describedby-attach' => { + '@type' => 'Link', + href => "http://describedby/attach", + rel => "describedby", + }, + 'describedby-url' => { + '@type' => 'Link', + href => "http://describedby/url", + rel => 'describedby', + } + }; - my $event = $self->putandget_vevent($id, $ical); - $self->assert_deep_equals($links, $event->{links}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_deep_equals($links, $event->{links}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_location_newline b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_location_newline index 7e04a7e094..716689dafa 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_location_newline +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_location_newline @@ -2,20 +2,18 @@ use Cassandane::Tiny; sub test_calendarevent_get_location_newline - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my ($id, $ical) = $self->icalfile('location-newline'); - my $event = $self->putandget_vevent($id, $ical); - my @locations = values(%{$event->{locations}}); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj == 3 && $min >= 6) { - $self->assert_num_equals(1, scalar @locations); - $self->assert_str_equals("xyz\nxyz", $locations[0]{name}); - } - else { - $self->assert_num_equals(2, scalar @locations); - $self->assert_str_equals("xyz\nxyz", $locations[0]{name}); - $self->assert_str_equals("xyz\nxyz", $locations[1]{name}); - } + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my ($id, $ical) = $self->icalfile('location-newline'); + my $event = $self->putandget_vevent($id, $ical); + my @locations = values(%{ $event->{locations} }); + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj == 3 && $min >= 6) { + $self->assert_num_equals(1, scalar @locations); + $self->assert_str_equals("xyz\nxyz", $locations[0]{name}); + } else { + $self->assert_num_equals(2, scalar @locations); + $self->assert_str_equals("xyz\nxyz", $locations[0]{name}); + $self->assert_str_equals("xyz\nxyz", $locations[1]{name}); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations index 4c4bac346c..9b0322069a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations @@ -2,14 +2,14 @@ use Cassandane::Tiny; sub test_calendarevent_get_locations - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('locations'); + my ($id, $ical) = $self->icalfile('locations'); - my $event = $self->putandget_vevent($id, $ical); - my @locations = values %{$event->{locations}}; - $self->assert_num_equals(1, scalar @locations); - $self->assert_str_equals("A location with a comma,\nand a newline.", $locations[0]{name}); + my $event = $self->putandget_vevent($id, $ical); + my @locations = values %{ $event->{locations} }; + $self->assert_num_equals(1, scalar @locations); + $self->assert_str_equals("A location with a comma,\nand a newline.", + $locations[0]{name}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_apple b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_apple index 0fac84c55b..ec14276ae6 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_apple +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_apple @@ -2,15 +2,15 @@ use Cassandane::Tiny; sub test_calendarevent_get_locations_apple - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('locations-apple'); + my ($id, $ical) = $self->icalfile('locations-apple'); - my $event = $self->putandget_vevent($id, $ical); - my @locations = values %{$event->{locations}}; - $self->assert_num_equals(1, scalar @locations); - $self->assert_str_equals("a place in Vienna", $locations[0]{name}); - $self->assert_str_equals("geo:48.208304,16.371602", $locations[0]{coordinates}); + my $event = $self->putandget_vevent($id, $ical); + my @locations = values %{ $event->{locations} }; + $self->assert_num_equals(1, scalar @locations); + $self->assert_str_equals("a place in Vienna", $locations[0]{name}); + $self->assert_str_equals("geo:48.208304,16.371602", + $locations[0]{coordinates}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_geo b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_geo index e5711ba31f..8f2cd910e4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_geo +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_geo @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_calendarevent_get_locations_geo - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('locations-geo'); + my ($id, $ical) = $self->icalfile('locations-geo'); - my $event = $self->putandget_vevent($id, $ical); - my @locations = values %{$event->{locations}}; - $self->assert_num_equals(1, scalar @locations); - $self->assert_matches(qr{\Ageo:37\.38601\d*,-122\.08290\d*\Z}, - $locations[0]{coordinates}); + my $event = $self->putandget_vevent($id, $ical); + my @locations = values %{ $event->{locations} }; + $self->assert_num_equals(1, scalar @locations); + $self->assert_matches(qr{\Ageo:37\.38601\d*,-122\.08290\d*\Z}, + $locations[0]{coordinates}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_uri b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_uri index 89ed622f4d..18bcd6c4ed 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_uri +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_locations_uri @@ -2,19 +2,18 @@ use Cassandane::Tiny; sub test_calendarevent_get_locations_uri - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('locations-uri'); + my ($id, $ical) = $self->icalfile('locations-uri'); - my $event = $self->putandget_vevent($id, $ical); - my @locations = values %{$event->{locations}}; - $self->assert_num_equals(1, scalar @locations); + my $event = $self->putandget_vevent($id, $ical); + my @locations = values %{ $event->{locations} }; + $self->assert_num_equals(1, scalar @locations); - $self->assert_str_equals("On planet Earth", $locations[0]->{name}); + $self->assert_str_equals("On planet Earth", $locations[0]->{name}); - my @links = values %{$locations[0]->{links}}; - $self->assert_num_equals(1, scalar @links); - $self->assert_equals("skype:foo", $links[0]->{href}); + my @links = values %{ $locations[0]->{links} }; + $self->assert_num_equals(1, scalar @links); + $self->assert_equals("skype:foo", $links[0]->{href}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ms_timezone b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ms_timezone index 26f43055eb..7ec445c078 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ms_timezone +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_ms_timezone @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_calendarevent_get_ms_timezone - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('ms_timezone'); + my ($id, $ical) = $self->icalfile('ms_timezone'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals("2016-09-28T13:00:00", $event->{start}); - $self->assert_str_equals("America/New_York", $event->{timeZone}); - $self->assert_str_equals("PT2H", $event->{duration}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals("2016-09-28T13:00:00", $event->{start}); + $self->assert_str_equals("America/New_York", $event->{timeZone}); + $self->assert_str_equals("PT2H", $event->{duration}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizer b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizer index 3a7e12ec83..758b242486 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizer +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizer @@ -2,38 +2,37 @@ use Cassandane::Tiny; sub test_calendarevent_get_organizer - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('organizer'); + my ($id, $ical) = $self->icalfile('organizer'); - my $event = $self->putandget_vevent($id, $ical); - my $wantParticipants = { - 'bf8360ce374961f497599431c4bacb50d4a67ca1' => { - '@type' => 'Participant', - name => 'Organizer', - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:organizer@local', - }, - expectReply => JSON::false, - participationStatus => 'needs-action', - }, - '29deb29d758dbb27ffa3c39b499edd85b53dd33f' => { - '@type' => 'Participant', - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee@local', - }, - expectReply => JSON::false, - participationStatus => 'needs-action', - }, - }; - $self->assert_deep_equals($wantParticipants, $event->{participants}); - $self->assert_equals('mailto:organizer@local', $event->{replyTo}{imip}); + my $event = $self->putandget_vevent($id, $ical); + my $wantParticipants = { + 'bf8360ce374961f497599431c4bacb50d4a67ca1' => { + '@type' => 'Participant', + name => 'Organizer', + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:organizer@local', + }, + expectReply => JSON::false, + participationStatus => 'needs-action', + }, + '29deb29d758dbb27ffa3c39b499edd85b53dd33f' => { + '@type' => 'Participant', + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee@local', + }, + expectReply => JSON::false, + participationStatus => 'needs-action', + }, + }; + $self->assert_deep_equals($wantParticipants, $event->{participants}); + $self->assert_equals('mailto:organizer@local', $event->{replyTo}{imip}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizer_bogusuri b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizer_bogusuri index 4cb3edda61..70d7f2acae 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizer_bogusuri +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizer_bogusuri @@ -2,44 +2,43 @@ use Cassandane::Tiny; sub test_calendarevent_get_organizer_bogusuri - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - # As seen in the wild: an ORGANIZER/ATTENDEE with a value - # that hasn't even an URI scheme. + # As seen in the wild: an ORGANIZER/ATTENDEE with a value + # that hasn't even an URI scheme. - my ($id, $ical) = $self->icalfile('organizer_bogusuri'); + my ($id, $ical) = $self->icalfile('organizer_bogusuri'); - my $event = $self->putandget_vevent($id, $ical); + my $event = $self->putandget_vevent($id, $ical); - my $wantParticipants = { - '55d3677ce6a79b250d0fc3b5eed5130807d93dd3' => { - '@type' => 'Participant', - name => 'Organizer', - roles => { - 'attendee' => JSON::true, - 'owner' => JSON::true, - }, - sendTo => { - other => '/foo-bar/principal/', - }, - expectReply => JSON::false, - participationStatus => 'needs-action', - }, - '29deb29d758dbb27ffa3c39b499edd85b53dd33f' => { - '@type' => 'Participant', - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee@local', - }, - expectReply => JSON::false, - participationStatus => 'needs-action', - }, - }; - $self->assert_deep_equals($wantParticipants, $event->{participants}); - $self->assert_null($event->{replyTo}{imip}); - $self->assert_str_equals('/foo-bar/principal/', $event->{replyTo}{other}); + my $wantParticipants = { + '55d3677ce6a79b250d0fc3b5eed5130807d93dd3' => { + '@type' => 'Participant', + name => 'Organizer', + roles => { + 'attendee' => JSON::true, + 'owner' => JSON::true, + }, + sendTo => { + other => '/foo-bar/principal/', + }, + expectReply => JSON::false, + participationStatus => 'needs-action', + }, + '29deb29d758dbb27ffa3c39b499edd85b53dd33f' => { + '@type' => 'Participant', + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee@local', + }, + expectReply => JSON::false, + participationStatus => 'needs-action', + }, + }; + $self->assert_deep_equals($wantParticipants, $event->{participants}); + $self->assert_null($event->{replyTo}{imip}); + $self->assert_str_equals('/foo-bar/principal/', $event->{replyTo}{other}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizermailto b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizermailto index d72db3d6c3..5d051bce71 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizermailto +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_organizermailto @@ -2,40 +2,39 @@ use Cassandane::Tiny; sub test_calendarevent_get_organizermailto - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('organizermailto'); + my ($id, $ical) = $self->icalfile('organizermailto'); - my $event = $self->putandget_vevent($id, $ical); + my $event = $self->putandget_vevent($id, $ical); - my $wantParticipants = { - 'bf8360ce374961f497599431c4bacb50d4a67ca1' => { - '@type' => 'Participant', - name => 'Organizer', - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:organizer@local', - }, - expectReply => JSON::false, - participationStatus => 'needs-action', - }, - '29deb29d758dbb27ffa3c39b499edd85b53dd33f' => { - '@type' => 'Participant', - name => 'Attendee', - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee@local', - }, - expectReply => JSON::false, - participationStatus => 'needs-action', - }, - }; - $self->assert_deep_equals($wantParticipants, $event->{participants}); + my $wantParticipants = { + 'bf8360ce374961f497599431c4bacb50d4a67ca1' => { + '@type' => 'Participant', + name => 'Organizer', + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:organizer@local', + }, + expectReply => JSON::false, + participationStatus => 'needs-action', + }, + '29deb29d758dbb27ffa3c39b499edd85b53dd33f' => { + '@type' => 'Participant', + name => 'Attendee', + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee@local', + }, + expectReply => JSON::false, + participationStatus => 'needs-action', + }, + }; + $self->assert_deep_equals($wantParticipants, $event->{participants}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_participants b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_participants index f554da8677..cea02c34be 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_participants +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_participants @@ -2,89 +2,88 @@ use Cassandane::Tiny; sub test_calendarevent_get_participants - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('participants'); + my ($id, $ical) = $self->icalfile('participants'); - my $event = $self->putandget_vevent($id, $ical); + my $event = $self->putandget_vevent($id, $ical); - my $wantParticipants = { - '375507f588e65ec6eb800757ab94ccd10ad58599' => { - '@type' => 'Participant', - name => 'Monty Burns', - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - participationStatus => 'accepted', - sendTo => { - imip => 'mailto:smithers@example.com', - }, - expectReply => JSON::false, - }, - '39b16b858076733c1d890cbcef73eca0e874064d' => { - '@type' => 'Participant', - name => 'Homer Simpson', - participationStatus => 'accepted', - roles => { - 'optional' => JSON::true, - }, - locationId => 'loc1', - sendTo => { - imip => 'mailto:homer@example.com', - }, - expectReply => JSON::false, - }, - 'carl' => { - '@type' => 'Participant', - name => 'Carl Carlson', - participationStatus => 'tentative', - roles => { - 'attendee' => JSON::true, - }, - scheduleSequence => 3, - scheduleUpdated => '2017-01-02T03:04:05Z', - delegatedFrom => { - 'a6ef900d284067bb327d7be1469fb44693a5ec13' => JSON::true, - }, - sendTo => { - imip => 'mailto:carl@example.com', - }, - expectReply => JSON::false, - }, - 'a6ef900d284067bb327d7be1469fb44693a5ec13' => { - '@type' => 'Participant', - name => 'Lenny Leonard', - participationStatus => 'delegated', - roles => { - 'attendee' => JSON::true, - }, - delegatedTo => { - 'carl' => JSON::true, - }, - sendTo => { - imip => 'mailto:lenny@example.com', - }, - expectReply => JSON::false, - }, - 'd6db3540fe51335b7154f144456e9eac2778fc8f' => { - '@type' => 'Participant', - name => 'Larry Burns', - participationStatus => 'declined', - roles => { - 'attendee' => JSON::true, - }, - memberOf => { - '29a545214b66cbd7635fdec3a35d074ff3484479' => JSON::true, - }, - scheduleUpdated => '2015-09-29T14:44:23Z', - sendTo => { - imip => 'mailto:larry@example.com', - }, - expectReply => JSON::false, - }, - }; - $self->assert_deep_equals($wantParticipants, $event->{participants}); + my $wantParticipants = { + '375507f588e65ec6eb800757ab94ccd10ad58599' => { + '@type' => 'Participant', + name => 'Monty Burns', + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + participationStatus => 'accepted', + sendTo => { + imip => 'mailto:smithers@example.com', + }, + expectReply => JSON::false, + }, + '39b16b858076733c1d890cbcef73eca0e874064d' => { + '@type' => 'Participant', + name => 'Homer Simpson', + participationStatus => 'accepted', + roles => { + 'optional' => JSON::true, + }, + locationId => 'loc1', + sendTo => { + imip => 'mailto:homer@example.com', + }, + expectReply => JSON::false, + }, + 'carl' => { + '@type' => 'Participant', + name => 'Carl Carlson', + participationStatus => 'tentative', + roles => { + 'attendee' => JSON::true, + }, + scheduleSequence => 3, + scheduleUpdated => '2017-01-02T03:04:05Z', + delegatedFrom => { + 'a6ef900d284067bb327d7be1469fb44693a5ec13' => JSON::true, + }, + sendTo => { + imip => 'mailto:carl@example.com', + }, + expectReply => JSON::false, + }, + 'a6ef900d284067bb327d7be1469fb44693a5ec13' => { + '@type' => 'Participant', + name => 'Lenny Leonard', + participationStatus => 'delegated', + roles => { + 'attendee' => JSON::true, + }, + delegatedTo => { + 'carl' => JSON::true, + }, + sendTo => { + imip => 'mailto:lenny@example.com', + }, + expectReply => JSON::false, + }, + 'd6db3540fe51335b7154f144456e9eac2778fc8f' => { + '@type' => 'Participant', + name => 'Larry Burns', + participationStatus => 'declined', + roles => { + 'attendee' => JSON::true, + }, + memberOf => { + '29a545214b66cbd7635fdec3a35d074ff3484479' => JSON::true, + }, + scheduleUpdated => '2015-09-29T14:44:23Z', + sendTo => { + imip => 'mailto:larry@example.com', + }, + expectReply => JSON::false, + }, + }; + $self->assert_deep_equals($wantParticipants, $event->{participants}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy index 181338c988..80837e730f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_get_privacy - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('privacy'); + my ($id, $ical) = $self->icalfile('privacy'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals("private", $event->{privacy}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals("private", $event->{privacy}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy_ignore_override b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy_ignore_override index 75162b430c..ca8d2d269a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy_ignore_override +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy_ignore_override @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_get_privacy_ignore_override - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "PUT event where privacy differs in override"; - my $ical = <<'EOF'; + xlog "PUT event where privacy differs in override"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -37,17 +36,22 @@ X-JMAP-PRIVACY:PUBLIC END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - xlog "Assert privacy of recurrence exception gets ignored"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['privacy', 'recurrenceOverrides'] - }, 'R1'], - ]); - $self->assert_str_equals('private', $res->[0][1]{list}[0]{privacy}); - $self->assert_deep_equals({ title => 'An event exception'}, - $res->[0][1]{list}[0]{recurrenceOverrides}{'2021-01-02T15:30:00'}); + xlog "Assert privacy of recurrence exception gets ignored"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'privacy', 'recurrenceOverrides' ] + }, + 'R1' + ], + ]); + $self->assert_str_equals('private', $res->[0][1]{list}[0]{privacy}); + $self->assert_deep_equals({ title => 'An event exception' }, + $res->[0][1]{list}[0]{recurrenceOverrides}{'2021-01-02T15:30:00'}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy_shared b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy_shared index cceb780673..32b4c4c393 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_privacy_shared @@ -2,209 +2,241 @@ use Cassandane::Tiny; sub test_calendarevent_get_privacy_shared - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "share calendar"; - my ($shareeJmap) = $self->create_user('sharee'); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - sharee => { - mayReadItems => JSON::true, - mayWriteAll => JSON::true, - }, - }, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - xlog "create fullblown event for each privacy setting"; - my $eventTemplate = { - calendarIds => { - 'Default' => JSON::true, - }, - color => 'blue', - created => '2020-12-21T07:47:00Z', - description => 'description', - duration => 'PT1H', - excludedRecurrenceRules => [{ - frequency => 'daily', - interval => 2, - count => 1, - }], - keywords => { - keyword1 => JSON::true, - }, - links => { - link1 => { - href => 'https://local/link1.jpg', - }, - }, - locale => 'en', - locations => { - loc1 => { - name => 'name', - }, - }, - participants => { - participant1 => { - sendTo => { - imip => 'mailto:participant1@local', - }, - roles => { - attendee => JSON::true, - }, + xlog "share calendar"; + my ($shareeJmap) = $self->create_user('sharee'); + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + sharee => { + mayReadItems => JSON::true, + mayWriteAll => JSON::true, + }, }, + }, }, - priority => 7, - prodId => '-//Foo//Bar//EN', - recurrenceOverrides => { - '2021-01-02T01:00:00' => { - title => 'overrideTitle', - duration => 'PT2H', - }, - }, - recurrenceRules => [{ - frequency => 'daily', - count => 3, - }], - relatedTo => { - '3a996522-dfc3-484c-bea9-070c408143ea' => { }, - }, - replyTo => { - imip => 'mailto:orga@local', + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + + xlog "create fullblown event for each privacy setting"; + my $eventTemplate = { + calendarIds => { + 'Default' => JSON::true, + }, + color => 'blue', + created => '2020-12-21T07:47:00Z', + description => 'description', + duration => 'PT1H', + excludedRecurrenceRules => [ { + frequency => 'daily', + interval => 2, + count => 1, + } ], + keywords => { + keyword1 => JSON::true, + }, + links => { + link1 => { + href => 'https://local/link1.jpg', + }, + }, + locale => 'en', + locations => { + loc1 => { + name => 'name', + }, + }, + participants => { + participant1 => { + sendTo => { + imip => 'mailto:participant1@local', }, - sequence => 3, - showWithoutTime => JSON::true, - start => '2021-01-01T01:00:00', - status => 'tentative', - timeZone => 'Europe/Berlin', - title => 'title', - updated => '2020-12-21T07:47:00Z', - virtualLocations => { - virtloc1 => { - name => 'name', - uri => 'tel:+1-555-555-5555', - }, + roles => { + attendee => JSON::true, }, - }; + }, + }, + priority => 7, + prodId => '-//Foo//Bar//EN', + recurrenceOverrides => { + '2021-01-02T01:00:00' => { + title => 'overrideTitle', + duration => 'PT2H', + }, + }, + recurrenceRules => [ { + frequency => 'daily', + count => 3, + } ], + relatedTo => { + '3a996522-dfc3-484c-bea9-070c408143ea' => {}, + }, + replyTo => { + imip => 'mailto:orga@local', + }, + sequence => 3, + showWithoutTime => JSON::true, + start => '2021-01-01T01:00:00', + status => 'tentative', + timeZone => 'Europe/Berlin', + title => 'title', + updated => '2020-12-21T07:47:00Z', + virtualLocations => { + virtloc1 => { + name => 'name', + uri => 'tel:+1-555-555-5555', + }, + }, + }; - my @wantProperties = keys %{$eventTemplate}; - push @wantProperties, 'calendarIds', 'isDraft', 'utcStart', 'utcEnd'; + my @wantProperties = keys %{$eventTemplate}; + push @wantProperties, 'calendarIds', 'isDraft', 'utcStart', 'utcEnd'; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - publicEvent => { ( privacy => 'public' ), %$eventTemplate }, - privateEvent => { ( privacy => 'private' ), %$eventTemplate }, - secretEvent => { ( privacy => 'secret' ), %$eventTemplate } - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#publicEvent'], - properties => \@wantProperties, - }, 'R2'], - ['CalendarEvent/get', { - ids => ['#privateEvent'], - properties => \@wantProperties, - }, 'R3'], - ['CalendarEvent/get', { - ids => ['#secretEvent'], - properties => \@wantProperties, - }, 'R4'], - ]); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + publicEvent => { (privacy => 'public'), %$eventTemplate }, + privateEvent => { (privacy => 'private'), %$eventTemplate }, + secretEvent => { (privacy => 'secret'), %$eventTemplate } + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#publicEvent'], + properties => \@wantProperties, + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => ['#privateEvent'], + properties => \@wantProperties, + }, + 'R3' + ], + [ + 'CalendarEvent/get', + { + ids => ['#secretEvent'], + properties => \@wantProperties, + }, + 'R4' + ], + ]); - my $publicEvent = $res->[1][1]{list}[0]; - $self->assert_not_null($publicEvent); + my $publicEvent = $res->[1][1]{list}[0]; + $self->assert_not_null($publicEvent); - my $privateEvent = $res->[2][1]{list}[0]; - $self->assert_not_null($privateEvent); + my $privateEvent = $res->[2][1]{list}[0]; + $self->assert_not_null($privateEvent); - my $secretEvent = $res->[3][1]{list}[0]; - $self->assert_not_null($secretEvent); + my $secretEvent = $res->[3][1]{list}[0]; + $self->assert_not_null($secretEvent); - xlog "calendar owner may see all events and properties"; - foreach my $event ($publicEvent, $privateEvent, $secretEvent) { - foreach my $prop (keys %{$eventTemplate}) { - $self->assert_not_null($event->{$prop}); - } + xlog "calendar owner may see all events and properties"; + foreach my $event ($publicEvent, $privateEvent, $secretEvent) { + foreach my $prop (keys %{$eventTemplate}) { + $self->assert_not_null($event->{$prop}); } + } + + xlog "sharee may see all properties of public event"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [ $publicEvent->{id} ], + properties => \@wantProperties, + }, + 'R1' + ], + ]); + my $sharedPublicEvent = $res->[0][1]{list}[0]; + delete($publicEvent->{'x-href'}); + delete($sharedPublicEvent->{'x-href'}); + delete($publicEvent->{'blobId'}); + delete($sharedPublicEvent->{'blobId'}); + delete($publicEvent->{'debugBlobId'}); + delete($sharedPublicEvent->{'debugBlobId'}); + $self->assert_deep_equals($publicEvent, $sharedPublicEvent); - xlog "sharee may see all properties of public event"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$publicEvent->{id}], - properties => \@wantProperties, - }, 'R1'], - ]); - my $sharedPublicEvent = $res->[0][1]{list}[0]; - delete($publicEvent->{'x-href'}); - delete($sharedPublicEvent->{'x-href'}); - delete($publicEvent->{'blobId'}); - delete($sharedPublicEvent->{'blobId'}); - delete($publicEvent->{'debugBlobId'}); - delete($sharedPublicEvent->{'debugBlobId'}); - $self->assert_deep_equals($publicEvent, $sharedPublicEvent); + xlog "sharee may only see public properties of private event"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [ $privateEvent->{id} ], + properties => \@wantProperties, + }, + 'R1' + ], + ]); + my $sharedPrivateEvent = $res->[0][1]{list}[0]; + my %publicProps = ( + '@type' => 1, + calendarIds => 1, + created => 1, + due => 1, + duration => 1, + estimatedDuration => 1, + excludedRecurrenceRules => 1, + freeBusyStatus => 1, + id => 1, + isDraft => 1, + privacy => 1, + recurrenceRules => 1, + recurrenceOverrides => 1, + sequence => 1, + showWithoutTime => 1, + start => 1, + timeZone => 1, + timeZones => 1, + uid => 1, + updated => 1, + utcStart => 1, + utcEnd => 1, + ); + my @nonPublic; - xlog "sharee may only see public properties of private event"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$privateEvent->{id}], - properties => \@wantProperties, - }, 'R1'], - ]); - my $sharedPrivateEvent = $res->[0][1]{list}[0]; - my %publicProps = ( - '@type' => 1, - calendarIds => 1, - created => 1, - due => 1, - duration => 1, - estimatedDuration => 1, - excludedRecurrenceRules => 1, - freeBusyStatus => 1, - id => 1, - isDraft => 1, - privacy => 1, - recurrenceRules => 1, - recurrenceOverrides => 1, - sequence => 1, - showWithoutTime => 1, - start => 1, - timeZone => 1, - timeZones => 1, - uid => 1, - updated => 1, - utcStart => 1, - utcEnd => 1, - ); - my @nonPublic; - foreach my $prop (keys %{$privateEvent}) { - if (not $publicProps{$prop}) { - push @nonPublic, $prop; - } + foreach my $prop (keys %{$privateEvent}) { + if (not $publicProps{$prop}) { + push @nonPublic, $prop; } - delete @{$privateEvent}{@nonPublic}; - delete $privateEvent->{recurrenceOverrides}{'2021-01-02T01:00:00'}{title}; - $self->assert_deep_equals($privateEvent, $sharedPrivateEvent); + } + delete @{$privateEvent}{@nonPublic}; + delete $privateEvent->{recurrenceOverrides}{'2021-01-02T01:00:00'}{title}; + $self->assert_deep_equals($privateEvent, $sharedPrivateEvent); - xlog "sharee must not see secret event"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$secretEvent->{id}], - properties => \@wantProperties, - }, 'R1'], - ]); - $self->assert_deep_equals([$secretEvent->{id}], $res->[0][1]{notFound}); + xlog "sharee must not see secret event"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [ $secretEvent->{id} ], + properties => \@wantProperties, + }, + 'R1' + ], + ]); + $self->assert_deep_equals([ $secretEvent->{id} ], $res->[0][1]{notFound}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_properties b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_properties index dd5992db33..8f6c033687 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_properties +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_properties @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_calendarevent_get_properties - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('simple'); + my ($id, $ical) = $self->icalfile('simple'); - my $event = $self->putandget_vevent($id, $ical, ["x-href", "calendarIds"]); - $self->assert_not_null($event); - $self->assert_not_null($event->{id}); - $self->assert_not_null($event->{uid}); - $self->assert_not_null($event->{"x-href"}); - $self->assert_not_null($event->{calendarIds}); - $self->assert_num_equals(5, scalar keys %$event); + my $event = $self->putandget_vevent($id, $ical, [ "x-href", "calendarIds" ]); + $self->assert_not_null($event); + $self->assert_not_null($event->{id}); + $self->assert_not_null($event->{uid}); + $self->assert_not_null($event->{"x-href"}); + $self->assert_not_null($event->{calendarIds}); + $self->assert_num_equals(5, scalar keys %$event); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_rdate_period b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_rdate_period index d2ae26b10c..9235b5548f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_rdate_period +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_rdate_period @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_calendarevent_get_rdate_period - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('rdate_period'); + my ($id, $ical) = $self->icalfile('rdate_period'); - my $event = $self->putandget_vevent($id, $ical); - my $o; + my $event = $self->putandget_vevent($id, $ical); + my $o; - $o = $event->{recurrenceOverrides}->{"2016-03-04T15:00:00"}; - $self->assert_not_null($o); - $self->assert_str_equals("PT1H", $o->{duration}); + $o = $event->{recurrenceOverrides}->{"2016-03-04T15:00:00"}; + $self->assert_not_null($o); + $self->assert_str_equals("PT1H", $o->{duration}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrence b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrence index c727f08a14..9d729c4e76 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrence +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrence @@ -2,41 +2,51 @@ use Cassandane::Tiny; sub test_calendarevent_get_recurrence - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('recurrence'); + my ($id, $ical) = $self->icalfile('recurrence'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event->{recurrenceRules}[0]); - $self->assert_str_equals("RecurrenceRule", $event->{recurrenceRules}[0]{q{@type}}); - $self->assert_str_equals("monthly", $event->{recurrenceRules}[0]{frequency}); - $self->assert_str_equals("gregorian", $event->{recurrenceRules}[0]{rscale}); - # This assertion is a bit brittle. It depends on the libical-internal - # sort order for BYDAY - $self->assert_deep_equals([{ - '@type' => 'NDay', - "day" => "mo", - "nthOfPeriod" => 2, - }, { - '@type' => 'NDay', - "day" => "mo", - "nthOfPeriod" => 1, - }, { - '@type' => 'NDay', - "day" => "tu", - }, { - '@type' => 'NDay', - "day" => "th", - "nthOfPeriod" => -2, - }, { - '@type' => 'NDay', - "day" => "sa", - "nthOfPeriod" => -1, - }, { - '@type' => 'NDay', - "day" => "su", - "nthOfPeriod" => -3, - }], $event->{recurrenceRules}[0]{byDay}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event->{recurrenceRules}[0]); + $self->assert_str_equals("RecurrenceRule", + $event->{recurrenceRules}[0]{q{@type}}); + $self->assert_str_equals("monthly", $event->{recurrenceRules}[0]{frequency}); + $self->assert_str_equals("gregorian", $event->{recurrenceRules}[0]{rscale}); + # This assertion is a bit brittle. It depends on the libical-internal + # sort order for BYDAY + $self->assert_deep_equals( + [ + { + '@type' => 'NDay', + "day" => "mo", + "nthOfPeriod" => 2, + }, + { + '@type' => 'NDay', + "day" => "mo", + "nthOfPeriod" => 1, + }, + { + '@type' => 'NDay', + "day" => "tu", + }, + { + '@type' => 'NDay', + "day" => "th", + "nthOfPeriod" => -2, + }, + { + '@type' => 'NDay', + "day" => "sa", + "nthOfPeriod" => -1, + }, + { + '@type' => 'NDay', + "day" => "su", + "nthOfPeriod" => -3, + } + ], + $event->{recurrenceRules}[0]{byDay} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceid index fed07a280a..fb70afb09c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceid @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_get_recurrenceid - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <Request('PUT', 'Default/2a358cee-6489-4f14-a57f-c104db4dc357.ics', $ical, - 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', 'Default/2a358cee-6489-4f14-a57f-c104db4dc357.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => [ - 'recurrenceId', - 'recurrenceIdTimeZone', - 'start', - 'timeZone', - ], - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - my $event = $res->[1][1]{list}[0]; + my $res = $jmap->CallMethods([ + [ 'CalendarEvent/query', {}, 'R1' ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => + [ 'recurrenceId', 'recurrenceIdTimeZone', 'start', 'timeZone', ], + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + my $event = $res->[1][1]{list}[0]; - $self->assert_str_equals('2016-09-28T16:00:00', $event->{start}); - $self->assert_str_equals('Europe/Berlin', $event->{timeZone}); - $self->assert_str_equals('2016-09-28T01:00:00', $event->{recurrenceId}); - $self->assert_str_equals('Europe/London', $event->{recurrenceIdTimeZone}); + $self->assert_str_equals('2016-09-28T16:00:00', $event->{start}); + $self->assert_str_equals('Europe/Berlin', $event->{timeZone}); + $self->assert_str_equals('2016-09-28T01:00:00', $event->{recurrenceId}); + $self->assert_str_equals('Europe/London', $event->{recurrenceIdTimeZone}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceid_date_start_datetime b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceid_date_start_datetime index b24a3f529a..5e74ca5e2b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceid_date_start_datetime +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceid_date_start_datetime @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_get_recurrenceid_date_start_datetime - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <putandget_vevent('2a358cee-6489-4f14-a57f-c104db4dc357', - $ical, ['recurrenceOverrides']); + my $event = $self->putandget_vevent('2a358cee-6489-4f14-a57f-c104db4dc357', + $ical, ['recurrenceOverrides']); - $self->assert_deep_equals({ - '2016-09-02T16:15:14' => { - title => 'testWithDateTimeRecurId', - }, - '2016-09-03T16:15:14' => { - title => 'testWithDateRecurId', - }, - }, $event->{recurrenceOverrides}); + $self->assert_deep_equals( + { + '2016-09-02T16:15:14' => { + title => 'testWithDateTimeRecurId', + }, + '2016-09-03T16:15:14' => { + title => 'testWithDateRecurId', + }, + }, + $event->{recurrenceOverrides} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceinstances b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceinstances index 53a0d47d61..a5855681a8 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceinstances +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceinstances @@ -2,86 +2,98 @@ use Cassandane::Tiny; sub test_calendarevent_get_recurrenceinstances - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - xlog $self, "create event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - $calid => JSON::true, - }, - uid => 'event1uid', - title => "event1", - description => "", - freeBusyStatus => "busy", - start => "2019-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'weekly', - count => 5, - }, { - frequency => 'daily', - count => 2, - }], - recurrenceOverrides => { - '2019-01-15T09:00:00' => { - title => 'override1', - }, - '2019-01-10T12:00:00' => { - # rdate - }, - '2019-01-22T09:00:00' => { - excluded => JSON::true, - }, - }, - }, + xlog $self, "create event"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calid => JSON::true, + }, + uid => 'event1uid', + title => "event1", + description => "", + freeBusyStatus => "busy", + start => "2019-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ + { + frequency => 'weekly', + count => 5, + }, + { + frequency => 'daily', + count => 2, } - }, 'R1'] - ]); + ], + recurrenceOverrides => { + '2019-01-15T09:00:00' => { + title => 'override1', + }, + '2019-01-10T12:00:00' => { + # rdate + }, + '2019-01-22T09:00:00' => { + excluded => JSON::true, + }, + }, + }, + } + }, + 'R1' + ] ]); - my @ids = ( - encode_eventid('event1uid','20190108T090000'), - encode_eventid('event1uid','20190115T090000'), - encode_eventid('event1uid','20190110T120000'), - encode_eventid('event1uid','20190122T090000'), # is excluded - encode_eventid('event1uid','20191201T090000'), # does not exist - encode_eventid('event1uid','20190102T090000'), # from second rrule - ); - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => \@ids, - properties => ['start', 'title', 'recurrenceId'], - }, 'R1'], - ]); - $self->assert_num_equals(4, scalar @{$res->[0][1]{list}}); + my @ids = ( + encode_eventid('event1uid', '20190108T090000'), + encode_eventid('event1uid', '20190115T090000'), + encode_eventid('event1uid', '20190110T120000'), + encode_eventid('event1uid', '20190122T090000'), # is excluded + encode_eventid('event1uid', '20191201T090000'), # does not exist + encode_eventid('event1uid', '20190102T090000'), # from second rrule + ); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => \@ids, + properties => [ 'start', 'title', 'recurrenceId' ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{list} }); - $self->assert_str_equals($ids[0], $res->[0][1]{list}[0]{id}); - $self->assert_str_equals('2019-01-08T09:00:00', $res->[0][1]{list}[0]{start}); - $self->assert_str_equals('2019-01-08T09:00:00', $res->[0][1]{list}[0]{recurrenceId}); + $self->assert_str_equals($ids[0], $res->[0][1]{list}[0]{id}); + $self->assert_str_equals('2019-01-08T09:00:00', $res->[0][1]{list}[0]{start}); + $self->assert_str_equals('2019-01-08T09:00:00', + $res->[0][1]{list}[0]{recurrenceId}); - $self->assert_str_equals($ids[1], $res->[0][1]{list}[1]{id}); - $self->assert_str_equals('override1', $res->[0][1]{list}[1]{title}); - $self->assert_str_equals('2019-01-15T09:00:00', $res->[0][1]{list}[1]{start}); - $self->assert_str_equals('2019-01-15T09:00:00', $res->[0][1]{list}[1]{recurrenceId}); + $self->assert_str_equals($ids[1], $res->[0][1]{list}[1]{id}); + $self->assert_str_equals('override1', $res->[0][1]{list}[1]{title}); + $self->assert_str_equals('2019-01-15T09:00:00', $res->[0][1]{list}[1]{start}); + $self->assert_str_equals('2019-01-15T09:00:00', + $res->[0][1]{list}[1]{recurrenceId}); - $self->assert_str_equals($ids[2], $res->[0][1]{list}[2]{id}); - $self->assert_str_equals('2019-01-10T12:00:00', $res->[0][1]{list}[2]{start}); - $self->assert_str_equals('2019-01-10T12:00:00', $res->[0][1]{list}[2]{recurrenceId}); + $self->assert_str_equals($ids[2], $res->[0][1]{list}[2]{id}); + $self->assert_str_equals('2019-01-10T12:00:00', $res->[0][1]{list}[2]{start}); + $self->assert_str_equals('2019-01-10T12:00:00', + $res->[0][1]{list}[2]{recurrenceId}); - $self->assert_str_equals($ids[5], $res->[0][1]{list}[3]{id}); - $self->assert_str_equals('2019-01-02T09:00:00', $res->[0][1]{list}[3]{start}); - $self->assert_str_equals('2019-01-02T09:00:00', $res->[0][1]{list}[3]{recurrenceId}); + $self->assert_str_equals($ids[5], $res->[0][1]{list}[3]{id}); + $self->assert_str_equals('2019-01-02T09:00:00', $res->[0][1]{list}[3]{start}); + $self->assert_str_equals('2019-01-02T09:00:00', + $res->[0][1]{list}[3]{recurrenceId}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{notFound}}); - $self->assert_str_equals($ids[3], $res->[0][1]{notFound}[0]); - $self->assert_str_equals($ids[4], $res->[0][1]{notFound}[1]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{notFound} }); + $self->assert_str_equals($ids[3], $res->[0][1]{notFound}[0]); + $self->assert_str_equals($ids[4], $res->[0][1]{notFound}[1]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceoverrides b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceoverrides index 18d4fc4549..cd68cb9930 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceoverrides +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceoverrides @@ -2,31 +2,31 @@ use Cassandane::Tiny; sub test_calendarevent_get_recurrenceoverrides - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('recurrenceoverrides'); - my $aid = $id . "-alarmuid"; + my ($id, $ical) = $self->icalfile('recurrenceoverrides'); + my $aid = $id . "-alarmuid"; - my $event = $self->putandget_vevent($id, $ical); - my $o; + my $event = $self->putandget_vevent($id, $ical); + my $o; - $o = $event->{recurrenceOverrides}->{"2016-12-24T20:00:00"}; - $self->assert_not_null($o); + $o = $event->{recurrenceOverrides}->{"2016-12-24T20:00:00"}; + $self->assert_not_null($o); - $self->assert(exists $event->{recurrenceOverrides}->{"2016-02-01T13:00:00"}); - $self->assert_equals(JSON::true, $event->{recurrenceOverrides}->{"2016-02-01T13:00:00"}{excluded}); + $self->assert(exists $event->{recurrenceOverrides}->{"2016-02-01T13:00:00"}); + $self->assert_equals(JSON::true, + $event->{recurrenceOverrides}->{"2016-02-01T13:00:00"}{excluded}); - $o = $event->{recurrenceOverrides}->{"2016-05-01T13:00:00"}; - $self->assert_not_null($o); - $self->assert_str_equals("foobarbazbla", $o->{"title"}); - $self->assert_str_equals("2016-05-01T17:00:00", $o->{"start"}); - $self->assert_str_equals("PT2H", $o->{"duration"}); - $self->assert_not_null($o->{alerts}{$aid}); + $o = $event->{recurrenceOverrides}->{"2016-05-01T13:00:00"}; + $self->assert_not_null($o); + $self->assert_str_equals("foobarbazbla", $o->{"title"}); + $self->assert_str_equals("2016-05-01T17:00:00", $o->{"start"}); + $self->assert_str_equals("PT2H", $o->{"duration"}); + $self->assert_not_null($o->{alerts}{$aid}); - $o = $event->{recurrenceOverrides}->{"2016-09-01T13:00:00"}; - $self->assert_not_null($o); - $self->assert_str_equals("foobarbazblabam", $o->{"title"}); - $self->assert(not exists $o->{"start"}); + $o = $event->{recurrenceOverrides}->{"2016-09-01T13:00:00"}; + $self->assert_not_null($o); + $self->assert_str_equals("foobarbazblabam", $o->{"title"}); + $self->assert(not exists $o->{"start"}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceoverrides_before_after b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceoverrides_before_after index 5f5289c681..a2c433c967 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceoverrides_before_after +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_recurrenceoverrides_before_after @@ -2,60 +2,70 @@ use Cassandane::Tiny; sub test_calendarevent_get_recurrenceoverrides_before_after - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "create events"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uidlocal', - title => "event1", - start => "2020-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'daily', - }], - recurrenceOverrides => { - '2020-01-02T09:00:00' => { - title => 'override1', - }, - '2020-01-03T09:00:00' => { - title => 'override2', - }, - '2020-01-04T09:00:00' => { - title => 'override3', - }, - '2020-01-05T09:00:00' => { - title => 'override4', - }, - }, - }, - } - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#1'], - properties => ['recurrenceOverrides'], - recurrenceOverridesAfter => '2020-01-03T08:00:00Z', - recurrenceOverridesBefore => '2020-01-05T08:00:00Z', - }, 'R2'], - ]); + xlog $self, "create events"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uidlocal', + title => "event1", + start => "2020-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'daily', + } ], + recurrenceOverrides => { + '2020-01-02T09:00:00' => { + title => 'override1', + }, + '2020-01-03T09:00:00' => { + title => 'override2', + }, + '2020-01-04T09:00:00' => { + title => 'override3', + }, + '2020-01-05T09:00:00' => { + title => 'override4', + }, + }, + }, + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#1'], + properties => ['recurrenceOverrides'], + recurrenceOverridesAfter => '2020-01-03T08:00:00Z', + recurrenceOverridesBefore => '2020-01-05T08:00:00Z', + }, + 'R2' + ], + ]); - $self->assert_deep_equals({ - '2020-01-03T09:00:00' => { - title => 'override2', - }, - '2020-01-04T09:00:00' => { - title => 'override3', - }, - }, $res->[1][1]{list}[0]{recurrenceOverrides}); + $self->assert_deep_equals( + { + '2020-01-03T09:00:00' => { + title => 'override2', + }, + '2020-01-04T09:00:00' => { + title => 'override3', + }, + }, + $res->[1][1]{list}[0]{recurrenceOverrides} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_reducepartitipants b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_reducepartitipants index d0e62506fc..0b4c562003 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_reducepartitipants +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_reducepartitipants @@ -2,109 +2,125 @@ use Cassandane::Tiny; sub test_calendarevent_get_reducepartitipants - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uidlocal', - title => "event1", - start => "2020-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - replyTo => { - imip => 'mailto:owner@example.com', - }, - participants => { - owner => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:owner@example.com', - }, - }, - attendee1 => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee1@example.com', - }, - }, - attendee2 => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee2@example.com', - }, - }, - cassandane => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uidlocal', + title => "event1", + start => "2020-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + replyTo => { + imip => 'mailto:owner@example.com', + }, + participants => { + owner => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:owner@example.com', + }, + }, + attendee1 => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee1@example.com', + }, + }, + attendee2 => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee2@example.com', + }, + }, + cassandane => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', }, + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event1'], - reduceParticipants => JSON::true, - properties => ['participants'], - }, 'R2'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event1'], + reduceParticipants => JSON::true, + properties => ['participants'], + }, + 'R2' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); - my $wantUris = { - 'mailto:owner@example.com' => 1, - 'mailto:cassandane@example.com' => 1, - }; - my %haveUris = map { $_->{sendTo}{imip} => 1 } - values %{$res->[1][1]{list}[0]{participants}}; - $self->assert_deep_equals($wantUris, \%haveUris); + my $wantUris = { + 'mailto:owner@example.com' => 1, + 'mailto:cassandane@example.com' => 1, + }; + my %haveUris = map { $_->{sendTo}{imip} => 1 } + values %{ $res->[1][1]{list}[0]{participants} }; + $self->assert_deep_equals($wantUris, \%haveUris); - $caldav->Request( - 'PROPPATCH', - '', - x('D:propertyupdate', $caldav->NS(), - x('D:set', - x('D:prop', - x('C:calendar-user-address-set', - x('D:href', 'attendee1@example.com'), - ) + $caldav->Request( + 'PROPPATCH', + '', + x( + 'D:propertyupdate', + $caldav->NS(), + x( + 'D:set', + x( + 'D:prop', + x( + 'C:calendar-user-address-set', + x('D:href', 'attendee1@example.com'), ) ) ) - ); + ) + ); - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$eventId], - reduceParticipants => JSON::true, - properties => ['participants'], - }, 'R1'], - ]); - $wantUris = { - 'mailto:owner@example.com' => 1, - 'mailto:attendee1@example.com' => 1, - }; - %haveUris = map { $_->{sendTo}{imip} => 1 } - values %{$res->[0][1]{list}[0]{participants}}; - $self->assert_deep_equals($wantUris, \%haveUris); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [$eventId], + reduceParticipants => JSON::true, + properties => ['participants'], + }, + 'R1' + ], + ]); + $wantUris = { + 'mailto:owner@example.com' => 1, + 'mailto:attendee1@example.com' => 1, + }; + %haveUris = map { $_->{sendTo}{imip} => 1 } + values %{ $res->[0][1]{list}[0]{participants} }; + $self->assert_deep_equals($wantUris, \%haveUris); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_relatedto b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_relatedto index aabf47a1d9..0d71807bd2 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_relatedto +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_relatedto @@ -2,38 +2,40 @@ use Cassandane::Tiny; sub test_calendarevent_get_relatedto - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('relatedto'); + my ($id, $ical) = $self->icalfile('relatedto'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals($id, $event->{uid}); - $self->assert_deep_equals({ - "58ADE31-001" => { - '@type' => 'Relation', - relation => { - 'first' => JSON::true, - } - }, - "58ADE31-003" => { - '@type' => 'Relation', - relation => { - 'next' => JSON::true, - } - }, - "foo" => { - '@type' => 'Relation', - relation => { - 'x-unknown1' => JSON::true, - 'x-unknown2' => JSON::true, - } - }, - "bar" => { - '@type' => 'Relation', - relation => {} - }, - }, $event->{relatedTo}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals($id, $event->{uid}); + $self->assert_deep_equals( + { + "58ADE31-001" => { + '@type' => 'Relation', + relation => { + 'first' => JSON::true, + } + }, + "58ADE31-003" => { + '@type' => 'Relation', + relation => { + 'next' => JSON::true, + } + }, + "foo" => { + '@type' => 'Relation', + relation => { + 'x-unknown1' => JSON::true, + 'x-unknown2' => JSON::true, + } + }, + "bar" => { + '@type' => 'Relation', + relation => {} + }, + }, + $event->{relatedTo} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_reset_iter b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_reset_iter index 02504c99c1..968aaac13e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_reset_iter +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_reset_iter @@ -2,62 +2,75 @@ use Cassandane::Tiny; sub test_calendarevent_get_reset_iter - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Create events in calendar A and B, both have IMAP uid 1"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendarA => { - name => 'A', - }, - calendarB => { - name => 'B', - }, + xlog "Create events in calendar A and B, both have IMAP uid 1"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + calendarA => { + name => 'A', + }, + calendarB => { + name => 'B', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + eventA => { + calendarIds => { + '#calendarA' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/set', { - create => { - eventA => { - calendarIds => { - '#calendarA' => JSON::true, - }, - '@type' => 'Event', - uid => 'eventA-uid', - title => 'eventA', - start => '2021-01-01T15:30:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - }, - eventB => { - calendarIds => { - '#calendarB' => JSON::true, - }, - '@type' => 'Event', - uid => 'eventB-uid', - title => 'eventB', - start => '2022-01-01T15:30:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - }, + '@type' => 'Event', + uid => 'eventA-uid', + title => 'eventA', + start => '2021-01-01T15:30:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + eventB => { + calendarIds => { + '#calendarB' => JSON::true, }, - }, 'R2'], - ['CalendarEvent/get', { - properties => ['calendarIds', 'uid', 'title', 'start'], - }, 'R2'], - ]); + '@type' => 'Event', + uid => 'eventB-uid', + title => 'eventB', + start => '2022-01-01T15:30:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + }, + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + properties => [ 'calendarIds', 'uid', 'title', 'start' ], + }, + 'R2' + ], + ]); - xlog "Assert CalendarEvent/get iterator state is reset properly"; - $self->assert_num_equals(2, scalar @{$res->[2][1]{list}}); - $self->assert_str_not_equals((keys %{$res->[2][1]{list}[0]{calendarIds}})[0], - (keys %{$res->[2][1]{list}[1]{calendarIds}})[0]); - $self->assert_str_not_equals($res->[2][1]{list}[0]{uid}, - $res->[2][1]{list}[1]{uid}); - $self->assert_str_not_equals($res->[2][1]{list}[0]{title}, - $res->[2][1]{list}[1]{title}); - $self->assert_str_not_equals($res->[2][1]{list}[0]{start}, - $res->[2][1]{list}[1]{start}); + xlog "Assert CalendarEvent/get iterator state is reset properly"; + $self->assert_num_equals(2, scalar @{ $res->[2][1]{list} }); + $self->assert_str_not_equals( + (keys %{ $res->[2][1]{list}[0]{calendarIds} })[0], + (keys %{ $res->[2][1]{list}[1]{calendarIds} })[0] + ); + $self->assert_str_not_equals($res->[2][1]{list}[0]{uid}, + $res->[2][1]{list}[1]{uid}); + $self->assert_str_not_equals($res->[2][1]{list}[0]{title}, + $res->[2][1]{list}[1]{title}); + $self->assert_str_not_equals($res->[2][1]{list}[0]{start}, + $res->[2][1]{list}[1]{start}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_rscale b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_rscale index 949e699080..b1284b66de 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_rscale +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_rscale @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_calendarevent_get_rscale - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('rscale'); + my ($id, $ical) = $self->icalfile('rscale'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_str_equals("Some day in Adar I", $event->{title}); - $self->assert_str_equals("yearly", $event->{recurrenceRules}[0]{frequency}); - $self->assert_str_equals("hebrew", $event->{recurrenceRules}[0]{rscale}); - $self->assert_str_equals("forward", $event->{recurrenceRules}[0]{skip}); - $self->assert_num_equals(8, $event->{recurrenceRules}[0]{byMonthDay}[0]); - $self->assert_str_equals("5L", $event->{recurrenceRules}[0]{byMonth}[0]); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_str_equals("Some day in Adar I", $event->{title}); + $self->assert_str_equals("yearly", $event->{recurrenceRules}[0]{frequency}); + $self->assert_str_equals("hebrew", $event->{recurrenceRules}[0]{rscale}); + $self->assert_str_equals("forward", $event->{recurrenceRules}[0]{skip}); + $self->assert_num_equals(8, $event->{recurrenceRules}[0]{byMonthDay}[0]); + $self->assert_str_equals("5L", $event->{recurrenceRules}[0]{byMonth}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sanitize_geouri b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sanitize_geouri index d1ef7657a6..0b044311fe 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sanitize_geouri +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sanitize_geouri @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_get_sanitize_geouri - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "Create event with TEXT-escaped geo: URIs"; - my $ical = <<'EOF'; + xlog $self, "Create event with TEXT-escaped geo: URIs"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -36,45 +35,52 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - my $res = $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + my $res = $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - xlog $self, "Assert text-escaped geo: URI values are sanitized when read"; + xlog $self, "Assert text-escaped geo: URI values are sanitized when read"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['locations'], - }, 'R1'] - ]); - $self->assert_str_equals('geo:13.4125,103.8667', - $res->[0][1]{list}[0]{locations}{location1}{coordinates}); - $self->assert_str_equals('geo:14.4125,104.8667', - $res->[0][1]{list}[0]{locations}{location2}{coordinates}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + properties => ['locations'], + }, + 'R1' + ] ]); + $self->assert_str_equals('geo:13.4125,103.8667', + $res->[0][1]{list}[0]{locations}{location1}{coordinates}); + $self->assert_str_equals('geo:14.4125,104.8667', + $res->[0][1]{list}[0]{locations}{location2}{coordinates}); - xlog $self, "Assert text-escaped geo: URI values are rejected when set"; + xlog $self, "Assert text-escaped geo: URI values are rejected when set"; - my $eventId = $res->[0][1]{list}[0]{id}; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'locations/location1/coordinates' => 'geo:13.4125\,103.8667', - } - }, - }, 'R1'] - ]); - $self->assert_deep_equals(['locations/location1/coordinates'], - $res->[0][1]{notUpdated}{$eventId}{properties}); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'locations/location2/coordinates' => 'geo:14.4125\,104.8667', - } - }, - }, 'R1'] - ]); - $self->assert_deep_equals(['locations/location2/coordinates'], - $res->[0][1]{notUpdated}{$eventId}{properties}); + my $eventId = $res->[0][1]{list}[0]{id}; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'locations/location1/coordinates' => 'geo:13.4125\,103.8667', + } + }, + }, + 'R1' + ] ]); + $self->assert_deep_equals(['locations/location1/coordinates'], + $res->[0][1]{notUpdated}{$eventId}{properties}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'locations/location2/coordinates' => 'geo:14.4125\,104.8667', + } + }, + }, + 'R1' + ] ]); + $self->assert_deep_equals(['locations/location2/coordinates'], + $res->[0][1]{notUpdated}{$eventId}{properties}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sentby_caldav b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sentby_caldav index b79dcc780d..7c82279eb4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sentby_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sentby_caldav @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_get_sentby_caldav - :needs_component_httpd :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : needs_component_httpd : needs_component_jmap : min_version_3_5 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <<'EOF'; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -28,17 +27,23 @@ ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:cassandane@example.com END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/testitip.ics', - $ical, 'Content-Type' => 'text/calendar', - 'Schedule-Sender-Address' => 'sender@example.net', - 'Schedule-Sender-Name' => 'Sally Sender', - ); + $caldav->Request( + 'PUT', + '/dav/calendars/user/cassandane/Default/testitip.ics', + $ical, + 'Content-Type' => 'text/calendar', + 'Schedule-Sender-Address' => 'sender@example.net', + 'Schedule-Sender-Name' => 'Sally Sender', + ); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'sentBy'] - }, 'R1'], - ]); - $self->assert_str_equals('sender@example.net', $res->[0][1]{list}[0]{sentBy}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'sentBy' ] + }, + 'R1' + ], + ]); + $self->assert_str_equals('sender@example.net', $res->[0][1]{list}[0]{sentBy}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sentby_imip b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sentby_imip index 67bfcb15f9..f24fc032e6 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sentby_imip +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_sentby_imip @@ -2,13 +2,14 @@ use Cassandane::Tiny; sub test_calendarevent_get_sentby_imip - :needs_component_sieve :needs_component_httpd :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_sieve : needs_component_httpd : needs_component_jmap : + min_version_3_5 { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -47,13 +48,17 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + xlog $self, "Deliver iMIP invite"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'sentBy'] - }, 'R1'], - ]); - $self->assert_str_equals('sender@example.net', $res->[0][1]{list}[0]{sentBy}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'sentBy' ] + }, + 'R1' + ], + ]); + $self->assert_str_equals('sender@example.net', $res->[0][1]{list}[0]{sentBy}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_simple b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_simple index e9d7410c39..d55b0cb5a8 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_simple +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_simple @@ -2,31 +2,31 @@ use Cassandane::Tiny; sub test_calendarevent_get_simple - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($uid, $ical) = $self->icalfile('simple'); + my ($uid, $ical) = $self->icalfile('simple'); - my $event = $self->putandget_vevent($uid, $ical); - $self->assert_not_null($event); - $self->assert_str_equals('Event', $event->{q{@type}}); - $self->assert_str_equals(encode_eventid($uid), $event->{id}); - $self->assert_str_equals($uid, $event->{uid}); - $self->assert_null($event->{relatedTo}); - $self->assert_str_equals("yo", $event->{title}); - $self->assert_str_equals("-//Apple Inc.//Mac OS X 10.9.5//EN", $event->{prodId}); - $self->assert_str_equals("en", $event->{locale}); - $self->assert_str_equals("turquoise", $event->{color}); - $self->assert_str_equals("double yo", $event->{description}); - $self->assert_str_equals("text/plain", $event->{descriptionContentType}); - $self->assert_equals($event->{freeBusyStatus}, "free"); - $self->assert_equals($event->{showWithoutTime}, JSON::false); - $self->assert_str_equals("2016-09-28T16:00:00", $event->{start}); - $self->assert_str_equals("Etc/UTC", $event->{timeZone}); - $self->assert_str_equals("PT1H", $event->{duration}); - $self->assert_str_equals("2015-09-28T12:52:12Z", $event->{created}); - $self->assert_str_equals("2015-09-28T13:24:34Z", $event->{updated}); - $self->assert_num_equals(9, $event->{sequence}); - $self->assert_num_equals(3, $event->{priority}); + my $event = $self->putandget_vevent($uid, $ical); + $self->assert_not_null($event); + $self->assert_str_equals('Event', $event->{q{@type}}); + $self->assert_str_equals(encode_eventid($uid), $event->{id}); + $self->assert_str_equals($uid, $event->{uid}); + $self->assert_null($event->{relatedTo}); + $self->assert_str_equals("yo", $event->{title}); + $self->assert_str_equals("-//Apple Inc.//Mac OS X 10.9.5//EN", + $event->{prodId}); + $self->assert_str_equals("en", $event->{locale}); + $self->assert_str_equals("turquoise", $event->{color}); + $self->assert_str_equals("double yo", $event->{description}); + $self->assert_str_equals("text/plain", $event->{descriptionContentType}); + $self->assert_equals($event->{freeBusyStatus}, "free"); + $self->assert_equals($event->{showWithoutTime}, JSON::false); + $self->assert_str_equals("2016-09-28T16:00:00", $event->{start}); + $self->assert_str_equals("Etc/UTC", $event->{timeZone}); + $self->assert_str_equals("PT1H", $event->{duration}); + $self->assert_str_equals("2015-09-28T12:52:12Z", $event->{created}); + $self->assert_str_equals("2015-09-28T13:24:34Z", $event->{updated}); + $self->assert_num_equals(9, $event->{sequence}); + $self->assert_num_equals(3, $event->{priority}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_standalone_instances b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_standalone_instances index c81f3107a9..918fd3d1c7 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_standalone_instances +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_standalone_instances @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_get_standalone_instances - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <<'EOF'; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -37,52 +36,48 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $ical, - 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', 'Default/test.ics', $ical, + 'Content-Type' => 'text/calendar'); - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => [ - 'recurrenceId', - 'recurrenceIdTimeZone', - 'start', - 'timeZone', - 'title', - 'uid', - ], - }, 'R2'], - ]); + my $res = $jmap->CallMethods([ + [ 'CalendarEvent/query', {}, 'R1' ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => [ + 'recurrenceId', 'recurrenceIdTimeZone', + 'start', 'timeZone', + 'title', 'uid', + ], + }, + 'R2' + ], + ]); - my %events = map { $_->{title} => $_ } @{$res->[1][1]{list}}; - $self->assert_num_equals(2, scalar keys %events); - $self->assert_str_not_equals($events{instance1}{id}, $events{instance2}{id}); + my %events = map { $_->{title} => $_ } @{ $res->[1][1]{list} }; + $self->assert_num_equals(2, scalar keys %events); + $self->assert_str_not_equals($events{instance1}{id}, $events{instance2}{id}); - $self->assert_str_equals('2021-01-01T12:00:00', - $events{instance1}{start}); - $self->assert_str_equals('Europe/Berlin', - $events{instance1}{timeZone}); - $self->assert_str_equals('2021-01-01T06:00:00', - $events{instance1}{recurrenceId}); - $self->assert_str_equals('America/New_York', - $events{instance1}{recurrenceIdTimeZone}); - $self->assert_str_equals('2a358cee-6489-4f14-a57f-c104db4dc357', - $events{instance1}{uid}); + $self->assert_str_equals('2021-01-01T12:00:00', $events{instance1}{start}); + $self->assert_str_equals('Europe/Berlin', $events{instance1}{timeZone}); + $self->assert_str_equals('2021-01-01T06:00:00', + $events{instance1}{recurrenceId}); + $self->assert_str_equals('America/New_York', + $events{instance1}{recurrenceIdTimeZone}); + $self->assert_str_equals('2a358cee-6489-4f14-a57f-c104db4dc357', + $events{instance1}{uid}); - $self->assert_str_equals('2021-03-01T08:00:00', - $events{instance2}{start}); - $self->assert_str_equals('America/New_York', - $events{instance2}{timeZone}); - $self->assert_str_equals('2021-03-01T06:00:00', - $events{instance2}{recurrenceId}); - $self->assert_str_equals('America/New_York', - $events{instance2}{recurrenceIdTimeZone}); - $self->assert_str_equals('2a358cee-6489-4f14-a57f-c104db4dc357', - $events{instance2}{uid}); + $self->assert_str_equals('2021-03-01T08:00:00', $events{instance2}{start}); + $self->assert_str_equals('America/New_York', $events{instance2}{timeZone}); + $self->assert_str_equals('2021-03-01T06:00:00', + $events{instance2}{recurrenceId}); + $self->assert_str_equals('America/New_York', + $events{instance2}{recurrenceIdTimeZone}); + $self->assert_str_equals('2a358cee-6489-4f14-a57f-c104db4dc357', + $events{instance2}{uid}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_standalone_instances_multi b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_standalone_instances_multi index 7fd26146be..913cb2d064 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_standalone_instances_multi +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_standalone_instances_multi @@ -2,30 +2,29 @@ use Cassandane::Tiny; sub test_calendarevent_get_standalone_instances_multi - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $now = DateTime->now(); - $now->set_time_zone('Etc/UTC'); - my $dtstamp = $now->strftime('%Y%m%dT%H%M%SZ'); + my $now = DateTime->now(); + $now->set_time_zone('Etc/UTC'); + my $dtstamp = $now->strftime('%Y%m%dT%H%M%SZ'); - my $n = 700; + my $n = 700; - my $ical = <clone(); - $t->add(DateTime::Duration->new(days => $i)); - my $recurid = $t->strftime('%Y%m%dT%H%M%SZ'); + for (my $i = 0; $i < $n; $i++) { + my $t = $now->clone(); + $t->add(DateTime::Duration->new(days => $i)); + my $recurid = $t->strftime('%Y%m%dT%H%M%SZ'); - $ical .= <Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); - - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['recurrenceId'], - }, 'R1'], - ]); - $self->assert_num_equals($n, scalar @{$res->[0][1]{list}}); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); + + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['recurrenceId'], + }, + 'R1' + ], + ]); + $self->assert_num_equals($n, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_utcstart b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_utcstart index ae971b80ed..e564c371e2 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_utcstart +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_utcstart @@ -2,106 +2,127 @@ use Cassandane::Tiny; sub test_calendarevent_get_utcstart - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Initialize calendar timezone. - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - timeZone => 'America/New_York', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + # Initialize calendar timezone. + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + timeZone => 'America/New_York', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - # Assert utcStart for main event and recurrenceOverrides. - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2019-12-06T11:21:01", - duration => "PT5M", - timeZone => "Europe/Vienna", - recurrenceRules => [{ - frequency => 'daily', - count => 3, - }], - recurrenceOverrides => { - '2019-12-07T11:21:01.8' => { - start => '2019-12-07T13:00:00', - }, - }, - }, + # Assert utcStart for main event and recurrenceOverrides. + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2019-12-06T11:21:01", + duration => "PT5M", + timeZone => "Europe/Vienna", + recurrenceRules => [ { + frequency => 'daily', + count => 3, + } ], + recurrenceOverrides => { + '2019-12-07T11:21:01.8' => { + start => '2019-12-07T13:00:00', + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#1'], - properties => ['utcStart', 'utcEnd', 'recurrenceOverrides'], - }, 'R2'] - ]); - my $eventId1 = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId1); - my $event = $res->[1][1]{list}[0]; - $self->assert_not_null($event); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#1'], + properties => [ 'utcStart', 'utcEnd', 'recurrenceOverrides' ], + }, + 'R2' + ] + ]); + my $eventId1 = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId1); + my $event = $res->[1][1]{list}[0]; + $self->assert_not_null($event); - $self->assert_str_equals('2019-12-06T10:21:01Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-06T10:26:01Z', $event->{utcEnd}); - $self->assert_str_equals('2019-12-07T12:00:00Z', - $event->{recurrenceOverrides}{'2019-12-07T11:21:01'}{utcStart}); - $self->assert_str_equals('2019-12-07T12:05:00Z', - $event->{recurrenceOverrides}{'2019-12-07T11:21:01'}{utcEnd}); + $self->assert_str_equals('2019-12-06T10:21:01Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-06T10:26:01Z', $event->{utcEnd}); + $self->assert_str_equals('2019-12-07T12:00:00Z', + $event->{recurrenceOverrides}{'2019-12-07T11:21:01'}{utcStart}); + $self->assert_str_equals('2019-12-07T12:05:00Z', + $event->{recurrenceOverrides}{'2019-12-07T11:21:01'}{utcEnd}); - # Assert utcStart for regular recurrence instance. - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [encode_eventid('eventuid1local', '20191208T112101')], - properties => ['utcStart', 'utcEnd'], - }, 'R2'] - ]); - $event = $res->[0][1]{list}[0]; - $self->assert_not_null($event); + # Assert utcStart for regular recurrence instance. + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + ids => [ encode_eventid('eventuid1local', '20191208T112101') ], + properties => [ 'utcStart', 'utcEnd' ], + }, + 'R2' + ] ]); + $event = $res->[0][1]{list}[0]; + $self->assert_not_null($event); - $self->assert_str_equals('2019-12-08T10:21:01Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-08T10:26:01Z', $event->{utcEnd}); + $self->assert_str_equals('2019-12-08T10:21:01Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-08T10:26:01Z', $event->{utcEnd}); - # Assert utcStart for floating event with calendar timeZone. - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 2 => { - uid => 'eventuid2local', - calendarIds => { - Default => JSON::true, - }, - title => "event2", - start => "2019-12-08T23:30:00", - duration => "PT2H", - timeZone => undef, - }, + # Assert utcStart for floating event with calendar timeZone. + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 2 => { + uid => 'eventuid2local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#2'], - properties => ['utcStart', 'utcEnd', 'timeZone'], - }, 'R2'] - ]); - my $eventId2 = $res->[0][1]{created}{2}{id}; - $self->assert_not_null($eventId2); - $event = $res->[1][1]{list}[0]; - $self->assert_not_null($event); + title => "event2", + start => "2019-12-08T23:30:00", + duration => "PT2H", + timeZone => undef, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#2'], + properties => [ 'utcStart', 'utcEnd', 'timeZone' ], + }, + 'R2' + ] + ]); + my $eventId2 = $res->[0][1]{created}{2}{id}; + $self->assert_not_null($eventId2); + $event = $res->[1][1]{list}[0]; + $self->assert_not_null($event); - # Floating event time falls back to calendar time zone America/New_York. - $self->assert_str_equals('2019-12-09T04:30:00Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-09T06:30:00Z', $event->{utcEnd}); + # Floating event time falls back to calendar time zone America/New_York. + $self->assert_str_equals('2019-12-09T04:30:00Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-09T06:30:00Z', $event->{utcEnd}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_utctime_with_tzid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_utctime_with_tzid index 1ce7e8c5b6..e4db1f8d60 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_utctime_with_tzid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_utctime_with_tzid @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_calendarevent_get_utctime_with_tzid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - # As seen on the wires... - my ($id, $ical) = $self->icalfile('utctime-with-tzid'); + # As seen on the wires... + my ($id, $ical) = $self->icalfile('utctime-with-tzid'); - my $event = $self->putandget_vevent($id, $ical, ['timeZone', 'start', 'duration']); - $self->assert_not_null($event); - $self->assert_str_equals('Europe/Vienna', $event->{timeZone}); - $self->assert_str_equals('2019-12-19T19:00:00', $event->{start}); - $self->assert_str_equals('PT2H20M', $event->{duration}); + my $event + = $self->putandget_vevent($id, $ical, [ 'timeZone', 'start', 'duration' ]); + $self->assert_not_null($event); + $self->assert_str_equals('Europe/Vienna', $event->{timeZone}); + $self->assert_str_equals('2019-12-19T19:00:00', $event->{start}); + $self->assert_str_equals('PT2H20M', $event->{duration}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_virtuallocations_conference b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_virtuallocations_conference index ef520446e9..a5d99cc9e1 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_virtuallocations_conference +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_get_virtuallocations_conference @@ -2,21 +2,20 @@ use Cassandane::Tiny; sub test_calendarevent_get_virtuallocations_conference - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my ($id, $ical) = $self->icalfile('locations-conference'); + my ($id, $ical) = $self->icalfile('locations-conference'); - my $event = $self->putandget_vevent($id, $ical); - my $virtualLocations = $event->{virtualLocations}; - $self->assert_num_equals(2, scalar (values %{$virtualLocations})); + my $event = $self->putandget_vevent($id, $ical); + my $virtualLocations = $event->{virtualLocations}; + $self->assert_num_equals(2, scalar(values %{$virtualLocations})); - my $loc1 = $virtualLocations->{loc1}; - $self->assert_str_equals('Moderator dial-in', $loc1->{name}); - $self->assert_str_equals('tel:+123451', $loc1->{uri}); + my $loc1 = $virtualLocations->{loc1}; + $self->assert_str_equals('Moderator dial-in', $loc1->{name}); + $self->assert_str_equals('tel:+123451', $loc1->{uri}); - my $loc2 = $virtualLocations->{loc2}; - $self->assert_str_equals('Chat room', $loc2->{name}); - $self->assert_str_equals('xmpp:chat123@conference.example.com', $loc2->{uri}); + my $loc2 = $virtualLocations->{loc2}; + $self->assert_str_equals('Chat room', $loc2->{name}); + $self->assert_str_equals('xmpp:chat123@conference.example.com', $loc2->{uri}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz index 9db8c8c9e9..2a31cb57a7 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_guesstz - :min_version_3_5 :needs_component_jmap :needs_dependency_guesstz -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap : needs_dependency_guesstz { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $eventId = '123456789'; - my $ical = <putandget_vevent($eventId, - $ical, ['timeZone']); - $self->assert_str_equals('America/New_York', $event->{timeZone}); + my $event = $self->putandget_vevent($eventId, $ical, ['timeZone']); + $self->assert_str_equals('America/New_York', $event->{timeZone}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_etc_gmt b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_etc_gmt index 61f6aa60c5..faa4a1b71e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_etc_gmt +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_etc_gmt @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_calendarevent_guesstz_etc_gmt - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "PUT non-IANA VTIMEZONE with an unknown UTC offset"; + xlog $self, "PUT non-IANA VTIMEZONE with an unknown UTC offset"; - my $ical = <<'EOF'; + my $ical = <<'EOF'; BEGIN:VCALENDAR PRODID:Xxx VERSION:2.0 @@ -40,16 +39,17 @@ END:VEVENT END:VCALENDAR EOF - my $href = '/dav/calendars/user/cassandane/Default/test.ics'; - $caldav->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar', - ); + my $href = '/dav/calendars/user/cassandane/Default/test.ics'; + $caldav->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar',); - xlog $self, "Assert VTIMEZONE converts to IANA timezone"; + xlog $self, "Assert VTIMEZONE converts to IANA timezone"; - my $res = $jmap->CallMethods([ - [ 'CalendarEvent/get', { - properties => ['timeZone'], - }, 'R1'] - ]); - $self->assert_str_equals('Etc/GMT-10', $res->[0][1]{list}[0]{timeZone}); + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + properties => ['timeZone'], + }, + 'R1' + ] ]); + $self->assert_str_equals('Etc/GMT-10', $res->[0][1]{list}[0]{timeZone}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_gmt b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_gmt index 0cd394b14b..a2db81124c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_gmt +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_gmt @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_guesstz_gmt - :min_version_3_5 :needs_component_jmap :needs_dependency_guesstz -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap : needs_dependency_guesstz { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $eventId = '123456789'; - my $ical = <putandget_vevent($eventId, - $ical, ['timeZone']); - $self->assert_str_equals('Etc/GMT+8', $event->{timeZone}); + my $event = $self->putandget_vevent($eventId, $ical, ['timeZone']); + $self->assert_str_equals('Etc/GMT+8', $event->{timeZone}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_ignore_xjmapid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_ignore_xjmapid index f9bd0ce6c5..835dc2913c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_ignore_xjmapid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_ignore_xjmapid @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_calendarevent_guesstz_ignore_xjmapid - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "PUT non-IANA VTIMEZONE with a X-JMAP-ID"; + xlog $self, "PUT non-IANA VTIMEZONE with a X-JMAP-ID"; - my $ical = <<'EOF'; + my $ical = <<'EOF'; BEGIN:VCALENDAR PRODID:Microsoft Exchange Server 2010 VERSION:2.0 @@ -45,46 +44,48 @@ END:VEVENT END:VCALENDAR EOF - my $href = '/dav/calendars/user/cassandane/Default/test.ics'; - $caldav->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar', - ); + my $href = '/dav/calendars/user/cassandane/Default/test.ics'; + $caldav->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar',); - xlog $self, "Assert VTIMEZONE converts to IANA timezone"; + xlog $self, "Assert VTIMEZONE converts to IANA timezone"; - my $res = $jmap->CallMethods([ - [ 'CalendarEvent/get', { - properties => ['timeZone'], - }, 'R1'] - ]); - $self->assert_str_equals('America/New_York', - $res->[0][1]{list}[0]{timeZone}); + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + properties => ['timeZone'], + }, + 'R1' + ] ]); + $self->assert_str_equals('America/New_York', $res->[0][1]{list}[0]{timeZone}); - my $eventId = $res->[0][1]{list}[0]{id}; - $res = $jmap->CallMethods([ - [ 'CalendarEvent/set', { - update => { - $eventId => { - title => 'test2', - }, - }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + my $eventId = $res->[0][1]{list}[0]{id}; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'test2', + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - $res = $jmap->CallMethods([ - [ 'CalendarEvent/get', { - properties => ['title', 'timeZone'], - }, 'R1'] - ]); - $self->assert_str_equals('test2', - $res->[0][1]{list}[0]{title}); - $self->assert_str_equals('America/New_York', - $res->[0][1]{list}[0]{timeZone}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + properties => [ 'title', 'timeZone' ], + }, + 'R1' + ] ]); + $self->assert_str_equals('test2', $res->[0][1]{list}[0]{title}); + $self->assert_str_equals('America/New_York', $res->[0][1]{list}[0]{timeZone}); - xlog "Assert non-IANA VTIMEZONE is kept in iCalendar"; - $res = $caldav->Request('GET', $href); - $self->assert($res->{content} =~ - m/DTSTART;TZID=\"\(UTC-05:00\) Eastern Time \(US & Canada\)\"/); - $self->assert($res->{content} =~ - m/TZID:\(UTC-05:00\) Eastern Time \(US & Canada\)/); + xlog "Assert non-IANA VTIMEZONE is kept in iCalendar"; + $res = $caldav->Request('GET', $href); + $self->assert($res->{content} =~ + m/DTSTART;TZID=\"\(UTC-05:00\) Eastern Time \(US & Canada\)\"/); + $self->assert( + $res->{content} =~ m/TZID:\(UTC-05:00\) Eastern Time \(US & Canada\)/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_recur b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_recur index 91ed101fc8..31f40f0d0f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_recur +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_guesstz_recur @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_guesstz_recur - :min_version_3_5 :needs_component_jmap :needs_dependency_guesstz -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap : needs_dependency_guesstz { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $eventId = '123456789'; - my $ical = <putandget_vevent($eventId, - $ical, ['timeZone']); - $self->assert_str_equals('Europe/Berlin', $event->{timeZone}); + my $event = $self->putandget_vevent($eventId, $ical, ['timeZone']); + $self->assert_str_equals('Europe/Berlin', $event->{timeZone}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_ignore_orphaned_ical_rows b/cassandane/tiny-tests/JMAPCalendars/calendarevent_ignore_orphaned_ical_rows index 45016f961f..482beec8a0 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_ignore_orphaned_ical_rows +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_ignore_orphaned_ical_rows @@ -3,36 +3,40 @@ use DBI; use Cassandane::Tiny; sub test_calendarevent_ignore_orphaned_ical_rows - :min_version_3_7 :needs_component_jmap :DelayedDelete -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{jmap}; + my $caldav = $self->{caldav}; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + <CallMethods([ - ['Calendar/set', { - create => { - 1 => { - name => 'test', - }, - }, - }, 'R1'], - ]); - my $calendarId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($calendarId); - - xlog "Create event via CalDAV"; - my $ical = <<'EOF'; + ); + + xlog "Create calendar"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + 1 => { + name => 'test', + }, + }, + }, + 'R1' + ], + ]); + my $calendarId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($calendarId); + + xlog "Create event via CalDAV"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -51,97 +55,135 @@ END:VEVENT END:VCALENDAR EOF - $res = $caldav->Request('PUT', - '/dav/calendars/user/cassandane/' . $calendarId . '/test.ics', - $ical, 'Content-Type' => 'text/calendar'); - - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ]); - my $eventId = $res->[0][1]{ids}[0]; - $self->assert_not_null($eventId); - - xlog "Fetch dav.db rows"; - my $dbfile = ($self->{instance}->run_mbpath(-u => 'cassandane'))->{user}{dav}; - $self->assert_not_null($dbfile); - my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "", { - PrintError => 1, RaiseError => 1, AutoCommit => 1 - }); - my $ical_rows = $dbh->selectall_hashref('SELECT * FROM ical_objs;', 'rowid'); - my $jscal_rows = $dbh->selectall_hashref('SELECT * FROM jscal_objs;', 'rowid'); - $dbh->disconnect; - - xlog "Destroy calendar and events"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - destroy => [$calendarId], - onDestroyRemoveEvents => JSON::true, - }, 'R1'], - ]); - $self->assert_deep_equals([$calendarId], $res->[0][1]{destroyed}); - - xlog "Write back rows into dav.db"; - $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "", { - PrintError => 1, RaiseError => 1, AutoCommit => 1 - }); - - while (my ($rowid, $row) = each %{$ical_rows}) { - my @cols = (), @vals = (); - while (my($k, $v) = each %{$row}) { - # make sure we insert in same order - push(@cols, $k); - push(@vals, $v); - } - my $stmt = 'INSERT INTO ical_objs (' . join(',', @cols) . ') VALUES (' . join(',', ('?') x scalar @vals) . ');'; - my $sth = $dbh->prepare($stmt); - $sth->execute(@vals) or die $sth->errorstr; + $res + = $caldav->Request('PUT', + '/dav/calendars/user/cassandane/' . $calendarId . '/test.ics', + $ical, 'Content-Type' => 'text/calendar'); + + $res = $jmap->CallMethods([ [ 'CalendarEvent/query', {}, 'R1' ], ]); + my $eventId = $res->[0][1]{ids}[0]; + $self->assert_not_null($eventId); + + xlog "Fetch dav.db rows"; + my $dbfile = ($self->{instance}->run_mbpath(-u => 'cassandane'))->{user}{dav}; + $self->assert_not_null($dbfile); + my $dbh = DBI->connect( + "dbi:SQLite:dbname=$dbfile", + "", "", + { + PrintError => 1, + RaiseError => 1, + AutoCommit => 1 } - - while (my ($rowid, $row) = each %{$jscal_rows}) { - my @cols = (), @vals = (); - while (my($k, $v) = each %{$row}) { - # make sure we insert in same order - push(@cols, $k); - push(@vals, $v); - } - my $stmt = 'INSERT INTO jscal_objs (' . join(',', @cols) . ') VALUES (' . join(',', ('?') x scalar @vals) . ');'; - my $sth = $dbh->prepare($stmt); - $sth->execute(@vals) or die $sth->errorstr; + ); + my $ical_rows = $dbh->selectall_hashref('SELECT * FROM ical_objs;', 'rowid'); + my $jscal_rows + = $dbh->selectall_hashref('SELECT * FROM jscal_objs;', 'rowid'); + $dbh->disconnect; + + xlog "Destroy calendar and events"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + destroy => [$calendarId], + onDestroyRemoveEvents => JSON::true, + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$calendarId], $res->[0][1]{destroyed}); + + xlog "Write back rows into dav.db"; + $dbh = DBI->connect( + "dbi:SQLite:dbname=$dbfile", + "", "", + { + PrintError => 1, + RaiseError => 1, + AutoCommit => 1 } - - $dbh->disconnect; - - xlog "Assert that event is not returned in JMAP"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id'], - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['id'], - }, 'R2'], - ['CalendarEvent/query', { - }, 'R3'], - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'updated', - }, - }, - }, 'R4'], - ['CalendarEvent/set', { - destroy => [$eventId], - }, 'R5'], - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{list}}); - $self->assert_deep_equals([], $res->[2][1]{ids}); - $self->assert(exists $res->[3][1]{notUpdated}{$eventId}); - $self->assert(exists $res->[4][1]{notDestroyed}{$eventId}); - - xlog "Assert that event is not updated via iMIP"; - my $imip = <<'EOF'; + ); + + while (my ($rowid, $row) = each %{$ical_rows}) { + my @cols = (), @vals = (); + while (my ($k, $v) = each %{$row}) { + # make sure we insert in same order + push(@cols, $k); + push(@vals, $v); + } + my $stmt + = 'INSERT INTO ical_objs (' + . join(',', @cols) + . ') VALUES (' + . join(',', ('?') x scalar @vals) . ');'; + my $sth = $dbh->prepare($stmt); + $sth->execute(@vals) or die $sth->errorstr; + } + + while (my ($rowid, $row) = each %{$jscal_rows}) { + my @cols = (), @vals = (); + while (my ($k, $v) = each %{$row}) { + # make sure we insert in same order + push(@cols, $k); + push(@vals, $v); + } + my $stmt + = 'INSERT INTO jscal_objs (' + . join(',', @cols) + . ') VALUES (' + . join(',', ('?') x scalar @vals) . ');'; + my $sth = $dbh->prepare($stmt); + $sth->execute(@vals) or die $sth->errorstr; + } + + $dbh->disconnect; + + xlog "Assert that event is not returned in JMAP"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['id'], + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['id'], + }, + 'R2' + ], + [ 'CalendarEvent/query', {}, 'R3' ], + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'updated', + }, + }, + }, + 'R4' + ], + [ + 'CalendarEvent/set', + { + destroy => [$eventId], + }, + 'R5' + ], + ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{list} }); + $self->assert_deep_equals([], $res->[2][1]{ids}); + $self->assert(exists $res->[3][1]{notUpdated}{$eventId}); + $self->assert(exists $res->[4][1]{notDestroyed}{$eventId}); + + xlog "Assert that event is not updated via iMIP"; + my $imip = <<'EOF'; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -167,13 +209,11 @@ END:VEVENT END:VCALENDAR EOF - $self->{instance}->getsyslog(); + $self->{instance}->getsyslog(); - xlog $self, "Deliver iMIP invite"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + xlog $self, "Deliver iMIP invite"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - $self->assert_syslog_does_not_match( - $self->{instance}, - qr/mailbox=assert_syslog_does_not_match($self->{instance}, + qr/mailbox={jmap}; - my $caldav = $self->{caldav}; + : needs_component_httpd : needs_component_jmap : min_version_3_5 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "PUT event for invitee"; - my $ical = <<'EOF'; + xlog "PUT event for invitee"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -28,42 +27,53 @@ ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;X-JMAP-ID=cassandane:MAILTO:cassandane@ END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/testitip.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', '/dav/calendars/user/cassandane/Default/testitip.ics', + $ical, 'Content-Type' => 'text/calendar'); - xlog "Assert sequence number"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'sequence'] - }, 'R1'], - ]); - $self->assert_num_equals(1, $res->[0][1]{list}[0]{sequence}); - my $eventId = $res->[0][1]{list}[0]{id}; + xlog "Assert sequence number"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'sequence' ] + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, $res->[0][1]{list}[0]{sequence}); + my $eventId = $res->[0][1]{list}[0]{id}; - xlog "Update invitee's participant"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - sendSchedulingMessages => JSON::true, - update => { - $eventId => { - 'participants/cassandane/expectReply' => JSON::false, - 'participants/cassandane/participationStatus' => 'accepted', - 'participants/cassandane/scheduleSequence' => 1, - 'participants/cassandane/scheduleUpdated' => '2022-01-20T14:56:36Z', - }, + xlog "Update invitee's participant"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + sendSchedulingMessages => JSON::true, + update => { + $eventId => { + 'participants/cassandane/expectReply' => JSON::false, + 'participants/cassandane/participationStatus' => 'accepted', + 'participants/cassandane/scheduleSequence' => 1, + 'participants/cassandane/scheduleUpdated' => '2022-01-20T14:56:36Z', + }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_null($res->[0][1]{updated}{$eventId}{sequence}); + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_null($res->[0][1]{updated}{$eventId}{sequence}); - xlog "Assert sequence number did not increase"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'sequence'] - }, 'R1'], - ]); - $self->assert_num_equals(1, $res->[0][1]{list}[0]{sequence}); + xlog "Assert sequence number did not increase"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'sequence' ] + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, $res->[0][1]{list}[0]{sequence}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_itip_request_sequence b/cassandane/tiny-tests/JMAPCalendars/calendarevent_itip_request_sequence index 535a9bcc67..81ce5abcea 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_itip_request_sequence +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_itip_request_sequence @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_itip_request_sequence - :needs_component_httpd :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : needs_component_httpd : needs_component_jmap : min_version_3_5 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "PUT event for organizer"; - my $ical = <<'EOF'; + xlog "PUT event for organizer"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -28,61 +27,80 @@ ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;X-JMAP-ID=invitee:MAILTO:invitee@exampl END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/testitip.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', '/dav/calendars/user/cassandane/Default/testitip.ics', + $ical, 'Content-Type' => 'text/calendar'); - xlog "Assert sequence number"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'sequence'] - }, 'R1'], - ]); - $self->assert_num_equals(1, $res->[0][1]{list}[0]{sequence}); - my $eventId = $res->[0][1]{list}[0]{id}; + xlog "Assert sequence number"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'sequence' ] + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, $res->[0][1]{list}[0]{sequence}); + my $eventId = $res->[0][1]{list}[0]{id}; - xlog "Update per-user prop"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - sendSchedulingMessages => JSON::true, - update => { - $eventId => { - color => 'blue', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_null($res->[0][1]{updated}{$eventId}{sequence}); + xlog "Update per-user prop"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + sendSchedulingMessages => JSON::true, + update => { + $eventId => { + color => 'blue', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_null($res->[0][1]{updated}{$eventId}{sequence}); - xlog "Assert sequence number did not increase"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'sequence'] - }, 'R1'], - ]); - $self->assert_num_equals(1, $res->[0][1]{list}[0]{sequence}); + xlog "Assert sequence number did not increase"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'sequence' ] + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, $res->[0][1]{list}[0]{sequence}); - xlog "Update shared prop"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - sendSchedulingMessages => JSON::true, - update => { - $eventId => { - title => 'updatedTitle', - }, + xlog "Update shared prop"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + sendSchedulingMessages => JSON::true, + update => { + $eventId => { + title => 'updatedTitle', + }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_num_equals(2, $res->[0][1]{updated}{$eventId}{sequence}); + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_num_equals(2, $res->[0][1]{updated}{$eventId}{sequence}); - xlog "Assert sequence number did increase"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'sequence'] - }, 'R1'], - ]); - $self->assert_num_equals(2, $res->[0][1]{list}[0]{sequence}); + xlog "Assert sequence number did increase"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'sequence' ] + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, $res->[0][1]{list}[0]{sequence}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_multiget_defaultalerts b/cassandane/tiny-tests/JMAPCalendars/calendarevent_multiget_defaultalerts index 37cea11338..12e466c880 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_multiget_defaultalerts +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_multiget_defaultalerts @@ -2,88 +2,103 @@ use Cassandane::Tiny; sub test_calendarevent_multiget_defaultalerts - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "share calendar"; - my ($shareeJmap, $shareeCaldav) = $self->create_user('sharee'); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - sharee => { - mayReadItems => JSON::true, - mayWriteAll => JSON::true, - }, - }, - }, + xlog "share calendar"; + my ($shareeJmap, $shareeCaldav) = $self->create_user('sharee'); + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + sharee => { + mayReadItems => JSON::true, + mayWriteAll => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Set default alert on calendar and personalized event"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ['CalendarEvent/set', { - create => { - 1 => { - uid => '5f0dec98-8952-418e-91fa-159cb2ba28da', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, + xlog "Set default alert on calendar and personalized event"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, + action => 'display', + }, }, - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - my $eventId = $res->[1][1]{created}{1}{id}; - $self->assert_not_null($eventId); - my $eventHref = $res->[1][1]{created}{1}{'x-href'}; - $self->assert_not_null($eventHref); - - xlog "Set per-user property to force per-user data split"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - color => 'red', - } + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => '5f0dec98-8952-418e-91fa-159cb2ba28da', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + title => "event1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + my $eventId = $res->[1][1]{created}{1}{id}; + $self->assert_not_null($eventId); + my $eventHref = $res->[1][1]{created}{1}{'x-href'}; + $self->assert_not_null($eventHref); + + xlog "Set per-user property to force per-user data split"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + color => 'red', + } + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog "Assert alerts and ETag"; + xlog "Assert alerts and ETag"; - my $xmlMultiget = < @@ -95,16 +110,18 @@ sub test_calendarevent_multiget_defaultalerts EOF - xlog "Run multiget"; - $mgRes = $caldav->Request('REPORT', 'Default', $xmlMultiget, - 'Content-Type' => 'application/xml', - ); - my $icaldata = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}; - $self->assert_matches(qr/TRIGGER:-PT5M/, $icaldata); - my $mgEtag = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{DAV:}getetag'}{content}; - $self->assert_not_null($mgEtag); + xlog "Run multiget"; + $mgRes = $caldav->Request('REPORT', 'Default', $xmlMultiget, + 'Content-Type' => 'application/xml',); + my $icaldata + = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}; + $self->assert_matches(qr/TRIGGER:-PT5M/, $icaldata); + my $mgEtag = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{DAV:}getetag'}{content}; + $self->assert_not_null($mgEtag); - my $xmlCalQuery = < @@ -114,50 +131,58 @@ EOF EOF - xlog "Run calendar query"; - $qrRes = $caldav->Request('REPORT', 'Default', $xmlCalQuery, - 'Content-Type' => 'application/xml', - ); - my $qrEtag = $qrRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{DAV:}getetag'}{content}; - $self->assert_str_equals($mgEtag, $qrEtag); + xlog "Run calendar query"; + $qrRes = $caldav->Request('REPORT', 'Default', $xmlCalQuery, + 'Content-Type' => 'application/xml',); + my $qrEtag = $qrRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{DAV:}getetag'}{content}; + $self->assert_str_equals($mgEtag, $qrEtag); - xlog "Update default alerts"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT15M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + xlog "Update default alerts"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT15M', + }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Assert iCalendar data and ETags changed"; + xlog "Assert iCalendar data and ETags changed"; - xlog "Run multiget"; - $mgRes = $caldav->Request('REPORT', 'Default', $xmlMultiget, - 'Content-Type' => 'application/xml', - ); - my $icaldata = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}; - $self->assert_matches(qr/TRIGGER:-PT15M/, $icaldata); - my $newMgEtag = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{DAV:}getetag'}{content}; - $self->assert_str_not_equals($mgEtag, $newMgEtag); + xlog "Run multiget"; + $mgRes = $caldav->Request('REPORT', 'Default', $xmlMultiget, + 'Content-Type' => 'application/xml',); + my $icaldata + = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content}; + $self->assert_matches(qr/TRIGGER:-PT15M/, $icaldata); + my $newMgEtag + = $mgRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{DAV:}getetag'}{content}; + $self->assert_str_not_equals($mgEtag, $newMgEtag); - xlog "Run calendar query"; - $qrRes = $caldav->Request('REPORT', 'Default', $xmlCalQuery, - 'Content-Type' => 'application/xml', - ); - my $newQrEtag = $qrRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{DAV:}getetag'}{content}; - $self->assert_str_equals($newMgEtag, $newQrEtag); + xlog "Run calendar query"; + $qrRes = $caldav->Request('REPORT', 'Default', $xmlCalQuery, + 'Content-Type' => 'application/xml',); + my $newQrEtag + = $qrRes->{'{DAV:}response'}[0]{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{DAV:}getetag'}{content}; + $self->assert_str_equals($newMgEtag, $newQrEtag); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_multiget_defaultalerts_per_calendar b/cassandane/tiny-tests/JMAPCalendars/calendarevent_multiget_defaultalerts_per_calendar index fc16a765f3..540d54b055 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_multiget_defaultalerts_per_calendar +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_multiget_defaultalerts_per_calendar @@ -2,102 +2,109 @@ use Cassandane::Tiny; sub test_calendarevent_multiget_defaultalerts_per_calendar - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create two calendars with default alerts"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendarA => { - name => 'calendarA', - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT1H', - }, - action => 'display', - }, - }, + xlog "Create two calendars with default alerts"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + calendarA => { + name => 'calendarA', + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT1H', }, - calendarB => { - name => 'calendarB', - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT2H', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - my $calendarAId = $res->[0][1]{created}{calendarA}{id}; - $self->assert_not_null($calendarAId); - my $calendarBId = $res->[0][1]{created}{calendarB}{id}; - $self->assert_not_null($calendarBId); - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventA1 => { - uid => '5f0dec98-8952-418e-91fa-159cb2ba28da', - calendarIds => { - $calendarAId => JSON::true, - }, - title => "eventA1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, - }, - eventA2 => { - uid => '68b31869-889e-49f2-ac6a-f94ce0179635', - calendarIds => { - $calendarAId => JSON::true, - }, - title => "eventA2", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, - }, - eventB1 => { - uid => 'b58fcc34-aca6-4ae5-a7d0-97411d1166a4', - calendarIds => { - $calendarBId => JSON::true, - }, - title => "eventB1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, + action => 'display', + }, + }, + }, + calendarB => { + name => 'calendarB', + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT2H', }, + action => 'display', + }, }, - }, 'R1'], - ]); + } + } + }, + 'R1' + ], + ]); + my $calendarAId = $res->[0][1]{created}{calendarA}{id}; + $self->assert_not_null($calendarAId); + my $calendarBId = $res->[0][1]{created}{calendarB}{id}; + $self->assert_not_null($calendarBId); - my $eventA1Href = $res->[0][1]{created}{eventA1}{'x-href'}; - $self->assert_not_null($eventA1Href); - my $eventA2Href = $res->[0][1]{created}{eventA2}{'x-href'}; - $self->assert_not_null($eventA2Href); - my $eventB1Href = $res->[0][1]{created}{eventB1}{'x-href'}; - $self->assert_not_null($eventB1Href); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + eventA1 => { + uid => '5f0dec98-8952-418e-91fa-159cb2ba28da', + calendarIds => { + $calendarAId => JSON::true, + }, + title => "eventA1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + eventA2 => { + uid => '68b31869-889e-49f2-ac6a-f94ce0179635', + calendarIds => { + $calendarAId => JSON::true, + }, + title => "eventA2", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + eventB1 => { + uid => 'b58fcc34-aca6-4ae5-a7d0-97411d1166a4', + calendarIds => { + $calendarBId => JSON::true, + }, + title => "eventB1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R1' + ], + ]); - xlog "Assert alerts and ETag"; - my $xml = <[0][1]{created}{eventA1}{'x-href'}; + $self->assert_not_null($eventA1Href); + my $eventA2Href = $res->[0][1]{created}{eventA2}{'x-href'}; + $self->assert_not_null($eventA2Href); + my $eventB1Href = $res->[0][1]{created}{eventB1}{'x-href'}; + $self->assert_not_null($eventB1Href); + + xlog "Assert alerts and ETag"; + my $xml = < @@ -110,16 +117,15 @@ sub test_calendarevent_multiget_defaultalerts_per_calendar $eventB1Href EOF - $res = $caldav->Request('REPORT', 'Default', $xml, - 'Content-Type' => 'application/xml', - ); - - my %icaldataPerHref = map { - $_->{'{DAV:}href'}{content} => $_->{'{DAV:}propstat'}[0]{'{DAV:}prop'}{'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content} - } @{$res->{'{DAV:}response'}}; + $res = $caldav->Request('REPORT', 'Default', $xml, + 'Content-Type' => 'application/xml',); + my %icaldataPerHref = map { + $_->{'{DAV:}href'}{content} => $_->{'{DAV:}propstat'}[0]{'{DAV:}prop'} + {'{urn:ietf:params:xml:ns:caldav}calendar-data'}{content} + } @{ $res->{'{DAV:}response'} }; - $self->assert_matches(qr/TRIGGER:-PT1H/, $icaldataPerHref{$eventA1Href}); - $self->assert_matches(qr/TRIGGER:-PT1H/, $icaldataPerHref{$eventA2Href}); - $self->assert_matches(qr/TRIGGER:-PT2H/, $icaldataPerHref{$eventB1Href}); + $self->assert_matches(qr/TRIGGER:-PT1H/, $icaldataPerHref{$eventA1Href}); + $self->assert_matches(qr/TRIGGER:-PT1H/, $icaldataPerHref{$eventA2Href}); + $self->assert_matches(qr/TRIGGER:-PT2H/, $icaldataPerHref{$eventB1Href}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_organizer_noattendees b/cassandane/tiny-tests/JMAPCalendars/calendarevent_organizer_noattendees index 5660664b74..a569197acd 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_organizer_noattendees +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_organizer_noattendees @@ -2,92 +2,107 @@ use Cassandane::Tiny; sub test_calendarevent_organizer_noattendees - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my ($event1Id, $ical) = $self->icalfile('organizer_noattendees'); - my $event = $self->putandget_vevent($event1Id, $ical); - my $wantParticipants = { - 'bf8360ce374961f497599431c4bacb50d4a67ca1' => { - '@type' => 'Participant', - name => 'Organizer', - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:organizer@local', - }, - expectReply => JSON::false, - participationStatus => 'needs-action', - }, - }; - my $wantReplyTo = { + xlog "Create event via CalDAV"; + my ($event1Id, $ical) = $self->icalfile('organizer_noattendees'); + my $event = $self->putandget_vevent($event1Id, $ical); + my $wantParticipants = { + 'bf8360ce374961f497599431c4bacb50d4a67ca1' => { + '@type' => 'Participant', + name => 'Organizer', + roles => { + 'owner' => JSON::true, + }, + sendTo => { imip => 'mailto:organizer@local', + }, + expectReply => JSON::false, + participationStatus => 'needs-action', }, + }; + my $wantReplyTo = { imip => 'mailto:organizer@local', }, $self->assert_deep_equals($wantParticipants, $event->{participants}); - $self->assert_deep_equals($wantReplyTo, $event->{replyTo}); + $self->assert_deep_equals($wantReplyTo, $event->{replyTo}); - xlog "Update event via JMAP"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $event1Id => { - participants => $wantParticipants, - replyTo => $wantReplyTo, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$event1Id], - properties => ['participants', 'replyTo', 'x-href'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$event1Id}); - $self->assert_deep_equals($wantParticipants, $res->[1][1]{list}[0]{participants}); - $self->assert_deep_equals($wantReplyTo, $res->[1][1]{list}[0]{replyTo}); + xlog "Update event via JMAP"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $event1Id => { + participants => $wantParticipants, + replyTo => $wantReplyTo, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$event1Id], + properties => [ 'participants', 'replyTo', 'x-href' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$event1Id}); + $self->assert_deep_equals($wantParticipants, + $res->[1][1]{list}[0]{participants}); + $self->assert_deep_equals($wantReplyTo, $res->[1][1]{list}[0]{replyTo}); - my $xhref1 = $res->[1][1]{list}[0]{'x-href'}; - $self->assert_not_null($xhref1); + my $xhref1 = $res->[1][1]{list}[0]{'x-href'}; + $self->assert_not_null($xhref1); - xlog "Validate no ATTENDEE got added"; - $res = $caldav->Request('GET', $xhref1); - $self->assert($res->{content} =~ m/ORGANIZER/); - $self->assert(not($res->{content} =~ m/ATTENDEE/)); + xlog "Validate no ATTENDEE got added"; + $res = $caldav->Request('GET', $xhref1); + $self->assert($res->{content} =~ m/ORGANIZER/); + $self->assert(not($res->{content} =~ m/ATTENDEE/)); - xlog "Create event with owner-only participant via JMAP"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event2 => { - calendarIds => { - 'Default' => JSON::true, - }, - title => "title", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT2H", - "timeZone" => "Europe/London", - replyTo => $wantReplyTo, - participants => $wantParticipants, - }, + xlog "Create event with owner-only participant via JMAP"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event2 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event2'], - properties => ['participants', 'replyTo'], - }, 'R2'], - ]); - $self->assert_deep_equals($wantParticipants, $res->[1][1]{list}[0]{participants}); - $self->assert_deep_equals($wantReplyTo, $res->[1][1]{list}[0]{replyTo}); + title => "title", + "start" => "2015-11-07T09:00:00", + "duration" => "PT2H", + "timeZone" => "Europe/London", + replyTo => $wantReplyTo, + participants => $wantParticipants, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event2'], + properties => [ 'participants', 'replyTo' ], + }, + 'R2' + ], + ]); + $self->assert_deep_equals($wantParticipants, + $res->[1][1]{list}[0]{participants}); + $self->assert_deep_equals($wantReplyTo, $res->[1][1]{list}[0]{replyTo}); - my $xhref2 = $res->[0][1]{created}{event2}{'x-href'}; - $self->assert_not_null($xhref2); + my $xhref2 = $res->[0][1]{created}{event2}{'x-href'}; + $self->assert_not_null($xhref2); - xlog "Validate an ATTENDEE got added"; - $res = $caldav->Request('GET', $xhref2); - $self->assert($res->{content} =~ m/ORGANIZER/); - $self->assert($res->{content} =~ m/ATTENDEE/); + xlog "Validate an ATTENDEE got added"; + $res = $caldav->Request('GET', $xhref2); + $self->assert($res->{content} =~ m/ORGANIZER/); + $self->assert($res->{content} =~ m/ATTENDEE/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_organizer_noattendees_legacy b/cassandane/tiny-tests/JMAPCalendars/calendarevent_organizer_noattendees_legacy index dbef5eb4c4..3684cfb1ad 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_organizer_noattendees_legacy +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_organizer_noattendees_legacy @@ -2,32 +2,31 @@ use Cassandane::Tiny; sub test_calendarevent_organizer_noattendees_legacy - :min_version_3_4 :max_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : max_version_3_4 : needs_component_jmap { + my ($self) = @_; - # It's allowed to have an ORGANIZER even if there are no ATTENDEEs. - # The expected behaviour is that there's just a single organizer in the - # participants + # It's allowed to have an ORGANIZER even if there are no ATTENDEEs. + # The expected behaviour is that there's just a single organizer in the + # participants - my ($id, $ical) = $self->icalfile('organizer_noattendees'); + my ($id, $ical) = $self->icalfile('organizer_noattendees'); - my $event = $self->putandget_vevent($id, $ical); + my $event = $self->putandget_vevent($id, $ical); - my $wantParticipants = { - 'bf8360ce374961f497599431c4bacb50d4a67ca1' => { - '@type' => 'Participant', - name => 'Organizer', - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:organizer@local', - }, - expectReply => JSON::false, - participationStatus => 'needs-action', - }, - }; - $self->assert_deep_equals($wantParticipants, $event->{participants}); - $self->assert_equals('mailto:organizer@local', $event->{replyTo}{imip}); + my $wantParticipants = { + 'bf8360ce374961f497599431c4bacb50d4a67ca1' => { + '@type' => 'Participant', + name => 'Organizer', + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:organizer@local', + }, + expectReply => JSON::false, + participationStatus => 'needs-action', + }, + }; + $self->assert_deep_equals($wantParticipants, $event->{participants}); + $self->assert_equals('mailto:organizer@local', $event->{replyTo}{imip}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_parse_repair_broken_ical b/cassandane/tiny-tests/JMAPCalendars/calendarevent_parse_repair_broken_ical index 866c39d14d..eb6d7723a8 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_parse_repair_broken_ical +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_parse_repair_broken_ical @@ -2,19 +2,19 @@ use Cassandane::Tiny; sub test_calendarevent_parse_repair_broken_ical - :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my @testCases = ({ - desc => 'Top-level component is VEVENT', - wantParsed => { - '@type' => 'Event', - title => 'test', - uid => '2a358cee-6489-4f14-a57f-c104db4dc357', - }, - ical => < 'Top-level component is VEVENT', + wantParsed => { + '@type' => 'Event', + title => 'test', + uid => '2a358cee-6489-4f14-a57f-c104db4dc357', + }, + ical => < 'iCalendar stream with two iCalendar objects', - wantParsed => { - '@type' => 'Event', - title => 'test1', - uid => '1a968fa5-3afd-4736-8fac-21958ef3db90', - }, - ical => < 'iCalendar stream with two iCalendar objects', + wantParsed => { + '@type' => 'Event', + title => 'test1', + uid => '1a968fa5-3afd-4736-8fac-21958ef3db90', + }, + ical => < 'VEVENT without mandatory UID property', - wantParsed => { - '@type' => 'Event', - title => 'test', - # this need not be exactly this uid value - uid => 'nouid218e89b7b9041f4b3c1999a93e6dec410b17b903', - }, - ical => < 'VEVENT without mandatory UID property', + wantParsed => { + '@type' => 'Event', + title => 'test', + # this need not be exactly this uid value + uid => 'nouid218e89b7b9041f4b3c1999a93e6dec410b17b903', + }, + ical => < 'METHOD=PUBLISH without ORGANIZER in VEVENT', - wantParsed => { - '@type' => 'Event', - uid => '01b1ee27-32c9-4c45-909b-c4c222666ebe', - }, - # We want to make sure that 'method' is NOT returned. - wantAlsoProperties => ['method'], - ical => < 'METHOD=PUBLISH without ORGANIZER in VEVENT', + wantParsed => { + '@type' => 'Event', + uid => '01b1ee27-32c9-4c45-909b-c4c222666ebe', + }, + # We want to make sure that 'method' is NOT returned. + wantAlsoProperties => ['method'], + ical => < 'Multiple repairs required', - wantParsed => { - '@type' => 'Event', - title => 'summary1', - # this need not be exactly this uid value - uid => 'nouidacf3612eeeeb579f42176697745fdc984d24aafc', - }, - # We want to make sure that 'method' is NOT returned. - wantAlsoProperties => ['method'], - ical => < 'Multiple repairs required', + wantParsed => { + '@type' => 'Event', + title => 'summary1', + # this need not be exactly this uid value + uid => 'nouidacf3612eeeeb579f42176697745fdc984d24aafc', + }, + # We want to make sure that 'method' is NOT returned. + wantAlsoProperties => ['method'], + ical => < 'Broken VALARMs: bad TRIGGER, no ACTION', - wantParsed => { - '@type' => 'Event', - alerts => { - valarmNoAction => { - '@type' => "Alert", - trigger => { - '@type' => "OffsetTrigger", - relativeTo => "start", - offset => "PT0S" - }, - action => "display" - } + }, + { + desc => 'Broken VALARMs: bad TRIGGER, no ACTION', + wantParsed => { + '@type' => 'Event', + alerts => { + valarmNoAction => { + '@type' => "Alert", + trigger => { + '@type' => "OffsetTrigger", + relativeTo => "start", + offset => "PT0S" }, - }, - ical => < "display" + } + }, + }, + ical => <{ical} =~ s/\r?\n/\r\n/gs; + for my $tc (@testCases) { + $tc->{ical} =~ s/\r?\n/\r\n/gs; - my @properties = keys %{$tc->{wantParsed}}; - push(@properties, @{$tc->{wantAlsoProperties}}); + my @properties = keys %{ $tc->{wantParsed} }; + push(@properties, @{ $tc->{wantAlsoProperties} }); - xlog $self, "Running test case: $tc->{desc}"; + xlog $self, "Running test case: $tc->{desc}"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - ical => { - data => [{ - 'data:asText' => $tc->{ical}, - }], - }, - }, - }, 'R0'], - ['CalendarEvent/parse', { - blobIds => [ "#ical" ], - repairBrokenIcal => JSON::true, - properties => \@properties, - }, 'R1'] - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/blob', - ]); - $self->assert_not_null($res->[0][1]{created}{ical}); - if (not grep(/^uid$/, @properties)) { - delete $res->[1][1]{parsed}{'#ical'}{uid}; - } - $self->assert_deep_equals($tc->{wantParsed}, - $res->[1][1]{parsed}{'#ical'}); + my $res = $jmap->CallMethods( + [ + [ + 'Blob/upload', + { + create => { + ical => { + data => [ { + 'data:asText' => $tc->{ical}, + } ], + }, + }, + }, + 'R0' + ], + [ + 'CalendarEvent/parse', + { + blobIds => ["#ical"], + repairBrokenIcal => JSON::true, + properties => \@properties, + }, + 'R1' + ] + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/blob', + ] + ); + $self->assert_not_null($res->[0][1]{created}{ical}); + if (not grep(/^uid$/, @properties)) { + delete $res->[1][1]{parsed}{'#ical'}{uid}; } + $self->assert_deep_equals($tc->{wantParsed}, $res->[1][1]{parsed}{'#ical'}); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_parse_singlecommand b/cassandane/tiny-tests/JMAPCalendars/calendarevent_parse_singlecommand index 5cf398b67a..03948e2c4b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_parse_singlecommand +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_parse_singlecommand @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_parse_singlecommand - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $id1 = '97c46ea4-4182-493c-87ef-aee4edc2d38b'; - my $ical1 = <CallMethods([ - ['Blob/upload', - { create => { - "ical1" => { data => [{'data:asText' => $ical1}], type => 'text/calendar' }, - "ical2" => { data => [{'data:asText' => $ical2}], type => 'text/calendar' }, - "junk" => { data => [{'data:asText' => 'foo bar'}], type => 'text/calendar' } - } }, 'R0'], - ['CalendarEvent/parse', { - blobIds => [ "#ical1", "foo", "#junk", "#ical2" ], - properties => [ "\@type", "uid", "title", "start", - "recurrenceRules", "recurrenceOverrides" ] - }, "R1"]], - $using); - $self->assert_not_null($res); - $self->assert_str_equals('Blob/upload', $res->[0][0]); - $self->assert_str_equals('R0', $res->[0][2]); + my $res = $jmap->CallMethods( + [ + [ + 'Blob/upload', + { + create => { + "ical1" => { + data => [ { 'data:asText' => $ical1 } ], + type => 'text/calendar' + }, + "ical2" => { + data => [ { 'data:asText' => $ical2 } ], + type => 'text/calendar' + }, + "junk" => { + data => [ { 'data:asText' => 'foo bar' } ], + type => 'text/calendar' + } + } + }, + 'R0' + ], + [ + 'CalendarEvent/parse', + { + blobIds => [ "#ical1", "foo", "#junk", "#ical2" ], + properties => [ + "\@type", "uid", + "title", "start", + "recurrenceRules", "recurrenceOverrides" + ] + }, + "R1" + ] + ], + $using + ); + $self->assert_not_null($res); + $self->assert_str_equals('Blob/upload', $res->[0][0]); + $self->assert_str_equals('R0', $res->[0][2]); - $self->assert_str_equals('CalendarEvent/parse', $res->[1][0]); - $self->assert_str_equals('R1', $res->[1][2]); - $self->assert_str_equals($id1, $res->[1][1]{parsed}{"#ical1"}{uid}); - $self->assert_str_equals("bar", $res->[1][1]{parsed}{"#ical1"}{title}); - $self->assert_str_equals("2015-10-08T00:00:00", $res->[1][1]{parsed}{"#ical1"}{start}); - $self->assert_null($res->[1][1]{parsed}{"#ical1"}{recurrenceRule}); - $self->assert_null($res->[1][1]{parsed}{"#ical1"}{recurrenceOverrides}); + $self->assert_str_equals('CalendarEvent/parse', $res->[1][0]); + $self->assert_str_equals('R1', $res->[1][2]); + $self->assert_str_equals($id1, $res->[1][1]{parsed}{"#ical1"}{uid}); + $self->assert_str_equals("bar", $res->[1][1]{parsed}{"#ical1"}{title}); + $self->assert_str_equals("2015-10-08T00:00:00", + $res->[1][1]{parsed}{"#ical1"}{start}); + $self->assert_null($res->[1][1]{parsed}{"#ical1"}{recurrenceRule}); + $self->assert_null($res->[1][1]{parsed}{"#ical1"}{recurrenceOverrides}); - $self->assert_str_equals("Group", $res->[1][1]{parsed}{"#ical2"}{"\@type"}); - $self->assert_num_equals(2, scalar @{$res->[1][1]{parsed}{"#ical2"}{entries}}); - $self->assert_str_equals($id2, $res->[1][1]{parsed}{"#ical2"}{entries}[1]{uid}); - $self->assert_str_equals("Event #2", $res->[1][1]{parsed}{"#ical2"}{entries}[1]{title}); - $self->assert_not_null($res->[1][1]{parsed}{"#ical2"}{entries}[1]{recurrenceRules}); - $self->assert_not_null($res->[1][1]{parsed}{"#ical2"}{entries}[1]{recurrenceOverrides}); - $self->assert_str_equals($id1, $res->[1][1]{parsed}{"#ical2"}{entries}[0]{uid}); - $self->assert_str_equals("foo", $res->[1][1]{parsed}{"#ical2"}{entries}[0]{title}); + $self->assert_str_equals("Group", $res->[1][1]{parsed}{"#ical2"}{"\@type"}); + $self->assert_num_equals(2, + scalar @{ $res->[1][1]{parsed}{"#ical2"}{entries} }); + $self->assert_str_equals($id2, + $res->[1][1]{parsed}{"#ical2"}{entries}[1]{uid}); + $self->assert_str_equals("Event #2", + $res->[1][1]{parsed}{"#ical2"}{entries}[1]{title}); + $self->assert_not_null( + $res->[1][1]{parsed}{"#ical2"}{entries}[1]{recurrenceRules}); + $self->assert_not_null( + $res->[1][1]{parsed}{"#ical2"}{entries}[1]{recurrenceOverrides}); + $self->assert_str_equals($id1, + $res->[1][1]{parsed}{"#ical2"}{entries}[0]{uid}); + $self->assert_str_equals("foo", + $res->[1][1]{parsed}{"#ical2"}{entries}[0]{title}); - $self->assert_str_equals("#junk", $res->[1][1]{notParsable}[0]); - $self->assert_str_equals("foo", $res->[1][1]{notFound}[0]); + $self->assert_str_equals("#junk", $res->[1][1]{notParsable}[0]); + $self->assert_str_equals("foo", $res->[1][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_caldav b/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_caldav index 7e8bd64686..f4c135359c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_caldav @@ -2,29 +2,28 @@ use Cassandane::Tiny; sub test_calendarevent_participantreply_caldav - :min_version_3_7 :needs_component_jmap :NoStartInstances -{ - my ($self) = @_; - - my $instance = $self->{instance}; - $instance->{config}->set(defaultdomain => 'internal'); - $instance->{config}->set(calendar_user_address_set => 'internal'); - - $self->_start_instances(); - $self->_setup_http_service_objects(); - - my $jmap = $self->{jmap}; - $jmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'urn:ietf:params:jmap:calendars:preferences', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/debug', - ]); - - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $card = <{instance}; + $instance->{config}->set(defaultdomain => 'internal'); + $instance->{config}->set(calendar_user_address_set => 'internal'); + + $self->_start_instances(); + $self->_setup_http_service_objects(); + + my $jmap = $self->{jmap}; + $jmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'urn:ietf:params:jmap:calendars:preferences', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/debug', + ]); + + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $card = <{instance}->getnotify(); - - xlog $self, "create scheduled event"; - my $event = $self->putandget_vevent($uuid, $card); - my $id = $event->{id}; - - xlog $self, "verify invitation sent from organizer to attendees"; - my $data = $self->{instance}->getnotify(); - my @imips = grep { $_->{METHOD} eq 'imip' } @$data; - my $imip = $imips[0]; - $self->assert_not_null($imip); - - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; - - $self->assert_str_equals("CalDAV", $payload->{schedulingMechanism}); - $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); - $self->assert_matches(qr/(bugs|rr)\@looneytunes.com/, $payload->{recipient}); - $self->assert_num_equals(0, $payload->{patch}{sequence}); - $self->assert($ical =~ "METHOD:REQUEST"); - $self->assert($ical =~ "SEQUENCE:0"); - $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); - - $imip = $imips[1]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("CalDAV", $payload->{schedulingMechanism}); - $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); - $self->assert_matches(qr/(bugs|rr)\@looneytunes.com/, $payload->{recipient}); - $self->assert_num_equals(0, $payload->{patch}{sequence}); - $self->assert($ical =~ "METHOD:REQUEST"); - $self->assert($ical =~ "SEQUENCE:0"); - $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); - - xlog $self, "set attendee status"; - my $res = $jmap->CallMethods([['CalendarEvent/participantReply', { - eventId => $id, - participantEmail => "bugs\@looneytunes.com", - updates => { - participationStatus => "accepted" - } - }, "R2"]]); - $self->assert_str_equals("1.1", $res->[0][1]{scheduleStatus}); - - xlog $self, "verify reply sent from attendee to organizer"; - $data = $self->{instance}->getnotify(); - @imips = grep { $_->{METHOD} eq 'imip' } @$data; - $imip = $imips[0]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("CalendarEvent/participantReply", - $payload->{schedulingMechanism}); - $self->assert_str_equals("bugs\@looneytunes.com", $payload->{sender}); - $self->assert_str_equals("cassandane\@example.com", $payload->{recipient}); - $self->assert_num_equals(0, $payload->{jsevent}{sequence}); - $self->assert($ical =~ "METHOD:REPLY"); - $self->assert($ical =~ "SEQUENCE:0"); - $self->assert($ical =~ "PARTSTAT=ACCEPTED"); + # clean notification cache + $self->{instance}->getnotify(); + + xlog $self, "create scheduled event"; + my $event = $self->putandget_vevent($uuid, $card); + my $id = $event->{id}; + + xlog $self, "verify invitation sent from organizer to attendees"; + my $data = $self->{instance}->getnotify(); + my @imips = grep { $_->{METHOD} eq 'imip' } @$data; + my $imip = $imips[0]; + $self->assert_not_null($imip); + + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; + + $self->assert_str_equals("CalDAV", $payload->{schedulingMechanism}); + $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); + $self->assert_matches(qr/(bugs|rr)\@looneytunes.com/, $payload->{recipient}); + $self->assert_num_equals(0, $payload->{patch}{sequence}); + $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert($ical =~ "SEQUENCE:0"); + $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); + + $imip = $imips[1]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("CalDAV", $payload->{schedulingMechanism}); + $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); + $self->assert_matches(qr/(bugs|rr)\@looneytunes.com/, $payload->{recipient}); + $self->assert_num_equals(0, $payload->{patch}{sequence}); + $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert($ical =~ "SEQUENCE:0"); + $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); + + xlog $self, "set attendee status"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/participantReply', + { + eventId => $id, + participantEmail => "bugs\@looneytunes.com", + updates => { + participationStatus => "accepted" + } + }, + "R2" + ] ]); + $self->assert_str_equals("1.1", $res->[0][1]{scheduleStatus}); + + xlog $self, "verify reply sent from attendee to organizer"; + $data = $self->{instance}->getnotify(); + @imips = grep { $_->{METHOD} eq 'imip' } @$data; + $imip = $imips[0]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("CalendarEvent/participantReply", + $payload->{schedulingMechanism}); + $self->assert_str_equals("bugs\@looneytunes.com", $payload->{sender}); + $self->assert_str_equals("cassandane\@example.com", $payload->{recipient}); + $self->assert_num_equals(0, $payload->{jsevent}{sequence}); + $self->assert($ical =~ "METHOD:REPLY"); + $self->assert($ical =~ "SEQUENCE:0"); + $self->assert($ical =~ "PARTSTAT=ACCEPTED"); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_simple b/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_simple index 864710c95c..57e9bfc383 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_simple +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_simple @@ -2,262 +2,293 @@ use Cassandane::Tiny; sub test_calendarevent_participantreply_simple - :min_version_3_7 :needs_component_jmap :NoStartInstances -{ - my ($self) = @_; - - my $instance = $self->{instance}; - $instance->{config}->set(defaultdomain => 'internal'); - $instance->{config}->set(calendar_user_address_set => 'internal'); - - $self->_start_instances(); - $self->_setup_http_service_objects(); - - my $jmap = $self->{jmap}; - $jmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'urn:ietf:params:jmap:calendars:preferences', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/debug', - ]); - - my $participants = { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'cassandane@example.com', - }, - }, - "att1" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'bugs@looneytunes.com', - }, - }, - "att2" => { - "name" => "Road Runner", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'rr@looneytunes.com', - }, - }, - }; - - # clean notification cache - $self->{instance}->getnotify(); - - xlog $self, "create scheduled event"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - Default => JSON::true, - }, - "sequence" => 1, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2022-11-23T16:45:00", - "recurrenceRules" => [{ - "\@type" => "RecurrenceRule", - "frequency" => "weekly" - }], - "timeZone" => "Australia/Melbourne", - "duration" => "PT1H", - "replyTo" => { imip => "mailto:cassandane\@example.com"}, - "participants" => $participants, - "recurrenceOverrides" => { - "2022-11-30T16:45:00" => { - "start" => "2022-12-01T16:45:00", - } - } - } - }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "verify invitation sent from organizer to attendees"; - my $data = $self->{instance}->getnotify(); - my @imips = grep { $_->{METHOD} eq 'imip' } @$data; - my $imip = $imips[0]; - $self->assert_not_null($imip); - - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; - - $self->assert_str_equals("CalendarEvent/set", $payload->{schedulingMechanism}); - $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); - $self->assert_matches(qr/(bugs|rr)\@looneytunes.com/, $payload->{recipient}); - $self->assert_num_equals(1, $payload->{patch}{sequence}); - $self->assert($ical =~ "METHOD:REQUEST"); - $self->assert($ical =~ "SEQUENCE:1"); - $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); - - $imip = $imips[1]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("CalendarEvent/set", $payload->{schedulingMechanism}); - $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); - $self->assert_matches(qr/(bugs|rr)\@looneytunes.com/, $payload->{recipient}); - $self->assert_num_equals(1, $payload->{patch}{sequence}); - $self->assert($ical =~ "METHOD:REQUEST"); - $self->assert($ical =~ "SEQUENCE:1"); - $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); - - xlog $self, "set attendee status"; - $res = $jmap->CallMethods([['CalendarEvent/participantReply', { - eventId => $id, + : min_version_3_7 : needs_component_jmap : NoStartInstances { + my ($self) = @_; + + my $instance = $self->{instance}; + $instance->{config}->set(defaultdomain => 'internal'); + $instance->{config}->set(calendar_user_address_set => 'internal'); + + $self->_start_instances(); + $self->_setup_http_service_objects(); + + my $jmap = $self->{jmap}; + $jmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'urn:ietf:params:jmap:calendars:preferences', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/debug', + ]); + + my $participants = { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'cassandane@example.com', + }, + }, + "att1" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'bugs@looneytunes.com', + }, + }, + "att2" => { + "name" => "Road Runner", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'rr@looneytunes.com', + }, + }, + }; + + # clean notification cache + $self->{instance}->getnotify(); + + xlog $self, "create scheduled event"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + Default => JSON::true, + }, + "sequence" => 1, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2022-11-23T16:45:00", + "recurrenceRules" => [ { + "\@type" => "RecurrenceRule", + "frequency" => "weekly" + } ], + "timeZone" => "Australia/Melbourne", + "duration" => "PT1H", + "replyTo" => { imip => "mailto:cassandane\@example.com" }, + "participants" => $participants, + "recurrenceOverrides" => { + "2022-11-30T16:45:00" => { + "start" => "2022-12-01T16:45:00", + } + } + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "verify invitation sent from organizer to attendees"; + my $data = $self->{instance}->getnotify(); + my @imips = grep { $_->{METHOD} eq 'imip' } @$data; + my $imip = $imips[0]; + $self->assert_not_null($imip); + + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; + + $self->assert_str_equals("CalendarEvent/set", + $payload->{schedulingMechanism}); + $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); + $self->assert_matches(qr/(bugs|rr)\@looneytunes.com/, $payload->{recipient}); + $self->assert_num_equals(1, $payload->{patch}{sequence}); + $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert($ical =~ "SEQUENCE:1"); + $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); + + $imip = $imips[1]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("CalendarEvent/set", + $payload->{schedulingMechanism}); + $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); + $self->assert_matches(qr/(bugs|rr)\@looneytunes.com/, $payload->{recipient}); + $self->assert_num_equals(1, $payload->{patch}{sequence}); + $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert($ical =~ "SEQUENCE:1"); + $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); + + xlog $self, "set attendee status"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/participantReply', + { + eventId => $id, + participantEmail => "bugs\@looneytunes.com", + updates => { + participationStatus => "accepted" + } + }, + "R2" + ] ]); + $self->assert_str_equals("1.1", $res->[0][1]{scheduleStatus}); + + xlog $self, "verify reply sent from attendee to organizer"; + $data = $self->{instance}->getnotify(); + @imips = grep { $_->{METHOD} eq 'imip' } @$data; + $imip = $imips[0]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("CalendarEvent/participantReply", + $payload->{schedulingMechanism}); + $self->assert_str_equals("bugs\@looneytunes.com", $payload->{sender}); + $self->assert_str_equals("cassandane\@example.com", $payload->{recipient}); + $self->assert_num_equals(1, $payload->{jsevent}{sequence}); + $self->assert($ical =~ "METHOD:REPLY"); + $self->assert($ical =~ "SEQUENCE:1"); + $self->assert($ical =~ "PARTSTAT=ACCEPTED"); + + xlog $self, "verify updated request sent from organizer to attendee"; + $imip = $imips[1]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("CalendarEvent/participantReply", + $payload->{schedulingMechanism}); + $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); + $self->assert_str_equals("bugs\@looneytunes.com", $payload->{recipient}); + $self->assert_num_equals(1, $payload->{jsevent}{sequence}); + $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert($ical =~ "SEQUENCE:1"); + $self->assert($ical =~ "PARTSTAT=ACCEPTED"); + + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min >= 9)) { + xlog $self, "verify no other update requests from organizer"; + $imip = $imips[2]; + $self->assert_null($imip); + + xlog $self, "actually update attendee status on the event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id => { "participants/att1/participationStatus" => "accepted" } + } + }, + "R1" + ] ]); + + xlog $self, "set attendee status again"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/participantReply', + { + eventId => $id, participantEmail => "bugs\@looneytunes.com", - updates => { - participationStatus => "accepted" + updates => { + participationStatus => "accepted" } - }, "R2"]]); - $self->assert_str_equals("1.1", $res->[0][1]{scheduleStatus}); + }, + "R2" + ] ]); + $self->assert_str_equals("2.0", $res->[0][1]{scheduleStatus}); - xlog $self, "verify reply sent from attendee to organizer"; - $data = $self->{instance}->getnotify(); + xlog $self, "verify that NO iMIP messages are sent to organizer/attendee"; + $data = $self->{instance}->getnotify(); @imips = grep { $_->{METHOD} eq 'imip' } @$data; - $imip = $imips[0]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("CalendarEvent/participantReply", - $payload->{schedulingMechanism}); - $self->assert_str_equals("bugs\@looneytunes.com", $payload->{sender}); - $self->assert_str_equals("cassandane\@example.com", $payload->{recipient}); - $self->assert_num_equals(1, $payload->{jsevent}{sequence}); - $self->assert($ical =~ "METHOD:REPLY"); - $self->assert($ical =~ "SEQUENCE:1"); - $self->assert($ical =~ "PARTSTAT=ACCEPTED"); - - xlog $self, "verify updated request sent from organizer to attendee"; - $imip = $imips[1]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("CalendarEvent/participantReply", - $payload->{schedulingMechanism}); - $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); - $self->assert_str_equals("bugs\@looneytunes.com", $payload->{recipient}); - $self->assert_num_equals(1, $payload->{jsevent}{sequence}); - $self->assert($ical =~ "METHOD:REQUEST"); - $self->assert($ical =~ "SEQUENCE:1"); - $self->assert($ical =~ "PARTSTAT=ACCEPTED"); - - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min >= 9)) { - xlog $self, "verify no other update requests from organizer"; - $imip = $imips[2]; - $self->assert_null($imip); - - xlog $self, "actually update attendee status on the event"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - update => { - $id => { "participants/att1/participationStatus" => "accepted" } - } - }, "R1"]]); - - xlog $self, "set attendee status again"; - $res = $jmap->CallMethods([['CalendarEvent/participantReply', { - eventId => $id, - participantEmail => "bugs\@looneytunes.com", - updates => { - participationStatus => "accepted" - } - }, "R2"]]); - $self->assert_str_equals("2.0", $res->[0][1]{scheduleStatus}); - - xlog $self, "verify that NO iMIP messages are sent to organizer/attendee"; - $data = $self->{instance}->getnotify(); - @imips = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($imips); - } - - xlog $self, "set attendee status on override"; - $res = $jmap->CallMethods([['CalendarEvent/participantReply', { - eventId => encode_eventid(substr($id, 2), "20221130T164500"), + $self->assert_null($imips); + } + + xlog $self, "set attendee status on override"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/participantReply', + { + eventId => encode_eventid(substr($id, 2), "20221130T164500"), + participantEmail => "rr\@looneytunes.com", + updates => { + participationStatus => "declined" + } + }, + "R2" + ] ]); + $self->assert_str_equals("1.1", $res->[0][1]{scheduleStatus}); + + xlog $self, "verify reply sent from attendee to organizer"; + $data = $self->{instance}->getnotify(); + @imips = grep { $_->{METHOD} eq 'imip' } @$data; + $imip = $imips[0]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("CalendarEvent/participantReply", + $payload->{schedulingMechanism}); + $self->assert_str_equals("rr\@looneytunes.com", $payload->{sender}); + $self->assert_str_equals("cassandane\@example.com", $payload->{recipient}); + $self->assert_num_equals(2, $payload->{jsevent}{sequence}); + $self->assert($ical =~ "METHOD:REPLY"); + $self->assert($ical =~ "SEQUENCE:2"); + $self->assert($ical =~ "PARTSTAT=DECLINED"); + + xlog $self, "verify updated request sent from organizer to attendee"; + $imip = $imips[1]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("CalendarEvent/participantReply", + $payload->{schedulingMechanism}); + $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); + $self->assert_str_equals("rr\@looneytunes.com", $payload->{recipient}); + $self->assert_num_equals(2, $payload->{jsevent}{sequence}); + $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert($ical =~ "SEQUENCE:2"); + $self->assert($ical =~ "PARTSTAT=DECLINED"); + + if ($maj > 3 || ($maj == 3 && $min >= 9)) { + xlog $self, "verify no other update requests from organizer"; + $imip = $imips[2]; + $self->assert_null($imip); + + xlog $self, "actually update attendee status on the event"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id => { "participants/att2/participationStatus" => "declined" } + } + }, + "R1" + ] ]); + + xlog $self, "set attendee status again"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/participantReply', + { + eventId => encode_eventid(substr($id, 2), "20221130T164500"), participantEmail => "rr\@looneytunes.com", - updates => { - participationStatus => "declined" + updates => { + participationStatus => "declined" } - }, "R2"]]); - $self->assert_str_equals("1.1", $res->[0][1]{scheduleStatus}); + }, + "R2" + ] ]); + $self->assert_str_equals("2.0", $res->[0][1]{scheduleStatus}); - xlog $self, "verify reply sent from attendee to organizer"; - $data = $self->{instance}->getnotify(); + xlog $self, "verify that NO iMIP messages are sent to organizer/attendee"; + $data = $self->{instance}->getnotify(); @imips = grep { $_->{METHOD} eq 'imip' } @$data; - $imip = $imips[0]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("CalendarEvent/participantReply", - $payload->{schedulingMechanism}); - $self->assert_str_equals("rr\@looneytunes.com", $payload->{sender}); - $self->assert_str_equals("cassandane\@example.com", $payload->{recipient}); - $self->assert_num_equals(2, $payload->{jsevent}{sequence}); - $self->assert($ical =~ "METHOD:REPLY"); - $self->assert($ical =~ "SEQUENCE:2"); - $self->assert($ical =~ "PARTSTAT=DECLINED"); - - xlog $self, "verify updated request sent from organizer to attendee"; - $imip = $imips[1]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("CalendarEvent/participantReply", - $payload->{schedulingMechanism}); - $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); - $self->assert_str_equals("rr\@looneytunes.com", $payload->{recipient}); - $self->assert_num_equals(2, $payload->{jsevent}{sequence}); - $self->assert($ical =~ "METHOD:REQUEST"); - $self->assert($ical =~ "SEQUENCE:2"); - $self->assert($ical =~ "PARTSTAT=DECLINED"); - - if ($maj > 3 || ($maj == 3 && $min >= 9)) { - xlog $self, "verify no other update requests from organizer"; - $imip = $imips[2]; - $self->assert_null($imip); - - xlog $self, "actually update attendee status on the event"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { - update => { - $id => { "participants/att2/participationStatus" => "declined" } - } - }, "R1"]]); - - xlog $self, "set attendee status again"; - $res = $jmap->CallMethods([['CalendarEvent/participantReply', { - eventId => encode_eventid(substr($id, 2), "20221130T164500"), - participantEmail => "rr\@looneytunes.com", - updates => { - participationStatus => "declined" - } - }, "R2"]]); - $self->assert_str_equals("2.0", $res->[0][1]{scheduleStatus}); - - xlog $self, "verify that NO iMIP messages are sent to organizer/attendee"; - $data = $self->{instance}->getnotify(); - @imips = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($imips); - } + $self->assert_null($imips); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_standalone b/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_standalone index 02705284ec..fff81d7b0b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_standalone +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_participantreply_standalone @@ -2,149 +2,164 @@ use Cassandane::Tiny; sub test_calendarevent_participantreply_standalone - :min_version_3_7 :needs_component_jmap :NoStartInstances -{ - my ($self) = @_; - - my $instance = $self->{instance}; - $instance->{config}->set(defaultdomain => 'internal'); - $instance->{config}->set(calendar_user_address_set => 'internal'); - - $self->_start_instances(); - $self->_setup_http_service_objects(); - - my $jmap = $self->{jmap}; - $jmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'urn:ietf:params:jmap:calendars:preferences', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/debug', - ]); - - my $participants = { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'cassandane@example.com', - }, - }, - "att" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'bugs@looneytunes.com', + : min_version_3_7 : needs_component_jmap : NoStartInstances { + my ($self) = @_; + + my $instance = $self->{instance}; + $instance->{config}->set(defaultdomain => 'internal'); + $instance->{config}->set(calendar_user_address_set => 'internal'); + + $self->_start_instances(); + $self->_setup_http_service_objects(); + + my $jmap = $self->{jmap}; + $jmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'urn:ietf:params:jmap:calendars:preferences', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/debug', + ]); + + my $participants = { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'cassandane@example.com', + }, + }, + "att" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'bugs@looneytunes.com', + }, + }, + }; + + # clean notification cache + $self->{instance}->getnotify(); + + xlog "Create scheduled standalone instance"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance1 => { + calendarIds => { + 'Default' => JSON::true, }, + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-01-01T01:01:01', + recurrenceIdTimeZone => 'Europe/London', + participants => $participants, + }, }, - }; - - # clean notification cache - $self->{instance}->getnotify(); - - xlog "Create scheduled standalone instance"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-01-01T01:01:01', - recurrenceIdTimeZone => 'Europe/London', - participants => $participants, - }, - }, - }, 'R1'], - ]); - my $id = $res->[0][1]{created}{instance1}{id}; - - xlog $self, "verify invitation sent from organizer to attendees"; - my $data = $self->{instance}->getnotify(); - my @imips = grep { $_->{METHOD} eq 'imip' } @$data; - my $imip = $imips[0]; - $self->assert_not_null($imip); - - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; - - $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); - $self->assert_str_equals("bugs\@looneytunes.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REQUEST"); - $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); - - xlog $self, "set attendee status"; - $res = $jmap->CallMethods([['CalendarEvent/participantReply', { - eventId => $id, + }, + 'R1' + ], + ]); + my $id = $res->[0][1]{created}{instance1}{id}; + + xlog $self, "verify invitation sent from organizer to attendees"; + my $data = $self->{instance}->getnotify(); + my @imips = grep { $_->{METHOD} eq 'imip' } @$data; + my $imip = $imips[0]; + $self->assert_not_null($imip); + + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; + + $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); + $self->assert_str_equals("bugs\@looneytunes.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert($ical =~ "PARTSTAT=NEEDS-ACTION"); + + xlog $self, "set attendee status"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/participantReply', + { + eventId => $id, + participantEmail => "bugs\@looneytunes.com", + updates => { + participationStatus => "accepted" + } + }, + "R2" + ] ]); + + xlog $self, "verify reply sent from attendee to organizer"; + $data = $self->{instance}->getnotify(); + @imips = grep { $_->{METHOD} eq 'imip' } @$data; + $imip = $imips[0]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("bugs\@looneytunes.com", $payload->{sender}); + $self->assert_str_equals("cassandane\@example.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REPLY"); + $self->assert($ical =~ "PARTSTAT=ACCEPTED"); + + xlog $self, "verify updated request sent from organizer to attendee"; + $imip = $imips[1]; + $self->assert_not_null($imip); + + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; + + $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); + $self->assert_str_equals("bugs\@looneytunes.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert($ical =~ "PARTSTAT=ACCEPTED"); + + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min >= 9)) { + xlog $self, "verify no other update requests from organizer"; + $imip = $imips[2]; + $self->assert_null($imip); + + xlog $self, "actually update attendee status on the event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id => { "participants/att/participationStatus" => "accepted" } + } + }, + "R1" + ] ]); + + xlog $self, "set attendee status again"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/participantReply', + { + eventId => $id, participantEmail => "bugs\@looneytunes.com", - updates => { - participationStatus => "accepted" + updates => { + participationStatus => "accepted" } - }, "R2"]]); + }, + "R2" + ] ]); + $self->assert_str_equals("2.0", $res->[0][1]{scheduleStatus}); - xlog $self, "verify reply sent from attendee to organizer"; - $data = $self->{instance}->getnotify(); + xlog $self, "verify that NO iMIP messages are sent to organizer/attendee"; + $data = $self->{instance}->getnotify(); @imips = grep { $_->{METHOD} eq 'imip' } @$data; - $imip = $imips[0]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("bugs\@looneytunes.com", $payload->{sender}); - $self->assert_str_equals("cassandane\@example.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REPLY"); - $self->assert($ical =~ "PARTSTAT=ACCEPTED"); - - xlog $self, "verify updated request sent from organizer to attendee"; - $imip = $imips[1]; - $self->assert_not_null($imip); - - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; - - $self->assert_str_equals("cassandane\@example.com", $payload->{sender}); - $self->assert_str_equals("bugs\@looneytunes.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REQUEST"); - $self->assert($ical =~ "PARTSTAT=ACCEPTED"); - - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min >= 9)) { - xlog $self, "verify no other update requests from organizer"; - $imip = $imips[2]; - $self->assert_null($imip); - - xlog $self, "actually update attendee status on the event"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - update => { - $id => { "participants/att/participationStatus" => "accepted" } - } - }, "R1"]]); - - xlog $self, "set attendee status again"; - $res = $jmap->CallMethods([['CalendarEvent/participantReply', { - eventId => $id, - participantEmail => "bugs\@looneytunes.com", - updates => { - participationStatus => "accepted" - } - }, "R2"]]); - $self->assert_str_equals("2.0", $res->[0][1]{scheduleStatus}); - - xlog $self, "verify that NO iMIP messages are sent to organizer/attendee"; - $data = $self->{instance}->getnotify(); - @imips = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($imips); - } + $self->assert_null($imips); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query index d8c4a49e57..68df6f2987 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query @@ -2,286 +2,312 @@ use Cassandane::Tiny; sub test_calendarevent_query - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my ($maj, $min) = Cassandane::Instance->get_version(); + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my ($maj, $min) = Cassandane::Instance->get_version(); - xlog $self, "create calendars"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendarA => { - name => "A", - }, - calendarB => { - name => "B", - } - } - }, "R1"] - ]); - my $calendarIdA = $res->[0][1]{created}{calendarA}{id}; - $self->assert_not_null($calendarIdA); - my $calendarIdB = $res->[0][1]{created}{calendarB}{id}; - $self->assert_not_null($calendarIdB); + xlog $self, "create calendars"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + calendarA => { + name => "A", + }, + calendarB => { + name => "B", + } + } + }, + "R1" + ] ]); + my $calendarIdA = $res->[0][1]{created}{calendarA}{id}; + $self->assert_not_null($calendarIdA); + my $calendarIdB = $res->[0][1]{created}{calendarB}{id}; + $self->assert_not_null($calendarIdB); - my %eventA1 = ( - uid => 'a1-03df209b28-4005-a458-751e2f6058b5' - ); - my %eventA2 = ( - uid => 'a2-73e05c2f12fa-43c4-a17f-9c6e35ddd8' - ); - my %eventB1 = ( - uid => 'b1-8528b44b7cdd-4867-85f0-09746080d9' - ); + my %eventA1 = (uid => 'a1-03df209b28-4005-a458-751e2f6058b5'); + my %eventA2 = (uid => 'a2-73e05c2f12fa-43c4-a17f-9c6e35ddd8'); + my %eventB1 = (uid => 'b1-8528b44b7cdd-4867-85f0-09746080d9'); - xlog $self, "create events"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventA1 => { - calendarIds => { - $calendarIdA => JSON::true, - }, - uid => $eventA1{uid}, - title => 'eventA1', - description => 'test', - start => '2023-01-01T01:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - }, - eventB1 => { - calendarIds => { - $calendarIdB => JSON::true, - }, - uid => $eventB1{uid}, - title => 'eventB1', - description => 'test', - start => '2023-02-01T01:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - }, - eventA2 => { - calendarIds => { - $calendarIdA => JSON::true, - }, - uid => $eventA2{uid}, - title => 'eventA2', - description => 'test', - start => '2023-03-01T01:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - }, - } - }, 'R1'] - ]); + xlog $self, "create events"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + eventA1 => { + calendarIds => { + $calendarIdA => JSON::true, + }, + uid => $eventA1{uid}, + title => 'eventA1', + description => 'test', + start => '2023-01-01T01:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + }, + eventB1 => { + calendarIds => { + $calendarIdB => JSON::true, + }, + uid => $eventB1{uid}, + title => 'eventB1', + description => 'test', + start => '2023-02-01T01:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + }, + eventA2 => { + calendarIds => { + $calendarIdA => JSON::true, + }, + uid => $eventA2{uid}, + title => 'eventA2', + description => 'test', + start => '2023-03-01T01:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + }, + } + }, + 'R1' + ] ]); - $eventA1{id} = $res->[0][1]{created}{eventA1}{id}; - $self->assert_not_null($eventA1{id}); - $eventA2{id} = $res->[0][1]{created}{eventA2}{id}; - $self->assert_not_null($eventA2{id}); - $eventB1{id} = $res->[0][1]{created}{eventB1}{id}; - $self->assert_not_null($eventB1{id}); + $eventA1{id} = $res->[0][1]{created}{eventA1}{id}; + $self->assert_not_null($eventA1{id}); + $eventA2{id} = $res->[0][1]{created}{eventA2}{id}; + $self->assert_not_null($eventA2{id}); + $eventB1{id} = $res->[0][1]{created}{eventB1}{id}; + $self->assert_not_null($eventB1{id}); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my @testCases = ({ - filter => undef, - wantIds => [$eventA1{id}, $eventA2{id}, $eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - before => '2023-03-01T01:00:00', - }, - wantIds => [$eventA1{id}, $eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { + my @testCases = ( + { + filter => undef, + wantIds => [ $eventA1{id}, $eventA2{id}, $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + before => '2023-03-01T01:00:00', + }, + wantIds => [ $eventA1{id}, $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + after => '2023-01-01T02:00:00', + }, + wantIds => [ $eventA2{id}, $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + after => '2023-01-01T02:00:00', + before => '2023-03-01T01:00:00', + }, + wantIds => [ $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + operator => 'AND', + conditions => [ + { after => '2023-01-01T02:00:00', - }, - wantIds => [$eventA2{id}, $eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - after => '2023-01-01T02:00:00', + }, + { before => '2023-03-01T01:00:00', + } + ], + }, + wantIds => [ $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + operator => 'NOT', + conditions => [ { + after => '2023-01-01T02:00:00', + } ], + }, + wantIds => [ $eventA1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + uid => $eventA2{uid}, + }, + wantIds => [ $eventA2{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + operator => 'NOT', + conditions => [ { + uid => $eventA2{uid}, + } ], + }, + wantIds => [ $eventA1{id}, $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + operator => 'OR', + conditions => [ + { + uid => $eventA1{uid}, + }, + { + uid => $eventB1{uid}, + } + ], + }, + wantIds => [ $eventA1{id}, $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + inCalendars => [ $calendarIdA, $calendarIdB ], + }, + wantIds => [ $eventA1{id}, $eventA2{id}, $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + operator => 'NOT', + conditions => [ { + inCalendars => [$calendarIdA], + } ], + }, + wantIds => [ $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + operator => 'OR', + conditions => [ { + inCalendars => [ $calendarIdA, $calendarIdB ], + } ], + }, + wantIds => [ $eventA1{id}, $eventA2{id}, $eventB1{id} ], + wantFastPath => JSON::true, + }, + { + filter => { + operator => 'AND', + conditions => [ + { + inCalendars => [$calendarIdA], + }, + { + text => 'test', + } + ], + }, + wantIds => [ $eventA1{id}, $eventA2{id} ], + wantFastPath => JSON::false, + }, + { + filter => undef, + position => 1, + limit => 1, + wantTotal => 3, + wantIds => [ $eventA2{id} ], + wantFastPath => JSON::true, + }, + { + filter => undef, + position => -1, + wantTotal => 3, + wantIds => [ $eventB1{id} ], + wantFastPath => JSON::false, + }, + { + filter => { + operator => 'NOT', + conditions => [ { + blarg => 'foo', # invalid - blarg is not a calevent property + } ], + }, + wantErr => { + type => 'invalidArguments', + arguments => ['filter/conditions[0]/blarg'], + }, + }, + { + filter => { + operator => 'NOT', + conditions => { # invalid - object rather than array + blarg => 'foo', }, - wantIds => [$eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - operator => 'AND', - conditions => [{ - after => '2023-01-01T02:00:00', - }, { - before => '2023-03-01T01:00:00', - }], - }, - wantIds => [$eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - operator => 'NOT', - conditions => [{ - after => '2023-01-01T02:00:00', - }], - }, - wantIds => [$eventA1{id}], - wantFastPath => JSON::true, - }, { - filter => { - uid => $eventA2{uid}, - }, - wantIds => [$eventA2{id}], - wantFastPath => JSON::true, - }, { - filter => { - operator => 'NOT', - conditions => [{ - uid => $eventA2{uid}, - }], - }, - wantIds => [$eventA1{id}, $eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - operator => 'OR', - conditions => [{ - uid => $eventA1{uid}, - }, { - uid => $eventB1{uid}, - }], - }, - wantIds => [$eventA1{id}, $eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - inCalendars => [$calendarIdA, $calendarIdB], - }, - wantIds => [$eventA1{id}, $eventA2{id}, $eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - operator => 'NOT', - conditions => [{ - inCalendars => [$calendarIdA], - }], - }, - wantIds => [$eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - operator => 'OR', - conditions => [{ - inCalendars => [$calendarIdA, $calendarIdB], - }], - }, - wantIds => [$eventA1{id}, $eventA2{id}, $eventB1{id}], - wantFastPath => JSON::true, - }, { - filter => { - operator => 'AND', - conditions => [{ - inCalendars => [$calendarIdA], - }, { - text => 'test', - }], - }, - wantIds => [$eventA1{id}, $eventA2{id}], - wantFastPath => JSON::false, - }, { - filter => undef, - position => 1, - limit => 1, - wantTotal => 3, - wantIds => [$eventA2{id}], - wantFastPath => JSON::true, - }, { - filter => undef, - position => -1, - wantTotal => 3, - wantIds => [$eventB1{id}], - wantFastPath => JSON::false, - }, { - filter => { - operator => 'NOT', - conditions => [{ - blarg => 'foo', # invalid - blarg is not a calevent property - }], - }, - wantErr => { - type => 'invalidArguments', - arguments => [ 'filter/conditions[0]/blarg' ], - }, - }, { - filter => { - operator => 'NOT', - conditions => { # invalid - object rather than array - blarg => 'foo', - }, - }, - wantErr => { - type => 'invalidArguments', - arguments => [ 'filter/conditions' ], - }, - }, { - filter => { - operator => 'NOT', - # invalid - no conditions - }, - wantErr => { - type => 'invalidArguments', - arguments => [ 'filter/conditions' ], - }, - }, { - filter => { - operator => 'BLARG', # invalid operator - conditions => [{ - after => '2023-01-01T02:00:00', - }], - }, - wantErr => { - type => 'invalidArguments', - arguments => [ 'filter/operator' ], - }, - }); + }, + wantErr => { + type => 'invalidArguments', + arguments => ['filter/conditions'], + }, + }, + { + filter => { + operator => 'NOT', + # invalid - no conditions + }, + wantErr => { + type => 'invalidArguments', + arguments => ['filter/conditions'], + }, + }, + { + filter => { + operator => 'BLARG', # invalid operator + conditions => [ { + after => '2023-01-01T02:00:00', + } ], + }, + wantErr => { + type => 'invalidArguments', + arguments => ['filter/operator'], + }, + } + ); - for my $tc (@testCases) { - my $q = { - filter => $tc->{filter}, - sort => [{ - property => 'uid', - }], - }; + for my $tc (@testCases) { + my $q = { + filter => $tc->{filter}, + sort => [ { + property => 'uid', + } ], + }; - if (defined $tc->{position}) { - $q->{position} = $tc->{position}; - } + if (defined $tc->{position}) { + $q->{position} = $tc->{position}; + } - if (defined $tc->{limit}) { - $q->{limit} = $tc->{limit}; - } + if (defined $tc->{limit}) { + $q->{limit} = $tc->{limit}; + } - $res = $jmap->CallMethods([ - ['CalendarEvent/query', $q, 'R1'], - ]); - if ($tc->{wantErr}) { - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_deep_equals($tc->{wantErr}, $res->[0][1]); - } else { - my $wantTotal = defined $tc->{wantTotal} ? - $tc->{wantTotal} : scalar @{$tc->{wantIds}}; - $self->assert_num_equals($wantTotal, $res->[0][1]{total}); - $self->assert_deep_equals($tc->{wantIds}, $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ 'CalendarEvent/query', $q, 'R1' ], ]); + if ($tc->{wantErr}) { + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_deep_equals($tc->{wantErr}, $res->[0][1]); + } else { + my $wantTotal + = defined $tc->{wantTotal} + ? $tc->{wantTotal} + : scalar @{ $tc->{wantIds} }; + $self->assert_num_equals($wantTotal, $res->[0][1]{total}); + $self->assert_deep_equals($tc->{wantIds}, $res->[0][1]{ids}); - if ($maj > 3 || ($maj == 3 && $min > 8)) { - $self->assert_equals($tc->{wantFastPath}, - $res->[0][1]{debug}{isFastPath}); - } - } + if ($maj > 3 || ($maj == 3 && $min > 8)) { + $self->assert_equals($tc->{wantFastPath}, + $res->[0][1]{debug}{isFastPath}); + } } + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_anchor b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_anchor index 1e111bed71..e10ec75031 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_anchor +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_anchor @@ -2,100 +2,111 @@ use Cassandane::Tiny; sub test_calendarevent_query_anchor - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - '1' => { - calendarIds => { - $calid => JSON::true, - }, - 'uid' => 'event1uid', - 'title' => 'event1', - 'start' => '2019-10-01T10:00:00', - 'timeZone' => 'Etc/UTC', - }, - '2' => { - calendarIds => { - $calid => JSON::true, - }, - 'uid' => 'event2uid', - 'title' => 'event2', - 'start' => '2019-10-02T10:00:00', - 'timeZone' => 'Etc/UTC', - }, - '3' => { - calendarIds => { - $calid => JSON::true, - }, - 'uid' => 'event3uid', - 'title' => 'event3', - 'start' => '2019-10-03T10:00:00', - 'timeZone' => 'Etc/UTC', - }, - } - }, 'R1']]); - my $eventId1 = $res->[0][1]{created}{1}{id}; - my $eventId2 = $res->[0][1]{created}{2}{id}; - my $eventId3 = $res->[0][1]{created}{3}{id}; - $self->assert_not_null($eventId1); - $self->assert_not_null($eventId2); - $self->assert_not_null($eventId3); + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + '1' => { + calendarIds => { + $calid => JSON::true, + }, + 'uid' => 'event1uid', + 'title' => 'event1', + 'start' => '2019-10-01T10:00:00', + 'timeZone' => 'Etc/UTC', + }, + '2' => { + calendarIds => { + $calid => JSON::true, + }, + 'uid' => 'event2uid', + 'title' => 'event2', + 'start' => '2019-10-02T10:00:00', + 'timeZone' => 'Etc/UTC', + }, + '3' => { + calendarIds => { + $calid => JSON::true, + }, + 'uid' => 'event3uid', + 'title' => 'event3', + 'start' => '2019-10-03T10:00:00', + 'timeZone' => 'Etc/UTC', + }, + } + }, + 'R1' + ] ]); + my $eventId1 = $res->[0][1]{created}{1}{id}; + my $eventId2 = $res->[0][1]{created}{2}{id}; + my $eventId3 = $res->[0][1]{created}{3}{id}; + $self->assert_not_null($eventId1); + $self->assert_not_null($eventId2); + $self->assert_not_null($eventId3); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - sort => [{ - property => 'start', - isAscending => JSON::true, - }], - anchor => $eventId2, - }, 'R1'] - ]); - $self->assert_deep_equals([$eventId2,$eventId3], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + sort => [ { + property => 'start', + isAscending => JSON::true, + } ], + anchor => $eventId2, + }, + 'R1' + ] ]); + $self->assert_deep_equals([ $eventId2, $eventId3 ], $res->[0][1]{ids}); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - sort => [{ - property => 'start', - isAscending => JSON::true, - }], - anchor => $eventId3, - anchorOffset => -2, - limit => 1, - }, 'R1'] - ]); - $self->assert_deep_equals([$eventId1], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + sort => [ { + property => 'start', + isAscending => JSON::true, + } ], + anchor => $eventId3, + anchorOffset => -2, + limit => 1, + }, + 'R1' + ] ]); + $self->assert_deep_equals([$eventId1], $res->[0][1]{ids}); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - sort => [{ - property => 'start', - isAscending => JSON::true, - }], - anchor => $eventId2, - anchorOffset => -5, - }, 'R1'] - ]); - $self->assert_deep_equals([$eventId1, $eventId2, $eventId3], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + sort => [ { + property => 'start', + isAscending => JSON::true, + } ], + anchor => $eventId2, + anchorOffset => -5, + }, + 'R1' + ] ]); + $self->assert_deep_equals([ $eventId1, $eventId2, $eventId3 ], + $res->[0][1]{ids}); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - sort => [{ - property => 'start', - isAscending => JSON::true, - }], - anchor => $eventId2, - anchorOffset => 5, - }, 'R1'] - ]); - $self->assert_deep_equals([], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + sort => [ { + property => 'start', + isAscending => JSON::true, + } ], + anchor => $eventId2, + anchorOffset => 5, + }, + 'R1' + ] ]); + $self->assert_deep_equals([], $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_date b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_date index 9aeb117975..0349f9aecc 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_date +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_date @@ -2,134 +2,185 @@ use Cassandane::Tiny; sub test_calendarevent_query_date - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - xlog $self, "create events"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - # Start: 2016-01-01 End: 2016-01-03 - "1" => { - calendarIds => { - $calid => JSON::true, - }, - "title" => "1", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2016-01-01T00:00:00", - "duration" => "P3D", - }, - }}, "R1"]]); + xlog $self, "create events"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + # Start: 2016-01-01 End: 2016-01-03 + "1" => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "1", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2016-01-01T00:00:00", + "duration" => "P3D", + }, + } + }, + "R1" + ] ]); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - # Match on start and end day - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T00:00:00", - "before" => "2016-01-03T23:59:59", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Match on start and end day + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T00:00:00", + "before" => "2016-01-03T23:59:59", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # Match after on the first second of the start day - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T00:00:00", - "before" => "2016-01-03T00:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Match after on the first second of the start day + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T00:00:00", + "before" => "2016-01-03T00:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # Match before on the last second of the end day - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-03T23:59:59", - "before" => "2016-01-03T23:59:59", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Match before on the last second of the end day + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-03T23:59:59", + "before" => "2016-01-03T23:59:59", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # Match on interim day - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-02T00:00:00", - "before" => "2016-01-03T00:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Match on interim day + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-02T00:00:00", + "before" => "2016-01-03T00:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # Match on partially overlapping timerange - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2015-12-31T12:00:00", - "before" => "2016-01-01T12:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2015-01-03T12:00:00", - "before" => "2016-01-04T12:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Match on partially overlapping timerange + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2015-12-31T12:00:00", + "before" => "2016-01-01T12:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2015-01-03T12:00:00", + "before" => "2016-01-04T12:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # Difference from the spec: 'before' is defined to be exclusive, but - # a full-day event starting on that day still matches. - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2015-12-31T00:00:00", - "before" => "2016-01-01T00:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Difference from the spec: 'before' is defined to be exclusive, but + # a full-day event starting on that day still matches. + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2015-12-31T00:00:00", + "before" => "2016-01-01T00:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # In DAV db the event ends at 20160104. Test that it isn't returned. - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-04T00:00:00", - "before" => "2016-01-04T23:59:59", - }, - }, "R1"]]); - $self->assert_num_equals(0, $res->[0][1]{total}); + # In DAV db the event ends at 20160104. Test that it isn't returned. + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-04T00:00:00", + "before" => "2016-01-04T23:59:59", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, $res->[0][1]{total}); - # Create an infinite recurring datetime event - $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - # Start: 2017-01-01T08:00:00Z End: eternity - "1" => { - calendarIds => { - $calid => JSON::true, - }, - "title" => "2", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2017-01-01T00:00:00", - "duration" => "P1D", - "recurrenceRules" => [{ - "frequency" => "yearly", - }], - }, - }}, "R1"]]); - # Assert both events are found - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T00:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(2, $res->[0][1]{total}); - # Search close to eternity - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2038-01-01T00:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Create an infinite recurring datetime event + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + # Start: 2017-01-01T08:00:00Z End: eternity + "1" => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "2", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2017-01-01T00:00:00", + "duration" => "P1D", + "recurrenceRules" => [ { + "frequency" => "yearly", + } ], + }, + } + }, + "R1" + ] ]); + # Assert both events are found + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T00:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + # Search close to eternity + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2038-01-01T00:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_datetime b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_datetime index 43618e7031..d21ffeca7b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_datetime +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_datetime @@ -2,113 +2,156 @@ use Cassandane::Tiny; sub test_calendarevent_query_datetime - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - xlog $self, "create events"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - # Start: 2016-01-01T08:00:00Z End: 2016-01-01T09:00:00Z - "1" => { - calendarIds => { - $calid => JSON::true, - }, - "title" => "1", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2016-01-01T09:00:00", - "timeZone" => "Europe/Vienna", - "duration" => "PT1H", - }, - }}, "R1"]]); + xlog $self, "create events"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + # Start: 2016-01-01T08:00:00Z End: 2016-01-01T09:00:00Z + "1" => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "1", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2016-01-01T09:00:00", + "timeZone" => "Europe/Vienna", + "duration" => "PT1H", + }, + } + }, + "R1" + ] ]); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - # Exact start and end match - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T08:00:00", - "before" => "2016-01-01T09:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Exact start and end match + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T08:00:00", + "before" => "2016-01-01T09:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # Check that boundaries are exclusive - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T09:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(0, $res->[0][1]{total}); - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "before" => "2016-01-01T08:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(0, $res->[0][1]{total}); + # Check that boundaries are exclusive + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T09:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, $res->[0][1]{total}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "before" => "2016-01-01T08:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, $res->[0][1]{total}); - # Embedded subrange matches - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T08:15:00", - "before" => "2016-01-01T08:45:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Embedded subrange matches + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T08:15:00", + "before" => "2016-01-01T08:45:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # Overlapping subrange matches - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T08:15:00", - "before" => "2016-01-01T09:15:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T07:45:00", - "before" => "2016-01-01T08:15:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Overlapping subrange matches + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T08:15:00", + "before" => "2016-01-01T09:15:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T07:45:00", + "before" => "2016-01-01T08:15:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - # Create an infinite recurring datetime event - $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - # Start: 2017-01-01T08:00:00Z End: eternity - "1" => { - calendarIds => { - $calid => JSON::true, - }, - "title" => "e", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2017-01-01T09:00:00", - "timeZone" => "Europe/Vienna", - "duration" => "PT1H", - "recurrenceRules" => [{ - "frequency" => "yearly", - }], - }, - }}, "R1"]]); - # Assert both events are found - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2016-01-01T00:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(2, $res->[0][1]{total}); - # Search close to eternity - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "2038-01-01T00:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + # Create an infinite recurring datetime event + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + # Start: 2017-01-01T08:00:00Z End: eternity + "1" => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "e", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2017-01-01T09:00:00", + "timeZone" => "Europe/Vienna", + "duration" => "PT1H", + "recurrenceRules" => [ { + "frequency" => "yearly", + } ], + }, + } + }, + "R1" + ] ]); + # Assert both events are found + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2016-01-01T00:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + # Search close to eternity + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2038-01-01T00:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_deleted_calendar b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_deleted_calendar index 2a66c1c199..b064be78f4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_deleted_calendar +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_deleted_calendar @@ -2,81 +2,97 @@ use Cassandane::Tiny; sub test_calendarevent_query_deleted_calendar - :min_version_3_3 :needs_component_jmap :needs_component_httpd -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap : needs_component_httpd { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "create calendars A and B"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - "1" => { - name => "A", - }, - "2" => { - name => "B", - } - }}, "R1"] - ]); - my $calidA = $res->[0][1]{created}{"1"}{id}; - my $calidB = $res->[0][1]{created}{"2"}{id}; - my $state = $res->[0][1]{newState}; + xlog $self, "create calendars A and B"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "A", + }, + "2" => { + name => "B", + } + } + }, + "R1" + ] ]); + my $calidA = $res->[0][1]{created}{"1"}{id}; + my $calidB = $res->[0][1]{created}{"2"}{id}; + my $state = $res->[0][1]{newState}; - xlog $self, "create event #1 in calendar $calidA and event #2 in calendar $calidB"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - create => { - "1" => { - "calendarIds" => { - $calidA => JSON::true, - }, - "title" => "foo", - "description" => "bar", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2016-07-01T10:00:00", - "timeZone" => "Europe/Vienna", - "duration" => "PT1H", - }, - "2" => { - "calendarIds" => { - $calidB => JSON::true, - }, - "title" => "foo", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2016-01-01T00:00:00", - "duration" => "P2D", - "timeZone" => undef, - } - }}, "R1"]]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; + xlog $self, + "create event #1 in calendar $calidA and event #2 in calendar $calidB"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + "calendarIds" => { + $calidA => JSON::true, + }, + "title" => "foo", + "description" => "bar", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2016-07-01T10:00:00", + "timeZone" => "Europe/Vienna", + "duration" => "PT1H", + }, + "2" => { + "calendarIds" => { + $calidB => JSON::true, + }, + "title" => "foo", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2016-01-01T00:00:00", + "duration" => "P2D", + "timeZone" => undef, + } + } + }, + "R1" + ] ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; - xlog $self, "get filtered calendar event list"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - "filter" => { - "after" => "2015-12-31T00:00:00", - "before" => "2016-12-31T23:59:59" - } - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); + xlog $self, "get filtered calendar event list"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2015-12-31T00:00:00", + "before" => "2016-12-31T23:59:59" + } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); - xlog $self, "CalDAV delete calendar as cassandane"; - $caldav->DeleteCalendar("/dav/calendars/user/cassandane/$calidA"); + xlog $self, "CalDAV delete calendar as cassandane"; + $caldav->DeleteCalendar("/dav/calendars/user/cassandane/$calidA"); - xlog $self, "get filtered calendar event list"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - "filter" => { - "after" => "2015-12-31T00:00:00", - "before" => "2016-12-31T23:59:59" - } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id2, $res->[0][1]{ids}[0]); + xlog $self, "get filtered calendar event list"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "2015-12-31T00:00:00", + "before" => "2016-12-31T23:59:59" + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id2, $res->[0][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_expandrecurrences b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_expandrecurrences index 5e05308468..3305bfe544 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_expandrecurrences +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_expandrecurrences @@ -2,84 +2,93 @@ use Cassandane::Tiny; sub test_calendarevent_query_expandrecurrences - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - xlog $self, "create events"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - $calid => JSON::true, - }, - uid => 'event1uid', - title => "event1", - description => "", - freeBusyStatus => "busy", - start => "2019-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'weekly', - count => 3, - }, { - frequency => 'hourly', - byHour => [9, 14, 22], - count => 2, - }], - recurrenceOverrides => { - '2019-01-08T09:00:00' => { - start => '2019-01-08T12:00:00', - }, - '2019-01-03T13:00:00' => { - title => 'rdate', - }, - }, - }, - "2" => { - calendarIds => { - $calid => JSON::true, - }, - uid => 'event2uid', - title => "event2", - description => "", - freeBusyStatus => "busy", - start => "2019-01-02T11:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - }, + xlog $self, "create events"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calid => JSON::true, + }, + uid => 'event1uid', + title => "event1", + description => "", + freeBusyStatus => "busy", + start => "2019-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ + { + frequency => 'weekly', + count => 3, + }, + { + frequency => 'hourly', + byHour => [ 9, 14, 22 ], + count => 2, } - }, 'R1'] - ]); + ], + recurrenceOverrides => { + '2019-01-08T09:00:00' => { + start => '2019-01-08T12:00:00', + }, + '2019-01-03T13:00:00' => { + title => 'rdate', + }, + }, + }, + "2" => { + calendarIds => { + $calid => JSON::true, + }, + uid => 'event2uid', + title => "event2", + description => "", + freeBusyStatus => "busy", + start => "2019-01-02T11:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + }, + } + }, + 'R1' + ] ]); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - filter => { - before => '2019-02-01T00:00:00', - }, - sort => [{ - property => 'start', - isAscending => JSON::false, - }], - expandRecurrences => JSON::true, - }, 'R1'] - ]); - $self->assert_num_equals(6, $res->[0][1]{total}); - $self->assert_deep_equals([ - encode_eventid('event1uid','20190115T090000'), - encode_eventid('event1uid','20190108T090000'), - encode_eventid('event1uid','20190103T130000'), - encode_eventid('event2uid'), - encode_eventid('event1uid','20190101T140000'), - encode_eventid('event1uid','20190101T090000'), - ], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + filter => { + before => '2019-02-01T00:00:00', + }, + sort => [ { + property => 'start', + isAscending => JSON::false, + } ], + expandRecurrences => JSON::true, + }, + 'R1' + ] ]); + $self->assert_num_equals(6, $res->[0][1]{total}); + $self->assert_deep_equals( + [ + encode_eventid('event1uid', '20190115T090000'), + encode_eventid('event1uid', '20190108T090000'), + encode_eventid('event1uid', '20190103T130000'), + encode_eventid('event2uid'), + encode_eventid('event1uid', '20190101T140000'), + encode_eventid('event1uid', '20190101T090000'), + ], + $res->[0][1]{ids} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_expandrecurrences_with_exrule b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_expandrecurrences_with_exrule index 0221b24554..18ce39266f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_expandrecurrences_with_exrule +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_expandrecurrences_with_exrule @@ -2,71 +2,80 @@ use Cassandane::Tiny; sub test_calendarevent_query_expandrecurrences_with_exrule - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - xlog $self, "create events"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - $calid => JSON::true, - }, - uid => 'event1uid', - title => "event1", - description => "", - freeBusyStatus => "busy", - start => "2020-08-04T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'weekly', - interval => 4, - }], - excludedRecurrenceRules => [{ - frequency => 'monthly', - byMonthDay => [1], - }, { - frequency => 'monthly', - byMonthDay => [4,22], - }], - recurrenceOverrides => { - '2021-01-01T09:00:00' => { - title => 'rdate overrides exrule', - }, - }, - }, + xlog $self, "create events"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calid => JSON::true, + }, + uid => 'event1uid', + title => "event1", + description => "", + freeBusyStatus => "busy", + start => "2020-08-04T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'weekly', + interval => 4, + } ], + excludedRecurrenceRules => [ + { + frequency => 'monthly', + byMonthDay => [1], + }, + { + frequency => 'monthly', + byMonthDay => [ 4, 22 ], } - }, 'R1'] - ]); + ], + recurrenceOverrides => { + '2021-01-01T09:00:00' => { + title => 'rdate overrides exrule', + }, + }, + }, + } + }, + 'R1' + ] ]); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - filter => { - before => '2021-02-01T00:00:00', - }, - sort => [{ - property => 'start', - isAscending => JSON::false, - }], - expandRecurrences => JSON::true, - }, 'R1'] - ]); - $self->assert_num_equals(5, $res->[0][1]{total}); - $self->assert_deep_equals([ - encode_eventid('event1uid','20210119T090000'), - encode_eventid('event1uid','20210101T090000'), - encode_eventid('event1uid','20201124T090000'), - encode_eventid('event1uid','20201027T090000'), - encode_eventid('event1uid','20200929T090000'), - ], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + filter => { + before => '2021-02-01T00:00:00', + }, + sort => [ { + property => 'start', + isAscending => JSON::false, + } ], + expandRecurrences => JSON::true, + }, + 'R1' + ] ]); + $self->assert_num_equals(5, $res->[0][1]{total}); + $self->assert_deep_equals( + [ + encode_eventid('event1uid', '20210119T090000'), + encode_eventid('event1uid', '20210101T090000'), + encode_eventid('event1uid', '20201124T090000'), + encode_eventid('event1uid', '20201027T090000'), + encode_eventid('event1uid', '20200929T090000'), + ], + $res->[0][1]{ids} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_fastpath_position b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_fastpath_position index ab8ce78159..9a3ba6d741 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_fastpath_position +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_fastpath_position @@ -2,53 +2,65 @@ use Cassandane::Tiny; sub test_calendarevent_query_fastpath_position - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/debug', - ]; - - my $events = {}; - - for my $i (0..9) { - $events->{"event$i"} = { - calendarIds => { - 'Default' => JSON::true, - }, - title => "event$i", - start => "2021-01-01T02:00:00", - timeZone => 'Europe/Berlin', - duration => 'PT1H', - }; - } - - my $numEvents = scalar keys %$events; - - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => $events, - }, 'R1'], - ['CalendarEvent/query', { - }, 'R2'], - ], $using); - $self->assert_num_equals($numEvents, scalar keys %{$res->[0][1]{created}}); - - my $eventIds = $res->[1][1]{ids}; - $self->assert_num_equals($numEvents, scalar @${eventIds}); - $self->assert_equals(JSON::true, $res->[1][1]{debug}{isFastPath}); - - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - position => 3, - }, 'R1'], - ], $using); - my @wantIds = @{$eventIds}[3 .. ($numEvents-1)]; - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::true, $res->[0][1]{debug}{isFastPath}); + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/debug', + ]; + + my $events = {}; + + for my $i (0 .. 9) { + $events->{"event$i"} = { + calendarIds => { + 'Default' => JSON::true, + }, + title => "event$i", + start => "2021-01-01T02:00:00", + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }; + } + + my $numEvents = scalar keys %$events; + + my $res = $jmap->CallMethods( + [ + [ + 'CalendarEvent/set', + { + create => $events, + }, + 'R1' + ], + [ 'CalendarEvent/query', {}, 'R2' ], + ], + $using + ); + $self->assert_num_equals($numEvents, scalar keys %{ $res->[0][1]{created} }); + + my $eventIds = $res->[1][1]{ids}; + $self->assert_num_equals($numEvents, scalar @${eventIds}); + $self->assert_equals(JSON::true, $res->[1][1]{debug}{isFastPath}); + + $res = $jmap->CallMethods( + [ + [ + 'CalendarEvent/query', + { + position => 3, + }, + 'R1' + ], + ], + $using + ); + my @wantIds = @{$eventIds}[ 3 .. ($numEvents - 1) ]; + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::true, $res->[0][1]{debug}{isFastPath}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_no_sched_inbox b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_no_sched_inbox index f03f2eaea6..ddcfcc8f39 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_no_sched_inbox +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_no_sched_inbox @@ -2,21 +2,22 @@ use Cassandane::Tiny; sub test_calendarevent_query_no_sched_inbox - :needs_component_sieve :needs_component_httpd :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $caldav = $self->{caldav}; + : needs_component_sieve : needs_component_httpd : needs_component_jmap : + min_version_3_5 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $caldav = $self->{caldav}; - $self->{store}->_select(); - $self->assert_num_equals(1, $imap->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->_select(); + $self->assert_num_equals(1, $imap->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -55,49 +56,65 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Calendar/get', { }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - my $defaultCalendarId = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Calendar/get', {}, 'R1' ], ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + my $defaultCalendarId = $res->[0][1]{list}[0]{id}; - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => ['calendarIds'], - }, 'R2'], - ['CalendarEvent/query', { - filter => { - title => 'test', - }, - }, 'R3'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R3', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => ['calendarIds'], - }, 'R4'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_deep_equals({ - $defaultCalendarId => JSON::true, - }, $res->[1][1]{list}[0]{calendarIds}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{ids}}); - $self->assert_deep_equals({ - $defaultCalendarId => JSON::true, - }, $res->[3][1]{list}[0]{calendarIds}); + $res = $jmap->CallMethods([ + [ 'CalendarEvent/query', {}, 'R1' ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => ['calendarIds'], + }, + 'R2' + ], + [ + 'CalendarEvent/query', + { + filter => { + title => 'test', + }, + }, + 'R3' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R3', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => ['calendarIds'], + }, + 'R4' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_deep_equals( + { + $defaultCalendarId => JSON::true, + }, + $res->[1][1]{list}[0]{calendarIds} + ); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{ids} }); + $self->assert_deep_equals( + { + $defaultCalendarId => JSON::true, + }, + $res->[3][1]{list}[0]{calendarIds} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_shared b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_shared index af8222c5a0..d35fb6b290 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_shared @@ -2,171 +2,217 @@ use Cassandane::Tiny; sub test_calendarevent_query_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); - - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "share calendar home to user"; - $admintalk->setacl("user.manifold.#calendars", cassandane => 'lrswipkxtecdn'); - - # run tests for both the main and shared account - foreach ("cassandane", "manifold") { - my $account = $_; - - xlog $self, "create calendars A and B"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => $account, - create => { - "1" => { - name => "A", color => "coral", sortOrder => 1, isVisible => JSON::true, - }, - "2" => { - name => "B", color => "blue", sortOrder => 1, isVisible => JSON::true - } - }}, "R1"] - ]); - my $calidA = $res->[0][1]{created}{"1"}{id}; - my $calidB = $res->[0][1]{created}{"2"}{id}; - my $state = $res->[0][1]{newState}; - - if ($account eq 'manifold') { - $admintalk->setacl("user.manifold.#calendars.$calidA", cassandane => 'lrswipkxtecdn'); - $admintalk->setacl("user.manifold.#calendars.$calidB", cassandane => 'lrswipkxtecdn'); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admintalk = $self->{adminstore}->get_client(); + + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "share calendar home to user"; + $admintalk->setacl("user.manifold.#calendars", cassandane => 'lrswipkxtecdn'); + + # run tests for both the main and shared account + foreach ("cassandane", "manifold") { + my $account = $_; + + xlog $self, "create calendars A and B"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => $account, + create => { + "1" => { + name => "A", + color => "coral", + sortOrder => 1, + isVisible => JSON::true, + }, + "2" => { + name => "B", + color => "blue", + sortOrder => 1, + isVisible => JSON::true + } } - - xlog $self, "create event #1 in calendar $calidA and event #2 in calendar $calidB"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - accountId => $account, - create => { - "1" => { - calendarIds => { - $calidA => JSON::true, - }, - "title" => "foo", - "description" => "bar", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2016-07-01T10:00:00", - "timeZone" => "Europe/Vienna", - "duration" => "PT1H", - }, - "2" => { - calendarIds => { - $calidB => JSON::true, - }, - "title" => "foo", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2016-01-01T00:00:00", - "duration" => "P2D", - } - }}, "R1"]]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; - - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "get unfiltered calendar event list"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { accountId => $account }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($account, $res->[0][1]{accountId}); - - xlog $self, "get filtered calendar event list with flat filter"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - accountId => $account, - "filter" => { - "after" => "2015-12-31T00:00:00", - "before" => "2016-12-31T23:59:59", - "text" => "foo", - "description" => "bar" - } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - - xlog $self, "get filtered calendar event list"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - accountId => $account, - "filter" => { - "operator" => "AND", - "conditions" => [ - { - "after" => "2015-12-31T00:00:00", - "before" => "2016-12-31T23:59:59" - }, - { - "text" => "foo", - "description" => "bar" - } - ] - } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - - xlog $self, "filter by calendar $calidA"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - accountId => $account, - "filter" => { - "inCalendars" => [ $calidA ], - } - }, "R1"] ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - - xlog $self, "filter by calendar $calidA or $calidB"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - accountId => $account, - "filter" => { - "inCalendars" => [ $calidA, $calidB ], - } - }, "R1"] ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by calendar NOT in $calidA and $calidB"; - $res = $jmap->CallMethods([['CalendarEvent/query', { - accountId => $account, - "filter" => { - "operator" => "NOT", - "conditions" => [{ - "inCalendars" => [ $calidA, $calidB ], - }], - }}, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{ids}}); - - xlog $self, "limit results"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { accountId => $account, limit => 1 }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - - xlog $self, "skip result a position 1"; - $res = $jmap->CallMethods([ ['CalendarEvent/query', { accountId => $account, position => 1 }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + }, + "R1" + ] ]); + my $calidA = $res->[0][1]{created}{"1"}{id}; + my $calidB = $res->[0][1]{created}{"2"}{id}; + my $state = $res->[0][1]{newState}; + + if ($account eq 'manifold') { + $admintalk->setacl("user.manifold.#calendars.$calidA", + cassandane => 'lrswipkxtecdn'); + $admintalk->setacl("user.manifold.#calendars.$calidB", + cassandane => 'lrswipkxtecdn'); } + + xlog $self, + "create event #1 in calendar $calidA and event #2 in calendar $calidB"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => $account, + create => { + "1" => { + calendarIds => { + $calidA => JSON::true, + }, + "title" => "foo", + "description" => "bar", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2016-07-01T10:00:00", + "timeZone" => "Europe/Vienna", + "duration" => "PT1H", + }, + "2" => { + calendarIds => { + $calidB => JSON::true, + }, + "title" => "foo", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2016-01-01T00:00:00", + "duration" => "P2D", + } + } + }, + "R1" + ] ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; + + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "get unfiltered calendar event list"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/query', { accountId => $account }, "R1" ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($account, $res->[0][1]{accountId}); + + xlog $self, "get filtered calendar event list with flat filter"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + accountId => $account, + "filter" => { + "after" => "2015-12-31T00:00:00", + "before" => "2016-12-31T23:59:59", + "text" => "foo", + "description" => "bar" + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + + xlog $self, "get filtered calendar event list"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + accountId => $account, + "filter" => { + "operator" => "AND", + "conditions" => [ + { + "after" => "2015-12-31T00:00:00", + "before" => "2016-12-31T23:59:59" + }, + { + "text" => "foo", + "description" => "bar" + } + ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + + xlog $self, "filter by calendar $calidA"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + accountId => $account, + "filter" => { + "inCalendars" => [$calidA], + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + + xlog $self, "filter by calendar $calidA or $calidB"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + accountId => $account, + "filter" => { + "inCalendars" => [ $calidA, $calidB ], + } + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by calendar NOT in $calidA and $calidB"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + accountId => $account, + "filter" => { + "operator" => "NOT", + "conditions" => [ { + "inCalendars" => [ $calidA, $calidB ], + } ], + } + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "limit results"; + $res + = $jmap->CallMethods( + [ [ 'CalendarEvent/query', { accountId => $account, limit => 1 }, "R1" ] ] + ); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "skip result a position 1"; + $res = $jmap->CallMethods( + [ [ + 'CalendarEvent/query', { accountId => $account, position => 1 }, + "R1" + ] ] + ); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_sort b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_sort index 4688c1528d..73f89e5787 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_sort +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_sort @@ -2,62 +2,68 @@ use Cassandane::Tiny; sub test_calendarevent_query_sort - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - '1' => { - calendarIds => { - $calid => JSON::true, - }, - 'uid' => 'event1uid', - 'title' => 'event1', - 'start' => '2019-10-01T10:00:00', - 'timeZone' => 'Etc/UTC', - }, - '2' => { - calendarIds => { - $calid => JSON::true, - }, - 'uid' => 'event2uid', - 'title' => 'event2', - 'start' => '2018-10-01T12:00:00', - 'timeZone' => 'Etc/UTC', - }, - } - }, 'R1']]); - my $eventId1 = $res->[0][1]{created}{1}{id}; - my $eventId2 = $res->[0][1]{created}{2}{id}; - $self->assert_not_null($eventId1); - $self->assert_not_null($eventId2); + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + '1' => { + calendarIds => { + $calid => JSON::true, + }, + 'uid' => 'event1uid', + 'title' => 'event1', + 'start' => '2019-10-01T10:00:00', + 'timeZone' => 'Etc/UTC', + }, + '2' => { + calendarIds => { + $calid => JSON::true, + }, + 'uid' => 'event2uid', + 'title' => 'event2', + 'start' => '2018-10-01T12:00:00', + 'timeZone' => 'Etc/UTC', + }, + } + }, + 'R1' + ] ]); + my $eventId1 = $res->[0][1]{created}{1}{id}; + my $eventId2 = $res->[0][1]{created}{2}{id}; + $self->assert_not_null($eventId1); + $self->assert_not_null($eventId2); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - sort => [{ - property => 'start', - isAscending => JSON::true, - }] - }, 'R1'] - ]); - $self->assert_deep_equals([$eventId2,$eventId1], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + sort => [ { + property => 'start', + isAscending => JSON::true, + } ] + }, + 'R1' + ] ]); + $self->assert_deep_equals([ $eventId2, $eventId1 ], $res->[0][1]{ids}); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - sort => [{ - property => 'start', - isAscending => JSON::false, - }] - }, 'R1'] - ]); - $self->assert_deep_equals([$eventId1,$eventId2], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + sort => [ { + property => 'start', + isAscending => JSON::false, + } ] + }, + 'R1' + ] ]); + $self->assert_deep_equals([ $eventId1, $eventId2 ], $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_text b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_text index b4f1c4569c..4b8035cc57 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_text +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_text @@ -2,123 +2,144 @@ use Cassandane::Tiny; sub test_calendarevent_query_text - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - Default => JSON::true, - }, - "title" => "foo", - "description" => "bar", - "locations" => { - "loc1" => { - name => "baz", - }, - }, - "freeBusyStatus" => "busy", - "start"=> "2016-01-01T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "replyTo" => { imip => "mailto:tux\@local" }, - "participants" => { - "tux" => { - name => "", - roles => { - 'owner' => JSON::true, - }, - locationId => "loc1", - sendTo => { - imip => 'tux@local', - }, - }, - "qux" => { - name => "Quuks", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'qux@local', - }, - }, - }, - recurrenceRules => [{ - frequency => "monthly", - count => 12, - }], - "recurrenceOverrides" => { - "2016-04-01T10:00:00" => { - "description" => "blah", - "locations/loc1/name" => "blep", - }, - "2016-05-01T10:00:00" => { - "title" => "boop", - }, - }, - }, - }}, "R1"]]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($id1); + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + Default => JSON::true, + }, + "title" => "foo", + "description" => "bar", + "locations" => { + "loc1" => { + name => "baz", + }, + }, + "freeBusyStatus" => "busy", + "start" => "2016-01-01T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "replyTo" => { imip => "mailto:tux\@local" }, + "participants" => { + "tux" => { + name => "", + roles => { + 'owner' => JSON::true, + }, + locationId => "loc1", + sendTo => { + imip => 'tux@local', + }, + }, + "qux" => { + name => "Quuks", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'qux@local', + }, + }, + }, + recurrenceRules => [ { + frequency => "monthly", + count => 12, + } ], + "recurrenceOverrides" => { + "2016-04-01T10:00:00" => { + "description" => "blah", + "locations/loc1/name" => "blep", + }, + "2016-05-01T10:00:00" => { + "title" => "boop", + }, + }, + }, + } + }, + "R1" + ] ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($id1); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my %textqueries = ( - title => "foo", - title => "boop", - description => "bar", - description => "blah", - location => "baz", - location => "blep", - owner => "tux", - owner => "tux\@local", - attendee => "qux", - attendee => "qux\@local", - attendee => "Quuks", - ); + my %textqueries = ( + title => "foo", + title => "boop", + description => "bar", + description => "blah", + location => "baz", + location => "blep", + owner => "tux", + owner => "tux\@local", + attendee => "qux", + attendee => "qux\@local", + attendee => "Quuks", + ); - while (my ($propname, $propval) = each %textqueries) { + while (my ($propname, $propval) = each %textqueries) { - # Assert that catch-all text search matches - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - "filter" => { - "text" => $propval, - } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + # Assert that catch-all text search matches + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "text" => $propval, + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - # Sanity check catch-all text search - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - "filter" => { - "text" => "nope", - } - }, "R1"] ]); - $self->assert_num_equals(0, $res->[0][1]{total}); + # Sanity check catch-all text search + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "text" => "nope", + } + }, + "R1" + ] ]); + $self->assert_num_equals(0, $res->[0][1]{total}); - # Assert that search by property name matches - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - "filter" => { - $propname => $propval, - } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + # Assert that search by property name matches + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + $propname => $propval, + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - # Sanity check property name search - $res = $jmap->CallMethods([ ['CalendarEvent/query', { - "filter" => { - $propname => "nope", - } - }, "R1"] ]); - $self->assert_num_equals(0, $res->[0][1]{total}); - } + # Sanity check property name search + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + $propname => "nope", + } + }, + "R1" + ] ]); + $self->assert_num_equals(0, $res->[0][1]{total}); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_unixepoch b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_unixepoch index d39aadf425..7ba0443c01 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_unixepoch +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_unixepoch @@ -2,45 +2,58 @@ use Cassandane::Tiny; sub test_calendarevent_query_unixepoch - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - xlog $self, "create events"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { + xlog $self, "create events"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { $calid => JSON::true, + }, + "title" => "Establish first ARPANET link between UCLA and SRI", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "1969-11-21T17:00:00", + "timeZone" => "America/Los_Angeles", + "duration" => "PT1H", }, - "title" => "Establish first ARPANET link between UCLA and SRI", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "1969-11-21T17:00:00", - "timeZone" => "America/Los_Angeles", - "duration" => "PT1H", - }, - }}, "R1"]]); + } + }, + "R1" + ] ]); - xlog $self, "Run squatter"; + xlog $self, "Run squatter"; - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "1969-01-01T00:00:00", - "before" => "1969-12-31T23:59:59", - }, - }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "1969-01-01T00:00:00", + "before" => "1969-12-31T23:59:59", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); - $res = $jmap->CallMethods([['CalendarEvent/query', { - "filter" => { - "after" => "1949-06-20T00:00:00", - "before" => "1968-10-14T00:00:00", - }, - }, "R1"]]); - $self->assert_num_equals(0, $res->[0][1]{total}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/query', + { + "filter" => { + "after" => "1949-06-20T00:00:00", + "before" => "1968-10-14T00:00:00", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, $res->[0][1]{total}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_unsupported b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_unsupported index 71a6a16f6d..4010f1a556 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_unsupported +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_unsupported @@ -3,37 +3,43 @@ use Cassandane::Tiny; use Data::UUID; sub test_calendarevent_query_unsupported - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $uuidgen = Data::UUID->new; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $uuidgen = Data::UUID->new; - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $filter = { - operator => 'OR', - conditions => [] - }; + my $filter = { + operator => 'OR', + conditions => [] + }; - # evoke a sqlite error for too complex expression trees. - # this filter is non- - for (1 .. 1001) { # this is the internal sqlite3 limit - push(@{$filter->{conditions}}, { - uid => $uuidgen->create_str, - after => '2023-03-04T14:00:00', - }); - } + # evoke a sqlite error for too complex expression trees. + # this filter is non- + for (1 .. 1001) { # this is the internal sqlite3 limit + push( + @{ $filter->{conditions} }, + { + uid => $uuidgen->create_str, + after => '2023-03-04T14:00:00', + } + ); + } - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - filter => $filter - }, 'R1'], - ]); - $self->assert_str_equals("unsupportedFilter", $res->[0][1]{type}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/query', + { + filter => $filter + }, + 'R1' + ], + ]); + $self->assert_str_equals("unsupportedFilter", $res->[0][1]{type}); - $self->{instance}->getsyslog(); # ignore seen.db DBERROR + $self->{instance}->getsyslog(); # ignore seen.db DBERROR } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_with_timezone b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_with_timezone index 6d34c94937..ce7c5c3cc9 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_with_timezone +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_query_with_timezone @@ -2,63 +2,67 @@ use Cassandane::Tiny; sub test_calendarevent_query_with_timezone - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Create event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - Default => JSON::true, - }, - title => 'event', - start => '2021-08-24T14:30:00', - duration => 'PT1H', - timeZone => 'Etc/UTC', - }, + xlog "Create event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); - - my @testCases = ({ - filter => { - after => '2021-08-24T14:30:00', - }, - wantIds => [$eventId], - }, { - filter => { - after => '2021-08-25T00:30:00', - }, - timeZone => 'Australia/Melbourne', - wantIds => [$eventId], - }, { - filter => { - before => '2021-08-24T15:30:00', - }, - wantIds => [$eventId], - }, { - filter => { - before => '2021-08-25T01:30:00', + title => 'event', + start => '2021-08-24T14:30:00', + duration => 'PT1H', + timeZone => 'Etc/UTC', + }, }, - timeZone => 'Australia/Melbourne', - wantIds => [$eventId], - }); + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); - foreach(@testCases) { - my $args = { - filter => $_->{filter}, - }; - $args->{timeZone} = $_->{timeZone} if defined; - - $res = $jmap->CallMethods([ - ['CalendarEvent/query', $args, 'R1'], - ]); - $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + my @testCases = ( + { + filter => { + after => '2021-08-24T14:30:00', + }, + wantIds => [$eventId], + }, + { + filter => { + after => '2021-08-25T00:30:00', + }, + timeZone => 'Australia/Melbourne', + wantIds => [$eventId], + }, + { + filter => { + before => '2021-08-24T15:30:00', + }, + wantIds => [$eventId], + }, + { + filter => { + before => '2021-08-25T01:30:00', + }, + timeZone => 'Australia/Melbourne', + wantIds => [$eventId], } + ); + + foreach (@testCases) { + my $args = { filter => $_->{filter}, }; + $args->{timeZone} = $_->{timeZone} if defined; + + $res = $jmap->CallMethods([ [ 'CalendarEvent/query', $args, 'R1' ], ]); + $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts index b997ec0a5c..f861a9a9bc 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts @@ -2,76 +2,75 @@ use Cassandane::Tiny; sub test_calendarevent_set_alerts - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $alerts = { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - acknowledged => "2015-11-07T08:57:00Z", - action => "email", + my $alerts = { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", + }, + acknowledged => "2015-11-07T08:57:00Z", + action => "email", + }, + alert2 => { + trigger => { + '@type' => 'AbsoluteTrigger', + when => "2019-03-04T04:05:06Z", + }, + action => "display", + relatedTo => { + 'alert1' => { + relation => { + 'parent' => JSON::true, + }, }, - alert2 => { - trigger => { - '@type' => 'AbsoluteTrigger', - when => "2019-03-04T04:05:06Z", - }, - action => "display", - relatedTo => { - 'alert1' => { - relation => { - 'parent' => JSON::true, - }, - }, - }, - }, - alert3 => { - trigger => { - '@type' => 'OffsetTrigger', - offset => "PT1S", - } - }, - alert4 => { - trigger => { - '@type' => 'AbsoluteTrigger', - when => "2019-03-04T05:06:07Z", - }, - action => "display", - relatedTo => { - 'alert1' => { - relation => { }, - }, - }, + }, + }, + alert3 => { + trigger => { + '@type' => 'OffsetTrigger', + offset => "PT1S", + } + }, + alert4 => { + trigger => { + '@type' => 'AbsoluteTrigger', + when => "2019-03-04T05:06:07Z", + }, + action => "display", + relatedTo => { + 'alert1' => { + relation => {}, }, + }, + }, - }; + }; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT2H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "status" => "confirmed", - "alerts" => $alerts, - "useDefaultAlerts" => JSON::true, - }; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT2H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "status" => "confirmed", + "alerts" => $alerts, + "useDefaultAlerts" => JSON::true, + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - $self->assert_normalized_event_equals($ret, $event); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + $self->assert_normalized_event_equals($ret, $event); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_description b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_description index b5990ae1a4..55de2e4c0d 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_description +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_description @@ -2,83 +2,89 @@ use Cassandane::Tiny; sub test_calendarevent_set_alerts_description - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - calendarIds => { - Default => JSON::true, - }, - title => 'title', - description => 'description', - start => '2015-11-07T09:00:00', - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + calendarIds => { + Default => JSON::true, + }, + title => 'title', + description => 'description', + start => '2015-11-07T09:00:00', + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, - 2 => { - calendarIds => { - Default => JSON::true, - }, - description => 'description', - start => '2016-11-07T09:00:00', - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, + action => 'display', + }, + }, + }, + 2 => { + calendarIds => { + Default => JSON::true, + }, + description => 'description', + start => '2016-11-07T09:00:00', + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, - 3 => { - calendarIds => { - Default => JSON::true, - }, - start => '2017-11-07T09:00:00', - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, + action => 'display', + }, + }, + }, + 3 => { + calendarIds => { + Default => JSON::true, + }, + start => '2017-11-07T09:00:00', + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, + action => 'display', + }, }, - }, 'R1'], - ]); - my $blobId1 = $res->[0][1]{created}{1}{'blobId'}; - $self->assert_not_null($blobId1); + }, + }, + }, + 'R1' + ], + ]); + my $blobId1 = $res->[0][1]{created}{1}{'blobId'}; + $self->assert_not_null($blobId1); - my $blobId2 = $res->[0][1]{created}{2}{'blobId'}; - $self->assert_not_null($blobId2); + my $blobId2 = $res->[0][1]{created}{2}{'blobId'}; + $self->assert_not_null($blobId2); - my $blobId3 = $res->[0][1]{created}{3}{'blobId'}; - $self->assert_not_null($blobId3); + my $blobId3 = $res->[0][1]{created}{3}{'blobId'}; + $self->assert_not_null($blobId3); - $res = $jmap->Download('cassandane', $blobId1); - $self->assert($res->{content} =~ /BEGIN:VALARM[\s\S]+DESCRIPTION:title[\s\S]+END:VALARM/g); + $res = $jmap->Download('cassandane', $blobId1); + $self->assert($res->{content} =~ + /BEGIN:VALARM[\s\S]+DESCRIPTION:title[\s\S]+END:VALARM/g); - $res = $jmap->Download('cassandane', $blobId2); - $self->assert($res->{content} =~ /BEGIN:VALARM[\s\S]+DESCRIPTION:description[\s\S]+END:VALARM/g); + $res = $jmap->Download('cassandane', $blobId2); + $self->assert($res->{content} =~ + /BEGIN:VALARM[\s\S]+DESCRIPTION:description[\s\S]+END:VALARM/g); - $res = $jmap->Download('cassandane', $blobId3); - $self->assert($res->{content} =~ /BEGIN:VALARM[\s\S]+DESCRIPTION:Reminder[\s\S]+END:VALARM/g); + $res = $jmap->Download('cassandane', $blobId3); + $self->assert($res->{content} =~ + /BEGIN:VALARM[\s\S]+DESCRIPTION:Reminder[\s\S]+END:VALARM/g); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_owner b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_owner index 1fe8bc994f..7e2795d286 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_owner +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_owner @@ -2,120 +2,131 @@ use Cassandane::Tiny; sub test_calendarevent_set_alerts_owner - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $ownerJmap = $self->{jmap}; + my $ownerJmap = $self->{jmap}; - xlog $self, "create sharee and share calendar"; - my ($shareeJmap) = $self->create_user('sharee'); - my $res = $ownerJmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - sharee => { - mayReadItems => JSON::true, - mayWriteAll => JSON::true, - }, - }, - }, + xlog $self, "create sharee and share calendar"; + my ($shareeJmap) = $self->create_user('sharee'); + my $res = $ownerJmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + sharee => { + mayReadItems => JSON::true, + mayWriteAll => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog $self, "sharee creates event"; - my $now = DateTime->now(); - $now->set_time_zone('Etc/UTC'); - # bump everything forward so a slow run (say: valgrind) - # doesn't cause things to magically fire... - $now->add(DateTime::Duration->new(seconds => 300)); + xlog $self, "sharee creates event"; + my $now = DateTime->now(); + $now->set_time_zone('Etc/UTC'); + # bump everything forward so a slow run (say: valgrind) + # doesn't cause things to magically fire... + $now->add(DateTime::Duration->new(seconds => 300)); - # define the event to start in a few seconds - my $startdt = $now->clone(); - $startdt->add(DateTime::Duration->new(seconds => 2)); - my $start = $startdt->strftime('%Y-%m-%dT%H:%M:%S'); + # define the event to start in a few seconds + my $startdt = $now->clone(); + $startdt->add(DateTime::Duration->new(seconds => 2)); + my $start = $startdt->strftime('%Y-%m-%dT%H:%M:%S'); - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - start => $start, - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - }, + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + create => { + event => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); + start => $start, + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); - xlog $self, "owner sets alert on event"; - $res = $ownerJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - alerts => { - ownerAlert => { - '@type' => 'Alert', - uid => '97d7c889-272f-4ce3-8d21-4a32b17ecece', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - }, - }, + xlog $self, "owner sets alert on event"; + $res = $ownerJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + alerts => { + ownerAlert => { + '@type' => 'Alert', + uid => '97d7c889-272f-4ce3-8d21-4a32b17ecece', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', }, + action => 'display', + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog $self, "clear notifications"; - $self->{instance}->getnotify(); + xlog $self, "clear notifications"; + $self->{instance}->getnotify(); - xlog $self, "simulate previous calalarmd run"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() - 60); + xlog $self, "simulate previous calalarmd run"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() - 60); - xlog $self, "assert no alarm is fired"; - my $data = $self->{instance}->getnotify(); - my @events; - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - $self->assert_str_not_equals("CalendarAlarm", $e->{event}); - } + xlog $self, "assert no alarm is fired"; + my $data = $self->{instance}->getnotify(); + my @events; + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + $self->assert_str_not_equals("CalendarAlarm", $e->{event}); } + } - xlog $self, "clear notifications"; - $self->{instance}->getnotify(); + xlog $self, "clear notifications"; + $self->{instance}->getnotify(); - xlog $self, "run calalarmd"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60); + xlog $self, "run calalarmd"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - xlog $self, "assert alarm is fired"; - my $data = $self->{instance}->getnotify(); - my @events; - foreach (@$data) { - if ($_->{CLASS} eq 'EVENT') { - my $e = decode_json($_->{MESSAGE}); - if ($e->{event} eq "CalendarAlarm") { - push @events, $e; - } - } + xlog $self, "assert alarm is fired"; + my $data = $self->{instance}->getnotify(); + my @events; + foreach (@$data) { + if ($_->{CLASS} eq 'EVENT') { + my $e = decode_json($_->{MESSAGE}); + if ($e->{event} eq "CalendarAlarm") { + push @events, $e; + } } - $self->assert_str_equals('CalendarAlarm', $events[0]{event}); - $self->assert_str_equals('ownerAlert', $events[0]{alertId}); + } + $self->assert_str_equals('CalendarAlarm', $events[0]{event}); + $self->assert_str_equals('ownerAlert', $events[0]{alertId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_uid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_uid index c9fb71729c..092af8e38f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_uid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_alerts_uid @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_calendarevent_set_alerts_uid - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "Create CalDAV event with VALARM without UID"; - $caldav->Request('PUT', - "/dav/calendars/user/cassandane/Default/alarmnouid.ics", -<Request( + 'PUT', + "/dav/calendars/user/cassandane/Default/alarmnouid.ics", + < 'text/calendar'); + , 'Content-Type' => 'text/calendar' + ); - xlog $self, "Create CalDAV event with VALARM with UID"; - my $alertFixedUid = '8378e120-cd1c-43fb-805e-06348592b644'; - $caldav->Request('PUT', - "/dav/calendars/user/cassandane/Default/alarmfixeduid.ics", -<Request( + 'PUT', + "/dav/calendars/user/cassandane/Default/alarmfixeduid.ics", + < 'text/calendar'); + , 'Content-Type' => 'text/calendar' + ); - xlog $self, "Create JMAP events having UUID, SHA1 and simple alert ids"; + xlog $self, "Create JMAP events having UUID, SHA1 and simple alert ids"; - my $uuidJmapId = 'b23087c0-8822-4f29-a279-741c102fdc26'; - my $sha1JmapId = 'f142b15de0ad6e1631fd2db106dd3e906a260747'; - my $simpleJmapId = 'alert1'; + my $uuidJmapId = 'b23087c0-8822-4f29-a279-741c102fdc26'; + my $sha1JmapId = 'f142b15de0ad6e1631fd2db106dd3e906a260747'; + my $simpleJmapId = 'alert1'; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - uuidJmapId => { - title => 'uuidJmapId', - calendarIds => { - Default => JSON::true, - }, - start => '2023-08-01T17:07:00', - timeZone => 'Etc/UTC', - alerts => { - $uuidJmapId => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - action => "display", - } - } - }, - sha1JmapId => { - title => 'sha1JmapId', - calendarIds => { - Default => JSON::true, - }, - start => '2023-08-02T17:07:00', - timeZone => 'Etc/UTC', - alerts => { - $sha1JmapId => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - action => "display", - } - } + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + uuidJmapId => { + title => 'uuidJmapId', + calendarIds => { + Default => JSON::true, + }, + start => '2023-08-01T17:07:00', + timeZone => 'Etc/UTC', + alerts => { + $uuidJmapId => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", }, - simpleJmapId => { - title => 'simpleJmapId', - calendarIds => { - Default => JSON::true, - }, - start => '2023-08-03T17:07:00', - timeZone => 'Etc/UTC', - alerts => { - $simpleJmapId => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - action => "display", - } - } + action => "display", + } + } + }, + sha1JmapId => { + title => 'sha1JmapId', + calendarIds => { + Default => JSON::true, + }, + start => '2023-08-02T17:07:00', + timeZone => 'Etc/UTC', + alerts => { + $sha1JmapId => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", }, + action => "display", + } + } + }, + simpleJmapId => { + title => 'simpleJmapId', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{created}{uuidJmapId}); - $self->assert_not_null($res->[0][1]{created}{sha1JmapId}); - $self->assert_not_null($res->[0][1]{created}{simpleJmapId}); + start => '2023-08-03T17:07:00', + timeZone => 'Etc/UTC', + alerts => { + $simpleJmapId => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", + }, + action => "display", + } + } + }, + }, + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{uuidJmapId}); + $self->assert_not_null($res->[0][1]{created}{sha1JmapId}); + $self->assert_not_null($res->[0][1]{created}{simpleJmapId}); - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => ['title', 'alerts', 'cyrusimap.org:iCalProps'], - }, 'R2'], - ]); + $res = $jmap->CallMethods([ + [ 'CalendarEvent/query', {}, 'R1' ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => [ 'title', 'alerts', 'cyrusimap.org:iCalProps' ], + }, + 'R2' + ], + ]); - my %eventAlerts = map { $_->{title} => $_->{alerts} } @{$res->[1][1]{list}}; + my %eventAlerts = map { $_->{title} => $_->{alerts} } @{ $res->[1][1]{list} }; - # Alert with no UID gets some JMAP id assigned - $self->assert_num_equals(1, scalar keys %{$eventAlerts{noUid}}); - $self->assert_null((values %{$eventAlerts{noUid}})[0]{uid}); + # Alert with no UID gets some JMAP id assigned + $self->assert_num_equals(1, scalar keys %{ $eventAlerts{noUid} }); + $self->assert_null((values %{ $eventAlerts{noUid} })[0]{uid}); - # Alarm with UUID JMAP id gets the same value as UID - $self->assert_str_equals('uid', $eventAlerts{uuidJmapId}{ - $uuidJmapId}{'cyrusimap.org:iCalProps'}[0][0]); - $self->assert_str_equals($uuidJmapId, $eventAlerts{uuidJmapId}{ - $uuidJmapId}{'cyrusimap.org:iCalProps'}[0][3]); + # Alarm with UUID JMAP id gets the same value as UID + $self->assert_str_equals('uid', + $eventAlerts{uuidJmapId}{$uuidJmapId}{'cyrusimap.org:iCalProps'}[0][0]); + $self->assert_str_equals($uuidJmapId, + $eventAlerts{uuidJmapId}{$uuidJmapId}{'cyrusimap.org:iCalProps'}[0][3]); - # Alarm with simple JMAP id gets some other value as UID - $self->assert_str_equals('uid', $eventAlerts{simpleJmapId}{ - $simpleJmapId}{'cyrusimap.org:iCalProps'}[0][0]); - $self->assert_str_not_equals($simpleJmapId, $eventAlerts{simpleJmapId}{ - $simpleJmapId}{'cyrusimap.org:iCalProps'}[0][3]); + # Alarm with simple JMAP id gets some other value as UID + $self->assert_str_equals('uid', + $eventAlerts{simpleJmapId}{$simpleJmapId}{'cyrusimap.org:iCalProps'}[0][0]); + $self->assert_str_not_equals($simpleJmapId, + $eventAlerts{simpleJmapId}{$simpleJmapId}{'cyrusimap.org:iCalProps'}[0][3]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_attachbinary_blobid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_attachbinary_blobid index 38ba765281..14ed3a7032 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_attachbinary_blobid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_attachbinary_blobid @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_set_attachbinary_blobid - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + my $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -28,56 +27,68 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $rawIcal, - 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', 'Default/test.ics', $rawIcal, + 'Content-Type' => 'text/calendar'); - xlog "Fetch Link.blobId"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['links'], - }, 'R1'], - ]); - my $event1 = $res->[0][1]{list}[0]; - $self->assert_not_null($event1); - my $blobId1 = (values %{$event1->{links}})[0]->{blobId}; - $self->assert_not_null($blobId1); + xlog "Fetch Link.blobId"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['links'], + }, + 'R1' + ], + ]); + my $event1 = $res->[0][1]{list}[0]; + $self->assert_not_null($event1); + my $blobId1 = (values %{ $event1->{links} })[0]->{blobId}; + $self->assert_not_null($blobId1); - xlog "Assert blobId is a smart blob"; - $self->assert_str_equals("I", substr($blobId1, 0, 1)); + xlog "Assert blobId is a smart blob"; + $self->assert_str_equals("I", substr($blobId1, 0, 1)); - xlog "Create event with same blobId"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event2 => { - calendarIds => { - Default => JSON::true, - }, - title => "event2", - start => "2021-08-01T23:30:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - links => { - link => { - blobId => $blobId1, - }, - }, - }, + xlog "Create event with same blobId"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event2 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event2'], - properties => ['links', 'x-href'], - }, 'R2'], - ]); - my $event2 = $res->[1][1]{list}[0]; - $self->assert_not_null($event2); - my $blobId2 = (values %{$event2->{links}})[0]->{blobId}; + title => "event2", + start => "2021-08-01T23:30:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + links => { + link => { + blobId => $blobId1, + }, + }, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event2'], + properties => [ 'links', 'x-href' ], + }, + 'R2' + ], + ]); + my $event2 = $res->[1][1]{list}[0]; + $self->assert_not_null($event2); + my $blobId2 = (values %{ $event2->{links} })[0]->{blobId}; - xlog "Assert blobId is a G blob"; - $self->assert_str_equals("G", substr($blobId2, 0, 1)); + xlog "Assert blobId is a G blob"; + $self->assert_str_equals("G", substr($blobId2, 0, 1)); - xlog "Assert /set response reported new blobId"; - $self->assert_str_equals($blobId2, - $res->[0][1]{created}{event2}{"links/link/blobId"}); + xlog "Assert /set response reported new blobId"; + $self->assert_str_equals($blobId2, + $res->[0][1]{created}{event2}{"links/link/blobId"}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_attachbinary_datauri b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_attachbinary_datauri index 7abe9de40a..0616094de9 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_attachbinary_datauri +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_attachbinary_datauri @@ -2,86 +2,105 @@ use Cassandane::Tiny; sub test_calendarevent_set_attachbinary_datauri - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event with data: URI in Link.href"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2019-12-10T23:30:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - links => { - link => { - href =>'data:text/plain;base64,aGVsbG8=', - }, - }, - }, + xlog "Create event with data: URI in Link.href"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event1'], - properties => ['links', 'x-href'], - }, 'R2'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); + title => "event1", + start => "2019-12-10T23:30:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + links => { + link => { + href => 'data:text/plain;base64,aGVsbG8=', + }, + }, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event1'], + properties => [ 'links', 'x-href' ], + }, + 'R2' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); - xlog "Fetch event without Cyrus extension"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => ['#event1'], - properties => ['links'], - }, 'R1'], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - ]); - my $linkWithoutExt = (values %{$res->[0][1]{list}[0]{links}})[0]; - $self->assert_str_equals('data:text/plain;base64,aGVsbG8=', - $linkWithoutExt->{href}); - $self->assert_null($linkWithoutExt->{blobId}); - $self->assert_str_equals('text/plain', - $linkWithoutExt->{contentType}); + xlog "Fetch event without Cyrus extension"; + $res = $jmap->CallMethods( + [ + [ + 'CalendarEvent/get', + { + ids => ['#event1'], + properties => ['links'], + }, + 'R1' + ], + ], + [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + ] + ); + my $linkWithoutExt = (values %{ $res->[0][1]{list}[0]{links} })[0]; + $self->assert_str_equals('data:text/plain;base64,aGVsbG8=', + $linkWithoutExt->{href}); + $self->assert_null($linkWithoutExt->{blobId}); + $self->assert_str_equals('text/plain', $linkWithoutExt->{contentType}); - xlog "Fetch event with Cyrus extension"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => ['#event1'], - properties => ['links', 'x-href'], - }, 'R1'], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - my $linkWithExt = (values %{$res->[0][1]{list}[0]{links}})[0]; - $self->assert_null($linkWithExt->{href}); - $self->assert_not_null($linkWithExt->{blobId}); - $self->assert_str_equals('text/plain', $linkWithExt->{contentType}); - my $xhref = $res->[0][1]{list}[0]{'x-href'}; - $self->assert_not_null($xhref); + xlog "Fetch event with Cyrus extension"; + $res = $jmap->CallMethods( + [ + [ + 'CalendarEvent/get', + { + ids => ['#event1'], + properties => [ 'links', 'x-href' ], + }, + 'R1' + ], + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ] + ); + my $linkWithExt = (values %{ $res->[0][1]{list}[0]{links} })[0]; + $self->assert_null($linkWithExt->{href}); + $self->assert_not_null($linkWithExt->{blobId}); + $self->assert_str_equals('text/plain', $linkWithExt->{contentType}); + my $xhref = $res->[0][1]{list}[0]{'x-href'}; + $self->assert_not_null($xhref); - xlog "Assert ATTACH BINARY in VEVENT"; - my $caldavResponse = $caldav->Request('GET', $xhref); - my $ical = Data::ICal->new(data => $caldavResponse->{content}); - my %entries = map { $_->ical_entry_type() => $_ } @{$ical->entries()}; - my $vevent = $entries{'VEVENT'}; - $self->assert_not_null($vevent); + xlog "Assert ATTACH BINARY in VEVENT"; + my $caldavResponse = $caldav->Request('GET', $xhref); + my $ical = Data::ICal->new(data => $caldavResponse->{content}); + my %entries = map { $_->ical_entry_type() => $_ } @{ $ical->entries() }; + my $vevent = $entries{'VEVENT'}; + $self->assert_not_null($vevent); - my $attach = $vevent->property('ATTACH'); - $self->assert_num_equals(1, scalar @{$attach}); - $self->assert_str_equals('BINARY', $attach->[0]->parameters()->{VALUE}); + my $attach = $vevent->property('ATTACH'); + $self->assert_num_equals(1, scalar @{$attach}); + $self->assert_str_equals('BINARY', $attach->[0]->parameters()->{VALUE}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_baseeventid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_baseeventid index 0df5696079..4269507494 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_baseeventid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_baseeventid @@ -2,125 +2,138 @@ use Cassandane::Tiny; sub test_calendarevent_set_baseeventid - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $event1Uid = '1cf5da26-38e9-47ac-8449-04354ae3772d'; - my $event2Uid = '20623313-524c-487f-bd20-beab02e87f88'; + my $jmap = $self->{jmap}; + my $event1Uid = '1cf5da26-38e9-47ac-8449-04354ae3772d'; + my $event2Uid = '20623313-524c-487f-bd20-beab02e87f88'; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - 'Default' => JSON::true, - }, - uid => $event1Uid, - title => "event1", - start => "2023-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'daily', - count => 3, - }], - }, - event2 => { - calendarIds => { - 'Default' => JSON::true, - }, - uid => $event2Uid, - title => "event2", - start => "2023-02-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'daily', - count => 3, - }], - }, - } - }, 'R1'], - ]); - my $event1Id = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($event1Id); - my $event2Id = $res->[0][1]{created}{event2}{id}; - $self->assert_not_null($event2Id); - - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - filter => { - before => '2023-01-03T00:00:00', + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + 'Default' => JSON::true, }, - sort => [{ - property => 'start', - }], - expandRecurrences => JSON::true, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' + uid => $event1Uid, + title => "event1", + start => "2023-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'daily', + count => 3, + } ], + }, + event2 => { + calendarIds => { + 'Default' => JSON::true, }, - properties => ['baseEventId', 'utcStart',], - }, 'R2'], - ]); + uid => $event2Uid, + title => "event2", + start => "2023-02-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'daily', + count => 3, + } ], + }, + } + }, + 'R1' + ], + ]); + my $event1Id = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($event1Id); + my $event2Id = $res->[0][1]{created}{event2}{id}; + $self->assert_not_null($event2Id); - my $eventInstance1Id = encode_eventid($event1Uid, '20230101T090000'); - my $eventInstance2Id = encode_eventid($event1Uid, '20230102T090000'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $self->assert_deep_equals([ - $eventInstance1Id, $eventInstance2Id - ], $res->[0][1]{ids}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/query', + { + filter => { + before => '2023-01-03T00:00:00', + }, + sort => [ { + property => 'start', + } ], + expandRecurrences => JSON::true, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => [ 'baseEventId', 'utcStart', ], + }, + 'R2' + ], + ]); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - rdate => { - calendarIds => { - Default => JSON::true, - }, - baseEventId => $event1Id, - recurrenceId => '20230101T230000', - recurrenceIdTimeZone => 'Europe/Vienna', - start => '20230101T230000', - title => 'rdateevent', - }, - }, - update => { - $event1Id => { - baseEventId => $event2Id, - }, - $eventInstance1Id => { - baseEventId => $event2Id, - }, - $eventInstance2Id => { - baseEventId => $event1Id, - }, - }, - }, 'R1'], - ]); + my $eventInstance1Id = encode_eventid($event1Uid, '20230101T090000'); + my $eventInstance2Id = encode_eventid($event1Uid, '20230102T090000'); + + $self->assert_deep_equals([ $eventInstance1Id, $eventInstance2Id ], + $res->[0][1]{ids}); - # Can't create an event with a baseEventId - $self->assert(grep $_ eq 'baseEventId', - @{$res->[0][1]{notCreated}{rdate}{properties}}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + rdate => { + calendarIds => { + Default => JSON::true, + }, + baseEventId => $event1Id, + recurrenceId => '20230101T230000', + recurrenceIdTimeZone => 'Europe/Vienna', + start => '20230101T230000', + title => 'rdateevent', + }, + }, + update => { + $event1Id => { + baseEventId => $event2Id, + }, + $eventInstance1Id => { + baseEventId => $event2Id, + }, + $eventInstance2Id => { + baseEventId => $event1Id, + }, + }, + }, + 'R1' + ], + ]); - # Can't set the baseEventId on a non-instance - $self->assert(grep $_ eq 'baseEventId', - @{$res->[0][1]{notUpdated}{$event1Id}{properties}}); + # Can't create an event with a baseEventId + $self->assert(grep $_ eq 'baseEventId', + @{ $res->[0][1]{notCreated}{rdate}{properties} }); - # Can't change the baseEventId of an instance - $self->assert(grep $_ eq 'baseEventId', - @{$res->[0][1]{notUpdated}{$eventInstance1Id}{properties}}); + # Can't set the baseEventId on a non-instance + $self->assert(grep $_ eq 'baseEventId', + @{ $res->[0][1]{notUpdated}{$event1Id}{properties} }); - # Can keep the baseEventId of an instance - $self->assert(exists $res->[0][1]{updated}{$eventInstance2Id}); + # Can't change the baseEventId of an instance + $self->assert(grep $_ eq 'baseEventId', + @{ $res->[0][1]{notUpdated}{$eventInstance1Id}{properties} }); + # Can keep the baseEventId of an instance + $self->assert(exists $res->[0][1]{updated}{$eventInstance2Id}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_bogus_replyto b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_bogus_replyto index 2e2a222238..cc392dc24d 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_bogus_replyto +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_bogus_replyto @@ -2,53 +2,56 @@ use Cassandane::Tiny; sub test_calendarevent_set_bogus_replyto - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - 'Default' => JSON::true, - }, - title => "event1", - start => "2021-01-01T02:00:00", - timeZone => 'Europe/Berlin', - duration => 'PT1H', - replyTo => 'cassandane@example.com', - participants => { - part1 => { - sendTo => { - imip => 'part1@example.com', - }, - }, - }, - }, - event2 => { - calendarIds => { - 'Default' => JSON::true, - }, - title => "event2", - start => "2021-01-01T02:00:00", - timeZone => 'Europe/Berlin', - duration => 'PT1H', - replyTo => { - imip => 'cassandane@example.com', - }, - participants => { - part1 => { - sendTo => 'part1@example.com', - }, - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + 'Default' => JSON::true, + }, + title => "event1", + start => "2021-01-01T02:00:00", + timeZone => 'Europe/Berlin', + duration => 'PT1H', + replyTo => 'cassandane@example.com', + participants => { + part1 => { + sendTo => { + imip => 'part1@example.com', }, + }, + }, + }, + event2 => { + calendarIds => { + 'Default' => JSON::true, + }, + title => "event2", + start => "2021-01-01T02:00:00", + timeZone => 'Europe/Berlin', + duration => 'PT1H', + replyTo => { + imip => 'cassandane@example.com', + }, + participants => { + part1 => { + sendTo => 'part1@example.com', + }, }, - }, 'R1'], - ]); - $self->assert_deep_equals(['replyTo'], - $res->[0][1]{notCreated}{event1}{properties}); - $self->assert_deep_equals(['participants/part1/sendTo'], - $res->[0][1]{notCreated}{event2}{properties}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals(['replyTo'], + $res->[0][1]{notCreated}{event1}{properties}); + $self->assert_deep_equals(['participants/part1/sendTo'], + $res->[0][1]{notCreated}{event2}{properties}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_bymonth b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_bymonth index 6a5994c8a3..1e32ddfe83 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_bymonth +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_bymonth @@ -2,43 +2,40 @@ use Cassandane::Tiny; sub test_calendarevent_set_bymonth - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "start"=> "2010-02-12T00:00:00", - "recurrenceRules"=> [{ - "frequency"=> "monthly", - "interval"=> 13, - "byMonth"=> [ - "4L" - ], - "count"=> 3, - }], - "\@type"=> "Event", - "title"=> "", - "description"=> "", - "locations"=> undef, - "links"=> undef, - "showWithoutTime"=> JSON::false, - "duration"=> "PT0S", - "timeZone"=> undef, - "recurrenceOverrides"=> undef, - "status"=> "confirmed", - "freeBusyStatus"=> "busy", - "replyTo"=> undef, - "participants"=> undef, - "useDefaultAlerts"=> JSON::false, - "alerts"=> undef - }; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "start" => "2010-02-12T00:00:00", + "recurrenceRules" => [ { + "frequency" => "monthly", + "interval" => 13, + "byMonth" => ["4L"], + "count" => 3, + } ], + "\@type" => "Event", + "title" => "", + "description" => "", + "locations" => undef, + "links" => undef, + "showWithoutTime" => JSON::false, + "duration" => "PT0S", + "timeZone" => undef, + "recurrenceOverrides" => undef, + "status" => "confirmed", + "freeBusyStatus" => "busy", + "replyTo" => undef, + "participants" => undef, + "useDefaultAlerts" => JSON::false, + "alerts" => undef + }; - my $ret = $self->createandget_event($event); - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_caldav b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_caldav index b19d6113c7..92dcbaaaf3 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_caldav @@ -2,64 +2,80 @@ use Cassandane::Tiny; sub test_calendarevent_set_caldav - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - - xlog $self, "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { - "1" => { - name => "A", color => "coral", sortOrder => 1, isVisible => JSON::true - } - }}, "R1"]]); - my $calid = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "create event in calendar"; - $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - $calid => JSON::true, - }, - "title" => "foo", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2015-10-06T00:00:00", - "duration" => "P1D", - "timeZone" => undef, - } - }}, "R1"]]); - my $eventId1 = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get x-href of event $eventId1"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$eventId1]}, "R1"]]); - my $xhref = $res->[0][1]{list}[0]{"x-href"}; - my $state = $res->[0][1]{state}; - - xlog $self, "GET event $eventId1 in CalDAV"; - $res = $caldav->Request('GET', $xhref); - my $ical = $res->{content}; - $self->assert_matches(qr/SUMMARY:foo/, $ical); - - xlog $self, "DELETE event $eventId1 via CalDAV"; - $res = $caldav->Request('DELETE', $xhref); - - xlog $self, "get (non-existent) event $eventId1"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$eventId1]}, "R1"]]); - $self->assert_str_equals($eventId1, $res->[0][1]{notFound}[0]); - - xlog $self, "get calendar event updates"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { sinceState => $state }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($eventId1, $res->[0][1]{destroyed}[0]); - $state = $res->[0][1]{newState}; - - my $uid2 = '97c46ea4-4182-493c-87ef-aee4edc2d38b'; - $ical = <{jmap}; + my $caldav = $self->{caldav}; + + xlog $self, "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "A", + color => "coral", + sortOrder => 1, + isVisible => JSON::true + } + } + }, + "R1" + ] ]); + my $calid = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "create event in calendar"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2015-10-06T00:00:00", + "duration" => "P1D", + "timeZone" => undef, + } + } + }, + "R1" + ] ]); + my $eventId1 = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get x-href of event $eventId1"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/get', { ids => [$eventId1] }, "R1" ] ]); + my $xhref = $res->[0][1]{list}[0]{"x-href"}; + my $state = $res->[0][1]{state}; + + xlog $self, "GET event $eventId1 in CalDAV"; + $res = $caldav->Request('GET', $xhref); + my $ical = $res->{content}; + $self->assert_matches(qr/SUMMARY:foo/, $ical); + + xlog $self, "DELETE event $eventId1 via CalDAV"; + $res = $caldav->Request('DELETE', $xhref); + + xlog $self, "get (non-existent) event $eventId1"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/get', { ids => [$eventId1] }, "R1" ] ]); + $self->assert_str_equals($eventId1, $res->[0][1]{notFound}[0]); + + xlog $self, "get calendar event updates"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($eventId1, $res->[0][1]{destroyed}[0]); + $state = $res->[0][1]{newState}; + + my $uid2 = '97c46ea4-4182-493c-87ef-aee4edc2d38b'; + $ical = <Request('PUT', "$calid/$uid2.ics", $ical, 'Content-Type' => 'text/calendar'); - - xlog $self, "get calendar event updates"; - $res = $jmap->CallMethods([['CalendarEvent/changes', { sinceState => $state }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_equals($eventId2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "get x-href of event $eventId2"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$eventId2]}, "R1"]]); - $xhref = $res->[0][1]{list}[0]{"x-href"}; - $state = $res->[0][1]{state}; - - xlog $self, "update event $eventId2"; - $res = $jmap->CallMethods([['CalendarEvent/set', { update => { - "$eventId2" => { - calendarIds => { - $calid => JSON::true, - }, - "title" => "bam", - "description" => "", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2015-10-10T00:00:00", - "duration" => "P1D", - "timeZone" => undef, - } - }}, "R1"]]); - - xlog $self, "GET event $eventId2 in CalDAV"; - $res = $caldav->Request('GET', $xhref); - $ical = $res->{content}; - $self->assert_matches(qr/SUMMARY:bam/, $ical); - - xlog $self, "destroy event $eventId2"; - $res = $jmap->CallMethods([['CalendarEvent/set', { destroy => [$eventId2] }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_equals($eventId2, $res->[0][1]{destroyed}[0]); - - xlog $self, "PROPFIND calendar $calid for non-existent event UID $uid2 in CalDAV"; - # We'd like to GET the just destroyed event, to make sure that it also - # vanished on the CalDAV layer. Unfortunately, that GET would cause - # Net-DAVTalk to burst into flames with a 404 error. Instead, issue a - # PROPFIND and make sure that the event id doesn't show in the returned - # DAV resources. - my $xml = <Request('PUT', "$calid/$uid2.ics", $ical, + 'Content-Type' => 'text/calendar'); + + xlog $self, "get calendar event updates"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_equals($eventId2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "get x-href of event $eventId2"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/get', { ids => [$eventId2] }, "R1" ] ]); + $xhref = $res->[0][1]{list}[0]{"x-href"}; + $state = $res->[0][1]{state}; + + xlog $self, "update event $eventId2"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + "$eventId2" => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "bam", + "description" => "", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2015-10-10T00:00:00", + "duration" => "P1D", + "timeZone" => undef, + } + } + }, + "R1" + ] ]); + + xlog $self, "GET event $eventId2 in CalDAV"; + $res = $caldav->Request('GET', $xhref); + $ical = $res->{content}; + $self->assert_matches(qr/SUMMARY:bam/, $ical); + + xlog $self, "destroy event $eventId2"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/set', { destroy => [$eventId2] }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_equals($eventId2, $res->[0][1]{destroyed}[0]); + + xlog $self, + "PROPFIND calendar $calid for non-existent event UID $uid2 in CalDAV"; + # We'd like to GET the just destroyed event, to make sure that it also + # vanished on the CalDAV layer. Unfortunately, that GET would cause + # Net-DAVTalk to burst into flames with a 404 error. Instead, issue a + # PROPFIND and make sure that the event id doesn't show in the returned + # DAV resources. + my $xml = < EOF - $res = $caldav->Request('PROPFIND', "$calid", $xml, - 'Content-Type' => 'application/xml', - 'Depth' => '1' - ); - $self->assert_does_not_match(qr{$uid2}, $res); + $res = $caldav->Request( + 'PROPFIND', "$calid", $xml, + 'Content-Type' => 'application/xml', + 'Depth' => '1' + ); + $self->assert_does_not_match(qr{$uid2}, $res); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_create_ignore_uid_in_special_calendar b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_create_ignore_uid_in_special_calendar index d283ad19d5..c26372c408 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_create_ignore_uid_in_special_calendar +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_create_ignore_uid_in_special_calendar @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_calendarevent_set_create_ignore_uid_in_special_calendar - :min_version_3_7 :needs_component_jmap :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap : needs_component_sieve { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admin = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admin = $self->{adminstore}->get_client(); - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -46,45 +46,57 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + xlog $self, "Deliver iMIP invite"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - xlog "Lookup event uid"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'uid'], - }, 'R0'], - ]); - my $eventId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($eventId); - my $eventUid = $res->[0][1]{list}[0]{uid}; - $self->assert_not_null($eventUid); + xlog "Lookup event uid"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'uid' ], + }, + 'R0' + ], + ]); + my $eventId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($eventId); + my $eventUid = $res->[0][1]{list}[0]{uid}; + $self->assert_not_null($eventUid); - xlog "Destroy event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [$eventId], - }, 'R0'], - ]); - $self->assert_deep_equals([$eventId], $res->[0][1]{destroyed}); + xlog "Destroy event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + destroy => [$eventId], + }, + 'R0' + ], + ]); + $self->assert_deep_equals([$eventId], $res->[0][1]{destroyed}); - xlog "Create event having the same uid"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => $eventUid, - title => 'test', - start => '2021-01-01T01:01:01', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - }, + xlog "Create event having the same uid"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{created}{event}); + '@type' => 'Event', + uid => $eventUid, + title => 'test', + start => '2021-01-01T01:01:01', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{event}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_create_no_reply_exdate b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_create_no_reply_exdate index 04b8eb51ed..59c3767944 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_create_no_reply_exdate +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_create_no_reply_exdate @@ -2,8 +2,7 @@ use Cassandane::Tiny; sub test_calendarevent_set_create_no_reply_exdate - : needs_component_jmap -{ + : needs_component_jmap { my ($self) = @_; my $jmap = $self->{jmap}; diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created index 50fd567aa0..93fe12b3f8 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created @@ -2,99 +2,122 @@ use Cassandane::Tiny; sub test_calendarevent_set_created - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $t = DateTime->now(); - $t->set_time_zone('Etc/UTC'); - my $start = $t->strftime('%Y-%m-%dT%H:%M:%S'); - $t->add(DateTime::Duration->new(days => -2)); - my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - $t->add(DateTime::Duration->new(days => 4)); - my $future = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + my $t = DateTime->now(); + $t->set_time_zone('Etc/UTC'); + my $start = $t->strftime('%Y-%m-%dT%H:%M:%S'); + $t->add(DateTime::Duration->new(days => -2)); + my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + $t->add(DateTime::Duration->new(days => 4)); + my $future = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventNoCreated => { - calendarIds => { - 'Default' => JSON::true, - }, - start => $start, - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'eventNoCreated', - }, - eventCreatedInPast => { - calendarIds => { - 'Default' => JSON::true, - }, - start => $start, - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'eventCreatedInPast', - created => $past, - }, - eventCreatedInFuture => { - calendarIds => { - 'Default' => JSON::true, - }, - start => $start, - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'eventCreatedInPast', - created => $future, - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + eventNoCreated => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ '#eventNoCreated' ], - properties => ['created', 'title'], - }, 'R2'], - ['CalendarEvent/get', { - ids => [ '#eventCreatedInPast' ], - properties => ['created', 'title'], - }, 'R3'], - ['CalendarEvent/get', { - ids => [ '#eventCreatedInFuture' ], - properties => ['created', 'title'], - }, 'R4'], - ]); + start => $start, + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'eventNoCreated', + }, + eventCreatedInPast => { + calendarIds => { + 'Default' => JSON::true, + }, + start => $start, + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'eventCreatedInPast', + created => $past, + }, + eventCreatedInFuture => { + calendarIds => { + 'Default' => JSON::true, + }, + start => $start, + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'eventCreatedInPast', + created => $future, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#eventNoCreated'], + properties => [ 'created', 'title' ], + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => ['#eventCreatedInPast'], + properties => [ 'created', 'title' ], + }, + 'R3' + ], + [ + 'CalendarEvent/get', + { + ids => ['#eventCreatedInFuture'], + properties => [ 'created', 'title' ], + }, + 'R4' + ], + ]); - xlog "Event with no created property get set to now"; - my $created = $res->[1][1]{list}[0]{created}; - $self->assert(($past lt $created) and ($created lt $future)); - $self->assert_str_equals($created, - $res->[0][1]{created}{eventNoCreated}{created}); - my $eventNoCreatedId = $res->[1][1]{list}[0]{id}; + xlog "Event with no created property get set to now"; + my $created = $res->[1][1]{list}[0]{created}; + $self->assert(($past lt $created) and ($created lt $future)); + $self->assert_str_equals($created, + $res->[0][1]{created}{eventNoCreated}{created}); + my $eventNoCreatedId = $res->[1][1]{list}[0]{id}; - xlog "Event with past created preserves value"; - $created = $res->[2][1]{list}[0]{created}; - $self->assert_str_equals($past, $created); - $self->assert_null($res->[0][1]{created}{eventCreatedInPast}{created}); + xlog "Event with past created preserves value"; + $created = $res->[2][1]{list}[0]{created}; + $self->assert_str_equals($past, $created); + $self->assert_null($res->[0][1]{created}{eventCreatedInPast}{created}); - xlog "Event with future created gets clamped to now"; - $created = $res->[3][1]{list}[0]{created}; - $self->assert(($past lt $created) and ($created lt $future)); - $self->assert_str_equals($created, - $res->[0][1]{created}{eventCreatedInFuture}{created}); + xlog "Event with future created gets clamped to now"; + $created = $res->[3][1]{list}[0]{created}; + $self->assert(($past lt $created) and ($created lt $future)); + $self->assert_str_equals($created, + $res->[0][1]{created}{eventCreatedInFuture}{created}); - xlog "Can update created value"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventNoCreatedId => { - created => $past, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ $eventNoCreatedId ], - properties => ['created'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventNoCreatedId}); - $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); + xlog "Can update created value"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventNoCreatedId => { + created => $past, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventNoCreatedId], + properties => ['created'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventNoCreatedId}); + $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created_legacy b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created_legacy index 1c983e7c6c..eafc4f68d2 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created_legacy +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created_legacy @@ -2,71 +2,86 @@ use Cassandane::Tiny; sub test_calendarevent_set_created_legacy - :min_version_3_1 :max_version_3_6 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_6 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + }; - my $ret = $self->createandget_event($event); - $self->assert_normalized_event_equals($event, $ret); - my $eventId = $ret->{id}; - my $created = $ret->{created}; - $self->assert_not_null($created); - my $updated = $ret->{updated}; - $self->assert_not_null($updated); + my $ret = $self->createandget_event($event); + $self->assert_normalized_event_equals($event, $ret); + my $eventId = $ret->{id}; + my $created = $ret->{created}; + $self->assert_not_null($created); + my $updated = $ret->{updated}; + $self->assert_not_null($updated); - sleep 1; + sleep 1; - # Created is preserved, updated isn't. - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'bar', - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $ret = $res->[1][1]{list}[0]; - $self->assert_str_equals($created, $ret->{created}); - $self->assert_str_not_equals($updated, $ret->{updated}); + # Created is preserved, updated isn't. + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'bar', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $ret = $res->[1][1]{list}[0]; + $self->assert_str_equals($created, $ret->{created}); + $self->assert_str_not_equals($updated, $ret->{updated}); - # Client can overwrite created and updated - $created = '2015-01-01T00:00:01Z'; - $updated = '2015-01-01T00:00:02Z'; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - created => $created, - updated => $updated - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $ret = $res->[1][1]{list}[0]; - $self->assert_str_equals($created, $ret->{created}); - $self->assert_str_equals($updated, $ret->{updated}); + # Client can overwrite created and updated + $created = '2015-01-01T00:00:01Z'; + $updated = '2015-01-01T00:00:02Z'; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + created => $created, + updated => $updated + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $ret = $res->[1][1]{list}[0]; + $self->assert_str_equals($created, $ret->{created}); + $self->assert_str_equals($updated, $ret->{updated}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created_override b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created_override index a565a9505a..0ee0b06931 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created_override +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_created_override @@ -2,127 +2,168 @@ use Cassandane::Tiny; sub test_calendarevent_set_created_override - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $t = DateTime->now(); - $t->set_time_zone('Etc/UTC'); - my $now = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - $t->add(DateTime::Duration->new(days => -2)); - my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - $t->add(DateTime::Duration->new(days => -2)); - my $waypast = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - $t->add(DateTime::Duration->new(days => 8)); - my $future = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + my $t = DateTime->now(); + $t->set_time_zone('Etc/UTC'); + my $now = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + $t->add(DateTime::Duration->new(days => -2)); + my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + $t->add(DateTime::Duration->new(days => -2)); + my $waypast = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + $t->add(DateTime::Duration->new(days => 8)); + my $future = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - xlog "Create recurring event and set 'created' timestamp"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'event', - created => $past, - start => '2021-01-01T15:30:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceRules => [{ - frequency => 'daily', - count => 30, - }], - }, + xlog "Create recurring event and set 'created' timestamp"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); + '@type' => 'Event', + uid => 'event1uid', + title => 'event', + created => $past, + start => '2021-01-01T15:30:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceRules => [ { + frequency => 'daily', + count => 30, + } ], + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); - xlog "Add new override: created > main:created"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - recurrenceOverrides => { - '2021-01-02T15:30:00' => { - title => 'eventOverride', - created => $now, - }, - }, - }, + xlog "Add new override: created > main:created"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + recurrenceOverrides => { + '2021-01-02T15:30:00' => { + title => 'eventOverride', + created => $now, + }, }, - }, 'R1'], - ['CalendarEvent/get', { - properties => ['created', 'recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); - $self->assert_str_equals($now, $res->[1][1]{list}[0] - {recurrenceOverrides}{'2021-01-02T15:30:00'}{created}); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => [ 'created', 'recurrenceOverrides' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); + $self->assert_str_equals($now, + $res->[1][1]{list}[0]{recurrenceOverrides}{'2021-01-02T15:30:00'}{created}); - xlog "Add new override: created < main:created"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides/2021-01-03T15:30:00' => { - title => 'eventOverride', - created => $waypast, - }, - }, + xlog "Add new override: created < main:created"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides/2021-01-03T15:30:00' => { + title => 'eventOverride', + created => $waypast, }, - }, 'R1'], - ['CalendarEvent/get', { - properties => ['created', 'recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); - $self->assert_str_equals($waypast, $res->[1][1]{list}[0] - {recurrenceOverrides}{'2021-01-03T15:30:00'}{created}); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => [ 'created', 'recurrenceOverrides' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); + $self->assert_str_equals($waypast, + $res->[1][1]{list}[0]{recurrenceOverrides}{'2021-01-03T15:30:00'}{created}); - xlog "Add new override: created > now: server clamps to now"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides/2021-01-04T15:30:00' => { - title => 'eventOverride', - created => $future, - }, - }, + xlog "Add new override: created > now: server clamps to now"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides/2021-01-04T15:30:00' => { + title => 'eventOverride', + created => $future, }, - }, 'R1'], - ['CalendarEvent/get', { - properties => ['created', 'recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); - $self->assert_str_equals(substr($now, 0, 15), - substr($res->[1][1]{list}[0]{recurrenceOverrides} - {'2021-01-04T15:30:00'}{created}, 0, 15)); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => [ 'created', 'recurrenceOverrides' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); + $self->assert_str_equals( + substr($now, 0, 15), + substr( + $res->[1][1]{list}[0]{recurrenceOverrides}{'2021-01-04T15:30:00'} + {created}, + 0, + 15 + ) + ); - xlog "Can change created of existing override"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides/2021-01-02T15:30:00/created' => $waypast, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - properties => ['created', 'recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_str_equals($waypast, $res->[1][1]{list}[0]{recurrenceOverrides} - {'2021-01-02T15:30:00'}{created}); + xlog "Can change created of existing override"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides/2021-01-02T15:30:00/created' => $waypast, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => [ 'created', 'recurrenceOverrides' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_str_equals($waypast, + $res->[1][1]{list}[0]{recurrenceOverrides}{'2021-01-02T15:30:00'}{created}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts index aceebc8f3f..62d3f5c691 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts @@ -2,81 +2,88 @@ use Cassandane::Tiny; sub test_calendarevent_set_defaultalerts - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - xlog "Set default alerts on calendar and event"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - defaultAlertsWithoutTime => { - alert2 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => 'PT0S', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, + xlog "Set default alerts on calendar and event"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, - 2 => { - uid => 'eventuid2local', - calendarIds => { - Default => JSON::true, - }, - title => "event2", - start => "2020-01-19T00:00:00", - showWithoutTime => JSON::true, - duration => "P1D", - useDefaultAlerts => JSON::true, + action => 'display', + }, + }, + defaultAlertsWithoutTime => { + alert2 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => 'PT0S', }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + 2 => { + uid => 'eventuid2local', + calendarIds => { + Default => JSON::true, }, - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - my $event1Href = $res->[1][1]{created}{1}{'x-href'}; - $self->assert_not_null($event1Href); - my $event2Href = $res->[1][1]{created}{2}{'x-href'}; - $self->assert_not_null($event2Href); + title => "event2", + start => "2020-01-19T00:00:00", + showWithoutTime => JSON::true, + duration => "P1D", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + my $event1Href = $res->[1][1]{created}{1}{'x-href'}; + $self->assert_not_null($event1Href); + my $event2Href = $res->[1][1]{created}{2}{'x-href'}; + $self->assert_not_null($event2Href); - my $CaldavResponse = $CalDAV->Request('GET', $event1Href); - my $icaldata = $CaldavResponse->{content}; - $self->assert_matches(qr/TRIGGER:-PT5M/, $icaldata); + my $CaldavResponse = $CalDAV->Request('GET', $event1Href); + my $icaldata = $CaldavResponse->{content}; + $self->assert_matches(qr/TRIGGER:-PT5M/, $icaldata); - $CaldavResponse = $CalDAV->Request('GET', $event2Href); - $icaldata = $CaldavResponse->{content}; - $self->assert_matches(qr/TRIGGER:PT0S/, $icaldata); + $CaldavResponse = $CalDAV->Request('GET', $event2Href); + $icaldata = $CaldavResponse->{content}; + $self->assert_matches(qr/TRIGGER:PT0S/, $icaldata); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_caldav_etag b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_caldav_etag index 788f97f99a..a78faae188 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_caldav_etag +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_caldav_etag @@ -2,116 +2,141 @@ use Cassandane::Tiny; sub test_calendarevent_set_defaultalerts_caldav_etag - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Update default alerts on calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - '4c7086e0-6114-4a71-b6ab-4b237c66f079' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - xlog "Create events with and without default alerts"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - uid => '0b610a32-82ca-48b0-a01e-a77bcf932242', - calendarIds => { - Default => JSON::true, - }, - title => "event2", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, - }, - event2 => { - uid => '9c908bf3-f69e-4fc3-8c0f-1986342f1fe5', - calendarIds => { - Default => JSON::true, - }, - title => "event2", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", + xlog "Update default alerts on calendar"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + '4c7086e0-6114-4a71-b6ab-4b237c66f079' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, + action => 'display', + }, }, - }, 'R1'], - ]); - my $event1Href = $res->[0][1]{created}{event1}{'x-href'}; - $self->assert_not_null($event1Href); - my $event2Href = $res->[0][1]{created}{event2}{'x-href'}; - $self->assert_not_null($event2Href); + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - my %Headers = ( - 'Authorization' => $caldav->auth_header(), - ); + xlog "Create events with and without default alerts"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + uid => '0b610a32-82ca-48b0-a01e-a77bcf932242', + calendarIds => { + Default => JSON::true, + }, + title => "event2", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + event2 => { + uid => '9c908bf3-f69e-4fc3-8c0f-1986342f1fe5', + calendarIds => { + Default => JSON::true, + }, + title => "event2", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + }, + }, + }, + 'R1' + ], + ]); + my $event1Href = $res->[0][1]{created}{event1}{'x-href'}; + $self->assert_not_null($event1Href); + my $event2Href = $res->[0][1]{created}{event2}{'x-href'}; + $self->assert_not_null($event2Href); - xlog "Get ETags for events"; - $res = $caldav->{ua}->request('HEAD', $caldav->request_url($event1Href), { - headers => \%Headers, - }); - my $event1ETag = $res->{headers}{etag}; - $self->assert_not_null($event1ETag); - $res = $caldav->{ua}->request('HEAD', $caldav->request_url($event2Href), { - headers => \%Headers, - }); - my $event2ETag = $res->{headers}{etag}; - $self->assert_not_null($event2ETag); + my %Headers = ('Authorization' => $caldav->auth_header(),); - xlog "Update default alerts on calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - '56bd7c39-e618-41c9-91cb-fd2f2674e674' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + xlog "Get ETags for events"; + $res = $caldav->{ua}->request( + 'HEAD', + $caldav->request_url($event1Href), + { + headers => \%Headers, + } + ); + my $event1ETag = $res->{headers}{etag}; + $self->assert_not_null($event1ETag); + $res = $caldav->{ua}->request( + 'HEAD', + $caldav->request_url($event2Href), + { + headers => \%Headers, + } + ); + my $event2ETag = $res->{headers}{etag}; + $self->assert_not_null($event2ETag); + + xlog "Update default alerts on calendar"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + '56bd7c39-e618-41c9-91cb-fd2f2674e674' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', + }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "ETag for event with useDefaultAlerts must not match former ETag"; - $res = $caldav->{ua}->request('HEAD', $caldav->request_url($event1Href), { - headers => \%Headers, - }); - $self->assert_str_not_equals($event1ETag, $res->{headers}{etag}); + xlog "ETag for event with useDefaultAlerts must not match former ETag"; + $res = $caldav->{ua}->request( + 'HEAD', + $caldav->request_url($event1Href), + { + headers => \%Headers, + } + ); + $self->assert_str_not_equals($event1ETag, $res->{headers}{etag}); - xlog "ETag for event without useDefaultAlerts must match former ETag"; - $res = $caldav->{ua}->request('HEAD', $caldav->request_url($event2Href), { - headers => \%Headers, - }); - $self->assert_str_equals($event2ETag, $res->{headers}{etag}); + xlog "ETag for event without useDefaultAlerts must match former ETag"; + $res = $caldav->{ua}->request( + 'HEAD', + $caldav->request_url($event2Href), + { + headers => \%Headers, + } + ); + $self->assert_str_equals($event2ETag, $res->{headers}{etag}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_caldav_xapple b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_caldav_xapple index 3bf7f6bc39..eabd9b5831 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_caldav_xapple +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_caldav_xapple @@ -2,68 +2,76 @@ use Cassandane::Tiny; sub test_calendarevent_set_defaultalerts_caldav_xapple - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Update default alerts on calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - - xlog "Create event with default alerts"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, + xlog "Update default alerts on calendar"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + + xlog "Create event with default alerts"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - my $event1Href = $res->[0][1]{created}{event1}{'x-href'}; - $self->assert_not_null($event1Href); + title => "event1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + my $event1Href = $res->[0][1]{created}{event1}{'x-href'}; + $self->assert_not_null($event1Href); - xlog "Get event via CalDAV"; - $res = $caldav->Request('GET', $event1Href); + xlog "Get event via CalDAV"; + $res = $caldav->Request('GET', $event1Href); - $ical = Data::ICal->new(data => $res->{content}); - my $vevent = (grep { $_->ical_entry_type() eq 'VEVENT' } @{$ical->entries()})[0]; - $self->assert_not_null($vevent); + $ical = Data::ICal->new(data => $res->{content}); + my $vevent + = (grep { $_->ical_entry_type() eq 'VEVENT' } @{ $ical->entries() })[0]; + $self->assert_not_null($vevent); - my $valarm = (grep { $_->ical_entry_type() eq 'VALARM' } @{$vevent->entries()})[0]; - $self->assert_not_null($valarm); + my $valarm + = (grep { $_->ical_entry_type() eq 'VALARM' } @{ $vevent->entries() })[0]; + $self->assert_not_null($valarm); - xlog "Assert X-JMAP-DEFAULT-ALARM is set on VALARM"; - my $xprop = $valarm->property('x-jmap-default-alarm')->[0]; - $self->assert_str_equals('TRUE', $xprop->value()); + xlog "Assert X-JMAP-DEFAULT-ALARM is set on VALARM"; + my $xprop = $valarm->property('x-jmap-default-alarm')->[0]; + $self->assert_str_equals('TRUE', $xprop->value()); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_description b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_description index 0404da9641..728cc8c62b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_description +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_description @@ -2,53 +2,61 @@ use Cassandane::Tiny; sub test_calendarevent_set_defaultalerts_description - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - xlog "Set default alerts on calendar and event"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, + xlog "Set default alerts on calendar and event"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, + action => 'display', + }, }, - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - my $event1Href = $res->[1][1]{created}{1}{'x-href'}; - $self->assert_not_null($event1Href); + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + }, + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + my $event1Href = $res->[1][1]{created}{1}{'x-href'}; + $self->assert_not_null($event1Href); - my $CaldavResponse = $CalDAV->Request('GET', $event1Href); - my $icaldata = $CaldavResponse->{content}; - $self->assert($icaldata =~ /BEGIN:VALARM[\s\S]+DESCRIPTION:event1[\s\S]+END:VALARM/g); + my $CaldavResponse = $CalDAV->Request('GET', $event1Href); + my $icaldata = $CaldavResponse->{content}; + $self->assert( + $icaldata =~ /BEGIN:VALARM[\s\S]+DESCRIPTION:event1[\s\S]+END:VALARM/g); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_etag b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_etag index a1d1d9d29e..8ece58026e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_etag +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_etag @@ -2,114 +2,141 @@ use Cassandane::Tiny; sub test_calendarevent_set_defaultalerts_etag - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - xlog "Set default alerts on calendar and event"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, - }, - 2 => { - uid => 'eventuid2local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-21T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::false, + xlog "Set default alerts on calendar and event"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - my $event1Href = $res->[1][1]{created}{1}{'x-href'}; - $self->assert_not_null($event1Href); - my $event2Href = $res->[1][1]{created}{2}{'x-href'}; - $self->assert_not_null($event2Href); + title => "event1", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + }, + 2 => { + uid => 'eventuid2local', + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2020-01-21T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::false, + }, + }, + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + my $event1Href = $res->[1][1]{created}{1}{'x-href'}; + $self->assert_not_null($event1Href); + my $event2Href = $res->[1][1]{created}{2}{'x-href'}; + $self->assert_not_null($event2Href); - xlog "Get ETags of events"; - my %Headers; - if ($CalDAV->{user}) { - $Headers{'Authorization'} = $CalDAV->auth_header(); + xlog "Get ETags of events"; + my %Headers; + if ($CalDAV->{user}) { + $Headers{'Authorization'} = $CalDAV->auth_header(); + } + my $event1URI = $CalDAV->request_url($event1Href); + my $Response = $CalDAV->{ua}->request( + 'HEAD', + $event1URI, + { + headers => \%Headers, + } + ); + my $event1ETag = $Response->{headers}{etag}; + $self->assert_not_null($event1ETag); + my $event2URI = $CalDAV->request_url($event2Href); + $Response = $CalDAV->{ua}->request( + 'HEAD', + $event2URI, + { + headers => \%Headers, } - my $event1URI = $CalDAV->request_url($event1Href); - my $Response = $CalDAV->{ua}->request('HEAD', $event1URI, { - headers => \%Headers, - }); - my $event1ETag = $Response->{headers}{etag}; - $self->assert_not_null($event1ETag); - my $event2URI = $CalDAV->request_url($event2Href); - $Response = $CalDAV->{ua}->request('HEAD', $event2URI, { - headers => \%Headers, - }); - my $event2ETag = $Response->{headers}{etag}; - $self->assert_not_null($event2ETag); + ); + my $event2ETag = $Response->{headers}{etag}; + $self->assert_not_null($event2ETag); - xlog "Update default alerts"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert2 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT10M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + xlog "Update default alerts"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert2 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT10M', + }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Refetch ETags of events"; - $Response = $CalDAV->{ua}->request('HEAD', $event1URI, { - headers => \%Headers, - }); - $self->assert_not_null($Response->{headers}{etag}); - $self->assert_str_not_equals($event1ETag, $Response->{headers}{etag}); - $Response = $CalDAV->{ua}->request('HEAD', $event2URI, { - headers => \%Headers, - }); - $self->assert_not_null($Response->{headers}{etag}); - $self->assert_str_equals($event2ETag, $Response->{headers}{etag}); + xlog "Refetch ETags of events"; + $Response = $CalDAV->{ua}->request( + 'HEAD', + $event1URI, + { + headers => \%Headers, + } + ); + $self->assert_not_null($Response->{headers}{etag}); + $self->assert_str_not_equals($event1ETag, $Response->{headers}{etag}); + $Response = $CalDAV->{ua}->request( + 'HEAD', + $event2URI, + { + headers => \%Headers, + } + ); + $self->assert_not_null($Response->{headers}{etag}); + $self->assert_str_equals($event2ETag, $Response->{headers}{etag}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_etag_shared b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_etag_shared index 5391ccc4b2..b7954713ef 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_etag_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_defaultalerts_etag_shared @@ -2,178 +2,218 @@ use Cassandane::Tiny; sub test_calendarevent_set_defaultalerts_etag_shared - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - xlog "Set default alerts on calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert1 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + xlog "Set default alerts on calendar"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert1 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', + }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Create other user and share owner calendar"; - my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user("other"); - $admintalk->setacl("user.cassandane.#calendars.Default", "other", "lrsiwntex") or die; - my $service = $self->{instance}->get_service("http"); - my $otherJMAP = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - my $otherCalDAV = Net::CalDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + xlog "Create other user and share owner calendar"; + my $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->create_user("other"); + $admintalk->setacl("user.cassandane.#calendars.Default", "other", "lrsiwntex") + or die; + my $service = $self->{instance}->get_service("http"); + my $otherJMAP = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + my $otherCalDAV = Net::CalDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog "Create event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "eventCass", - start => "2020-01-19T11:00:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - useDefaultAlerts => JSON::true, - color => 'yellow', - }, + xlog "Create event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); - my $cassHref = $res->[0][1]{created}{1}{'x-href'}; - $self->assert_not_null($cassHref); + title => "eventCass", + start => "2020-01-19T11:00:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + useDefaultAlerts => JSON::true, + color => 'yellow', + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); + my $cassHref = $res->[0][1]{created}{1}{'x-href'}; + $self->assert_not_null($cassHref); - xlog "Get event as other user"; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - 'urn:ietf:params:jmap:mail', - ]; - $res = $otherJMAP->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - properties => ['x-href'], - }, 'R1'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - my $otherHref = $res->[0][1]{list}[0]{'x-href'}; - $self->assert_not_null($otherHref); + xlog "Get event as other user"; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + 'urn:ietf:params:jmap:mail', + ]; + $res = $otherJMAP->CallMethods( + [ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + properties => ['x-href'], + }, + 'R1' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + my $otherHref = $res->[0][1]{list}[0]{'x-href'}; + $self->assert_not_null($otherHref); - xlog "Set per-user prop to force per-user data split"; - $res = $otherJMAP->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - color => 'green', - }, + xlog "Set per-user prop to force per-user data split"; + $res = $otherJMAP->CallMethods( + [ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + color => 'green', }, - }, 'R1'], - ], $using); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog "Get ETag of event as cassandane"; - my %Headers; - if ($CalDAV->{user}) { - $Headers{'Authorization'} = $CalDAV->auth_header(); + xlog "Get ETag of event as cassandane"; + my %Headers; + if ($CalDAV->{user}) { + $Headers{'Authorization'} = $CalDAV->auth_header(); + } + my $cassURI = $CalDAV->request_url($cassHref); + my $ua = $CalDAV->ua(); + my $Response = $ua->request( + 'HEAD', $cassURI, + { + headers => \%Headers, } - my $cassURI = $CalDAV->request_url($cassHref); - my $ua = $CalDAV->ua(); - my $Response = $ua->request('HEAD', $cassURI, { - headers => \%Headers, - }); - my $cassETag = $Response->{headers}{etag}; - $self->assert_not_null($cassETag); + ); + my $cassETag = $Response->{headers}{etag}; + $self->assert_not_null($cassETag); - xlog "Get ETag of event as other"; - %Headers = (); - if ($otherCalDAV->{user}) { - $Headers{'Authorization'} = $otherCalDAV->auth_header(); + xlog "Get ETag of event as other"; + %Headers = (); + if ($otherCalDAV->{user}) { + $Headers{'Authorization'} = $otherCalDAV->auth_header(); + } + my $otherURI = $otherCalDAV->request_url($otherHref); + my $otherUa = $otherCalDAV->ua(); + $Response = $otherUa->request( + 'HEAD', + $otherURI, + { + headers => \%Headers, } - my $otherURI = $otherCalDAV->request_url($otherHref); - my $otherUa = $otherCalDAV->ua(); - $Response = $otherUa->request('HEAD', $otherURI, { - headers => \%Headers, - }); - my $otherETag = $Response->{headers}{etag}; - $self->assert_not_null($otherETag); + ); + my $otherETag = $Response->{headers}{etag}; + $self->assert_not_null($otherETag); - xlog "Update default alerts for cassandane"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - defaultAlertsWithTime => { - alert2 => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT10M', - }, - action => 'display', - }, - }, - } - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + xlog "Update default alerts for cassandane"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + defaultAlertsWithTime => { + alert2 => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT10M', + }, + action => 'display', + }, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Refetch ETags of events"; - %Headers = (); - if ($CalDAV->{user}) { - $Headers{'Authorization'} = $CalDAV->auth_header(); + xlog "Refetch ETags of events"; + %Headers = (); + if ($CalDAV->{user}) { + $Headers{'Authorization'} = $CalDAV->auth_header(); + } + $Response = $CalDAV->{ua}->request( + 'HEAD', $cassURI, + { + headers => \%Headers, } - $Response = $CalDAV->{ua}->request('HEAD', $cassURI, { - headers => \%Headers, - }); - $self->assert_not_null($Response->{headers}{etag}); - $self->assert_str_not_equals($cassETag, $Response->{headers}{etag}); + ); + $self->assert_not_null($Response->{headers}{etag}); + $self->assert_str_not_equals($cassETag, $Response->{headers}{etag}); - %Headers = (); - if ($otherCalDAV->{user}) { - $Headers{'Authorization'} = $otherCalDAV->auth_header(); + %Headers = (); + if ($otherCalDAV->{user}) { + $Headers{'Authorization'} = $otherCalDAV->auth_header(); + } + $Response = $otherCalDAV->{ua}->request( + 'HEAD', + $otherURI, + { + headers => \%Headers, } - $Response = $otherCalDAV->{ua}->request('HEAD', $otherURI, { - headers => \%Headers, - }); - $self->assert_not_null($Response->{headers}{etag}); - $self->assert_str_equals($otherETag, $Response->{headers}{etag}); + ); + $self->assert_not_null($Response->{headers}{etag}); + $self->assert_str_equals($otherETag, $Response->{headers}{etag}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_destroy_itip b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_destroy_itip index 8030d1838e..35f6362348 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_destroy_itip +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_destroy_itip @@ -2,24 +2,23 @@ use Cassandane::Tiny; sub test_calendarevent_set_destroy_itip - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my %expectNotif = ( - 'NEEDS-ACTION' => undef, - 'TENTATIVE' => 'REPLY', - 'ACCEPTED' => 'REPLY', - ); + my %expectNotif = ( + 'NEEDS-ACTION' => undef, + 'TENTATIVE' => 'REPLY', + 'ACCEPTED' => 'REPLY', + ); - while (my ($partstat, $wantNotif) = each %expectNotif) { + while (my ($partstat, $wantNotif) = each %expectNotif) { - xlog "Create invite with PARTSTAT=$partstat"; - my $uid = 'event' . $partstat . 'uid'; - my $ical = <Request('PUT', "/dav/calendars/user/cassandane/Default/event$partstat.ics", - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', + "/dav/calendars/user/cassandane/Default/event$partstat.ics", + $ical, 'Content-Type' => 'text/calendar'); - my $eventId = encode_eventid($uid); + my $eventId = encode_eventid($uid); - xlog "Clean notifications"; - $self->{instance}->getnotify(); + xlog "Clean notifications"; + $self->{instance}->getnotify(); - xlog "Destroy event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [ $eventId ], - }, 'R1'], - ]); - $self->assert_deep_equals([ $eventId ], $res->[0][1]{destroyed}); + xlog "Destroy event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + destroy => [$eventId], + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$eventId], $res->[0][1]{destroyed}); - my $data = $self->{instance}->getnotify(); - my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - if ($wantNotif) { - xlog "Assert iTIP notification is sent"; - $self->assert_not_null($notif); + my $data = $self->{instance}->getnotify(); + my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + if ($wantNotif) { + xlog "Assert iTIP notification is sent"; + $self->assert_not_null($notif); - my $expect_id = encode_eventid($uid); - my $notif_payload = decode_json($notif->{MESSAGE}); - $self->assert_str_equals($expect_id, $notif_payload->{id}); - $self->assert_str_equals($wantNotif, $notif_payload->{method}); - } else { - xlog "Assert no iTIP notification is sent"; - $self->assert_null($notif); - } + my $expect_id = encode_eventid($uid); + my $notif_payload = decode_json($notif->{MESSAGE}); + $self->assert_str_equals($expect_id, $notif_payload->{id}); + $self->assert_str_equals($wantNotif, $notif_payload->{method}); + } else { + xlog "Assert no iTIP notification is sent"; + $self->assert_null($notif); } + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_endtimezone b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_endtimezone index a3b460d767..c29e1f79e1 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_endtimezone +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_endtimezone @@ -2,44 +2,43 @@ use Cassandane::Tiny; sub test_calendarevent_set_endtimezone - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "description"=> "", - "freeBusyStatus"=> "busy", - "prodId" => "foo", - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "description" => "", + "freeBusyStatus" => "busy", + "prodId" => "foo", + }; - my $ret; + my $ret; - $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - $self->assert_normalized_event_equals($event, $ret); + $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + $self->assert_normalized_event_equals($event, $ret); - $event->{locations} = { - "loc1" => { - "timeZone" => "Europe/Berlin", - "relativeTo" => "end", - }, - }; - $ret = $self->updateandget_event({ - id => $event->{id}, - calendarIds => $event->{calendarIds}, - locations => $event->{locations}, - }); + $event->{locations} = { + "loc1" => { + "timeZone" => "Europe/Berlin", + "relativeTo" => "end", + }, + }; + $ret = $self->updateandget_event({ + id => $event->{id}, + calendarIds => $event->{calendarIds}, + locations => $event->{locations}, + }); - $self->assert_normalized_event_equals($event, $ret); + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_endtimezone_recurrence b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_endtimezone_recurrence index db33720239..f7d9dcda5b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_endtimezone_recurrence +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_endtimezone_recurrence @@ -2,45 +2,44 @@ use Cassandane::Tiny; sub test_calendarevent_set_endtimezone_recurrence - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "locations" => { - "loc1" => { - "timeZone" => "Europe/Berlin", - "relativeTo" => "end", - }, - }, - "showWithoutTime"=> JSON::false, - "description"=> "", - "freeBusyStatus"=> "busy", - "prodId" => "foo", - "recurrenceRules" => [{ - "frequency" => "monthly", - count => 12, - }], - "recurrenceOverrides" => { - "2015-12-07T09:00:00" => { - "locations/loc1/timeZone" => "America/New_York", - }, - }, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "locations" => { + "loc1" => { + "timeZone" => "Europe/Berlin", + "relativeTo" => "end", + }, + }, + "showWithoutTime" => JSON::false, + "description" => "", + "freeBusyStatus" => "busy", + "prodId" => "foo", + "recurrenceRules" => [ { + "frequency" => "monthly", + count => 12, + } ], + "recurrenceOverrides" => { + "2015-12-07T09:00:00" => { + "locations/loc1/timeZone" => "America/New_York", + }, + }, + }; - my $ret; + my $ret; - $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - $self->assert_normalized_event_equals($event, $ret); + $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_exrule b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_exrule index 947b799918..b5b19f78de 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_exrule +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_exrule @@ -2,33 +2,32 @@ use Cassandane::Tiny; sub test_calendarevent_set_exrule - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $event = { - calendarIds => { - Default => JSON::true, - }, - title => "title", - description => "description", - start => "2020-12-03T09:00:00", - duration => "PT1H", - timeZone => "Europe/London", - showWithoutTime => JSON::false, - freeBusyStatus => "busy", - recurrenceRules => [{ - frequency => 'weekly', - }], - excludedRecurrenceRules => [{ - frequency => 'monthly', - byMonthDay => [1], - }], - }; + my $event = { + calendarIds => { + Default => JSON::true, + }, + title => "title", + description => "description", + start => "2020-12-03T09:00:00", + duration => "PT1H", + timeZone => "Europe/London", + showWithoutTime => JSON::false, + freeBusyStatus => "busy", + recurrenceRules => [ { + frequency => 'weekly', + } ], + excludedRecurrenceRules => [ { + frequency => 'monthly', + byMonthDay => [1], + } ], + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_fullblown b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_fullblown index a3d2968276..0be756b466 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_fullblown +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_fullblown @@ -2,259 +2,266 @@ use Cassandane::Tiny; sub test_calendarevent_set_fullblown - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my ($maj, $min) = Cassandane::Instance->get_version(); + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my ($maj, $min) = Cassandane::Instance->get_version(); - my $event1 = { - calendarIds => { - 'Default' => JSON::true, + my $event1 = { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + uid => 'event1uid', + relatedTo => { + relatedEventUid => { + '@type' => 'Relation', + relation => { + first => JSON::true, + next => JSON::true, + child => JSON::true, + parent => JSON::true, }, - '@type' => 'Event', - uid => 'event1uid', - relatedTo => { - relatedEventUid => { - '@type' => 'Relation', - relation => { - first => JSON::true, - next => JSON::true, - child => JSON::true, - parent => JSON::true, - }, - }, + }, + }, + prodId => '-//Foo//Bar//EN', + created => '2020-12-21T07:47:00Z', + updated => '2020-12-21T07:47:00Z', + sequence => 3, + title => 'event1title', + description => 'event1description', + descriptionContentType => 'text/plain', + showWithoutTime => JSON::true, + locations => { + loc1 => { + '@type' => 'Location', + name => 'loc1name', + description => 'loc1description', + locationTypes => { + hotel => JSON::true, + other => JSON::true, }, - prodId => '-//Foo//Bar//EN', - created => '2020-12-21T07:47:00Z', - updated => '2020-12-21T07:47:00Z', - sequence => 3, - title => 'event1title', - description => 'event1description', - descriptionContentType => 'text/plain', - showWithoutTime => JSON::true, - locations => { - loc1 => { - '@type' => 'Location', - name => 'loc1name', - description => 'loc1description', - locationTypes => { - hotel => JSON::true, - other => JSON::true, - }, - relativeTo => 'end', - timeZone => 'Africa/Windhoek', - coordinates => 'geo:-22.55941,17.08323', - links => { - link1 => { - '@type' => 'Link', - href => 'https://local/loc1link1.jpg', - cid => 'foo@local', - contentType => 'image/jpeg', - size => 123, - rel => 'icon', - display => 'fullsize', - title => 'loc1title', - }, - }, - }, + relativeTo => 'end', + timeZone => 'Africa/Windhoek', + coordinates => 'geo:-22.55941,17.08323', + links => { + link1 => { + '@type' => 'Link', + href => 'https://local/loc1link1.jpg', + cid => 'foo@local', + contentType => 'image/jpeg', + size => 123, + rel => 'icon', + display => 'fullsize', + title => 'loc1title', + }, }, - virtualLocations => { - virtloc1 => { - '@type' => 'VirtualLocation', - name => 'virtloc1name', - description => 'virtloca1description', - uri => 'tel:+1-555-555-5555', - features => { - audio => JSON::true, - chat => JSON::true, - feed => JSON::true, - moderator => JSON::true, - phone => JSON::true, - screen => JSON::true, - video => JSON::true, - }, - }, + }, + }, + virtualLocations => { + virtloc1 => { + '@type' => 'VirtualLocation', + name => 'virtloc1name', + description => 'virtloca1description', + uri => 'tel:+1-555-555-5555', + features => { + audio => JSON::true, + chat => JSON::true, + feed => JSON::true, + moderator => JSON::true, + phone => JSON::true, + screen => JSON::true, + video => JSON::true, + }, + }, + }, + links => { + link1 => { + '@type' => 'Link', + href => 'https://local/link1.jpg', + cid => 'foo@local', + contentType => 'image/jpeg', + size => 123, + rel => 'icon', + display => 'fullsize', + title => 'link1title', + }, + }, + locale => 'en', + keywords => { + keyword1 => JSON::true, + keyword2 => JSON::true, + }, + color => 'silver', + recurrenceRules => [ { + '@type' => 'RecurrenceRule', + frequency => 'monthly', + interval => 2, + rscale => 'gregorian', + skip => 'forward', + firstDayOfWeek => 'tu', + byDay => [ { + '@type' => 'NDay', + day => 'we', + nthOfPeriod => 3, + } ], + byMonthDay => [ 1, 6, 13, 16, 30 ], + byHour => [ 7, 13 ], + byMinute => [ 2, 46 ], + bySecond => [ 5, 10 ], + bySetPosition => [ 1, 5, 9 ], + count => 7, + } ], + excludedRecurrenceRules => [ { + '@type' => 'RecurrenceRule', + frequency => 'monthly', + interval => 3, + rscale => 'gregorian', + skip => 'forward', + firstDayOfWeek => 'tu', + byDay => [ { + '@type' => 'NDay', + day => 'we', + nthOfPeriod => 3, + } ], + byMonthDay => [ 1, 6, 13, 16, 30 ], + byHour => [ 7, 13 ], + byMinute => [ 2, 46 ], + bySecond => [ 5, 10 ], + bySetPosition => [ 1, 5, 9 ], + count => 7, + } ], + recurrenceOverrides => { + '2021-02-02T02:00:00' => { + title => 'recurrenceOverrideTitle', + }, + }, + priority => 7, + freeBusyStatus => 'free', + privacy => 'secret', + replyTo => { + imip => 'mailto:orga@local', + }, + participants => { + orga => { + '@type' => 'Participant', + email => 'orga@local', + sendTo => { + imip => 'mailto:orga@local', + }, + roles => { + owner => JSON::true, + }, + }, + participant1 => { + '@type' => 'Participant', + name => 'participant1Name', + email => 'participant1@local', + description => 'participant1Description', + sendTo => { + imip => 'mailto:participant1@local', + web => 'https://local/participant1', + }, + kind => 'individual', + roles => { + attendee => JSON::true, + chair => JSON::true, + }, + locationId => 'loc1', + language => 'de', + participationStatus => 'tentative', + participationComment => 'participant1Comment', + expectReply => JSON::true, + delegatedTo => { + participant2 => JSON::true, + }, + delegatedFrom => { + participant3 => JSON::true, }, links => { - link1 => { - '@type' => 'Link', - href => 'https://local/link1.jpg', - cid => 'foo@local', - contentType => 'image/jpeg', - size => 123, - rel => 'icon', - display => 'fullsize', - title => 'link1title', - }, + link1 => { + '@type' => 'Link', + href => 'https://local/participant1link1.jpg', + cid => 'foo@local', + contentType => 'image/jpeg', + size => 123, + rel => 'describedby', + title => 'participant1title', + }, }, - locale => 'en', - keywords => { - keyword1 => JSON::true, - keyword2 => JSON::true, + }, + participant2 => { + '@type' => 'Participant', + email => 'participant2@local', + sendTo => { + imip => 'mailto:participant2@local', }, - color => 'silver', - recurrenceRules => [{ - '@type' => 'RecurrenceRule', - frequency => 'monthly', - interval => 2, - rscale => 'gregorian', - skip => 'forward', - firstDayOfWeek => 'tu', - byDay => [{ - '@type' => 'NDay', - day => 'we', - nthOfPeriod => 3, - }], - byMonthDay => [1,6,13,16,30], - byHour => [7,13], - byMinute => [2,46], - bySecond => [5,10], - bySetPosition => [1,5,9], - count => 7, - }], - excludedRecurrenceRules => [{ - '@type' => 'RecurrenceRule', - frequency => 'monthly', - interval => 3, - rscale => 'gregorian', - skip => 'forward', - firstDayOfWeek => 'tu', - byDay => [{ - '@type' => 'NDay', - day => 'we', - nthOfPeriod => 3, - }], - byMonthDay => [1,6,13,16,30], - byHour => [7,13], - byMinute => [2,46], - bySecond => [5,10], - bySetPosition => [1,5,9], - count => 7, - }], - recurrenceOverrides => { - '2021-02-02T02:00:00' => { - title => 'recurrenceOverrideTitle', - }, + roles => { + attendee => JSON::true, }, - priority => 7, - freeBusyStatus => 'free', - privacy => 'secret', - replyTo => { - imip => 'mailto:orga@local', + }, + participant3 => { + '@type' => 'Participant', + email => 'participant3@local', + sendTo => { + imip => 'mailto:participant3@local', }, - participants => { - orga => { - '@type' => 'Participant', - email => 'orga@local', - sendTo => { - imip => 'mailto:orga@local', - }, - roles => { - owner => JSON::true, - }, - }, - participant1 => { - '@type' => 'Participant', - name => 'participant1Name', - email => 'participant1@local', - description => 'participant1Description', - sendTo => { - imip => 'mailto:participant1@local', - web => 'https://local/participant1', - }, - kind => 'individual', - roles => { - attendee => JSON::true, - chair => JSON::true, - }, - locationId => 'loc1', - language => 'de', - participationStatus => 'tentative', - participationComment => 'participant1Comment', - expectReply => JSON::true, - delegatedTo => { - participant2 => JSON::true, - }, - delegatedFrom => { - participant3 => JSON::true, - }, - links => { - link1 => { - '@type' => 'Link', - href => 'https://local/participant1link1.jpg', - cid => 'foo@local', - contentType => 'image/jpeg', - size => 123, - rel => 'describedby', - title => 'participant1title', - }, - }, - }, - participant2 => { - '@type' => 'Participant', - email => 'participant2@local', - sendTo => { - imip => 'mailto:participant2@local', - }, - roles => { - attendee => JSON::true, - }, - }, - participant3 => { - '@type' => 'Participant', - email => 'participant3@local', - sendTo => { - imip => 'mailto:participant3@local', - }, - roles => { - attendee => JSON::true, - }, - }, + roles => { + attendee => JSON::true, }, - alerts => { - 'cb777aa2-0dcd-4489-a0ac-700d1f859934' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - offset => '-PT5M', - relativeTo => 'end', - }, - }, - 'b3dc4bdc-119f-4fae-ab94-556a07aa5514' => { - '@type' => 'Alert', - trigger => { - '@type' => 'AbsoluteTrigger', - when => '2021-01-01T01:00:00Z', - }, - acknowledged => '2020-12-21T07:47:00Z', - relatedTo => { - 'cb777aa2-0dcd-4489-a0ac-700d1f859934' => { - '@type' => 'Relation', - relation => { - parent => JSON::true, - }, - }, - }, - action => 'email', + }, + }, + alerts => { + 'cb777aa2-0dcd-4489-a0ac-700d1f859934' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + offset => '-PT5M', + relativeTo => 'end', + }, + }, + 'b3dc4bdc-119f-4fae-ab94-556a07aa5514' => { + '@type' => 'Alert', + trigger => { + '@type' => 'AbsoluteTrigger', + when => '2021-01-01T01:00:00Z', + }, + acknowledged => '2020-12-21T07:47:00Z', + relatedTo => { + 'cb777aa2-0dcd-4489-a0ac-700d1f859934' => { + '@type' => 'Relation', + relation => { + parent => JSON::true, }, + }, }, + action => 'email', + }, + }, - start => '2021-01-01T01:00:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - status => 'tentative', - }; + start => '2021-01-01T01:00:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + status => 'tentative', + }; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => $event1, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event1'], - }, 'R2'], - ]); - $self->assert_normalized_event_equals($event1, $res->[1][1]{list}[0]); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => $event1, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event1'], + }, + 'R2' + ], + ]); + $self->assert_normalized_event_equals($event1, $res->[1][1]{list}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_hideattendees b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_hideattendees index e8252270d8..c5575fa4d3 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_hideattendees +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_hideattendees @@ -2,122 +2,139 @@ use Cassandane::Tiny; sub test_calendarevent_set_hideattendees - :needs_component_jmap :min_version_0_0 :max_version_0_0 -{ - my ($self) = @_; + : needs_component_jmap : min_version_0_0 : max_version_0_0 { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my ($shareeJmap, $shareeCalDAV) = $self->create_user('sharee'); + my ($shareeJmap, $shareeCalDAV) = $self->create_user('sharee'); - xlog "create event and share with sharee"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uidlocal', - title => "event1", - start => "2020-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'daily', - count => 3, - }], - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - sharee => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:sharee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'needs-action', - }, - attendee1 => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee1@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, - recurrenceOverrides => { - '2020-01-02T09:00:00' => { - 'participants/attendee2' => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee2@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - 'participants/attendee1/participationStatus' => 'tentative', - }, - - }, - hideAttendees => JSON::true, + xlog "create event and share with sharee"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uidlocal', + title => "event1", + start => "2020-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'daily', + count => 3, + } ], + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + sharee => { + roles => { + 'attendee' => JSON::true, }, + sendTo => { + imip => 'mailto:sharee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'needs-action', + }, + attendee1 => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee1@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', + }, }, - }, 'R1'], - ['Calendar/set', { - update => { - Default => { - shareWith => { - 'sharee' => { - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayRSVP => JSON::true, - }, - }, + recurrenceOverrides => { + '2020-01-02T09:00:00' => { + 'participants/attendee2' => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee2@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, + 'participants/attendee1/participationStatus' => 'tentative', + }, + + }, + hideAttendees => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + 'sharee' => { + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayRSVP => JSON::true, + }, }, - }, 'R2'], - ['CalendarEvent/get', { - ids => ['#event1'], - properties => ['hideAttendees'], - }, 'R3'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); - $self->assert(exists $res->[1][1]{updated}{Default}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{hideAttendees}); + }, + }, + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event1'], + properties => ['hideAttendees'], + }, + 'R3' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); + $self->assert(exists $res->[1][1]{updated}{Default}); + $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{hideAttendees}); - xlog "get event as sharee"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => ['participants', 'hideAttendees', 'recurrenceOverrides'], - }, 'R1'], - ]); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{hideAttendees}); + xlog "get event as sharee"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => + [ 'participants', 'hideAttendees', 'recurrenceOverrides' ], + }, + 'R1' + ], + ]); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{hideAttendees}); - $self->assert_not_null($res->[0][1]{list}[0]{participants}{cassandane}); - $self->assert_not_null($res->[0][1]{list}[0]{participants}{sharee}); - $self->assert_num_equals(2, scalar keys %{$res->[0][1]{list}[0]{participants}}); - $self->assert_deep_equals({ '2020-01-02T09:00:00' => {} }, - $res->[0][1]{list}[0]{recurrenceOverrides}); + $self->assert_not_null($res->[0][1]{list}[0]{participants}{cassandane}); + $self->assert_not_null($res->[0][1]{list}[0]{participants}{sharee}); + $self->assert_num_equals(2, + scalar keys %{ $res->[0][1]{list}[0]{participants} }); + $self->assert_deep_equals({ '2020-01-02T09:00:00' => {} }, + $res->[0][1]{list}[0]{recurrenceOverrides}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_hideattendees_itip b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_hideattendees_itip index ab6571a3e4..63644af931 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_hideattendees_itip +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_hideattendees_itip @@ -2,86 +2,89 @@ use Cassandane::Tiny; sub test_calendarevent_set_hideattendees_itip - :needs_component_jmap :min_version_0_0 :max_version_0_0 -{ - my ($self) = @_; + : needs_component_jmap : min_version_0_0 : max_version_0_0 { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uidlocal', - title => "event1", - start => "2020-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - attendee1 => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee1@example.com', - }, - }, - attendee2 => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee2@example.com', - }, - }, - }, - hideAttendees => JSON::true, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uidlocal', + title => "event1", + start => "2020-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + attendee1 => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee1@example.com', + }, + }, + attendee2 => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:attendee2@example.com', }, + }, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); + hideAttendees => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); - my $data = $self->{instance}->getnotify(); + my $data = $self->{instance}->getnotify(); - my $imip = {}; - foreach my $notif (@$data) { - if (not $notif->{METHOD} eq 'imip') { - next; - } - my $msg = decode_json($notif->{MESSAGE}); - $imip->{$msg->{recipient}} = $msg; + my $imip = {}; + foreach my $notif (@$data) { + if (not $notif->{METHOD} eq 'imip') { + next; } + my $msg = decode_json($notif->{MESSAGE}); + $imip->{ $msg->{recipient} } = $msg; + } - $self->assert_num_equals(2, scalar keys %{$imip}); + $self->assert_num_equals(2, scalar keys %{$imip}); - $self->assert(not $imip->{'attendee1@example.com'}->{ical} =~ - m/attendee2\@example.com/); - $self->assert($imip->{'attendee1@example.com'}->{ical} =~ - m/attendee1\@example.com/); + $self->assert( + not $imip->{'attendee1@example.com'}->{ical} =~ m/attendee2\@example.com/); + $self->assert( + $imip->{'attendee1@example.com'}->{ical} =~ m/attendee1\@example.com/); - $self->assert(not $imip->{'attendee2@example.com'}->{ical} =~ - m/attendee1\@example.com/); - $self->assert($imip->{'attendee2@example.com'}->{ical} =~ - m/attendee2\@example.com/); + $self->assert( + not $imip->{'attendee2@example.com'}->{ical} =~ m/attendee1\@example.com/); + $self->assert( + $imip->{'attendee2@example.com'}->{ical} =~ m/attendee2\@example.com/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_htmldescription b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_htmldescription index 60df239870..168a893530 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_htmldescription +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_htmldescription @@ -2,32 +2,38 @@ use Cassandane::Tiny; sub test_calendarevent_set_htmldescription - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "description"=> 'HTML with special chars : and ; and "', - "descriptionContentType" => 'text/html', - "privacy" => "secret", - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "description" => + 'HTML with special chars : and ; and "', + "descriptionContentType" => 'text/html', + "privacy" => "secret", + }; - # This actually tests that Cyrus doesn't support HTML descriptions! - my $res = $jmap->CallMethods([['CalendarEvent/set', { - create => { "1" => $event, } - }, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notCreated}{"1"}{type}); - $self->assert_str_equals("descriptionContentType", $res->[0][1]{notCreated}{"1"}{properties}[0]); + # This actually tests that Cyrus doesn't support HTML descriptions! + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { "1" => $event, } + }, + "R1" + ] ]); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notCreated}{"1"}{type}); + $self->assert_str_equals("descriptionContentType", + $res->[0][1]{notCreated}{"1"}{properties}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_invalidpatch b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_invalidpatch index 5f71faddca..8b8bb93f88 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_invalidpatch +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_invalidpatch @@ -2,40 +2,44 @@ use Cassandane::Tiny; sub test_calendarevent_set_invalidpatch - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uid', - title => "event1", - description => "", - freeBusyStatus => "busy", - start => "2019-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - }, - } - }, 'R1'] - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); + my $jmap = $self->{jmap}; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uid', + title => "event1", + description => "", + freeBusyStatus => "busy", + start => "2019-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + }, + } + }, + 'R1' + ] ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'alerts/alert1/trigger/offset' => '-PT5M', - }, - } - }, 'R1'] - ]); - $self->assert_str_equals("invalidPatch", $res->[0][1]{notUpdated}{$eventId}{type}); + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'alerts/alert1/trigger/offset' => '-PT5M', + }, + } + }, + 'R1' + ] ]); + $self->assert_str_equals("invalidPatch", + $res->[0][1]{notUpdated}{$eventId}{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_isdraft b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_isdraft index 1cd3fa8e5a..76e5e18198 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_isdraft +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_isdraft @@ -2,97 +2,123 @@ use Cassandane::Tiny; sub test_calendarevent_set_isdraft - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - # Create events as draft and non-draft. + # Create events as draft and non-draft. - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "draft", - "start"=> "2019-12-05T09:00:00", - "duration"=> "PT5M", - "timeZone"=> "Etc/UTC", - "isDraft" => JSON::true, - }, - 2 => { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "non-draft", - "start"=> "2019-12-05T10:00:00", - "duration"=> "PT5M", - "timeZone"=> "Etc/UTC", - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + calendarIds => { + $calid => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#1', '#2'], properties => ['isDraft'], - }, 'R2'] - ]); - my $eventDraftId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventDraftId); - my $eventNonDraftId = $res->[0][1]{created}{2}{id}; - $self->assert_not_null($eventNonDraftId); + "title" => "draft", + "start" => "2019-12-05T09:00:00", + "duration" => "PT5M", + "timeZone" => "Etc/UTC", + "isDraft" => JSON::true, + }, + 2 => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "non-draft", + "start" => "2019-12-05T10:00:00", + "duration" => "PT5M", + "timeZone" => "Etc/UTC", + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [ '#1', '#2' ], + properties => ['isDraft'], + }, + 'R2' + ] + ]); + my $eventDraftId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventDraftId); + my $eventNonDraftId = $res->[0][1]{created}{2}{id}; + $self->assert_not_null($eventNonDraftId); - my %events = map { $_->{id} => $_ } @{$res->[1][1]{list}}; - $self->assert_equals(JSON::true, $events{$eventDraftId}{isDraft}); - $self->assert_equals(JSON::false, $events{$eventNonDraftId}{isDraft}); + my %events = map { $_->{id} => $_ } @{ $res->[1][1]{list} }; + $self->assert_equals(JSON::true, $events{$eventDraftId}{isDraft}); + $self->assert_equals(JSON::false, $events{$eventNonDraftId}{isDraft}); - # Updating an arbitrary property preserves draft flag. + # Updating an arbitrary property preserves draft flag. - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventDraftId => { - description => "updated", - }, - $eventNonDraftId => { - description => "updated", - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventDraftId, $eventNonDraftId], properties => ['isDraft'], - }, 'R2'] - ]); - $self->assert_not_null($res->[0][1]{updated}{$eventDraftId}); - $self->assert_not_null($res->[0][1]{updated}{$eventNonDraftId}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventDraftId => { + description => "updated", + }, + $eventNonDraftId => { + description => "updated", + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [ $eventDraftId, $eventNonDraftId ], + properties => ['isDraft'], + }, + 'R2' + ] + ]); + $self->assert_not_null($res->[0][1]{updated}{$eventDraftId}); + $self->assert_not_null($res->[0][1]{updated}{$eventNonDraftId}); - %events = map { $_->{id} => $_ } @{$res->[1][1]{list}}; - $self->assert_equals(JSON::true, $events{$eventDraftId}{isDraft}); - $self->assert_equals(JSON::false, $events{$eventNonDraftId}{isDraft}); + %events = map { $_->{id} => $_ } @{ $res->[1][1]{list} }; + $self->assert_equals(JSON::true, $events{$eventDraftId}{isDraft}); + $self->assert_equals(JSON::false, $events{$eventNonDraftId}{isDraft}); - # Toggle isDraft flags (only allowed from draft to non-draft) + # Toggle isDraft flags (only allowed from draft to non-draft) - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventDraftId => { - "isDraft" => JSON::false, - }, - $eventNonDraftId => { - "isDraft" => JSON::true, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventDraftId, $eventNonDraftId], properties => ['isDraft'], - }, 'R2'] - ]); - $self->assert_not_null($res->[0][1]{updated}{$eventDraftId}); - $self->assert_not_null($res->[0][1]{notUpdated}{$eventNonDraftId}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventDraftId => { + "isDraft" => JSON::false, + }, + $eventNonDraftId => { + "isDraft" => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [ $eventDraftId, $eventNonDraftId ], + properties => ['isDraft'], + }, + 'R2' + ] + ]); + $self->assert_not_null($res->[0][1]{updated}{$eventDraftId}); + $self->assert_not_null($res->[0][1]{notUpdated}{$eventNonDraftId}); - %events = map { $_->{id} => $_ } @{$res->[1][1]{list}}; - $self->assert_equals(JSON::false, $events{$eventDraftId}{isDraft}); - $self->assert_equals(JSON::false, $events{$eventNonDraftId}{isDraft}); + %events = map { $_->{id} => $_ } @{ $res->[1][1]{list} }; + $self->assert_equals(JSON::false, $events{$eventDraftId}{isDraft}); + $self->assert_equals(JSON::false, $events{$eventNonDraftId}{isDraft}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_itip_preserve_partstat b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_itip_preserve_partstat index 5b4d8f2c37..10256da97b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_itip_preserve_partstat +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_itip_preserve_partstat @@ -2,111 +2,133 @@ use Cassandane::Tiny; sub test_calendarevent_set_itip_preserve_partstat - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my ($otherJmap, $otherCalDAV) = $self->create_user('other'); + my ($otherJmap, $otherCalDAV) = $self->create_user('other'); - xlog 'create event and invite other user'; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uidlocal', - title => 'event1', - start => '2020-01-01T09:00:00', - timeZone => 'Europe/Vienna', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - other => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:other@example.com', - }, - expectReply => JSON::true, - participationStatus => 'needs-action', - }, - }, + xlog 'create event and invite other user'; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uidlocal', + title => 'event1', + start => '2020-01-01T09:00:00', + timeZone => 'Europe/Vienna', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + other => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:other@example.com', }, + expectReply => JSON::true, + participationStatus => 'needs-action', + }, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); - xlog 'Other user accepts invitation'; - $res = $otherJmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['participants'], - }, 'R1'], - ]); - my $otherId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($otherId); - $self->assert_str_equals('needs-action', - $res->[0][1]{list}[0]{participants}{other}{participationStatus}); + xlog 'Other user accepts invitation'; + $res = $otherJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['participants'], + }, + 'R1' + ], + ]); + my $otherId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($otherId); + $self->assert_str_equals('needs-action', + $res->[0][1]{list}[0]{participants}{other}{participationStatus}); - $res = $otherJmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $otherId => { - 'participants/other/participationStatus' => 'accepted', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$otherId}); + $res = $otherJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $otherId => { + 'participants/other/participationStatus' => 'accepted', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$otherId}); - xlog 'Reschedule event and send to other user'; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['participants'], - }, 'R1'], - ]); - $self->assert_str_equals('accepted', - $res->[0][1]{list}[0]{participants}{other}{participationStatus}); + xlog 'Reschedule event and send to other user'; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['participants'], + }, + 'R1' + ], + ]); + $self->assert_str_equals('accepted', + $res->[0][1]{list}[0]{participants}{other}{participationStatus}); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - start => '2020-01-08T09:00:00', - }, - }, - }, 'R1'], - ['CalendarEvent/get', { }, 'R2'], + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + start => '2020-01-08T09:00:00', + }, + }, + }, + 'R1' + ], + [ 'CalendarEvent/get', {}, 'R2' ], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog 'Other user receives updated event, is still accepted'; - $res = $otherJmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['start', 'participants'], - }, 'R1'], - ]); - $self->assert_str_equals('2020-01-08T09:00:00', - $res->[0][1]{list}[0]{start}); - $self->assert_str_equals('accepted', - $res->[0][1]{list}[0]{participants}{other}{participationStatus}); + xlog 'Other user receives updated event, is still accepted'; + $res = $otherJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'start', 'participants' ], + }, + 'R1' + ], + ]); + $self->assert_str_equals('2020-01-08T09:00:00', $res->[0][1]{list}[0]{start}); + $self->assert_str_equals('accepted', + $res->[0][1]{list}[0]{participants}{other}{participationStatus}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_keywords b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_keywords index ccae2d9bf9..e996f14fab 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_keywords +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_keywords @@ -2,31 +2,30 @@ use Cassandane::Tiny; sub test_calendarevent_set_keywords - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "keywords" => { - 'foo' => JSON::true, - 'bar' => JSON::true, - 'baz' => JSON::true, - }, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "keywords" => { + 'foo' => JSON::true, + 'bar' => JSON::true, + 'baz' => JSON::true, + }, + }; - my $ret = $self->createandget_event($event); - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_keywords_patch b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_keywords_patch index 63edee97eb..2c277d3475 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_keywords_patch +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_keywords_patch @@ -2,53 +2,60 @@ use Cassandane::Tiny; sub test_calendarevent_set_keywords_patch - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "keywords" => { - 'foo' => JSON::true, - 'bar' => JSON::true, - 'baz' => JSON::true, - }, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "keywords" => { + 'foo' => JSON::true, + 'bar' => JSON::true, + 'baz' => JSON::true, + }, + }; - my $ret = $self->createandget_event($event); - $self->assert_normalized_event_equals($event, $ret); - my $eventId = $ret->{id}; + my $ret = $self->createandget_event($event); + $self->assert_normalized_event_equals($event, $ret); + my $eventId = $ret->{id}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'keywords/foo' => undef, - 'keywords/bam' => JSON::true, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $ret = $res->[1][1]{list}[0]; - $self->assert_not_null($ret); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'keywords/foo' => undef, + 'keywords/bam' => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $ret = $res->[1][1]{list}[0]; + $self->assert_not_null($ret); - delete $event->{keywords}{foo}; - $event->{keywords}{bam} = JSON::true; - $self->assert_normalized_event_equals($event, $ret); + delete $event->{keywords}{foo}; + $event->{keywords}{bam} = JSON::true; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_linkblobid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_linkblobid index 229fb828e3..91fc1d4b7e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_linkblobid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_linkblobid @@ -2,157 +2,179 @@ use Cassandane::Tiny; sub test_calendarevent_set_linkblobid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - xlog "Upload blob via JMAP"; - my $res = $jmap->Upload('jmapblob', "application/octet-stream"); - my $blobId = $res->{blobId}; - $self->assert_not_null($blobId); + xlog "Upload blob via JMAP"; + my $res = $jmap->Upload('jmapblob', "application/octet-stream"); + my $blobId = $res->{blobId}; + $self->assert_not_null($blobId); - xlog "Create and assert event with a Link.blobId"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2019-12-10T23:30:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - links => { - link1 => { - rel => 'enclosure', - blobId => $blobId, - }, - }, - }, + xlog "Create and assert event with a Link.blobId"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#1'], - properties => ['links', 'x-href'], - }, 'R2'] - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); - my $event = $res->[1][1]{list}[0]; - $self->assert_str_equals('enclosure', $event->{links}{link1}{rel}); - $self->assert_str_equals($blobId, $event->{links}{link1}{blobId}); - $self->assert_null($event->{links}{link1}{href}); - - xlog "download blob via CalDAV"; - my $service = $self->{instance}->get_service("http"); - my $href = 'http://' . $service->host() . ':'. $service->port() . - '/dav/calendars/user/cassandane/Attachments/' . - substr $event->{links}{link1}{blobId}, 1; - my $RawRequest = { - headers => { - 'Authorization' => $CalDAV->auth_header(), + title => "event1", + start => "2019-12-10T23:30:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + links => { + link1 => { + rel => 'enclosure', + blobId => $blobId, + }, + }, + }, }, - }; - $res = $CalDAV->ua->get($href, $RawRequest); - $self->assert_str_equals('jmapblob', $res->{content}); + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#1'], + properties => [ 'links', 'x-href' ], + }, + 'R2' + ] + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); + my $event = $res->[1][1]{list}[0]; + $self->assert_str_equals('enclosure', $event->{links}{link1}{rel}); + $self->assert_str_equals($blobId, $event->{links}{link1}{blobId}); + $self->assert_null($event->{links}{link1}{href}); - xlog "Remove link from event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - links => undef, - }, - }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog "download blob via CalDAV"; + my $service = $self->{instance}->get_service("http"); + my $href + = 'http://' + . $service->host() . ':' + . $service->port() + . '/dav/calendars/user/cassandane/Attachments/' + . substr $event->{links}{link1}{blobId}, 1; + my $RawRequest = { + headers => { + 'Authorization' => $CalDAV->auth_header(), + }, + }; + $res = $CalDAV->ua->get($href, $RawRequest); + $self->assert_str_equals('jmapblob', $res->{content}); - xlog "Add attachment via CalDAV"; - $RawRequest = { - headers => { - 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment;filename=test', - 'Prefer' => 'return=representation', - 'Authorization' => $CalDAV->auth_header(), + xlog "Remove link from event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $eventId => { + links => undef, }, - content => 'davattach', - }; - my $URI = $CalDAV->request_url($event->{'x-href'}) . '?action=attachment-add'; - my $RawResponse = $CalDAV->ua->post($URI, $RawRequest); + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog "Add attachment via CalDAV"; + $RawRequest = { + headers => { + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment;filename=test', + 'Prefer' => 'return=representation', + 'Authorization' => $CalDAV->auth_header(), + }, + content => 'davattach', + }; + my $URI = $CalDAV->request_url($event->{'x-href'}) . '?action=attachment-add'; + my $RawResponse = $CalDAV->ua->post($URI, $RawRequest); - warn "CalDAV " . Dumper($RawRequest, $RawResponse); - $self->assert_str_equals('201', $RawResponse->{status}); + warn "CalDAV " . Dumper($RawRequest, $RawResponse); + $self->assert_str_equals('201', $RawResponse->{status}); - xlog "Download attachment via JMAP"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$event->{id}], - properties => ['links', 'x-href'], - }, 'R1'] - ]); - $event = $res->[0][1]{list}[0]; - my $attachmentBlobId = (values %{$event->{links}})[0]{blobId}; - $self->assert_not_null($attachmentBlobId); - $res = $jmap->Download('cassandane', $attachmentBlobId); - $self->assert_str_equals('davattach', $res->{content}); + xlog "Download attachment via JMAP"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + ids => [ $event->{id} ], + properties => [ 'links', 'x-href' ], + }, + 'R1' + ] ]); + $event = $res->[0][1]{list}[0]; + my $attachmentBlobId = (values %{ $event->{links} })[0]{blobId}; + $self->assert_not_null($attachmentBlobId); + $res = $jmap->Download('cassandane', $attachmentBlobId); + $self->assert_str_equals('davattach', $res->{content}); - xlog "Delete event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [ - $eventId, - ], - }, 'R1'] - ]); - $self->assert_str_equals($eventId, $res->[0][1]{destroyed}[0]); + xlog "Delete event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + destroy => [ $eventId, ], + }, + 'R1' + ] ]); + $self->assert_str_equals($eventId, $res->[0][1]{destroyed}[0]); - xlog "blobId and href are mutually exclusive"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2019-12-10T23:30:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - links => { - link1 => { - rel => 'enclosure', - blobId => $blobId, - href => 'somehref', - }, - }, - }, - 2 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2019-12-10T23:30:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - links => { - link1 => { - rel => 'enclosure', - }, - }, - }, + xlog "blobId and href are mutually exclusive"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R2'], - ]); - $self->assert_deep_equals(['links/link1/href', 'links/link1/blobId'], - $res->[0][1]{notCreated}{1}{properties}); - $self->assert_deep_equals(['links/link1/href', 'links/link1/blobId'], - $res->[0][1]{notCreated}{2}{properties}); + title => "event1", + start => "2019-12-10T23:30:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + links => { + link1 => { + rel => 'enclosure', + blobId => $blobId, + href => 'somehref', + }, + }, + }, + 2 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2019-12-10T23:30:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + links => { + link1 => { + rel => 'enclosure', + }, + }, + }, + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals( + [ 'links/link1/href', 'links/link1/blobId' ], + $res->[0][1]{notCreated}{1}{properties} + ); + $self->assert_deep_equals( + [ 'links/link1/href', 'links/link1/blobId' ], + $res->[0][1]{notCreated}{2}{properties} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_links b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_links index 77c076b5e5..879201e57e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_links +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_links @@ -2,50 +2,49 @@ use Cassandane::Tiny; sub test_calendarevent_set_links - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/Vienna", - "showWithoutTime"=> JSON::false, - "description"=> "", - "freeBusyStatus"=> "busy", - "links" => { - "spec" => { - href => "http://jmap.io/spec.html#calendar-events", - title => "the spec", - rel => "enclosure", - }, - "rfc5545" => { - href => "https://tools.ietf.org/html/rfc5545", - rel => "describedby", - }, - "image" => { - href => "https://foo.local/favicon.png", - rel => "icon", - cid => '123456789asd', - display => 'badge', - }, - "attach" => { - href => "http://example.com/some.url", - rel => "enclosure", - }, - }, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/Vienna", + "showWithoutTime" => JSON::false, + "description" => "", + "freeBusyStatus" => "busy", + "links" => { + "spec" => { + href => "http://jmap.io/spec.html#calendar-events", + title => "the spec", + rel => "enclosure", + }, + "rfc5545" => { + href => "https://tools.ietf.org/html/rfc5545", + rel => "describedby", + }, + "image" => { + href => "https://foo.local/favicon.png", + rel => "icon", + cid => '123456789asd', + display => 'badge', + }, + "attach" => { + href => "http://example.com/some.url", + rel => "enclosure", + }, + }, + }; - my $ret; + my $ret; - $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - $self->assert_normalized_event_equals($event, $ret); + $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_links_dupids b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_links_dupids index 06032c98ea..c7f57ac410 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_links_dupids +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_links_dupids @@ -2,113 +2,112 @@ use Cassandane::Tiny; sub test_calendarevent_set_links_dupids - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $event = { - calendarIds => { - Default => JSON::true, + my $event = { + calendarIds => { + Default => JSON::true, + }, + title => 'event1', + calendarIds => { + Default => JSON::true, + }, + start => '2011-01-01T04:05:06', + duration => 'PT1H', + links => { + link1 => { + href => 'https://local/link1', + title => 'link1', + }, + link2 => { + href => 'https://local/link2', + title => 'link2', + }, + }, + locations => { + loc1 => { + name => 'loc1', + links => { + link1 => { + href => 'https://local/loc1/link1', + title => 'loc1link1', + }, + link2 => { + href => 'https://local/loc1/link2', + title => 'loc1link2', + }, + }, + }, + loc2 => { + name => 'loc2', + links => { + link1 => { + href => 'https://local/loc2/link1', + title => 'loc2link1', + }, + link2 => { + href => 'https://local/loc2/link2', + title => 'loc2link2', + }, + }, + }, + }, + replyTo => { + imip => 'mailto:orga@local', + }, + participants => { + part1 => { + email => 'part1@local', + sendTo => { + imip => 'mailto:part1@local', + }, + roles => { + attendee => JSON::true, + }, + links => { + link1 => { + href => 'https://local/part1/link1', + title => 'part1link1', + }, + link2 => { + href => 'https://local/part1/link2', + title => 'part1link2', + }, + }, + }, + part2 => { + email => 'part2@local', + sendTo => { + imip => 'mailto:part2@local', }, - title => 'event1', - calendarIds => { - Default => JSON::true, + roles => { + attendee => JSON::true, }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', links => { - link1 => { - href => 'https://local/link1', - title => 'link1', - }, - link2 => { - href => 'https://local/link2', - title => 'link2', - }, + link1 => { + href => 'https://local/part2/link1', + title => 'part2link1', + }, + link2 => { + href => 'https://local/part2/link2', + title => 'part2link2', + }, }, - locations => { - loc1 => { - name => 'loc1', - links => { - link1 => { - href => 'https://local/loc1/link1', - title => 'loc1link1', - }, - link2 => { - href => 'https://local/loc1/link2', - title => 'loc1link2', - }, - }, - }, - loc2 => { - name => 'loc2', - links => { - link1 => { - href => 'https://local/loc2/link1', - title => 'loc2link1', - }, - link2 => { - href => 'https://local/loc2/link2', - title => 'loc2link2', - }, - }, - }, + }, + orga => { + email => 'orga@local', + sendTo => { + imip => 'mailto:orga@local', }, - replyTo => { - imip => 'mailto:orga@local', + roles => { + owner => JSON::true, + attendee => JSON::true, }, - participants => { - part1 => { - email => 'part1@local', - sendTo => { - imip => 'mailto:part1@local', - }, - roles => { - attendee => JSON::true, - }, - links => { - link1 => { - href => 'https://local/part1/link1', - title => 'part1link1', - }, - link2 => { - href => 'https://local/part1/link2', - title => 'part1link2', - }, - }, - }, - part2 => { - email => 'part2@local', - sendTo => { - imip => 'mailto:part2@local', - }, - roles => { - attendee => JSON::true, - }, - links => { - link1 => { - href => 'https://local/part2/link1', - title => 'part2link1', - }, - link2 => { - href => 'https://local/part2/link2', - title => 'part2link2', - }, - }, - }, - orga => { - email => 'orga@local', - sendTo => { - imip => 'mailto:orga@local', - }, - roles => { - owner => JSON::true, - attendee => JSON::true, - }, - }, - } - }; - my $ret = $self->createandget_event($event); - $self->assert_normalized_event_equals($event, $ret); + }, + } + }; + my $ret = $self->createandget_event($event); + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_linksurl b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_linksurl index c463c66982..373dd9cc12 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_linksurl +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_linksurl @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_set_linksurl - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <Request('PUT', '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => ['links'], - }, 'R2'], - ]); - my $eventId = $res->[1][1]{list}[0]{id}; - $self->assert_not_null($eventId); + my $res = $jmap->CallMethods([ + [ 'CalendarEvent/query', {}, 'R1' ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => ['links'], + }, + 'R2' + ], + ]); + my $eventId = $res->[1][1]{list}[0]{id}; + $self->assert_not_null($eventId); - my $wantLinks = [{ - '@type' => 'Link', - href => 'https://url.example.com', - rel => 'describedby', - }]; + my $wantLinks = [ { + '@type' => 'Link', + href => 'https://url.example.com', + rel => 'describedby', + } ]; - my @links = values %{$res->[1][1]{list}[0]{links}}; - $self->assert_deep_equals($wantLinks, \@links); + my @links = values %{ $res->[1][1]{list}[0]{links} }; + $self->assert_deep_equals($wantLinks, \@links); - # Set some property other than links - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'update' - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['links'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + # Set some property other than links + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'update' + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['links'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - @links = values %{$res->[1][1]{list}[0]{links}}; - $self->assert_deep_equals($wantLinks, \@links); - my $linkId = (keys %{$res->[1][1]{list}[0]{links}})[0]; - $self->assert_not_null($linkId); + @links = values %{ $res->[1][1]{list}[0]{links} }; + $self->assert_deep_equals($wantLinks, \@links); + my $linkId = (keys %{ $res->[1][1]{list}[0]{links} })[0]; + $self->assert_not_null($linkId); - $res = $caldav->Request('GET', '/dav/calendars/user/cassandane/Default/test.ics'); - $ical = $res->{content} =~ s/\r\n[ \t]//rg; - $self->assert($ical =~ /\nURL[^:]*:https:\/\/url\.example\.com/); + $res = $caldav->Request('GET', + '/dav/calendars/user/cassandane/Default/test.ics'); + $ical = $res->{content} =~ s/\r\n[ \t]//rg; + $self->assert($ical =~ /\nURL[^:]*:https:\/\/url\.example\.com/); - # Even changing rel sticks links to their former iCalendar property - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - "links/$linkId/rel" => 'enclosure', - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['links'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $wantLinks->[0]{rel} = 'enclosure'; + # Even changing rel sticks links to their former iCalendar property + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + "links/$linkId/rel" => 'enclosure', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['links'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $wantLinks->[0]{rel} = 'enclosure'; - @links = values %{$res->[1][1]{list}[0]{links}}; - $self->assert_deep_equals($wantLinks, \@links); + @links = values %{ $res->[1][1]{list}[0]{links} }; + $self->assert_deep_equals($wantLinks, \@links); - $res = $caldav->Request('GET', '/dav/calendars/user/cassandane/Default/test.ics'); - $ical = $res->{content} =~ s/\r\n[ \t]//rg; - $self->assert($ical =~ /\nURL[^:]*:https:\/\/url\.example\.com/); + $res = $caldav->Request('GET', + '/dav/calendars/user/cassandane/Default/test.ics'); + $ical = $res->{content} =~ s/\r\n[ \t]//rg; + $self->assert($ical =~ /\nURL[^:]*:https:\/\/url\.example\.com/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_locations b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_locations index 64dfa020c5..29fe615fcb 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_locations +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_locations @@ -2,81 +2,80 @@ use Cassandane::Tiny; sub test_calendarevent_set_locations - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $locations = { - # A couple of sparse locations - locA => { - name => "location A", - description => "my great description", + my $locations = { + # A couple of sparse locations + locA => { + name => "location A", + description => "my great description", + }, + locB => { + name => "location B", + }, + locC => { + coordinates => "geo:48.208304,16.371602", + name => "a place in Vienna", + }, + locD => { + coordinates => "geo:48.208304,16.371602", + }, + locE => { + name => "location E", + links => { + link1 => { + href => 'https://foo.local', + rel => "enclosure", }, - locB => { - name => "location B", + link2 => { + href => 'https://bar.local', + rel => "enclosure", }, - locC => { - coordinates => "geo:48.208304,16.371602", - name => "a place in Vienna", - }, - locD => { - coordinates => "geo:48.208304,16.371602", - }, - locE => { - name => "location E", - links => { - link1 => { - href => 'https://foo.local', - rel => "enclosure", - }, - link2 => { - href => 'https://bar.local', - rel => "enclosure", - }, - }, - }, - # A full-blown location - locG => { - name => "location G", - description => "a description", - timeZone => "Europe/Vienna", - coordinates => "geo:48.2010,16.3695,183", - }, - # A location with name that needs escaping - locH => { - name => "location H,\nhas funny chars.", - description => "some boring\tdescription", - timeZone => "Europe/Vienna", - }, - }; - my $virtualLocations = { - locF => { - name => "location F", - description => "a description", - uri => "https://somewhere.local", - }, - }; + }, + }, + # A full-blown location + locG => { + name => "location G", + description => "a description", + timeZone => "Europe/Vienna", + coordinates => "geo:48.2010,16.3695,183", + }, + # A location with name that needs escaping + locH => { + name => "location H,\nhas funny chars.", + description => "some boring\tdescription", + timeZone => "Europe/Vienna", + }, + }; + my $virtualLocations = { + locF => { + name => "location F", + description => "a description", + uri => "https://somewhere.local", + }, + }; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "free", - "locations" => $locations, - "virtualLocations" => $virtualLocations, - }; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "free", + "locations" => $locations, + "virtualLocations" => $virtualLocations, + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_locations_keep_location b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_locations_keep_location index 5dd72b8518..43dfdab10c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_locations_keep_location +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_locations_keep_location @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_set_locations_keep_location - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "PUT iCalendar event with apple location"; - my $ical = <<'EOF'; + xlog "PUT iCalendar event with apple location"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -30,69 +29,85 @@ X-APPLE-STRUCTURED-LOCATION END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - xlog "Assert locations in CalendarEvent"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['locations', 'x-href'] - }, 'R1'], - ]); + xlog "Assert locations in CalendarEvent"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'locations', 'x-href' ] + }, + 'R1' + ], + ]); - my $eventId = $res->[0][1]{list}[0]{id}; - my $locations = $res->[0][1]{list}[0]{locations}; - $self->assert_num_equals(1, scalar values %{$locations}); - $self->assert_deep_equals({ - '@type' => 'Location', - name => 'mainloc', - coordinates => 'geo:48.208304,16.371602', - }, (values %{$locations})[0]); - my $xhref = $res->[0][1]{list}[0]{'x-href'}; - $self->assert_not_null($xhref); + my $eventId = $res->[0][1]{list}[0]{id}; + my $locations = $res->[0][1]{list}[0]{locations}; + $self->assert_num_equals(1, scalar values %{$locations}); + $self->assert_deep_equals( + { + '@type' => 'Location', + name => 'mainloc', + coordinates => 'geo:48.208304,16.371602', + }, + (values %{$locations})[0] + ); + my $xhref = $res->[0][1]{list}[0]{'x-href'}; + $self->assert_not_null($xhref); - xlog "Add location but preserve existing one"; - $locations->{'newlocation'} = { - '@type' => 'Location', - name => 'newloc', - coordinates => 'geo:27.175015,78.042155', - }; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - locations => $locations, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog "Add location but preserve existing one"; + $locations->{'newlocation'} = { + '@type' => 'Location', + name => 'newloc', + coordinates => 'geo:27.175015,78.042155', + }; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + locations => $locations, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - $res = $caldav->Request('GET', $xhref); + $res = $caldav->Request('GET', $xhref); - my $vcal = Data::ICal->new(data => $res->{content}); - my %vcomps = map { $_->ical_entry_type() => $_ } @{$vcal->entries()}; - my $vevent = $vcomps{'VEVENT'}; + my $vcal = Data::ICal->new(data => $res->{content}); + my %vcomps = map { $_->ical_entry_type() => $_ } @{ $vcal->entries() }; + my $vevent = $vcomps{'VEVENT'}; - my $props = $vevent->property('X-APPLE-STRUCTURED-LOCATION'); - $self->assert_num_equals(1, scalar @{$props}); - $self->assert_not_null($props->[0]->parameters()->{'X-APPLE-RADIUS'}); - $self->assert_str_equals('geo:48.208304,16.371602', $props->[0]->value()); + my $props = $vevent->property('X-APPLE-STRUCTURED-LOCATION'); + $self->assert_num_equals(1, scalar @{$props}); + $self->assert_not_null($props->[0]->parameters()->{'X-APPLE-RADIUS'}); + $self->assert_str_equals('geo:48.208304,16.371602', $props->[0]->value()); - $props = $vevent->property('LOCATION'); - $self->assert_num_equals(1, scalar @{$props}); - $self->assert_str_equals('mainloc', $props->[0]->value()); + $props = $vevent->property('LOCATION'); + $self->assert_num_equals(1, scalar @{$props}); + $self->assert_str_equals('mainloc', $props->[0]->value()); - $props = $vevent->property('X-JMAP-LOCATION'); - $self->assert_num_equals(1, scalar @{$props}); - $self->assert_str_equals('newloc', $props->[0]->value()); + $props = $vevent->property('X-JMAP-LOCATION'); + $self->assert_num_equals(1, scalar @{$props}); + $self->assert_str_equals('newloc', $props->[0]->value()); - xlog "Assert locations in CalendarEvent"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['locations', 'x-href'] - }, 'R1'], - ]); - $self->assert_deep_equals($locations, $res->[0][1]{list}[0]{locations}); + xlog "Assert locations in CalendarEvent"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'locations', 'x-href' ] + }, + 'R1' + ], + ]); + $self->assert_deep_equals($locations, $res->[0][1]{list}[0]{locations}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinvite_preserve_caldav b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinvite_preserve_caldav index d7bfc6af56..88f5098e4a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinvite_preserve_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinvite_preserve_caldav @@ -2,87 +2,97 @@ use Cassandane::Tiny; sub test_calendarevent_set_mayinvite_preserve_caldav - :min_version_3_5 :needs_component_jmap :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap : JMAPExtensions : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "create event with mayInviteSelf and mayInviteOthers set"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'eventuid', - title => 'test', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - someone => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - expectReply => JSON::true, - participationStatus => 'needs-action', - }, - }, - mayInviteSelf => JSON::true, - mayInviteOthers => JSON::true, + xlog "create event with mayInviteSelf and mayInviteOthers set"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + uid => 'eventuid', + title => 'test', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + someone => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:someone@example.com', }, + expectReply => JSON::true, + participationStatus => 'needs-action', + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event'], - properties => ['mayInviteSelf', 'mayInviteOthers'], - }, 'R2'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); - my $href = $res->[0][1]{created}{event}{'x-href'}; - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mayInviteSelf}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mayInviteOthers}); + mayInviteSelf => JSON::true, + mayInviteOthers => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => [ 'mayInviteSelf', 'mayInviteOthers' ], + }, + 'R2' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); + my $href = $res->[0][1]{created}{event}{'x-href'}; + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mayInviteSelf}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mayInviteOthers}); - xlog "remove mayInviteSelf via CalDAV"; - my $ical = $caldav->Request('GET', $href)->{content}; - $self->assert($ical =~ m/X-JMAP-MAY-INVITE-SELF;VALUE=BOOLEAN:TRUE/); + xlog "remove mayInviteSelf via CalDAV"; + my $ical = $caldav->Request('GET', $href)->{content}; + $self->assert($ical =~ m/X-JMAP-MAY-INVITE-SELF;VALUE=BOOLEAN:TRUE/); - $ical = join("\r\n", - grep { !($_ =~ m/X-JMAP-MAY-INVITE-SELF;VALUE=BOOLEAN:TRUE/) } - split(/\r\n/, $ical) - ); - $ical = join("\r\n", - grep { !($_ =~ m/X-JMAP-MAY-INVITE-OTHERS;VALUE=BOOLEAN:TRUE/) } - split(/\r\n/, $ical) - ); - $res = $caldav->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); + $ical = join("\r\n", + grep { !($_ =~ m/X-JMAP-MAY-INVITE-SELF;VALUE=BOOLEAN:TRUE/) } + split(/\r\n/, $ical)); + $ical = join("\r\n", + grep { !($_ =~ m/X-JMAP-MAY-INVITE-OTHERS;VALUE=BOOLEAN:TRUE/) } + split(/\r\n/, $ical)); + $res + = $caldav->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); - xlog "assert mayInviteSelf and mayInviteOthers are preserved"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['mayInviteSelf', 'mayInviteOthers'], - }, 'R2'], - ]); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{mayInviteSelf}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{mayInviteOthers}); + xlog "assert mayInviteSelf and mayInviteOthers are preserved"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => [ 'mayInviteSelf', 'mayInviteOthers' ], + }, + 'R2' + ], + ]); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{mayInviteSelf}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{mayInviteOthers}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinviteothers b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinviteothers index f9ae1d8d79..ad67fda64e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinviteothers +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinviteothers @@ -2,225 +2,275 @@ use Cassandane::Tiny; sub test_calendarevent_set_mayinviteothers - :needs_component_jmap :JMAPExtensions :NoAltNameSpace :min_version_0_0 :max_version_0_0 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_jmap : JMAPExtensions : NoAltNameSpace : min_version_0_0 : + max_version_0_0 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my ($shareeJmap, $shareeCalDAV) = $self->create_user('sharee'); + my ($shareeJmap, $shareeCalDAV) = $self->create_user('sharee'); - xlog "create event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'eventuid', - title => 'test', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - someone => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "create event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + uid => 'eventuid', + title => 'test', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + someone => { + roles => { + 'attendee' => JSON::true, }, + sendTo => { + imip => 'mailto:someone@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', + }, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); - xlog "can not set mayInviteOthers on override"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - recurrenceOverrides => { - '2022-02-03T22:22:22' => { - mayInviteOthers => JSON::true, - }, - }, - }, + xlog "can not set mayInviteOthers on override"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + recurrenceOverrides => { + '2022-02-03T22:22:22' => { + mayInviteOthers => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert_deep_equals({ - type => 'invalidProperties', - properties => ['recurrenceOverrides/2022-02-03T22:22:22/mayInviteOthers'], - }, $res->[0][1]{notUpdated}{$eventId}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals( + { + type => 'invalidProperties', + properties => ['recurrenceOverrides/2022-02-03T22:22:22/mayInviteOthers'], + }, + $res->[0][1]{notUpdated}{$eventId} + ); - xlog "assign mayUpdatePrivate and mayRSVP to sharee", + xlog "assign mayUpdatePrivate and mayRSVP to sharee", $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - 'sharee' => { - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayRSVP => JSON::true, - }, - }, - }, + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + 'sharee' => { + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayRSVP => JSON::true, + }, }, - }, 'R1'], + }, + }, + }, + 'R1' + ], ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "sharee can not invite others"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/invitee' => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:invitee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "sharee can not invite others"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/invitee' => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:invitee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$eventId}{type}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$eventId}{type}); - xlog "set mayInviteOthers on event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - mayInviteOthers => JSON::true, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['mayInviteOthers'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mayInviteOthers}); + xlog "set mayInviteOthers on event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + mayInviteOthers => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['mayInviteOthers'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mayInviteOthers}); - xlog "sharee still can not invite others"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/invitee' => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:invitee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "sharee still can not invite others"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/invitee' => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:invitee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$eventId}{type}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$eventId}{type}); - xlog "add sharee to participants"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'participants/sharee' => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:sharee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "add sharee to participants"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'participants/sharee' => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:sharee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog "sharee can not invite others as attendee and chair"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/invitee' => { - roles => { - 'attendee' => JSON::true, - 'chair' => JSON::true, - }, - sendTo => { - imip => 'mailto:invitee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "sharee can not invite others as attendee and chair"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/invitee' => { + roles => { + 'attendee' => JSON::true, + 'chair' => JSON::true, + }, + sendTo => { + imip => 'mailto:invitee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$eventId}{type}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$eventId}{type}); - xlog "sharee invites other"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/invitee' => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:invitee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "sharee invites other"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/invitee' => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:invitee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => ['participants'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_not_null($res->[1][1]{list}[0]{participants}{invitee}); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => ['participants'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_not_null($res->[1][1]{list}[0]{participants}{invitee}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinviteself b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinviteself index 74206cfa93..05f41252c8 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinviteself +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayinviteself @@ -2,221 +2,271 @@ use Cassandane::Tiny; sub test_calendarevent_set_mayinviteself - :needs_component_jmap :JMAPExtensions :NoAltNameSpace :min_version_0_0 :max_version_0_0 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_jmap : JMAPExtensions : NoAltNameSpace : min_version_0_0 : + max_version_0_0 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my ($shareeJmap, $shareeCalDAV) = $self->create_user('sharee'); + my ($shareeJmap, $shareeCalDAV) = $self->create_user('sharee'); - xlog "create event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'eventuid', - title => 'test', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - someone => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - expectReply => JSON::true, - participationStatus => 'needs-action', - }, - }, + xlog "create event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + uid => 'eventuid', + title => 'test', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', }, + }, + someone => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:someone@example.com', + }, + expectReply => JSON::true, + participationStatus => 'needs-action', + }, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); - xlog "can not set mayInviteSelf on override"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - recurrenceOverrides => { - '2022-02-03T22:22:22' => { - mayInviteSelf => JSON::true, - }, - }, - }, + xlog "can not set mayInviteSelf on override"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + recurrenceOverrides => { + '2022-02-03T22:22:22' => { + mayInviteSelf => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert_deep_equals({ - type => 'invalidProperties', - properties => ['recurrenceOverrides/2022-02-03T22:22:22/mayInviteSelf'], - }, $res->[0][1]{notUpdated}{$eventId}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals( + { + type => 'invalidProperties', + properties => ['recurrenceOverrides/2022-02-03T22:22:22/mayInviteSelf'], + }, + $res->[0][1]{notUpdated}{$eventId} + ); - xlog "assign mayUpdatePrivate to sharee", + xlog "assign mayUpdatePrivate to sharee", $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - 'sharee' => { - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - }, - }, - }, + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + 'sharee' => { + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + }, }, - }, 'R1'], + }, + }, + }, + 'R1' + ], ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "sharee can not invite self"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/sharee' => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:sharee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "sharee can not invite self"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/sharee' => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:sharee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$eventId}{type}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$eventId}{type}); - xlog "set mayInviteSelf on event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - mayInviteSelf => JSON::true, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['mayInviteSelf'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mayInviteSelf}); + xlog "set mayInviteSelf on event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + mayInviteSelf => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['mayInviteSelf'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mayInviteSelf}); - xlog "sharee can not invite self due to missing mayRSVP permission"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/sharee' => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:sharee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "sharee can not invite self due to missing mayRSVP permission"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/sharee' => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:sharee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$eventId}{type}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$eventId}{type}); - xlog "assign mayRSVP to sharee", + xlog "assign mayRSVP to sharee", $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - 'sharee' => { - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayRSVP => JSON::true, - }, - }, - }, + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + 'sharee' => { + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayRSVP => JSON::true, + }, }, - }, 'R1'], + }, + }, + }, + 'R1' + ], ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "sharee invites self as attendee and chair"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/sharee' => { - roles => { - 'attendee' => JSON::true, - 'chair' => JSON::true, - }, - sendTo => { - imip => 'mailto:sharee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "sharee invites self as attendee and chair"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/sharee' => { + roles => { + 'attendee' => JSON::true, + 'chair' => JSON::true, + }, + sendTo => { + imip => 'mailto:sharee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$eventId}{type}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$eventId}{type}); - xlog "sharee invites self as attendee"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/sharee' => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:sharee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, + xlog "sharee invites self as attendee"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/sharee' => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:sharee@example.com', + }, + expectReply => JSON::true, + participationStatus => 'accepted', }, - }, 'R1'], - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => ['participants'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_not_null($res->[1][1]{list}[0]{participants}{sharee}); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => ['participants'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_not_null($res->[1][1]{list}[0]{participants}{sharee}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayrsvp b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayrsvp index f838773ac0..a0219a0ce9 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayrsvp +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_mayrsvp @@ -2,112 +2,133 @@ use Cassandane::Tiny; sub test_calendarevent_set_mayrsvp - :needs_component_jmap :JMAPExtensions :NoAltNameSpace :min_version_0_0 :max_version_0_0 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_jmap : JMAPExtensions : NoAltNameSpace : min_version_0_0 : + max_version_0_0 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my ($shareeJmap, $shareCalDAV) = $self->create_user('sharee'); + my ($shareeJmap, $shareCalDAV) = $self->create_user('sharee'); - xlog "create and share event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'test', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - sharee => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:sharee@example.com', - }, - expectReply => JSON::true, - participationStatus => 'needs-action', - }, - }, - }, + xlog "create and share event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['Calendar/set', { - update => { - Default => { - shareWith => { - 'sharee' => { - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - }, - }, - }, + '@type' => 'Event', + uid => 'event1uid', + title => 'test', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + replyTo => { + imip => 'mailto:cassandane@example.com', }, - }, 'R2'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); - $self->assert(exists $res->[1][1]{updated}{Default}); - - xlog "update as sharee without mayRSVP"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/sharee/participationStatus' => 'accepted', + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + sharee => { + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:sharee@example.com', }, + expectReply => JSON::true, + participationStatus => 'needs-action', + }, }, - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$eventId}{type}); + }, + }, + }, + 'R1' + ], + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + 'sharee' => { + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + }, + }, + }, + }, + }, + 'R2' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); + $self->assert(exists $res->[1][1]{updated}{Default}); - xlog "assign mayRSVP to sharee", + xlog "update as sharee without mayRSVP"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/sharee/participationStatus' => 'accepted', + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$eventId}{type}); + + xlog "assign mayRSVP to sharee", $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - 'sharee' => { - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayRSVP => JSON::true, - }, - }, - }, + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + 'sharee' => { + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayRSVP => JSON::true, + }, }, - }, 'R1'], + }, + }, + }, + 'R1' + ], ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "update as sharee with mayRSVP"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - 'participants/sharee/participationStatus' => 'accepted', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog "update as sharee with mayRSVP"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + 'participants/sharee/participationStatus' => 'accepted', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_method b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_method index 8390b966c5..926e58f31e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_method +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_method @@ -2,110 +2,128 @@ use Cassandane::Tiny; sub test_calendarevent_set_method - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "method on main event is rejected"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - start => '2022-01-28T09:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - method => 'request', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - someone => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - }, - }, - }, + xlog "method on main event is rejected"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notCreated}{event}{type}); - $self->assert_deep_equals(['method'], - $res->[0][1]{notCreated}{event}{properties}); - - xlog "method on override event is ignored"; # see RFC8984, section 4.3.5 - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - start => '2022-01-28T09:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - someone => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - }, - }, - recurrenceRules => [{ - frequency => 'daily', - }], - recurrenceOverrides => { - '2022-01-29T09:00:00' => { - title => 'override', - method => 'request', - }, - }, + start => '2022-01-28T09:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + method => 'request', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + someone => { + roles => { + attendee => JSON::true, }, + sendTo => { + imip => 'mailto:someone@example.com', + }, + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event'], - properties => ['title', 'method', 'recurrenceOverrides'], - }, 'R2'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); - $self->assert_null($res->[1][1]{list}[0]{method}); - $self->assert_deep_equals({ - '2022-01-29T09:00:00' => { - title => 'override', + }, }, - }, $res->[1][1]{list}[0]{recurrenceOverrides}); + }, + 'R1' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{event}{type}); + $self->assert_deep_equals(['method'], + $res->[0][1]{notCreated}{event}{properties}); - xlog "can't set method in /update either"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - method => 'request', + xlog "method on override event is ignored"; # see RFC8984, section 4.3.5 + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, + }, + start => '2022-01-28T09:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + someone => { + roles => { + attendee => JSON::true, }, + sendTo => { + imip => 'mailto:someone@example.com', + }, + }, + }, + recurrenceRules => [ { + frequency => 'daily', + } ], + recurrenceOverrides => { + '2022-01-29T09:00:00' => { + title => 'override', + method => 'request', + }, }, - }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{$eventId}{type}); - $self->assert_deep_equals(['method'], - $res->[0][1]{notUpdated}{$eventId}{properties}); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => [ 'title', 'method', 'recurrenceOverrides' ], + }, + 'R2' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); + $self->assert_null($res->[1][1]{list}[0]{method}); + $self->assert_deep_equals( + { + '2022-01-29T09:00:00' => { + title => 'override', + }, + }, + $res->[1][1]{list}[0]{recurrenceOverrides} + ); + + xlog "can't set method in /update either"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + method => 'request', + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$eventId}{type}); + $self->assert_deep_equals(['method'], + $res->[0][1]{notUpdated}{$eventId}{properties}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_move b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_move index 803980f58c..1605916bfa 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_move +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_move @@ -2,81 +2,112 @@ use Cassandane::Tiny; sub test_calendarevent_set_move - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "create calendars A and B"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { - "1" => { - name => "A", color => "coral", sortOrder => 1, isVisible => JSON::true, - }, - "2" => { - name => "B", color => "blue", sortOrder => 1, isVisible => JSON::true - } - }}, "R1"] - ]); - my $calidA = $res->[0][1]{created}{"1"}{id}; - my $calidB = $res->[0][1]{created}{"2"}{id}; + xlog $self, "create calendars A and B"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "A", + color => "coral", + sortOrder => 1, + isVisible => JSON::true, + }, + "2" => { + name => "B", + color => "blue", + sortOrder => 1, + isVisible => JSON::true + } + } + }, + "R1" + ] ]); + my $calidA = $res->[0][1]{created}{"1"}{id}; + my $calidB = $res->[0][1]{created}{"2"}{id}; - xlog $self, "create event in calendar $calidA"; - $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - $calidA => JSON::true, - }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2015-10-06T00:00:00", - } - }}, "R1"]]); - my $state = $res->[0][1]{newState}; - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create event in calendar $calidA"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calidA => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2015-10-06T00:00:00", + } + } + }, + "R1" + ] ]); + my $state = $res->[0][1]{newState}; + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "get calendar $id"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$id]}, "R1"]]); - my $event = $res->[0][1]{list}[0]; - $self->assert_str_equals($id, $event->{id}); - $self->assert_deep_equals({$calidA => JSON::true}, $event->{calendarIds}); - $self->assert_str_equals($state, $res->[0][1]{state}); + xlog $self, "get calendar $id"; + $res + = $jmap->CallMethods([ [ 'CalendarEvent/get', { ids => [$id] }, "R1" ] ]); + my $event = $res->[0][1]{list}[0]; + $self->assert_str_equals($id, $event->{id}); + $self->assert_deep_equals({ $calidA => JSON::true }, $event->{calendarIds}); + $self->assert_str_equals($state, $res->[0][1]{state}); - xlog $self, "move event to unknown calendar"; - $res = $jmap->CallMethods([['CalendarEvent/set', { update => { - $id => { - calendarIds => { - nope => JSON::true, - }, - } - }}, "R1"]]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals($state, $res->[0][1]{newState}); + xlog $self, "move event to unknown calendar"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id => { + calendarIds => { + nope => JSON::true, + }, + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals($state, $res->[0][1]{newState}); - xlog $self, "get calendar $id from untouched calendar $calidA"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$id]}, "R1"]]); - $event = $res->[0][1]{list}[0]; - $self->assert_str_equals($id, $event->{id}); - $self->assert_deep_equals({$calidA => JSON::true}, $event->{calendarIds}); + xlog $self, "get calendar $id from untouched calendar $calidA"; + $res + = $jmap->CallMethods([ [ 'CalendarEvent/get', { ids => [$id] }, "R1" ] ]); + $event = $res->[0][1]{list}[0]; + $self->assert_str_equals($id, $event->{id}); + $self->assert_deep_equals({ $calidA => JSON::true }, $event->{calendarIds}); - xlog $self, "move event to calendar $calidB"; - $res = $jmap->CallMethods([['CalendarEvent/set', { update => { - $id => { - calendarIds => { - $calidB => JSON::true, - }, - } - }}, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $state = $res->[0][1]{newState}; + xlog $self, "move event to calendar $calidB"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id => { + calendarIds => { + $calidB => JSON::true, + }, + } + } + }, + "R1" + ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $state = $res->[0][1]{newState}; - xlog $self, "get calendar $id"; - $res = $jmap->CallMethods([['CalendarEvent/get', {ids => [$id]}, "R1"]]); - $event = $res->[0][1]{list}[0]; - $self->assert_str_equals($id, $event->{id}); - $self->assert_deep_equals({$calidB => JSON::true}, $event->{calendarIds}); + xlog $self, "get calendar $id"; + $res + = $jmap->CallMethods([ [ 'CalendarEvent/get', { ids => [$id] }, "R1" ] ]); + $event = $res->[0][1]{list}[0]; + $self->assert_str_equals($id, $event->{id}); + $self->assert_deep_equals({ $calidB => JSON::true }, $event->{calendarIds}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_no_preserve_iana_timezone b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_no_preserve_iana_timezone index 2980bbab93..777047f2bd 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_no_preserve_iana_timezone +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_no_preserve_iana_timezone @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_set_no_preserve_iana_timezone - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event with custom IANA timezone via CalDAV"; - my $ical = <Request('PUT', $ics, $ical, 'Content-Type' => 'text/calendar'); + my $ics = '/dav/calendars/user/cassandane/Default/test.ics'; + $caldav->Request('PUT', $ics, $ical, 'Content-Type' => 'text/calendar'); - xlog "Assert timeZone and UTC times are correct"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => ['timeZone', 'utcStart', 'utcEnd'], - }, 'R2'], - ]); - my $eventId = $res->[1][1]{list}[0]{id}; - $self->assert_not_null($eventId); - $self->assert_str_equals('Europe/Vienna', $res->[1][1]{list}[0]{timeZone}); - $self->assert_str_equals('2022-04-12T06:00:00Z', $res->[1][1]{list}[0]{utcStart}); - $self->assert_str_equals('2022-04-12T06:30:00Z', $res->[1][1]{list}[0]{utcEnd}); + xlog "Assert timeZone and UTC times are correct"; + my $res = $jmap->CallMethods([ + [ 'CalendarEvent/query', {}, 'R1' ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => [ 'timeZone', 'utcStart', 'utcEnd' ], + }, + 'R2' + ], + ]); + my $eventId = $res->[1][1]{list}[0]{id}; + $self->assert_not_null($eventId); + $self->assert_str_equals('Europe/Vienna', $res->[1][1]{list}[0]{timeZone}); + $self->assert_str_equals('2022-04-12T06:00:00Z', + $res->[1][1]{list}[0]{utcStart}); + $self->assert_str_equals('2022-04-12T06:30:00Z', + $res->[1][1]{list}[0]{utcEnd}); - xlog "Update event title, keep timeZone untouched"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'updatedTitle', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog "Update event title, keep timeZone untouched"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'updatedTitle', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog "Assert VTIMEZONE got replaced"; - $res = $caldav->Request('GET', $ics); - $self->assert(not $res->{content} =~ m/TZOFFSETFROM:\+1000/); - $self->assert($res->{content} =~ m/TZOFFSETFROM:\+0200/); + xlog "Assert VTIMEZONE got replaced"; + $res = $caldav->Request('GET', $ics); + $self->assert(not $res->{content} =~ m/TZOFFSETFROM:\+1000/); + $self->assert($res->{content} =~ m/TZOFFSETFROM:\+0200/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_notitle b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_notitle index c2069e6691..899fc8854e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_notitle +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_notitle @@ -2,43 +2,50 @@ use Cassandane::Tiny; sub test_calendarevent_set_notitle - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE314231-some-UID", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE314231-some-UID", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + }; - my $ret = $self->createandget_event($event); - $self->assert_str_equals("", $ret->{title}); - my $eventId= $ret->{id}; + my $ret = $self->createandget_event($event); + $self->assert_str_equals("", $ret->{title}); + my $eventId = $ret->{id}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'foo', - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['title'] - }, 'R2'], + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'foo', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['title'] + }, + 'R2' + ], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_str_equals('foo', $res->[1][1]{list}[0]{title}); + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_str_equals('foo', $res->[1][1]{list}[0]{title}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participant_links_dir b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participant_links_dir index 009b9d9250..46c1d15c84 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participant_links_dir +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participant_links_dir @@ -2,42 +2,42 @@ use Cassandane::Tiny; sub test_calendarevent_set_participant_links_dir - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my ($id, $ical) = $self->icalfile('attendeedir'); + my ($id, $ical) = $self->icalfile('attendeedir'); - my $icshref = '/dav/calendars/user/cassandane/Default/attendeedir.ics'; - $caldav->Request('PUT', $icshref, $ical, 'Content-Type' => 'text/calendar'); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - }, 'R1'], - ]); - my $event = $res->[0][1]{list}[0]; - $self->assert_not_null($event); + my $icshref = '/dav/calendars/user/cassandane/Default/attendeedir.ics'; + $caldav->Request('PUT', $icshref, $ical, 'Content-Type' => 'text/calendar'); + my $res = $jmap->CallMethods([ [ 'CalendarEvent/get', {}, 'R1' ], ]); + my $event = $res->[0][1]{list}[0]; + $self->assert_not_null($event); - # Links generated from DIR parameter loop back to DIR. + # Links generated from DIR parameter loop back to DIR. - my $linkId = (keys %{$event->{participants}{attendee}{links}})[0]; + my $linkId = (keys %{ $event->{participants}{attendee}{links} })[0]; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $event->{id} => { - 'participants/attendee/links' => { - $linkId => { - href => 'https://local/attendee/dir2', - }, - }, - }, + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $event->{id} => { + 'participants/attendee/links' => { + $linkId => { + href => 'https://local/attendee/dir2', + }, }, - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{updated}{$event->{id}}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{updated}{ $event->{id} }); - $res = $caldav->Request('GET', $icshref); - $self->assert_matches(qr/DIR="https:/, $res->{content}); + $res = $caldav->Request('GET', $icshref); + $self->assert_matches(qr/DIR="https:/, $res->{content}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participantid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participantid index 4323b16075..2594a33d0b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participantid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participantid @@ -2,63 +2,63 @@ use Cassandane::Tiny; sub test_calendarevent_set_participantid - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $participants = { - "foo" => { - email => 'foo@local', - roles => { - 'attendee' => JSON::true, - }, - locationId => "locX", - sendTo => { - imip => 'mailto:foo@local', - }, - }, - "you" => { - name => "Cassandane", - email => 'cassandane@example.com', - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - }; + my $participants = { + "foo" => { + email => 'foo@local', + roles => { + 'attendee' => JSON::true, + }, + locationId => "locX", + sendTo => { + imip => 'mailto:foo@local', + }, + }, + "you" => { + name => "Cassandane", + email => 'cassandane@example.com', + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + }; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "status" => "confirmed", - "replyTo" => { imip => "mailto:cassandane\@example.com" }, - "participants" => $participants, - }; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "status" => "confirmed", + "replyTo" => { imip => "mailto:cassandane\@example.com" }, + "participants" => $participants, + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - delete($ret->{participants}{foo}{scheduleStatus}); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + delete($ret->{participants}{foo}{scheduleStatus}); - $self->assert_normalized_event_equals($event, $ret); + $self->assert_normalized_event_equals($event, $ret); - # check that we can fetch again a second time and still have the same data - my $res = $jmap->CallMethods([['CalendarEvent/get', { ids => [ $event->{id} ] }, 'R1']]); - $ret = $res->[0][1]{list}[0]; - delete($ret->{participants}{foo}{scheduleStatus}); - $self->assert_normalized_event_equals($event, $ret); + # check that we can fetch again a second time and still have the same data + my $res = $jmap->CallMethods( + [ [ 'CalendarEvent/get', { ids => [ $event->{id} ] }, 'R1' ] ]); + $ret = $res->[0][1]{list}[0]; + delete($ret->{participants}{foo}{scheduleStatus}); + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants index c0c90bd5d6..bc3102e007 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants @@ -2,141 +2,140 @@ use Cassandane::Tiny; sub test_calendarevent_set_participants - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "status" => "confirmed", - "replyTo" => { - "imip" => "mailto:foo\@local", - "web" => "http://local/rsvp", + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "status" => "confirmed", + "replyTo" => { + "imip" => "mailto:foo\@local", + "web" => "http://local/rsvp", + }, + "participants" => { + 'foo' => { + name => 'Foo', + kind => 'individual', + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + 'chair' => JSON::true, }, - "participants" => { - 'foo' => { - name => 'Foo', - kind => 'individual', - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - 'chair' => JSON::true, - }, - locationId => 'loc1', - participationStatus => 'accepted', - expectReply => JSON::false, - links => { - link1 => { - href => 'https://somelink.local', - rel => "enclosure", - }, - }, - participationComment => 'Sure; see you "soon"!', - sendTo => { - imip => 'mailto:foo@local', - }, - }, - 'bar' => { - name => 'Bar', - kind => 'individual', - roles => { - 'attendee' => JSON::true, - }, - locationId => 'loc2', - participationStatus => 'needs-action', - expectReply => JSON::true, - delegatedTo => { - 'bam' => JSON::true, - }, - memberOf => { - 'group' => JSON::true, - }, - links => { - link1 => { - href => 'https://somelink.local', - rel => "enclosure", - }, - }, - email => 'bar2@local', # different email than sendTo - sendTo => { - imip => 'mailto:bar@local', - }, - invitedBy => 'foo', - }, - 'bam' => { - name => 'Bam', - roles => { - 'attendee' => JSON::true, - }, - delegatedFrom => { - 'bar' => JSON::true, - }, - scheduleSequence => 7, - scheduleUpdated => '2018-07-06T05:03:02Z', - email => 'bam@local', # same email as sendTo - sendTo => { - imip => 'mailto:bam@local', - }, - }, - 'group' => { - name => 'Group', - kind => 'group', - roles => { - 'attendee' => JSON::true, - }, - email => 'group@local', - sendTo => { - 'imip' => 'mailto:groupimip@local', - 'other' => 'tel:+1-123-5555-1234', - }, - }, - 'resource' => { - name => 'Some resource', - kind => 'resource', - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:resource@local', - }, - }, - 'location' => { - name => 'Some location', - kind => 'location', - roles => { - 'attendee' => JSON::true, - }, - locationId => 'loc1', - sendTo => { - imip => 'mailto:location@local', - }, - }, - }, - locations => { - loc1 => { - name => 'location1', - }, - loc2 => { - name => 'location2', - }, - }, - }; + locationId => 'loc1', + participationStatus => 'accepted', + expectReply => JSON::false, + links => { + link1 => { + href => 'https://somelink.local', + rel => "enclosure", + }, + }, + participationComment => 'Sure; see you "soon"!', + sendTo => { + imip => 'mailto:foo@local', + }, + }, + 'bar' => { + name => 'Bar', + kind => 'individual', + roles => { + 'attendee' => JSON::true, + }, + locationId => 'loc2', + participationStatus => 'needs-action', + expectReply => JSON::true, + delegatedTo => { + 'bam' => JSON::true, + }, + memberOf => { + 'group' => JSON::true, + }, + links => { + link1 => { + href => 'https://somelink.local', + rel => "enclosure", + }, + }, + email => 'bar2@local', # different email than sendTo + sendTo => { + imip => 'mailto:bar@local', + }, + invitedBy => 'foo', + }, + 'bam' => { + name => 'Bam', + roles => { + 'attendee' => JSON::true, + }, + delegatedFrom => { + 'bar' => JSON::true, + }, + scheduleSequence => 7, + scheduleUpdated => '2018-07-06T05:03:02Z', + email => 'bam@local', # same email as sendTo + sendTo => { + imip => 'mailto:bam@local', + }, + }, + 'group' => { + name => 'Group', + kind => 'group', + roles => { + 'attendee' => JSON::true, + }, + email => 'group@local', + sendTo => { + 'imip' => 'mailto:groupimip@local', + 'other' => 'tel:+1-123-5555-1234', + }, + }, + 'resource' => { + name => 'Some resource', + kind => 'resource', + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:resource@local', + }, + }, + 'location' => { + name => 'Some location', + kind => 'location', + roles => { + 'attendee' => JSON::true, + }, + locationId => 'loc1', + sendTo => { + imip => 'mailto:location@local', + }, + }, + }, + locations => { + loc1 => { + name => 'location1', + }, + loc2 => { + name => 'location2', + }, + }, + }; - my $ret = $self->createandget_event($event); - $event->{participants}{foo}{sendTo} = { imip => 'mailto:foo@local' }; - delete $event->{method}; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{participants}{foo}{sendTo} = { imip => 'mailto:foo@local' }; + delete $event->{method}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_justorga b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_justorga index 8d77242f3b..1735882739 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_justorga +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_justorga @@ -2,47 +2,46 @@ use Cassandane::Tiny; sub test_calendarevent_set_participants_justorga - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "status" => "confirmed", + "replyTo" => { + "imip" => "mailto:foo\@local", + }, + "participants" => { + 'foo' => { + '@type' => 'Participant', + name => 'Foo', + roles => { + 'owner' => JSON::true, }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "status" => "confirmed", - "replyTo" => { - "imip" => "mailto:foo\@local", + "sendTo" => { + "imip" => "mailto:foo\@local", }, - "participants" => { - 'foo' => { - '@type' => 'Participant', - name => 'Foo', - roles => { - 'owner' => JSON::true, - }, - "sendTo" => { - "imip" => "mailto:foo\@local", - }, - email => 'foo@local', - participationStatus => 'needs-action', - scheduleSequence => 0, - expectReply => JSON::false, - }, - }, - }; + email => 'foo@local', + participationStatus => 'needs-action', + scheduleSequence => 0, + expectReply => JSON::false, + }, + }, + }; - my $ret = $self->createandget_event($event); - delete $event->{method}; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + delete $event->{method}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_organame b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_organame index 77256c4fe0..82b327a4ac 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_organame +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_organame @@ -2,54 +2,53 @@ use Cassandane::Tiny; sub test_calendarevent_set_participants_organame - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "status" => "confirmed", + "replyTo" => { + "imip" => "mailto:foo\@local", + }, + "participants" => { + 'foo' => { + '@type' => 'Participant', + name => 'Foo', + roles => { + 'owner' => JSON::true, }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "status" => "confirmed", - "replyTo" => { - "imip" => "mailto:foo\@local", + sendTo => { + imip => 'mailto:foo@local', }, - "participants" => { - 'foo' => { - '@type' => 'Participant', - name => 'Foo', - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:foo@local', - }, - }, - 'bar' => { - '@type' => 'Participant', - name => 'Bar', - kind => 'individual', - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:bar@local', - }, - }, + }, + 'bar' => { + '@type' => 'Participant', + name => 'Bar', + kind => 'individual', + roles => { + 'attendee' => JSON::true, }, - }; + sendTo => { + imip => 'mailto:bar@local', + }, + }, + }, + }; - my $ret = $self->createandget_event($event); - $event->{participants}{bar}{sendTo}{imip} = 'mailto:bar@local'; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{participants}{bar}{sendTo}{imip} = 'mailto:bar@local'; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_patch b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_patch index 6bf54a230b..3be86cc11a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_patch +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_patch @@ -2,72 +2,79 @@ use Cassandane::Tiny; sub test_calendarevent_set_participants_patch - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "status" => "confirmed", - "replyTo" => { - "imip" => "mailto:foo\@local", + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "status" => "confirmed", + "replyTo" => { + "imip" => "mailto:foo\@local", + }, + "participants" => { + 'bar' => { + name => 'Bar', + roles => { + 'attendee' => JSON::true, }, - "participants" => { - 'bar' => { - name => 'Bar', - roles => { - 'attendee' => JSON::true, - }, - participationStatus => 'needs-action', - expectReply => JSON::true, - sendTo => { - imip => 'mailto:bar@local', - }, - }, + participationStatus => 'needs-action', + expectReply => JSON::true, + sendTo => { + imip => 'mailto:bar@local', }, - }; + }, + }, + }; - my $ret = $self->createandget_event($event); - delete $event->{method}; + my $ret = $self->createandget_event($event); + delete $event->{method}; - # Add auto-generated owner participant for ORGANIZER. - $event->{participants}{'3e6a0e46cc0af22aff762f2e1869f23de7aca482'} = { - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:foo@local', - }, - }; - $self->assert_normalized_event_equals($event, $ret); - my $eventId = $ret->{id}; + # Add auto-generated owner participant for ORGANIZER. + $event->{participants}{'3e6a0e46cc0af22aff762f2e1869f23de7aca482'} = { + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:foo@local', + }, + }; + $self->assert_normalized_event_equals($event, $ret); + my $eventId = $ret->{id}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'participants/bar/participationStatus' => 'accepted', - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $event->{participants}{'bar'}{participationStatus} = 'accepted'; - $ret = $res->[1][1]{list}[0]; - $self->assert_normalized_event_equals($event, $ret); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'participants/bar/participationStatus' => 'accepted', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $event->{participants}{'bar'}{participationStatus} = 'accepted'; + $ret = $res->[1][1]{list}[0]; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_recur b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_recur index 5782dffe8e..bfcfc836c3 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_recur +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_participants_recur @@ -2,85 +2,91 @@ use Cassandane::Tiny; sub test_calendarevent_set_participants_recur - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "recurrenceRules" => [ { + "frequency" => "weekly", + } ], + "replyTo" => { + "imip" => "mailto:foo\@local", + }, + "participants" => { + 'bar' => { + roles => { + 'attendee' => JSON::true, }, - "title"=> "title", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "recurrenceRules"=> [{ - "frequency"=> "weekly", - }], - "replyTo" => { - "imip" => "mailto:foo\@local", + expectReply => JSON::true, + sendTo => { + imip => 'mailto:bar@local', }, - "participants" => { - 'bar' => { - roles => { - 'attendee' => JSON::true, - }, - expectReply => JSON::true, - sendTo => { - imip => 'mailto:bar@local', - }, - }, - 'bam' => { - email => 'bam@local', - roles => { - 'attendee' => JSON::true, - }, - expectReply => JSON::true, - sendTo => { - imip => 'mailto:bam@local', - }, - }, + }, + 'bam' => { + email => 'bam@local', + roles => { + 'attendee' => JSON::true, }, - }; + expectReply => JSON::true, + sendTo => { + imip => 'mailto:bam@local', + }, + }, + }, + }; - my $ret = $self->createandget_event($event); - my $eventId = $ret->{id}; - $self->assert_not_null($eventId); + my $ret = $self->createandget_event($event); + my $eventId = $ret->{id}; + $self->assert_not_null($eventId); - my $barParticipantId; - while (my ($key, $value) = each(%{$ret->{participants}})) { - if ($value->{sendTo}{imip} eq 'mailto:bar@local') { - $barParticipantId = $key; - last; - } + my $barParticipantId; + while (my ($key, $value) = each(%{ $ret->{participants} })) { + if ($value->{sendTo}{imip} eq 'mailto:bar@local') { + $barParticipantId = $key; + last; } - $self->assert_not_null($barParticipantId); + } + $self->assert_not_null($barParticipantId); - my $recurrenceOverrides = { - "2015-11-14T09:00:00" => { - ('participants/' . $barParticipantId) => undef, - }, - }; + my $recurrenceOverrides = { + "2015-11-14T09:00:00" => { + ('participants/' . $barParticipantId) => undef, + }, + }; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides' => $recurrenceOverrides - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides' => $recurrenceOverrides + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_deep_equals( - $recurrenceOverrides, $res->[1][1]{list}[0]{recurrenceOverrides} - ); + $self->assert_deep_equals($recurrenceOverrides, + $res->[1][1]{list}[0]{recurrenceOverrides}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_peruser b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_peruser index 22440a3fe1..2bb92bed28 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_peruser +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_peruser @@ -2,236 +2,272 @@ use Cassandane::Tiny; sub test_calendarevent_set_peruser - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my ($maj, $min) = Cassandane::Instance->get_version(); - - # These properties are set per-user. - my $proplist = [ - 'freeBusyStatus', - 'color', - 'keywords', - 'useDefaultAlerts', - 'alerts', - ]; - - xlog "Create an event and assert default per-user props"; - my $defaultPerUserProps = { - freeBusyStatus => 'busy', - # color omitted by default - keywords => undef, - useDefaultAlerts => JSON::false, - alerts => undef, - }; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2019-12-10T23:30:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - recurrenceRules => [{ - frequency => 'daily', - count => 5, - }], - }, + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my ($maj, $min) = Cassandane::Instance->get_version(); + + # These properties are set per-user. + my $proplist + = [ 'freeBusyStatus', 'color', 'keywords', 'useDefaultAlerts', 'alerts', ]; + + xlog "Create an event and assert default per-user props"; + my $defaultPerUserProps = { + freeBusyStatus => 'busy', + # color omitted by default + keywords => undef, + useDefaultAlerts => JSON::false, + alerts => undef, + }; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#1'], - properties => $proplist, - }, 'R2'] - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); - my $event = $res->[1][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($defaultPerUserProps, $event); - - xlog "Create other user and share owner calendar"; - my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user("other"); - $admintalk->setacl("user.cassandane.#calendars.Default", "other", "lrsiwntex") or die; - my $service = $self->{instance}->get_service("http"); - my $otherJMAPTalk = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $otherJMAPTalk->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/calendars', - 'urn:ietf:params:jmap:calendars', - ]); - - xlog "Set and assert per-user properties for owner"; - my $ownerPerUserProps = { - freeBusyStatus => 'free', - color => 'blue', - keywords => { - 'ownerKeyword' => JSON::true, + title => "event1", + start => "2019-12-10T23:30:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + recurrenceRules => [ { + frequency => 'daily', + count => 5, + } ], + }, }, - useDefaultAlerts => JSON::true, - alerts => { - '639d8761-81ee-404c-84cd-3e419ab6f883' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => "-PT5M", - }, - action => "email", - }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#1'], + properties => $proplist, + }, + 'R2' + ] + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); + my $event = $res->[1][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($defaultPerUserProps, $event); + + xlog "Create other user and share owner calendar"; + my $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->create_user("other"); + $admintalk->setacl("user.cassandane.#calendars.Default", "other", "lrsiwntex") + or die; + my $service = $self->{instance}->get_service("http"); + my $otherJMAPTalk = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $otherJMAPTalk->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'https://cyrusimap.org/ns/jmap/calendars', + 'urn:ietf:params:jmap:calendars', + ]); + + xlog "Set and assert per-user properties for owner"; + my $ownerPerUserProps = { + freeBusyStatus => 'free', + color => 'blue', + keywords => { + 'ownerKeyword' => JSON::true, + }, + useDefaultAlerts => JSON::true, + alerts => { + '639d8761-81ee-404c-84cd-3e419ab6f883' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => "-PT5M", }, - }; + action => "email", + }, + }, + }; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => $ownerPerUserProps, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => $proplist, - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $event = $res->[1][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($ownerPerUserProps, $event); - - xlog "Assert other user per-user properties for shared event"; - $res = $otherJMAPTalk->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => $proplist, - }, 'R1'] - ]); - $event = $res->[0][1]{list}[0]; - $self->assert_not_null($event); - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals({ - # inherited from owner - color => 'blue', - keywords => { - 'ownerKeyword' => JSON::true, + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => $ownerPerUserProps, }, - # not inherited from owner - freeBusyStatus => 'busy', - useDefaultAlerts => JSON::false, - alerts => undef, - }, $event); - - xlog "Update and assert per-user props as other user"; - my $otherPerUserProps = { - keywords => { - 'otherKeyword' => JSON::true, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => $proplist, + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $event = $res->[1][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($ownerPerUserProps, $event); + + xlog "Assert other user per-user properties for shared event"; + $res = $otherJMAPTalk->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => $proplist, + }, + 'R1' + ] ]); + $event = $res->[0][1]{list}[0]; + $self->assert_not_null($event); + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals( + { + # inherited from owner + color => 'blue', + keywords => { + 'ownerKeyword' => JSON::true, + }, + # not inherited from owner + freeBusyStatus => 'busy', + useDefaultAlerts => JSON::false, + alerts => undef, + }, + $event + ); + + xlog "Update and assert per-user props as other user"; + my $otherPerUserProps = { + keywords => { + 'otherKeyword' => JSON::true, + }, + color => 'red', + freeBusyStatus => 'free', + useDefaultAlerts => JSON::true, + alerts => { + 'ae3ce02e-8ad6-4250-b075-5449c2717c93' => { + '@type' => 'Alert', + trigger => { + '@type' => 'AbsoluteTrigger', + when => "2019-03-04T04:05:06Z", }, - color => 'red', - freeBusyStatus => 'free', - useDefaultAlerts => JSON::true, - alerts => { - 'ae3ce02e-8ad6-4250-b075-5449c2717c93' => { - '@type' => 'Alert', - trigger => { - '@type' => 'AbsoluteTrigger', - when => "2019-03-04T04:05:06Z", - }, - action => "display", - }, + action => "display", + }, + }, + }; + + $res = $otherJMAPTalk->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => $otherPerUserProps, }, - }; + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { - $res = $otherJMAPTalk->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => $otherPerUserProps, - }, - }, 'R1'], - ['CalendarEvent/get', { - - accountId => 'cassandane', - ids => [$eventId], - properties => $proplist, - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $event = $res->[1][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($otherPerUserProps, $event); - - xlog "Assert that owner kept their per-user props"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$eventId], - properties => $proplist, - }, 'R1'] - ]); - $event = $res->[0][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($ownerPerUserProps, $event); - - xlog "Remove per-user props as other user"; - $otherPerUserProps = { - keywords => undef, - freeBusyStatus => 'free', - useDefaultAlerts => JSON::true, - alerts => { - 'ae3ce02e-8ad6-4250-b075-5449c2717c93' => { - '@type' => 'Alert', - trigger => { - '@type' => 'AbsoluteTrigger', - when => "2019-03-04T04:05:06Z", - }, - action => "display", - }, + accountId => 'cassandane', + ids => [$eventId], + properties => $proplist, + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $event = $res->[1][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($otherPerUserProps, $event); + + xlog "Assert that owner kept their per-user props"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => $proplist, + }, + 'R1' + ] ]); + $event = $res->[0][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($ownerPerUserProps, $event); + + xlog "Remove per-user props as other user"; + $otherPerUserProps = { + keywords => undef, + freeBusyStatus => 'free', + useDefaultAlerts => JSON::true, + alerts => { + 'ae3ce02e-8ad6-4250-b075-5449c2717c93' => { + '@type' => 'Alert', + trigger => { + '@type' => 'AbsoluteTrigger', + when => "2019-03-04T04:05:06Z", }, - }; - - $res = $otherJMAPTalk->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => { - keywords => undef, - color => undef, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => $proplist, - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $event = $res->[1][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($otherPerUserProps, $event); - - xlog "Assert that owner kept their per-user props"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$eventId], - properties => $proplist, - }, 'R1'] - ]); - $event = $res->[0][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($ownerPerUserProps, $event); + action => "display", + }, + }, + }; + + $res = $otherJMAPTalk->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => { + keywords => undef, + color => undef, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => $proplist, + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $event = $res->[1][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($otherPerUserProps, $event); + + xlog "Assert that owner kept their per-user props"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => $proplist, + }, + 'R1' + ] ]); + $event = $res->[0][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($ownerPerUserProps, $event); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_peruser_secretary b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_peruser_secretary index 43cc438f0e..412587d88a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_peruser_secretary +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_peruser_secretary @@ -2,33 +2,34 @@ use Cassandane::Tiny; sub test_calendarevent_set_peruser_secretary - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog 'Create sharee and share cassandane calendar'; - my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user('sharee'); - $admintalk->setacl('user.cassandane.#calendars.Default', 'sharee', 'lrsiwntex') or die; - my $service = $self->{instance}->get_service('http'); - my $shareejmap = Mail::JMAPTalk->new( - user => 'sharee', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $shareejmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/calendars', - 'urn:ietf:params:jmap:calendars', - ]); + xlog 'Create sharee and share cassandane calendar'; + my $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->create_user('sharee'); + $admintalk->setacl('user.cassandane.#calendars.Default', + 'sharee', 'lrsiwntex') + or die; + my $service = $self->{instance}->get_service('http'); + my $shareejmap = Mail::JMAPTalk->new( + user => 'sharee', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $shareejmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'https://cyrusimap.org/ns/jmap/calendars', + 'urn:ietf:params:jmap:calendars', + ]); - xlog 'Set calendar home to secretary mode'; - my $xml = < @@ -38,127 +39,145 @@ sub test_calendarevent_set_peruser_secretary EOF - $caldav->Request('PROPPATCH', "/dav/calendars/user/cassandane", $xml, - 'Content-Type' => 'text/xml'); + $caldav->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane", + $xml, 'Content-Type' => 'text/xml' + ); - xlog 'Create an event with per-user props as owner'; - my $perUserProps = { - freeBusyStatus => 'free', - color => 'blue', - keywords => { - 'ownerKeyword' => JSON::true, + xlog 'Create an event with per-user props as owner'; + my $perUserProps = { + freeBusyStatus => 'free', + color => 'blue', + keywords => { + 'ownerKeyword' => JSON::true, + }, + useDefaultAlerts => JSON::true, + alerts => { + 'd5aad69a-db22-4524-8f2d-0c10a67778d1' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT5M', }, - useDefaultAlerts => JSON::true, - alerts => { - 'd5aad69a-db22-4524-8f2d-0c10a67778d1' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT5M', - }, - action => 'email', - }, - }, - }; + action => 'email', + }, + }, + }; - my @proplist = keys %$perUserProps; + my @proplist = keys %$perUserProps; - my $event = { - uid => 'eventuid1', - calendarIds => { - Default => JSON::true, + my $event = { + uid => 'eventuid1', + calendarIds => { + Default => JSON::true, + }, + title => 'event1', + start => '2019-12-10T23:30:00', + duration => 'PT1H', + timeZone => 'Australia/Melbourne', + }; + $event = { %$event, %$perUserProps }; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => $event, }, - title => 'event1', - start => '2019-12-10T23:30:00', - duration => 'PT1H', - timeZone => 'Australia/Melbourne', - }; - $event = { %$event, %$perUserProps }; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => $event, - }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); - xlog 'assert per-user properties for owner and sharee'; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => \@proplist, - }, 'R1'] - ]); - $event = $res->[0][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($perUserProps, $event); + xlog 'assert per-user properties for owner and sharee'; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => \@proplist, + }, + 'R1' + ] ]); + $event = $res->[0][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($perUserProps, $event); - $res = $shareejmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => \@proplist, - }, 'R1'] - ]); - $event = $res->[0][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($perUserProps, $event); + $res = $shareejmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => \@proplist, + }, + 'R1' + ] ]); + $event = $res->[0][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($perUserProps, $event); - xlog 'Update per-user props as sharee'; - $perUserProps = { - freeBusyStatus => 'busy', - color => 'red', - keywords => { - 'shareeKeyword' => JSON::true, - }, - useDefaultAlerts => JSON::false, - alerts => { - 'd5aad69a-db22-4524-8f2d-0c10a67778d1' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => 'start', - offset => '-PT10M', - }, - action => 'display', - }, + xlog 'Update per-user props as sharee'; + $perUserProps = { + freeBusyStatus => 'busy', + color => 'red', + keywords => { + 'shareeKeyword' => JSON::true, + }, + useDefaultAlerts => JSON::false, + alerts => { + 'd5aad69a-db22-4524-8f2d-0c10a67778d1' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => 'start', + offset => '-PT10M', }, - }; + action => 'display', + }, + }, + }; - $res = $shareejmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventId => $perUserProps, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + $res = $shareejmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventId => $perUserProps, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog 'assert per-user properties for owner and sharee'; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => \@proplist, - }, 'R1'] - ]); - $event = $res->[0][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($perUserProps, $event); + xlog 'assert per-user properties for owner and sharee'; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => \@proplist, + }, + 'R1' + ] ]); + $event = $res->[0][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($perUserProps, $event); - $res = $shareejmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - ids => [$eventId], - properties => \@proplist, - }, 'R1'] - ]); - $event = $res->[0][1]{list}[0]; - delete @{$event}{qw/id uid @type/}; - $self->assert_deep_equals($perUserProps, $event); + $res = $shareejmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [$eventId], + properties => \@proplist, + }, + 'R1' + ] ]); + $event = $res->[0][1]{list}[0]; + delete @{$event}{qw/id uid @type/}; + $self->assert_deep_equals($perUserProps, $event); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_class b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_class index a41bad0d3c..87a5f598bf 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_class +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_class @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_set_preserve_class - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <Request('PUT', '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); - - $res = $caldav->Request('GET', '/dav/calendars/user/cassandane/Default/test.ics'); - $self->assert_matches(qr/CLASS:PRIVATE/, $res->{content}); - - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ]); - my $eventId = $res->[0][1]{ids}[0]; - $self->assert_not_null($eventId); - - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'update' - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - $res = $caldav->Request('GET', '/dav/calendars/user/cassandane/Default/test.ics'); - $self->assert_matches(qr/CLASS:PRIVATE/, $res->{content}); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); + + $res = $caldav->Request('GET', + '/dav/calendars/user/cassandane/Default/test.ics'); + $self->assert_matches(qr/CLASS:PRIVATE/, $res->{content}); + + my $res = $jmap->CallMethods([ [ 'CalendarEvent/query', {}, 'R1' ], ]); + my $eventId = $res->[0][1]{ids}[0]; + $self->assert_not_null($eventId); + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'update' + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + $res = $caldav->Request('GET', + '/dav/calendars/user/cassandane/Default/test.ics'); + $self->assert_matches(qr/CLASS:PRIVATE/, $res->{content}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_icalxprops b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_icalxprops index 90933a3aa2..a0f40f10d1 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_icalxprops +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_icalxprops @@ -2,19 +2,18 @@ use Cassandane::Tiny; sub test_calendarevent_set_preserve_icalxprops - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "Create event with unknown x-properties"; + xlog $self, "Create event with unknown x-properties"; - my $mainevent_xprop = "X-MAINEVENT-PROP;X-FOO=1:foo"; - my $recurevent_xprop = "X-RECUREVENT-PROP;X-BAR=2:bar"; - my $alarm_xprop = "X-ALARM-PROP;X-BAZ=3:baz"; + my $mainevent_xprop = "X-MAINEVENT-PROP;X-FOO=1:foo"; + my $recurevent_xprop = "X-RECUREVENT-PROP;X-BAR=2:bar"; + my $alarm_xprop = "X-ALARM-PROP;X-BAZ=3:baz"; - my $ical = <Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); - - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => [ - 'cyrusimap.org:iCalProps', - 'alerts', - 'recurrenceOverrides' - ], - }, 'R1'] - ]); - my $eventId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($eventId); - - xlog $self, "Assert iCalProps property"; - $self->assert_str_equals('x-mainevent-prop', - $res->[0][1]{list}[0]{'cyrusimap.org:iCalProps'}[0][0]); - - my @alarmIcalProps = sort { $_->[0] cmp $_->[1] } @{ - $res->[0][1]{list}[0]{alerts - }{'E576FA39-51B6-45FA-AAF3-7CCE1F21802E' - }{'cyrusimap.org:iCalProps'}}; - $self->assert_num_equals(2, scalar @alarmIcalProps); - $self->assert_str_equals('uid', $alarmIcalProps[0][0]); - $self->assert_str_equals('x-alarm-prop', $alarmIcalProps[1][0]); - - $self->assert_str_equals('x-recurevent-prop', - $res->[0][1]{list}[0]{recurrenceOverrides}{'2023-03-10T16:00:00' - }{'cyrusimap.org:iCalProps'}[0][0]); - - - xlog $self, "Update some event property via JMAP"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'test2', - }, + my $res = $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); + + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + properties => + [ 'cyrusimap.org:iCalProps', 'alerts', 'recurrenceOverrides' ], + }, + 'R1' + ] ]); + my $eventId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($eventId); + + xlog $self, "Assert iCalProps property"; + $self->assert_str_equals('x-mainevent-prop', + $res->[0][1]{list}[0]{'cyrusimap.org:iCalProps'}[0][0]); + + my @alarmIcalProps + = sort { $_->[0] cmp $_->[1] } + @{ $res->[0][1]{list}[0]{alerts}{'E576FA39-51B6-45FA-AAF3-7CCE1F21802E'} + {'cyrusimap.org:iCalProps'} }; + $self->assert_num_equals(2, scalar @alarmIcalProps); + $self->assert_str_equals('uid', $alarmIcalProps[0][0]); + $self->assert_str_equals('x-alarm-prop', $alarmIcalProps[1][0]); + + $self->assert_str_equals('x-recurevent-prop', + $res->[0][1]{list}[0]{recurrenceOverrides}{'2023-03-10T16:00:00'} + {'cyrusimap.org:iCalProps'}[0][0]); + + xlog $self, "Update some event property via JMAP"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'test2', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => + [ 'cyrusimap.org:iCalProps', 'alerts', 'recurrenceOverrides' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog $self, "Assert iCalProps property"; + $self->assert_str_equals('x-mainevent-prop', + $res->[1][1]{list}[0]{'cyrusimap.org:iCalProps'}[0][0]); + + my @alarmIcalProps + = sort { $_->[0] cmp $_->[1] } + @{ $res->[1][1]{list}[0]{alerts}{'E576FA39-51B6-45FA-AAF3-7CCE1F21802E'} + {'cyrusimap.org:iCalProps'} }; + $self->assert_num_equals(2, scalar @alarmIcalProps); + $self->assert_str_equals('uid', $alarmIcalProps[0][0]); + $self->assert_str_equals('x-alarm-prop', $alarmIcalProps[1][0]); + + $self->assert_str_equals('x-recurevent-prop', + $res->[1][1]{list}[0]{recurrenceOverrides}{'2023-03-10T16:00:00'} + {'cyrusimap.org:iCalProps'}[0][0]); + + xlog $self, "Can not update iCalProps"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'cyrusimap.org:iCalProps' => + [ [ 'x-mainevent-prop', {}, 'text', 'newval' ], ], + 'alerts/E576FA39-51B6-45FA-AAF3-7CCE1F21802E/cyrusimap.org:iCalProps' + => [ [ 'x-alarm-prop', {}, 'text', 'newalarmval' ], ], + 'recurrenceOverrides/2023-03-10T16:00:00/cyrusimap.org:iCalProps/0/3' + => [ [ 'x-recurevent-prop', {}, 'text', 'newrecurval' ], ], + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => + [ 'cyrusimap.org:iCalProps', 'alerts', 'recurrenceOverrides' ], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$eventId}); + + xlog $self, "Assert iCalProps property"; + $self->assert_str_equals('x-mainevent-prop', + $res->[1][1]{list}[0]{'cyrusimap.org:iCalProps'}[0][0]); + + my @alarmIcalProps + = sort { $_->[0] cmp $_->[1] } + @{ $res->[1][1]{list}[0]{alerts}{'E576FA39-51B6-45FA-AAF3-7CCE1F21802E'} + {'cyrusimap.org:iCalProps'} }; + $self->assert_num_equals(2, scalar @alarmIcalProps); + $self->assert_str_equals('uid', $alarmIcalProps[0][0]); + $self->assert_str_equals('x-alarm-prop', $alarmIcalProps[1][0]); + + $self->assert_str_equals('x-recurevent-prop', + $res->[1][1]{list}[0]{recurrenceOverrides}{'2023-03-10T16:00:00'} + {'cyrusimap.org:iCalProps'}[0][0]); + + xlog $self, "Can not create iCalProps"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event2 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - properties => [ - 'cyrusimap.org:iCalProps', - 'alerts', - 'recurrenceOverrides' - ], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog $self, "Assert iCalProps property"; - $self->assert_str_equals('x-mainevent-prop', - $res->[1][1]{list}[0]{'cyrusimap.org:iCalProps'}[0][0]); - - my @alarmIcalProps = sort { $_->[0] cmp $_->[1] } @{ - $res->[1][1]{list}[0]{alerts - }{'E576FA39-51B6-45FA-AAF3-7CCE1F21802E' - }{'cyrusimap.org:iCalProps'}}; - $self->assert_num_equals(2, scalar @alarmIcalProps); - $self->assert_str_equals('uid', $alarmIcalProps[0][0]); - $self->assert_str_equals('x-alarm-prop', $alarmIcalProps[1][0]); - - $self->assert_str_equals('x-recurevent-prop', - $res->[1][1]{list}[0]{recurrenceOverrides}{'2023-03-10T16:00:00' - }{'cyrusimap.org:iCalProps'}[0][0]); - - xlog $self, "Can not update iCalProps"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'cyrusimap.org:iCalProps' => [ - ['x-mainevent-prop', {}, 'text', 'newval'], - ], - 'alerts/E576FA39-51B6-45FA-AAF3-7CCE1F21802E/cyrusimap.org:iCalProps' => [ - ['x-alarm-prop', {}, 'text', 'newalarmval'], - ], - 'recurrenceOverrides/2023-03-10T16:00:00/cyrusimap.org:iCalProps/0/3' => [ - ['x-recurevent-prop', {}, 'text', 'newrecurval'], - ], - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - properties => [ - 'cyrusimap.org:iCalProps', - 'alerts', - 'recurrenceOverrides' - ], - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{notUpdated}{$eventId}); - - xlog $self, "Assert iCalProps property"; - $self->assert_str_equals('x-mainevent-prop', - $res->[1][1]{list}[0]{'cyrusimap.org:iCalProps'}[0][0]); - - my @alarmIcalProps = sort { $_->[0] cmp $_->[1] } @{ - $res->[1][1]{list}[0]{alerts - }{'E576FA39-51B6-45FA-AAF3-7CCE1F21802E' - }{'cyrusimap.org:iCalProps'}}; - $self->assert_num_equals(2, scalar @alarmIcalProps); - $self->assert_str_equals('uid', $alarmIcalProps[0][0]); - $self->assert_str_equals('x-alarm-prop', $alarmIcalProps[1][0]); - - $self->assert_str_equals('x-recurevent-prop', - $res->[1][1]{list}[0]{recurrenceOverrides}{'2023-03-10T16:00:00' - }{'cyrusimap.org:iCalProps'}[0][0]); - - xlog $self, "Can not create iCalProps"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event2 => { - calendarIds => { - Default => JSON::true, - }, - title => 'event2', - start => '2023-08-03T17:45:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - 'cyrusimap.org:iCalProps' => [ - ['x-prop', {}, 'text', 'foo'], - ] - } - }, - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{notCreated}{event2}); + title => 'event2', + start => '2023-08-03T17:45:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + 'cyrusimap.org:iCalProps' => [ [ 'x-prop', {}, 'text', 'foo' ], ] + } + }, + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{notCreated}{event2}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_microsoft_timezone b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_microsoft_timezone index b3fa407042..239e4b7407 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_microsoft_timezone +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_preserve_microsoft_timezone @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarevent_set_preserve_microsoft_timezone - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event with Microsoft timezone via CalDAV"; - my $ical = <Request('PUT', $ics, $ical, 'Content-Type' => 'text/calendar'); + my $ics = '/dav/calendars/user/cassandane/Default/test.ics'; + $caldav->Request('PUT', $ics, $ical, 'Content-Type' => 'text/calendar'); - xlog "Assert timeZone is mapped to IANA timezone"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/query', { - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/query', - path => '/ids' - }, - properties => ['timeZone'], - }, 'R2'], - ]); - my $eventId = $res->[1][1]{list}[0]{id}; - $self->assert_not_null($eventId); - $self->assert_str_equals('Australia/Sydney', $res->[1][1]{list}[0]{timeZone}); + xlog "Assert timeZone is mapped to IANA timezone"; + my $res = $jmap->CallMethods([ + [ 'CalendarEvent/query', {}, 'R1' ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/query', + path => '/ids' + }, + properties => ['timeZone'], + }, + 'R2' + ], + ]); + my $eventId = $res->[1][1]{list}[0]{id}; + $self->assert_not_null($eventId); + $self->assert_str_equals('Australia/Sydney', $res->[1][1]{list}[0]{timeZone}); - xlog "Update event title, keep timeZone untouched"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'updatedTitle', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog "Update event title, keep timeZone untouched"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'updatedTitle', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog "Assert VTIMEZONE is kept"; - $res = $caldav->Request('GET', $ics); - $self->assert($res->{content} =~ - m/DTSTART;TZID=AUS Eastern Standard Time:20220412T080000/); - $self->assert($res->{content} =~ - m/TZID:AUS Eastern Standard Time/); + xlog "Assert VTIMEZONE is kept"; + $res = $caldav->Request('GET', $ics); + $self->assert($res->{content} =~ + m/DTSTART;TZID=AUS Eastern Standard Time:20220412T080000/); + $self->assert($res->{content} =~ m/TZID:AUS Eastern Standard Time/); - xlog "Update event timeZone to IANA identifier"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - timeZone => 'Europe/London', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog "Update event timeZone to IANA identifier"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + timeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog "Assert VTIMEZONE got replaced"; - $res = $caldav->Request('GET', $ics); - $self->assert(not $res->{content} =~ - m/DTSTART;TZID=AUS Eastern Standard Time:20220412T080000/); - $self->assert(not $res->{content} =~ - m/TZID:AUS Eastern Standard Time/); + xlog "Assert VTIMEZONE got replaced"; + $res = $caldav->Request('GET', $ics); + $self->assert( + not $res->{content} =~ + m/DTSTART;TZID=AUS Eastern Standard Time:20220412T080000/); + $self->assert(not $res->{content} =~ m/TZID:AUS Eastern Standard Time/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy index c671b1c115..036f15f1f4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy @@ -2,105 +2,127 @@ use Cassandane::Tiny; sub test_calendarevent_set_privacy - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "share calendar with cassandane user"; - my ($sharerJmap) = $self->create_user('sharer'); - my $res = $sharerJmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - cassandane => { - mayReadItems => JSON::true, - mayWriteAll => JSON::true, - }, - }, + xlog "share calendar with cassandane user"; + my ($sharerJmap) = $self->create_user('sharer'); + my $res = $sharerJmap->CallMethods( + [ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + cassandane => { + mayReadItems => JSON::true, + mayWriteAll => JSON::true, }, + }, }, - }, 'R1'], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + 'R1' + ], + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ] + ); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "may only create private event on owned calendar"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'sharer', - create => { - eventShared1 => { - calendarIds => { - 'Default' => JSON::true, - }, - title => 'eventShared1', - start => '2022-01-24T09:00:00', - timeZone => 'America/New_York', - privacy => 'public', - }, - eventShared2 => { - calendarIds => { - 'Default' => JSON::true, - }, - title => 'eventShared2', - start => '2022-01-24T10:00:00', - timeZone => 'America/New_York', - privacy => 'secret', - }, + xlog "may only create private event on owned calendar"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'sharer', + create => { + eventShared1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/set', { - create => { - eventOwned1 => { - calendarIds => { - 'Default' => JSON::true, - }, - title => 'eventOwned1', - start => '2022-01-24T11:00:00', - timeZone => 'America/New_York', - privacy => 'secret', - }, + title => 'eventShared1', + start => '2022-01-24T09:00:00', + timeZone => 'America/New_York', + privacy => 'public', + }, + eventShared2 => { + calendarIds => { + 'Default' => JSON::true, + }, + title => 'eventShared2', + start => '2022-01-24T10:00:00', + timeZone => 'America/New_York', + privacy => 'secret', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + eventOwned1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R2'], - ]); + title => 'eventOwned1', + start => '2022-01-24T11:00:00', + timeZone => 'America/New_York', + privacy => 'secret', + }, + }, + }, + 'R2' + ], + ]); - my $eventShared1Id = $res->[0][1]{created}{eventShared1}{id}; - $self->assert_not_null($eventShared1Id); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notCreated}{eventShared2}{type}); - $self->assert_deep_equals(['privacy'], - $res->[0][1]{notCreated}{eventShared2}{properties}); - my $eventOwned1Id = $res->[1][1]{created}{eventOwned1}{id}; - $self->assert_not_null($eventOwned1Id); + my $eventShared1Id = $res->[0][1]{created}{eventShared1}{id}; + $self->assert_not_null($eventShared1Id); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{eventShared2}{type}); + $self->assert_deep_equals(['privacy'], + $res->[0][1]{notCreated}{eventShared2}{properties}); + my $eventOwned1Id = $res->[1][1]{created}{eventOwned1}{id}; + $self->assert_not_null($eventOwned1Id); - xlog "may not change public privacy on shared calendar"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'sharer', - update => { - $eventShared1Id => { - privacy => 'secret', - }, - }, - }, 'R1'], - ['CalendarEvent/set', { - update => { - $eventOwned1Id => { - privacy => 'private', - }, - }, - }, 'R2'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{$eventShared1Id}{type}); - $self->assert_deep_equals(['privacy'], - $res->[0][1]{notUpdated}{$eventShared1Id}{properties}); - $self->assert(exists $res->[1][1]{updated}{$eventOwned1Id}); + xlog "may not change public privacy on shared calendar"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'sharer', + update => { + $eventShared1Id => { + privacy => 'secret', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + update => { + $eventOwned1Id => { + privacy => 'private', + }, + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$eventShared1Id}{type}); + $self->assert_deep_equals(['privacy'], + $res->[0][1]{notUpdated}{$eventShared1Id}{properties}); + $self->assert(exists $res->[1][1]{updated}{$eventOwned1Id}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_ignore_override b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_ignore_override index d683042413..1f58c8d6a2 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_ignore_override +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_ignore_override @@ -2,44 +2,47 @@ use Cassandane::Tiny; sub test_calendarevent_set_privacy_ignore_override - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Ignore overriden privacy in CalendarEvent/set"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uidlocal', - title => 'event1', - start => '2020-01-01T09:00:00', - timeZone => 'Europe/Vienna', - duration => 'PT1H', - privacy => 'private', - recurrenceRules => [{ - frequency => 'daily', - count => 3, - }], - recurrenceOverrides => { - '2020-01-02T09:00:00' => { - title => 'event1Override', - privacy => 'secret', - }, - }, - }, + xlog "Ignore overriden privacy in CalendarEvent/set"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - my $xhref = $res->[0][1]{created}{event1}{'x-href'}; - $self->assert_not_null($xhref); + uid => 'event1uidlocal', + title => 'event1', + start => '2020-01-01T09:00:00', + timeZone => 'Europe/Vienna', + duration => 'PT1H', + privacy => 'private', + recurrenceRules => [ { + frequency => 'daily', + count => 3, + } ], + recurrenceOverrides => { + '2020-01-02T09:00:00' => { + title => 'event1Override', + privacy => 'secret', + }, + }, + }, + }, + }, + 'R1' + ], + ]); + my $xhref = $res->[0][1]{created}{event1}{'x-href'}; + $self->assert_not_null($xhref); - $res = $caldav->Request('GET', $xhref); - $self->assert($res->{content} =~ m/X-JMAP-PRIVACY:PRIVATE/); - $self->assert(not $res->{content} =~ m/X-JMAP-PRIVACY:CONFIDENTIAL/); + $res = $caldav->Request('GET', $xhref); + $self->assert($res->{content} =~ m/X-JMAP-PRIVACY:PRIVATE/); + $self->assert(not $res->{content} =~ m/X-JMAP-PRIVACY:CONFIDENTIAL/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_private_shared b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_private_shared index aa089263dc..3446a33a0f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_private_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_private_shared @@ -2,106 +2,142 @@ use Cassandane::Tiny; sub test_calendarevent_set_privacy_private_shared - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "share calendar"; - my ($shareeJmap) = $self->create_user('sharee'); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - sharee => { - mayReadItems => JSON::true, - mayWriteAll => JSON::true, - }, - }, - }, + xlog "share calendar"; + my ($shareeJmap) = $self->create_user('sharee'); + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + sharee => { + mayReadItems => JSON::true, + mayWriteAll => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "get calendar event state as sharee"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', ids => [] - }, 'R1' ], - ]); - my $state = $res->[0][1]{state}; + xlog "get calendar event state as sharee"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [] + }, + 'R1' + ], + ]); + my $state = $res->[0][1]{state}; - xlog "create private event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - privateEvent => { - calendarIds => { - 'Default' => JSON::true, - }, - start => '2021-01-01T01:00:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - title => 'privateEvent', - privacy => 'private', - }, + xlog "create private event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + privateEvent => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $privateEventId = $res->[0][1]{created}{privateEvent}{id}; - $self->assert_not_null($privateEventId); + start => '2021-01-01T01:00:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + title => 'privateEvent', + privacy => 'private', + }, + }, + }, + 'R1' + ], + ]); + my $privateEventId = $res->[0][1]{created}{privateEvent}{id}; + $self->assert_not_null($privateEventId); - xlog "sharee sees event"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - properties => ['id'], - }, 'R1' ], - ['CalendarEvent/changes', { - accountId => 'cassandane', - sinceState => $state, - }, 'R1' ], - ['CalendarEvent/query', { - accountId => 'cassandane', - }, 'R2' ], - ]); - $self->assert_str_equals($privateEventId, $res->[0][1]{list}[0]{id}); - $self->assert_deep_equals([$privateEventId], $res->[1][1]{created}); - $self->assert_deep_equals([$privateEventId], $res->[2][1]{ids}); + xlog "sharee sees event"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + properties => ['id'], + }, + 'R1' + ], + [ + 'CalendarEvent/changes', + { + accountId => 'cassandane', + sinceState => $state, + }, + 'R1' + ], + [ + 'CalendarEvent/query', + { + accountId => 'cassandane', + }, + 'R2' + ], + ]); + $self->assert_str_equals($privateEventId, $res->[0][1]{list}[0]{id}); + $self->assert_deep_equals([$privateEventId], $res->[1][1]{created}); + $self->assert_deep_equals([$privateEventId], $res->[2][1]{ids}); - xlog "sharee can't update or destroy, or copy"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $privateEventId => { - start => '2022-02-02T02:00:00', - }, + xlog "sharee can't update or destroy, or copy"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $privateEventId => { + start => '2022-02-02T02:00:00', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + destroy => [$privateEventId], + }, + 'R2' + ], + [ + 'CalendarEvent/copy', + { + accountId => 'sharee', + fromAccountId => 'cassandane', + create => { + privateEventCopy => { + id => $privateEventId, + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1' ], - ['CalendarEvent/set', { - accountId => 'cassandane', - destroy => [ $privateEventId ], - }, 'R2' ], - ['CalendarEvent/copy', { - accountId => 'sharee', - fromAccountId => 'cassandane', - create => { - privateEventCopy => { - id => $privateEventId, - calendarIds => { - 'Default' => JSON::true, - }, - }, - }, - }, 'R3' ], - ]); - $self->assert_str_equals('forbidden', - $res->[0][1]{notUpdated}{$privateEventId}{type}); - $self->assert_str_equals('forbidden', - $res->[1][1]{notDestroyed}{$privateEventId}{type}); - $self->assert_str_equals('forbidden', - $res->[2][1]{notCreated}{privateEventCopy}{type}); + }, + }, + }, + 'R3' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$privateEventId}{type}); + $self->assert_str_equals('forbidden', + $res->[1][1]{notDestroyed}{$privateEventId}{type}); + $self->assert_str_equals('forbidden', + $res->[2][1]{notCreated}{privateEventCopy}{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_secret_shared b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_secret_shared index 47b2340dce..0af28f5da0 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_secret_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_privacy_secret_shared @@ -2,106 +2,142 @@ use Cassandane::Tiny; sub test_calendarevent_set_privacy_secret_shared - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "share calendar"; - my ($shareeJmap) = $self->create_user('sharee'); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - sharee => { - mayReadItems => JSON::true, - mayWriteAll => JSON::true, - }, - }, - }, + xlog "share calendar"; + my ($shareeJmap) = $self->create_user('sharee'); + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + sharee => { + mayReadItems => JSON::true, + mayWriteAll => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "get calendar event state as sharee"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', ids => [] - }, 'R1' ], - ]); - my $state = $res->[0][1]{state}; + xlog "get calendar event state as sharee"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + ids => [] + }, + 'R1' + ], + ]); + my $state = $res->[0][1]{state}; - xlog "create secret event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - secretEvent => { - calendarIds => { - 'Default' => JSON::true, - }, - start => '2021-01-01T01:00:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - title => 'secretEvent', - privacy => 'secret', - }, + xlog "create secret event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + secretEvent => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $secretEventId = $res->[0][1]{created}{secretEvent}{id}; - $self->assert_not_null($secretEventId); + start => '2021-01-01T01:00:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + title => 'secretEvent', + privacy => 'secret', + }, + }, + }, + 'R1' + ], + ]); + my $secretEventId = $res->[0][1]{created}{secretEvent}{id}; + $self->assert_not_null($secretEventId); - xlog "sharee can not see event"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/get', { - accountId => 'cassandane', - properties => ['id'], - }, 'R1' ], - ['CalendarEvent/changes', { - accountId => 'cassandane', - sinceState => $state, - }, 'R1' ], - ['CalendarEvent/query', { - accountId => 'cassandane', - }, 'R2' ], - ]); - $self->assert_deep_equals([], $res->[0][1]{list}); - $self->assert_deep_equals([], $res->[1][1]{created}); - $self->assert_deep_equals([], $res->[2][1]{ids}); + xlog "sharee can not see event"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/get', + { + accountId => 'cassandane', + properties => ['id'], + }, + 'R1' + ], + [ + 'CalendarEvent/changes', + { + accountId => 'cassandane', + sinceState => $state, + }, + 'R1' + ], + [ + 'CalendarEvent/query', + { + accountId => 'cassandane', + }, + 'R2' + ], + ]); + $self->assert_deep_equals([], $res->[0][1]{list}); + $self->assert_deep_equals([], $res->[1][1]{created}); + $self->assert_deep_equals([], $res->[2][1]{ids}); - xlog "sharee can't update or destroy, or copy"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $secretEventId => { - start => '2022-02-02T02:00:00', - }, + xlog "sharee can't update or destroy, or copy"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $secretEventId => { + start => '2022-02-02T02:00:00', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + destroy => [$secretEventId], + }, + 'R2' + ], + [ + 'CalendarEvent/copy', + { + accountId => 'sharee', + fromAccountId => 'cassandane', + create => { + secretEventCopy => { + id => $secretEventId, + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1' ], - ['CalendarEvent/set', { - accountId => 'cassandane', - destroy => [ $secretEventId ], - }, 'R2' ], - ['CalendarEvent/copy', { - accountId => 'sharee', - fromAccountId => 'cassandane', - create => { - secretEventCopy => { - id => $secretEventId, - calendarIds => { - 'Default' => JSON::true, - }, - }, - }, - }, 'R3' ], - ]); - $self->assert_str_equals('notFound', - $res->[0][1]{notUpdated}{$secretEventId}{type}); - $self->assert_str_equals('notFound', - $res->[1][1]{notDestroyed}{$secretEventId}{type}); - $self->assert_str_equals('notFound', - $res->[2][1]{notCreated}{secretEventCopy}{type}); + }, + }, + }, + 'R3' + ], + ]); + $self->assert_str_equals('notFound', + $res->[0][1]{notUpdated}{$secretEventId}{type}); + $self->assert_str_equals('notFound', + $res->[1][1]{notDestroyed}{$secretEventId}{type}); + $self->assert_str_equals('notFound', + $res->[2][1]{notCreated}{secretEventCopy}{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_prodid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_prodid index e2b2b14e52..1d5c2509e9 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_prodid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_prodid @@ -2,34 +2,33 @@ use Cassandane::Tiny; sub test_calendarevent_set_prodid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/Amsterdam", - "showWithoutTime"=> JSON::false, - "description"=> "", - "freeBusyStatus"=> "busy", - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/Amsterdam", + "showWithoutTime" => JSON::false, + "description" => "", + "freeBusyStatus" => "busy", + }; - my $ret; + my $ret; - # assert default prodId - $ret = $self->createandget_event($event); - $self->assert_not_null($ret->{prodId}); + # assert default prodId + $ret = $self->createandget_event($event); + $self->assert_not_null($ret->{prodId}); - # assert custom prodId - my $prodId = "my prodId"; - $event->{prodId} = $prodId; - $ret = $self->createandget_event($event); - $self->assert_str_equals($prodId, $ret->{prodId}); + # assert custom prodId + my $prodId = "my prodId"; + $event->{prodId} = $prodId; + $ret = $self->createandget_event($event); + $self->assert_str_equals($prodId, $ret->{prodId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_readonly b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_readonly index e43f83cf6e..8de0c218de 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_readonly +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_readonly @@ -2,76 +2,95 @@ use Cassandane::Tiny; sub test_calendarevent_set_readonly - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - # Assert that calendar ACLs are enforced also for mailbox owner. + # Assert that calendar ACLs are enforced also for mailbox owner. - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - "1" => { - name => "", - color => "coral", - isVisible => \1 - } - } - }, "R1"], - ['Calendar/get', { - ids => ['#1'], - properties => ['name'], - }, "R2"], - ]); - my $calendarId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($calendarId); - my $name = $res->[1][1]{list}[0]{'name'}; - $self->assert_not_null($name); + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + "1" => { + name => "", + color => "coral", + isVisible => \1 + } + } + }, + "R1" + ], + [ + 'Calendar/get', + { + ids => ['#1'], + properties => ['name'], + }, + "R2" + ], + ]); + my $calendarId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($calendarId); + my $name = $res->[1][1]{list}[0]{'name'}; + $self->assert_not_null($name); - $admintalk->setacl("user.cassandane.#calendars." . $name, "cassandane" => 'lrskxcan9') or die; + $admintalk->setacl("user.cassandane.#calendars." . $name, + "cassandane" => 'lrskxcan9') + or die; - $res = $jmap->CallMethods([ - ['Calendar/get',{ - ids => [$calendarId], - }, "R2"], - ['CalendarEvent/set',{ - create => { - "1" => { - calendarIds => { - $calendarId => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "privacy" => "secret", - "participants" => undef, - "alerts"=> undef, - } - } - }, "R2"], - ]); + $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + ids => [$calendarId], + }, + "R2" + ], + [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calendarId => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "privacy" => "secret", + "participants" => undef, + "alerts" => undef, + } + } + }, + "R2" + ], + ]); - my $calendar = $res->[0][1]{list}[0]; - $self->assert_equals(JSON::true, $calendar->{myRights}->{mayReadFreeBusy}); - $self->assert_equals(JSON::true, $calendar->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::false, $calendar->{myRights}->{mayWriteAll}); - $self->assert_equals(JSON::false, $calendar->{myRights}->{mayWriteOwn}); - $self->assert_equals(JSON::true, $calendar->{myRights}->{mayDelete}); - $self->assert_equals(JSON::true, $calendar->{myRights}->{mayAdmin}); + my $calendar = $res->[0][1]{list}[0]; + $self->assert_equals(JSON::true, $calendar->{myRights}->{mayReadFreeBusy}); + $self->assert_equals(JSON::true, $calendar->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::false, $calendar->{myRights}->{mayWriteAll}); + $self->assert_equals(JSON::false, $calendar->{myRights}->{mayWriteOwn}); + $self->assert_equals(JSON::true, $calendar->{myRights}->{mayDelete}); + $self->assert_equals(JSON::true, $calendar->{myRights}->{mayAdmin}); - $self->assert_not_null($res->[1][1]{notCreated}{1}); - $self->assert_str_equals("invalidProperties", $res->[1][1]{notCreated}{1}{type}); - $self->assert_str_equals("calendarIds", $res->[1][1]{notCreated}{1}{properties}[0]); + $self->assert_not_null($res->[1][1]{notCreated}{1}); + $self->assert_str_equals("invalidProperties", + $res->[1][1]{notCreated}{1}{type}); + $self->assert_str_equals("calendarIds", + $res->[1][1]{notCreated}{1}{properties}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence index bfd5b7112a..dbf841f572 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence @@ -2,61 +2,71 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrence - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $recurrenceRules = [{ - frequency => "monthly", - interval => 2, - firstDayOfWeek => "su", - count => 1024, - byDay => [{ - day => "mo", - nthOfPeriod => -2, - }, { - day => "sa", - }], - }]; + my $recurrenceRules = [ { + frequency => "monthly", + interval => 2, + firstDayOfWeek => "su", + count => 1024, + byDay => [ + { + day => "mo", + nthOfPeriod => -2, + }, + { + day => "sa", + } + ], + } ]; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "recurrenceRules" => $recurrenceRules, - }; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "recurrenceRules" => $recurrenceRules, + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + $self->assert_normalized_event_equals($event, $ret); - # Now delete the recurrence rule - my $res = $jmap->CallMethods([ - ['CalendarEvent/set',{ - update => { - $event->{id} => { - recurrenceRules => undef, - }, - }, - }, "R1"], - ['CalendarEvent/get',{ - ids => [$event->{id}], - }, "R2"], - ]); - $self->assert(exists $res->[0][1]{updated}{$event->{id}}); + # Now delete the recurrence rule + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $event->{id} => { + recurrenceRules => undef, + }, + }, + }, + "R1" + ], + [ + 'CalendarEvent/get', + { + ids => [ $event->{id} ], + }, + "R2" + ], + ]); + $self->assert(exists $res->[0][1]{updated}{ $event->{id} }); - delete $event->{recurrenceRules}; - $ret = $res->[1][1]{list}[0]; - $self->assert_normalized_event_equals($event, $ret); + delete $event->{recurrenceRules}; + $ret = $res->[1][1]{list}[0]; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_bymonthday b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_bymonthday index b6daba4d75..abc52cae4f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_bymonthday +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_bymonthday @@ -2,34 +2,31 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrence_bymonthday - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - "uid" => "90c2697e-acbc-4508-9e72-6b8828e8d9f3", - calendarIds => { - $calid => JSON::true, - }, - "start" => "2019-01-31T09:00:00", - "duration" => "PT1H", - "timeZone" => "Australia/Melbourne", - "\@type" => "Event", - "title" => "Recurrence test", - "description" => "", - "showWithoutTime" => JSON::false, - "recurrenceRules" => [{ - "frequency" => "monthly", - "byMonthDay" => [ - -1 - ] - }], - }; + my $event = { + "uid" => "90c2697e-acbc-4508-9e72-6b8828e8d9f3", + calendarIds => { + $calid => JSON::true, + }, + "start" => "2019-01-31T09:00:00", + "duration" => "PT1H", + "timeZone" => "Australia/Melbourne", + "\@type" => "Event", + "title" => "Recurrence test", + "description" => "", + "showWithoutTime" => JSON::false, + "recurrenceRules" => [ { + "frequency" => "monthly", + "byMonthDay" => [-1] + } ], + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_multivalued b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_multivalued index 4586f30e72..de652c0f73 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_multivalued +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_multivalued @@ -2,33 +2,35 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrence_multivalued - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $event = { - calendarIds => { - Default => JSON::true, - }, - title => "title", - description => "description", - start => "2015-11-07T09:00:00", - duration => "PT1H", - timeZone => "Europe/London", - showWithoutTime => JSON::false, - freeBusyStatus => "busy", - recurrenceRules => [{ - frequency => 'weekly', - count => 3, - }, { - frequency => 'daily', - count => 4, - }], - }; + my $event = { + calendarIds => { + Default => JSON::true, + }, + title => "title", + description => "description", + start => "2015-11-07T09:00:00", + duration => "PT1H", + timeZone => "Europe/London", + showWithoutTime => JSON::false, + freeBusyStatus => "busy", + recurrenceRules => [ + { + frequency => 'weekly', + count => 3, + }, + { + frequency => 'daily', + count => 4, + } + ], + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_patch b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_patch index be21adf3f6..c30d02a902 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_patch +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_patch @@ -2,59 +2,67 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrence_patch - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "Create a recurring event with alert"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - calendarIds => { - Default => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2019-01-01T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "recurrenceRules" => [{ - frequency => 'monthly', - }], - "recurrenceOverrides" => { - '2019-02-01T09:00:00' => { - duration => 'PT2H', - }, - }, - alerts => { - alert1 => { - trigger => { - relativeTo => "start", - offset => "-PT5M", - }, - }, - } - } + xlog $self, "Create a recurring event with alert"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + calendarIds => { + Default => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2019-01-01T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "recurrenceRules" => [ { + frequency => 'monthly', + } ], + "recurrenceOverrides" => { + '2019-02-01T09:00:00' => { + duration => 'PT2H', + }, + }, + alerts => { + alert1 => { + trigger => { + relativeTo => "start", + offset => "-PT5M", + }, + }, } - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); + } + } + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); - xlog $self, "Patch alert in a recurrence override"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides/2019-02-01T09:00:00/alerts/alert1/trigger/offset' => '-PT10M', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog $self, "Patch alert in a recurrence override"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides/2019-02-01T09:00:00/alerts/alert1/trigger/offset' + => '-PT10M', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_until b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_until index dbbf3f963d..f84cbfdbb4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_until +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_until @@ -2,36 +2,35 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrence_until - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - "status" =>"confirmed", - calendarIds => { - $calid => JSON::true, - }, - "showWithoutTime" => JSON::false, - "timeZone" => "America/New_York", - "freeBusyStatus" =>"busy", - "start" =>"2019-01-12T00:00:00", - "useDefaultAlerts" => JSON::false, - "uid" =>"76f46024-7284-4701-b93f-d9cd812f3f43", - "title" =>"timed event with non-zero time until", - "\@type" =>"Event", - "recurrenceRules" => [{ - "frequency" =>"weekly", - "until" =>"2019-04-20T23:59:59" - }], - "description" =>"", - "duration" =>"P1D" - }; + my $event = { + "status" => "confirmed", + calendarIds => { + $calid => JSON::true, + }, + "showWithoutTime" => JSON::false, + "timeZone" => "America/New_York", + "freeBusyStatus" => "busy", + "start" => "2019-01-12T00:00:00", + "useDefaultAlerts" => JSON::false, + "uid" => "76f46024-7284-4701-b93f-d9cd812f3f43", + "title" => "timed event with non-zero time until", + "\@type" => "Event", + "recurrenceRules" => [ { + "frequency" => "weekly", + "until" => "2019-04-20T23:59:59" + } ], + "description" => "", + "duration" => "P1D" + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{recurrenceRules}[0]{until} = '2019-04-20T23:59:59'; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{recurrenceRules}[0]{until} = '2019-04-20T23:59:59'; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_untilallday b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_untilallday index 962862d15b..2ebcb08636 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_untilallday +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrence_untilallday @@ -2,35 +2,34 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrence_untilallday - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $event = { - "status" =>"confirmed", - calendarIds => { - $calid => JSON::true, - }, - "showWithoutTime" => JSON::false, # for testing - "timeZone" =>undef, - "freeBusyStatus" =>"busy", - "start" =>"2019-01-12T00:00:00", - "useDefaultAlerts" => JSON::false, - "uid" =>"76f46024-7284-4701-b93f-d9cd812f3f43", - "title" =>"allday event with non-zero time until", - "\@type" =>"Event", - "recurrenceRules" => [{ - "frequency" =>"weekly", - "until" =>"2019-04-20T23:59:59" - }], - "description" =>"", - "duration" =>"P1D" - }; + my $event = { + "status" => "confirmed", + calendarIds => { + $calid => JSON::true, + }, + "showWithoutTime" => JSON::false, # for testing + "timeZone" => undef, + "freeBusyStatus" => "busy", + "start" => "2019-01-12T00:00:00", + "useDefaultAlerts" => JSON::false, + "uid" => "76f46024-7284-4701-b93f-d9cd812f3f43", + "title" => "allday event with non-zero time until", + "\@type" => "Event", + "recurrenceRules" => [ { + "frequency" => "weekly", + "until" => "2019-04-20T23:59:59" + } ], + "description" => "", + "duration" => "P1D" + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceinstances b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceinstances index 11376c1a41..4bb45681c1 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceinstances +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceinstances @@ -2,158 +2,217 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrenceinstances - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - xlog $self, "create event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - $calid => JSON::true, - }, - uid => 'event1uid', - title => "event1", - description => "", - freeBusyStatus => "busy", - start => "2019-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'weekly', - count => 5, - }], - }, - } - }, 'R1'] - ]); - my $eventId1 = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId1); + xlog $self, "create event"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calid => JSON::true, + }, + uid => 'event1uid', + title => "event1", + description => "", + freeBusyStatus => "busy", + start => "2019-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'weekly', + count => 5, + } ], + }, + } + }, + 'R1' + ] ]); + my $eventId1 = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId1); - # This test hard-codes the ids of recurrence instances. - # This might break if we change the id scheme. + # This test hard-codes the ids of recurrence instances. + # This might break if we change the id scheme. - xlog $self, "Override a regular recurrence instance"; - my $overrideId1 = encode_eventid('event1uid','20190115T090000'); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $overrideId1 => { - title => "override1", - }, - } - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$overrideId1}); - $self->assert_deep_equals({ - '2019-01-15T09:00:00' => { - title => "override1", - }, - }, $res->[1][1]{list}[0]{recurrenceOverrides} - ); + xlog $self, "Override a regular recurrence instance"; + my $overrideId1 = encode_eventid('event1uid', '20190115T090000'); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $overrideId1 => { + title => "override1", + }, + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$overrideId1}); + $self->assert_deep_equals( + { + '2019-01-15T09:00:00' => { + title => "override1", + }, + }, + $res->[1][1]{list}[0]{recurrenceOverrides} + ); - xlog $self, "Update an existing override"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $overrideId1 => { - title => "override1_updated", - }, - } - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$overrideId1}); - $self->assert_deep_equals({ - '2019-01-15T09:00:00' => { - title => "override1_updated", - }, - }, $res->[1][1]{list}[0]{recurrenceOverrides} - ); + xlog $self, "Update an existing override"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $overrideId1 => { + title => "override1_updated", + }, + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$overrideId1}); + $self->assert_deep_equals( + { + '2019-01-15T09:00:00' => { + title => "override1_updated", + }, + }, + $res->[1][1]{list}[0]{recurrenceOverrides} + ); - xlog $self, "Revert an override into a regular recurrence"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $overrideId1 => { - title => "event1", # original title - }, - } - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$overrideId1}); - $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); + xlog $self, "Revert an override into a regular recurrence"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $overrideId1 => { + title => "event1", # original title + }, + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$overrideId1}); + $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); - xlog $self, "Set regular recurrence excluded"; - my $overrideId2 = encode_eventid('event1uid','20190108T090000'); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $overrideId2 => { - excluded => JSON::true, - } - } - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$overrideId2}); - $self->assert_deep_equals({ - '2019-01-08T09:00:00' => { + xlog $self, "Set regular recurrence excluded"; + my $overrideId2 = encode_eventid('event1uid', '20190108T090000'); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $overrideId2 => { excluded => JSON::true, + } } - }, $res->[1][1]{list}[0]{recurrenceOverrides}); + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$overrideId2}); + $self->assert_deep_equals( + { + '2019-01-08T09:00:00' => { + excluded => JSON::true, + } + }, + $res->[1][1]{list}[0]{recurrenceOverrides} + ); - xlog $self, "Reset overrides"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId1 => { - recurrenceOverrides => undef, - } - } - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId1}); - $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); - - xlog $self, "Destroy regular recurrence instance"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [$overrideId2], - }, 'R1'], - ['CalendarEvent/get', { - ids => ['event1uid'], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert_str_equals($overrideId2, $res->[0][1]{destroyed}[0]); - $self->assert_deep_equals({ - '2019-01-08T09:00:00' => { - excluded => JSON::true, + xlog $self, "Reset overrides"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId1 => { + recurrenceOverrides => undef, + } } - }, $res->[1][1]{list}[0]{recurrenceOverrides}); + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId1}); + $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); + + xlog $self, "Destroy regular recurrence instance"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + destroy => [$overrideId2], + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['event1uid'], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert_str_equals($overrideId2, $res->[0][1]{destroyed}[0]); + $self->assert_deep_equals( + { + '2019-01-08T09:00:00' => { + excluded => JSON::true, + } + }, + $res->[1][1]{list}[0]{recurrenceOverrides} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceinstances_rdate b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceinstances_rdate index 0fcd1a25ff..cb083b79de 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceinstances_rdate +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceinstances_rdate @@ -2,94 +2,120 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrenceinstances_rdate - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $calid = 'Default'; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $calid = 'Default'; - xlog $self, "create event with RDATE"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "1" => { - calendarIds => { - $calid => JSON::true, - }, - uid => 'event1uid', - title => "event1", - description => "", - freeBusyStatus => "busy", - start => "2019-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - recurrenceRules => [{ - frequency => 'weekly', - count => 5, - }], - recurrenceOverrides => { - '2019-01-10T14:00:00' => {} - }, - }, - } - }, 'R1'] - ]); - my $eventId1 = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId1); + xlog $self, "create event with RDATE"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calid => JSON::true, + }, + uid => 'event1uid', + title => "event1", + description => "", + freeBusyStatus => "busy", + start => "2019-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + recurrenceRules => [ { + frequency => 'weekly', + count => 5, + } ], + recurrenceOverrides => { + '2019-01-10T14:00:00' => {} + }, + }, + } + }, + 'R1' + ] ]); + my $eventId1 = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId1); - xlog $self, "Delete RDATE by setting it excluded"; - my $overrideId1 = encode_eventid('event1uid','20190110T140000'); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $overrideId1 => { - excluded => JSON::true, - } - } - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$overrideId1}); - $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); + xlog $self, "Delete RDATE by setting it excluded"; + my $overrideId1 = encode_eventid('event1uid', '20190110T140000'); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $overrideId1 => { + excluded => JSON::true, + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$overrideId1}); + $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); - xlog $self, "Recreate RDATE"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId1 => { - recurrenceOverrides => { - '2019-01-10T14:00:00' => {} - }, - } - } - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId1}); - $self->assert_deep_equals({ - '2019-01-10T14:00:00' => { }, - }, - $res->[1][1]{list}[0]{recurrenceOverrides} - ); + xlog $self, "Recreate RDATE"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId1 => { + recurrenceOverrides => { + '2019-01-10T14:00:00' => {} + }, + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId1}); + $self->assert_deep_equals( + { + '2019-01-10T14:00:00' => {}, + }, + $res->[1][1]{list}[0]{recurrenceOverrides} + ); - xlog $self, "Destroy RDATE"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [$overrideId1], - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['recurrenceOverrides'], - }, 'R2'], - ]); - $self->assert_str_equals($overrideId1, $res->[0][1]{destroyed}[0]); - $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); + xlog $self, "Destroy RDATE"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + destroy => [$overrideId1], + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + $self->assert_str_equals($overrideId1, $res->[0][1]{destroyed}[0]); + $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceoverrides b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceoverrides index d45572baac..3e5a80e5e4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceoverrides +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceoverrides @@ -2,80 +2,80 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrenceoverrides - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $recurrenceRules = [{ - frequency => "monthly", - count => 12, - }]; + my $recurrenceRules = [ { + frequency => "monthly", + count => 12, + } ]; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2016-01-01T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - "freeBusyStatus"=> "busy", - "locations" => { - locA => { - "name" => "location A", - }, - locB => { - "coordinates" => "geo:48.208304,16.371602", - }, - }, - "links" => { - "link1" => { - href => "http://jmap.io/spec.html#calendar-events", - title => "the spec", - rel => 'enclosure', - }, - "link2" => { - href => "https://tools.ietf.org/html/rfc5545", - rel => 'enclosure', - }, - }, - "recurrenceRules" => $recurrenceRules, - "recurrenceOverrides" => { - "2016-02-01T09:00:00" => { excluded => JSON::true }, - "2016-02-03T09:00:00" => {}, - "2016-04-01T10:00:00" => { - "description" => "don't come in without an April's joke!", - "locations/locA/name" => "location A exception", - "links/link2/title" => "RFC 5545", - }, - "2016-05-01T10:00:00" => { - "title" => "Labour Day", - }, - "2016-06-01T10:00:00" => { - freeBusyStatus => "free", - }, - "2016-07-01T09:00:00" => { - "uid" => "foo", - }, - }, - }; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2016-01-01T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + "freeBusyStatus" => "busy", + "locations" => { + locA => { + "name" => "location A", + }, + locB => { + "coordinates" => "geo:48.208304,16.371602", + }, + }, + "links" => { + "link1" => { + href => "http://jmap.io/spec.html#calendar-events", + title => "the spec", + rel => 'enclosure', + }, + "link2" => { + href => "https://tools.ietf.org/html/rfc5545", + rel => 'enclosure', + }, + }, + "recurrenceRules" => $recurrenceRules, + "recurrenceOverrides" => { + "2016-02-01T09:00:00" => { excluded => JSON::true }, + "2016-02-03T09:00:00" => {}, + "2016-04-01T10:00:00" => { + "description" => "don't come in without an April's joke!", + "locations/locA/name" => "location A exception", + "links/link2/title" => "RFC 5545", + }, + "2016-05-01T10:00:00" => { + "title" => "Labour Day", + }, + "2016-06-01T10:00:00" => { + freeBusyStatus => "free", + }, + "2016-07-01T09:00:00" => { + "uid" => "foo", + }, + }, + }; - my $ret = $self->createandget_event($event); - $event->{id} = $ret->{id}; - $event->{calendarIds} = $ret->{calendarIds}; - delete $event->{recurrenceOverrides}{"2016-07-01T09:00:00"}; # ignore patch with 'uid' - $self->assert_normalized_event_equals($event, $ret); + my $ret = $self->createandget_event($event); + $event->{id} = $ret->{id}; + $event->{calendarIds} = $ret->{calendarIds}; + delete $event->{recurrenceOverrides}{"2016-07-01T09:00:00"} + ; # ignore patch with 'uid' + $self->assert_normalized_event_equals($event, $ret); - $ret = $self->updateandget_event({ - id => $event->{id}, - calendarIds => $event->{calendarIds}, - title => "updated title", - }); - $event->{title} = "updated title"; - $self->assert_normalized_event_equals($event, $ret); + $ret = $self->updateandget_event({ + id => $event->{id}, + calendarIds => $event->{calendarIds}, + title => "updated title", + }); + $event->{title} = "updated title"; + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceoverrides_mixed_datetypes b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceoverrides_mixed_datetypes index 694ec37a8c..5009457ace 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceoverrides_mixed_datetypes +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_recurrenceoverrides_mixed_datetypes @@ -2,65 +2,68 @@ use Cassandane::Tiny; sub test_calendarevent_set_recurrenceoverrides_mixed_datetypes - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my ($id, $ical) = $self->icalfile('recurrenceoverrides-mixed-datetypes'); + my ($id, $ical) = $self->icalfile('recurrenceoverrides-mixed-datetypes'); - my $event = $self->putandget_vevent($id, $ical); - my $wantOverrides = { - "2018-05-01T00:00:00" => { - start => "2018-05-02T17:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - showWithoutTime => JSON::false, - } - }; + my $event = $self->putandget_vevent($id, $ical); + my $wantOverrides = { + "2018-05-01T00:00:00" => { + start => "2018-05-02T17:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + showWithoutTime => JSON::false, + } + }; - # Validate main event. - $self->assert_str_equals('2016-01-01T00:00:00', $event->{start}); - $self->assert_equals(JSON::true, $event->{showWithoutTime}); - $self->assert_null($event->{timeZone}); - $self->assert_str_equals('P1D', $event->{duration}); - # Validate overrides. - $self->assert_deep_equals($wantOverrides, $event->{recurrenceOverrides}); - my $eventId = $event->{id}; + # Validate main event. + $self->assert_str_equals('2016-01-01T00:00:00', $event->{start}); + $self->assert_equals(JSON::true, $event->{showWithoutTime}); + $self->assert_null($event->{timeZone}); + $self->assert_str_equals('P1D', $event->{duration}); + # Validate overrides. + $self->assert_deep_equals($wantOverrides, $event->{recurrenceOverrides}); + my $eventId = $event->{id}; - # Add recurrenceOverrides with showWithoutTime=true - # and showWithoutTime=false. - $self->assert_not_null($eventId); - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - "recurrenceOverrides/2019-09-01T00:00:00" => { - start => "2019-09-02T00:00:00", - duration => 'P2D', - }, - "recurrenceOverrides/2019-10-01T00:00:00" => { - start => "2019-10-02T15:00:00", - timeZone => "Europe/London", - duration => "PT2H", - showWithoutTime => JSON::false, - }, - }, + # Add recurrenceOverrides with showWithoutTime=true + # and showWithoutTime=false. + $self->assert_not_null($eventId); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + "recurrenceOverrides/2019-09-01T00:00:00" => { + start => "2019-09-02T00:00:00", + duration => 'P2D', }, - }, 'R1'], - ['CalendarEvent/get', { ids => [$eventId] }, 'R2'], - ]); + "recurrenceOverrides/2019-10-01T00:00:00" => { + start => "2019-10-02T15:00:00", + timeZone => "Europe/London", + duration => "PT2H", + showWithoutTime => JSON::false, + }, + }, + }, + }, + 'R1' + ], + [ 'CalendarEvent/get', { ids => [$eventId] }, 'R2' ], + ]); - $wantOverrides->{'2019-09-01T00:00:00'} = { - start => "2019-09-02T00:00:00", - duration => 'P2D', - }; - $wantOverrides->{'2019-10-01T00:00:00'} = { - start => "2019-10-02T15:00:00", - timeZone => "Europe/London", - duration => "PT2H", - showWithoutTime => JSON::false, - }; - $event = $res->[1][1]{list}[0]; - $self->assert_deep_equals($wantOverrides, $event->{recurrenceOverrides}); + $wantOverrides->{'2019-09-01T00:00:00'} = { + start => "2019-09-02T00:00:00", + duration => 'P2D', + }; + $wantOverrides->{'2019-10-01T00:00:00'} = { + start => "2019-10-02T15:00:00", + timeZone => "Europe/London", + duration => "PT2H", + showWithoutTime => JSON::false, + }; + $event = $res->[1][1]{list}[0]; + $self->assert_deep_equals($wantOverrides, $event->{recurrenceOverrides}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_reject_duplicate_uid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_reject_duplicate_uid index f51ee05930..f631820a05 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_reject_duplicate_uid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_reject_duplicate_uid @@ -2,52 +2,63 @@ use Cassandane::Tiny; sub test_calendarevent_set_reject_duplicate_uid - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventA => { - calendarIds => { - 'Default' => JSON::true, - }, - uid => '123456789', - title => 'eventA', - start => '2021-04-06T12:30:00', - }, - } - }, 'R1'], - ]); - my $eventA = $res->[0][1]{created}{eventA}{id}; - $self->assert_not_null($eventA); + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + eventA => { + calendarIds => { + 'Default' => JSON::true, + }, + uid => '123456789', + title => 'eventA', + start => '2021-04-06T12:30:00', + }, + } + }, + 'R1' + ], + ]); + my $eventA = $res->[0][1]{created}{eventA}{id}; + $self->assert_not_null($eventA); - $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendarB => { - name => 'calendarB', - }, + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + calendarB => { + name => 'calendarB', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + eventB => { + calendarIds => { + '#calendarB' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/set', { - create => { - eventB => { - calendarIds => { - '#calendarB' => JSON::true, - }, - uid => '123456789', - title => 'eventB', - start => '2021-04-06T12:30:00', - }, - } - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}{calendarB}); - $self->assert_str_equals('invalidProperties', - $res->[1][1]{notCreated}{eventB}{type}); - $self->assert_deep_equals(['uid'], - $res->[1][1]{notCreated}{eventB}{properties}); + uid => '123456789', + title => 'eventB', + start => '2021-04-06T12:30:00', + }, + } + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{calendarB}); + $self->assert_str_equals('invalidProperties', + $res->[1][1]{notCreated}{eventB}{type}); + $self->assert_deep_equals(['uid'], + $res->[1][1]{notCreated}{eventB}{properties}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_relatedto b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_relatedto index 4e630bd209..fa2b4cde10 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_relatedto +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_relatedto @@ -2,45 +2,50 @@ use Cassandane::Tiny; sub test_calendarevent_set_relatedto - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "relatedTo" => { - "uid1" => { relation => { - 'first' => JSON::true, - }}, - "uid2" => { relation => { - 'parent' => JSON::true, - }}, - "uid3" => { relation => { - 'x-unknown1' => JSON::true, - 'x-unknown2' => JSON::true - }}, - "uid4" => { relation => {} }, - }, - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "participants" => undef, - "alerts"=> undef, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "relatedTo" => { + "uid1" => { + relation => { + 'first' => JSON::true, + } + }, + "uid2" => { + relation => { + 'parent' => JSON::true, + } + }, + "uid3" => { + relation => { + 'x-unknown1' => JSON::true, + 'x-unknown2' => JSON::true + } + }, + "uid4" => { relation => {} }, + }, + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "participants" => undef, + "alerts" => undef, + }; - my $ret = $self->createandget_event($event); - $self->assert_normalized_event_equals($event, $ret); - $self->assert_num_equals(42, $event->{sequence}); + my $ret = $self->createandget_event($event); + $self->assert_normalized_event_equals($event, $ret); + $self->assert_num_equals(42, $event->{sequence}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_replace_standalone_with_destroy b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_replace_standalone_with_destroy index 16811bc201..1460f9949e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_replace_standalone_with_destroy +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_replace_standalone_with_destroy @@ -2,63 +2,74 @@ use Cassandane::Tiny; sub test_calendarevent_set_replace_standalone_with_destroy - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Create standalone instance"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => '2021-01-02T01:01:01', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-01-01T01:01:01', - recurrenceIdTimeZone => 'Europe/London', - }, + xlog "Create standalone instance"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $instanceId = $res->[0][1]{created}{instance}{id}; - $self->assert_not_null($instanceId); + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => '2021-01-02T01:01:01', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-01-01T01:01:01', + recurrenceIdTimeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + ]); + my $instanceId = $res->[0][1]{created}{instance}{id}; + $self->assert_not_null($instanceId); - xlog "Destroy standalone instance and create main event for same uid"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => '2021-01-01T01:01:01', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceRules => [{ - frequency => 'daily', - count => 5, - }], - }, + xlog "Destroy standalone instance and create main event for same uid"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, }, - destroy => [ $instanceId ], - }, 'R1'], - ['CalendarEvent/get', { - properties => [ 'recurrenceOverrides' ], - }, 'R2'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); - $self->assert_deep_equals([ $instanceId ], $res->[0][1]{destroyed}); - $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => '2021-01-01T01:01:01', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceRules => [ { + frequency => 'daily', + count => 5, + } ], + }, + }, + destroy => [$instanceId], + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + properties => ['recurrenceOverrides'], + }, + 'R2' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); + $self->assert_deep_equals([$instanceId], $res->[0][1]{destroyed}); + $self->assert_null($res->[1][1]{list}[0]{recurrenceOverrides}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_reply_partstat_changed b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_reply_partstat_changed index fe5dda039b..de803b5550 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_reply_partstat_changed +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_reply_partstat_changed @@ -2,142 +2,161 @@ use Cassandane::Tiny; sub test_calendarevent_set_reply_partstat_changed - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - xlog "Clean notifications"; - $self->{instance}->getnotify(); - - xlog "Create scheduled event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'event', - start => '2021-01-01T15:30:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceRules => [{ - frequency => 'daily', - count => 30, - }], - replyTo => { - imip => 'mailto:organizer@example.com', - }, - participants => { - cassandane => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - participationStatus => 'needs-action', - expectReply => JSON::true, - }, - }, - }, + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + xlog "Clean notifications"; + $self->{instance}->getnotify(); + + xlog "Create scheduled event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); - - xlog "Assert that no iTIP notification is sent"; - my $data = $self->{instance}->getnotify(); - my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($notif); - - xlog "Clean notifications"; - $self->{instance}->getnotify(); - - xlog "Update participationStatus"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'participants/cassandane/participationStatus' => 'accepted', - }, + '@type' => 'Event', + uid => 'event1uid', + title => 'event', + start => '2021-01-01T15:30:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceRules => [ { + frequency => 'daily', + count => 30, + } ], + replyTo => { + imip => 'mailto:organizer@example.com', }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog "Assert that iTIP notification is sent"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - - xlog "Clean notifications"; - $self->{instance}->getnotify(); - - xlog "Update title"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'updated', + participants => { + cassandane => { + roles => { + attendee => JSON::true, }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog "Assert that no iTIP notification is sent"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($notif); - - xlog "Clean notifications"; - $self->{instance}->getnotify(); - - xlog "Update participationStatus in recurrence override"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - recurrenceOverrides => { - '2021-01-02T15:30:00' => { - 'participants/cassandane/participationStatus' => 'declined', - }, - }, + sendTo => { + imip => 'mailto:cassandane@example.com', }, + participationStatus => 'needs-action', + expectReply => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog "Assert that iTIP notification is sent"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - - xlog "Clean notifications"; - $self->{instance}->getnotify(); - - xlog "Update title in recurrence override"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides/2021-01-03T15:30:00' => { - title => 'updatedOverride', - }, - }, + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); + + xlog "Assert that no iTIP notification is sent"; + my $data = $self->{instance}->getnotify(); + my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_null($notif); + + xlog "Clean notifications"; + $self->{instance}->getnotify(); + + xlog "Update participationStatus"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'participants/cassandane/participationStatus' => 'accepted', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog "Assert that iTIP notification is sent"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + + xlog "Clean notifications"; + $self->{instance}->getnotify(); + + xlog "Update title"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'updated', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog "Assert that no iTIP notification is sent"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_null($notif); + + xlog "Clean notifications"; + $self->{instance}->getnotify(); + + xlog "Update participationStatus in recurrence override"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + recurrenceOverrides => { + '2021-01-02T15:30:00' => { + 'participants/cassandane/participationStatus' => 'declined', + }, + }, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog "Assert that iTIP notification is sent"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + + xlog "Clean notifications"; + $self->{instance}->getnotify(); + + xlog "Update title in recurrence override"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides/2021-01-03T15:30:00' => { + title => 'updatedOverride', }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog "Assert that no iTIP notification is sent"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($notif); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog "Assert that no iTIP notification is sent"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_null($notif); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_replyto b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_replyto index a4936ed139..920f4b4b39 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_replyto +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_replyto @@ -2,129 +2,156 @@ use Cassandane::Tiny; sub test_calendarevent_set_replyto - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventReplyTo => { - calendarIds => { - 'Default' => JSON::true, - }, - start => '2022-01-28T09:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - replyTo => { - imip => 'mailto:myreplyto@example.com', - }, - participants => { - someone => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + eventReplyTo => { + calendarIds => { + 'Default' => JSON::true, + }, + start => '2022-01-28T09:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + replyTo => { + imip => 'mailto:myreplyto@example.com', + }, + participants => { + someone => { + roles => { + attendee => JSON::true, }, - eventNoReplyTo => { - calendarIds => { - 'Default' => JSON::true, - }, - start => '2022-01-28T10:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - participants => { - someone => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - }, - }, + sendTo => { + imip => 'mailto:someone@example.com', }, - eventReplyToNoParticipants => { - calendarIds => { - 'Default' => JSON::true, - }, - start => '2022-01-28T11:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, + }, + }, + }, + eventNoReplyTo => { + calendarIds => { + 'Default' => JSON::true, + }, + start => '2022-01-28T10:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + participants => { + someone => { + roles => { + attendee => JSON::true, }, - eventNoScheduling => { - calendarIds => { - 'Default' => JSON::true, - }, - start => '2022-01-28T12:00:00', - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', + sendTo => { + imip => 'mailto:someone@example.com', }, + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#eventReplyTo'], - properties => ['replyTo'], - }, 'R2'], - ['CalendarEvent/get', { - ids => ['#eventNoReplyTo'], - properties => ['replyTo'], - }, 'R3'], - ]); + }, + eventReplyToNoParticipants => { + calendarIds => { + 'Default' => JSON::true, + }, + start => '2022-01-28T11:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + eventNoScheduling => { + calendarIds => { + 'Default' => JSON::true, + }, + start => '2022-01-28T12:00:00', + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#eventReplyTo'], + properties => ['replyTo'], + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => ['#eventNoReplyTo'], + properties => ['replyTo'], + }, + 'R3' + ], + ]); - xlog "Preserve client-set replyTo"; - $self->assert_deep_equals({ - imip => 'mailto:myreplyto@example.com', - }, $res->[1][1]{list}[0]{replyTo}); + xlog "Preserve client-set replyTo"; + $self->assert_deep_equals( + { + imip => 'mailto:myreplyto@example.com', + }, + $res->[1][1]{list}[0]{replyTo} + ); - xlog "Use server-set replyTo if not set by client"; - $self->assert_deep_equals({ - imip => 'mailto:cassandane@example.com', - }, $res->[0][1]{created}{eventNoReplyTo}{replyTo}); - $self->assert_deep_equals({ - imip => 'mailto:cassandane@example.com', - }, $res->[2][1]{list}[0]{replyTo}); + xlog "Use server-set replyTo if not set by client"; + $self->assert_deep_equals( + { + imip => 'mailto:cassandane@example.com', + }, + $res->[0][1]{created}{eventNoReplyTo}{replyTo} + ); + $self->assert_deep_equals( + { + imip => 'mailto:cassandane@example.com', + }, + $res->[2][1]{list}[0]{replyTo} + ); - xlog "Reject event with replyTo but no participants"; - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notCreated}{eventReplyToNoParticipants}{type}); - $self->assert_deep_equals(['replyTo', 'participants'], - $res->[0][1]{notCreated}{eventReplyToNoParticipants}{properties}); + xlog "Reject event with replyTo but no participants"; + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{eventReplyToNoParticipants}{type}); + $self->assert_deep_equals([ 'replyTo', 'participants' ], + $res->[0][1]{notCreated}{eventReplyToNoParticipants}{properties}); - xlog "Use server-set replyTo when participants added in update"; - my $eventId = $res->[0][1]{created}{eventNoScheduling}{id}; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - participants => { - someone => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - }, - }, + xlog "Use server-set replyTo when participants added in update"; + my $eventId = $res->[0][1]{created}{eventNoScheduling}{id}; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + participants => { + someone => { + roles => { + attendee => JSON::true, + }, + sendTo => { + imip => 'mailto:someone@example.com', }, + }, }, - }, 'R1'], - ]); - $self->assert_deep_equals({ - imip => 'mailto:cassandane@example.com', - }, $res->[0][1]{updated}{$eventId}{replyTo}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals( + { + imip => 'mailto:cassandane@example.com', + }, + $res->[0][1]{updated}{$eventId}{replyTo} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_rsvpsequence b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_rsvpsequence index c1acd4a164..4223df9dc0 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_rsvpsequence +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_rsvpsequence @@ -2,68 +2,91 @@ use Cassandane::Tiny; sub test_calendarevent_set_rsvpsequence - :min_version_3_1 :max_version_3_4 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : max_version_3_4 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my ($id, $ical) = $self->icalfile('rsvpsequence'); + my ($id, $ical) = $self->icalfile('rsvpsequence'); - my $event = $self->putandget_vevent($id, $ical); - $self->assert_not_null($event); - $self->assert_num_equals(1, $event->{sequence}); + my $event = $self->putandget_vevent($id, $ical); + $self->assert_not_null($event); + $self->assert_num_equals(1, $event->{sequence}); - my $eventId = $event->{id}; + my $eventId = $event->{id}; - # Update a partstat doesn't bump sequence. - my $res = $jmap->CallMethods([ - ['CalendarEvent/set',{ - update => { - $eventId => { - ('participants/me/participationStatus') => 'accepted', - } - } - }, "R1"], - ['CalendarEvent/get',{ - ids => [$eventId], - properties => ['sequence'], - }, "R2"], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_num_equals(1, $res->[1][1]{list}[0]->{sequence}); + # Update a partstat doesn't bump sequence. + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + ('participants/me/participationStatus') => 'accepted', + } + } + }, + "R1" + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['sequence'], + }, + "R2" + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_num_equals(1, $res->[1][1]{list}[0]->{sequence}); - # Neither does setting a per-user property. - $res = $jmap->CallMethods([ - ['CalendarEvent/set',{ - update => { - $eventId => { - color => 'red', - 'alerts/alert1/trigger/offset' => '-PT10M', - }, - } - }, "R1"], - ['CalendarEvent/get',{ - ids => [$eventId], - properties => ['sequence'], - }, "R2"], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_num_equals(1, $res->[1][1]{list}[0]->{sequence}); + # Neither does setting a per-user property. + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + color => 'red', + 'alerts/alert1/trigger/offset' => '-PT10M', + }, + } + }, + "R1" + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['sequence'], + }, + "R2" + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_num_equals(1, $res->[1][1]{list}[0]->{sequence}); - # But setting a property shared by all users does! - $res = $jmap->CallMethods([ - ['CalendarEvent/set',{ - update => { - $eventId => { - title => 'foo', - }, - } - }, "R1"], - ['CalendarEvent/get',{ - ids => [$eventId], - properties => ['sequence'], - }, "R2"], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_num_not_equals(1, $res->[1][1]{list}[0]->{sequence}); + # But setting a property shared by all users does! + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'foo', + }, + } + }, + "R1" + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['sequence'], + }, + "R2" + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_num_not_equals(1, $res->[1][1]{list}[0]->{sequence}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_cancel b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_cancel index 0d1cd8e5b6..cfa1d37679 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_cancel +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_cancel @@ -2,83 +2,101 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedule_cancel - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", color => "coral", sortOrder => 1, isVisible => \1 - }}}, "R1"] - ]); - my $calid = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 1, + isVisible => \1 + } + } + }, + "R1" + ] ]); + my $calid = $res->[0][1]{created}{"1"}{id}; - xlog $self, "send invitation as organizer"; - $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - $calid => JSON::true, - }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT15M", - "replyTo" => { - imip => "mailto:cassandane\@example.com", - }, - "participants" => { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - "att" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:bugs@example.com', - }, - }, - }, - } - }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($id); + xlog $self, "send invitation as organizer"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT15M", + "replyTo" => { + imip => "mailto:cassandane\@example.com", + }, + "participants" => { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + "att" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:bugs@example.com', + }, + }, + }, + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($id); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "cancel event as organizer"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $id => { - status => 'cancelled', - }, - }, - }, 'R1'], - ]); + xlog $self, "cancel event as organizer"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $id => { + status => 'cancelled', + }, + }, + }, + 'R1' + ], + ]); - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; - $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:CANCEL"); + $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:CANCEL"); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_destroy b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_destroy index 023d9397ad..2e8ff87321 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_destroy +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_destroy @@ -2,75 +2,90 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedule_destroy - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", color => "coral", sortOrder => 1, isVisible => \1 - }}}, "R1"] - ]); - my $calid = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 1, + isVisible => \1 + } + } + }, + "R1" + ] ]); + my $calid = $res->[0][1]{created}{"1"}{id}; - xlog $self, "send invitation as organizer"; - $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - $calid => JSON::true, - }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT15M", - "replyTo" => { - imip => "mailto:cassandane\@example.com", - }, - "participants" => { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - "att" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:bugs@example.com', - }, - }, - }, - } - }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($id); + xlog $self, "send invitation as organizer"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT15M", + "replyTo" => { + imip => "mailto:cassandane\@example.com", + }, + "participants" => { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + "att" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:bugs@example.com', + }, + }, + }, + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($id); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "cancel event as organizer"; - $res = $jmap->CallMethods([['CalendarEvent/set', { destroy => [$id]}, "R1"]]); + xlog $self, "cancel event as organizer"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/set', { destroy => [$id] }, "R1" ] ]); - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; - $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:CANCEL"); + $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:CANCEL"); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_omit b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_omit index 8f62fce247..a7aba6403b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_omit +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_omit @@ -2,57 +2,66 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedule_omit - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "create event"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { + xlog $self, "create event"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { "1" => { - calendarIds => { - Default => JSON::true, + calendarIds => { + Default => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT1H", + "replyTo" => { imip => "mailto:bugs\@example.com" }, + "participants" => { + "org" => { + "name" => "Bugs Bunny", + "email" => "bugs\@example.com", + roles => { + 'owner' => JSON::true, + }, }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT1H", - "replyTo" => { imip => "mailto:bugs\@example.com" }, - "participants" => { - "org" => { - "name" => "Bugs Bunny", - "email" => "bugs\@example.com", - roles => { - 'owner' => JSON::true, - }, - }, - "att" => { - "name" => "Cassandane", - "email" => "cassandane\@example.com", - roles => { - 'attendee' => JSON::true, - }, - }, + "att" => { + "name" => "Cassandane", + "email" => "cassandane\@example.com", + roles => { + 'attendee' => JSON::true, + }, }, + }, } - }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - # delete event as attendee without setting any partstat. - $res = $jmap->CallMethods([['CalendarEvent/set', { - destroy => [$id], - }, "R1"]]); + # delete event as attendee without setting any partstat. + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + destroy => [$id], + }, + "R1" + ] ]); - # assert no notification is sent. - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($imip); + # assert no notification is sent. + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_null($imip); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_reply b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_reply index ea8f890f84..7daa4ecf10 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_reply +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_reply @@ -2,72 +2,83 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedule_reply - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $participants = { - "org" => { - "name" => "Bugs Bunny", - sendTo => { - imip => 'mailto:bugs@example.com', - }, - roles => { - 'owner' => JSON::true, - }, - }, - "att" => { - "name" => "Cassandane", - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - roles => { - 'attendee' => JSON::true, - }, - }, - }; + my $participants = { + "org" => { + "name" => "Bugs Bunny", + sendTo => { + imip => 'mailto:bugs@example.com', + }, + roles => { + 'owner' => JSON::true, + }, + }, + "att" => { + "name" => "Cassandane", + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + roles => { + 'attendee' => JSON::true, + }, + }, + }; - xlog $self, "create event"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { + xlog $self, "create event"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { "1" => { - calendarIds => { - Default => JSON::true, - }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT1H", - "replyTo" => { imip => "mailto:bugs\@example.com" }, - "participants" => $participants, + calendarIds => { + Default => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT1H", + "replyTo" => { imip => "mailto:bugs\@example.com" }, + "participants" => $participants, } - }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "send reply as attendee to organizer"; - $participants->{att}->{participationStatus} = "tentative"; - $res = $jmap->CallMethods([['CalendarEvent/set', { update => { + xlog $self, "send reply as attendee to organizer"; + $participants->{att}->{participationStatus} = "tentative"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { $id => { - replyTo => { imip => "mailto:bugs\@example.com" }, - participants => $participants, - } - }}, "R1"]]); + replyTo => { imip => "mailto:bugs\@example.com" }, + participants => $participants, + } + } + }, + "R1" + ] ]); - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; - $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REPLY"); + $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REPLY"); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_reply_custom_tz b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_reply_custom_tz index 13ef733be3..c437178b41 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_reply_custom_tz +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_reply_custom_tz @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedule_reply_custom_tz - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $uid = "xxaaasdfasfhialskdjflaksjfdalskdfja"; - my $ical = <putandget_vevent($uid, $ical); - my $id = $event->{id}; + my $event = $self->putandget_vevent($uid, $ical); + my $id = $event->{id}; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "send reply as attendee to organizer"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { - sendSchedulingMessages => JSON::true, - update => { - $id => { - 'participants/att/participationStatus' => "tentative", - } + xlog $self, "send reply as attendee to organizer"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + sendSchedulingMessages => JSON::true, + update => { + $id => { + 'participants/att/participationStatus' => "tentative", } - }, "R1"]]); + } + }, + "R1" + ] ]); - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; + my $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; - $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REPLY"); + $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REPLY"); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_request b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_request index 716ab12cd1..0e8098cbd1 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_request +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_request @@ -2,63 +2,68 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedule_request - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $participants = { - "org" => { - "name" => "Cassandane", - roles => { - 'owner' => JSON::true, - }, - sendTo => { - imip => 'cassandane@example.com', - }, - }, - "att" => { - "name" => "Bugs Bunny", - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'bugs@example.com', - }, - }, - }; + my $participants = { + "org" => { + "name" => "Cassandane", + roles => { + 'owner' => JSON::true, + }, + sendTo => { + imip => 'cassandane@example.com', + }, + }, + "att" => { + "name" => "Bugs Bunny", + roles => { + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'bugs@example.com', + }, + }, + }; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "send invitation as organizer to attendee"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - Default => JSON::true, - }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT1H", - "replyTo" => { imip => "mailto:cassandane\@example.com"}, - "participants" => $participants, - } - }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "send invitation as organizer to attendee"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + Default => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT1H", + "replyTo" => { imip => "mailto:cassandane\@example.com" }, + "participants" => $participants, + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; - $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert_str_equals("bugs\@example.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REQUEST"); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_request_add_participant b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_request_add_participant index ff09d8ad64..cb5939d418 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_request_add_participant +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedule_request_add_participant @@ -2,94 +2,105 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedule_request_add_participant - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $participants = { - org => { - name => "Cassandane", - roles => { - attendee => JSON::true, - owner => JSON::true, - }, - sendTo => { - imip => 'cassandane@example.com', - }, - }, - att => { - name => "Bugs Bunny", - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'bugs@looneytunes.com', - }, - }, - }; + my $participants = { + org => { + name => "Cassandane", + roles => { + attendee => JSON::true, + owner => JSON::true, + }, + sendTo => { + imip => 'cassandane@example.com', + }, + }, + att => { + name => "Bugs Bunny", + roles => { + attendee => JSON::true, + }, + sendTo => { + imip => 'bugs@looneytunes.com', + }, + }, + }; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "send invitation as organizer to attendee"; - my $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - calendarIds => { - Default => JSON::true, - }, - "title" => "foo", - "description" => "foo's description", - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::false, - "start" => "2015-10-06T16:45:00", - "timeZone" => "Australia/Melbourne", - "duration" => "PT1H", - "replyTo" => { imip => "mailto:cassandane\@example.com"}, - "participants" => $participants, - } - }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "send invitation as organizer to attendee"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + calendarIds => { + Default => JSON::true, + }, + "title" => "foo", + "description" => "foo's description", + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::false, + "start" => "2015-10-06T16:45:00", + "timeZone" => "Australia/Melbourne", + "duration" => "PT1H", + "replyTo" => { imip => "mailto:cassandane\@example.com" }, + "participants" => $participants, + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; - $self->assert_str_equals("bugs\@looneytunes.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert_str_equals("bugs\@looneytunes.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REQUEST"); - xlog $self, "add an attendee"; - $res = $jmap->CallMethods([['CalendarEvent/set', { update => { + xlog $self, "add an attendee"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { $id => { - 'participants/att2' => { - '@type' => "Participant", - email => "rr@looneytunes.com", - expectReply => JSON::true, - kind => "individual", - participationStatus => "needs-action", - name => "Road Runner", - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:rr@looneytunes.com', - } + 'participants/att2' => { + '@type' => "Participant", + email => "rr@looneytunes.com", + expectReply => JSON::true, + kind => "individual", + participationStatus => "needs-action", + name => "Road Runner", + roles => { + attendee => JSON::true, + }, + sendTo => { + imip => 'mailto:rr@looneytunes.com', } - } - }}, "R1"]]); + } + } + } + }, + "R1" + ] ]); - $data = $self->{instance}->getnotify(); - ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); + $data = $self->{instance}->getnotify(); + ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); - $payload = decode_json($imip->{MESSAGE}); - $ical = $payload->{ical}; + $payload = decode_json($imip->{MESSAGE}); + $ical = $payload->{ical}; - $self->assert_str_equals("rr\@looneytunes.com", $payload->{recipient}); - $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert_str_equals("rr\@looneytunes.com", $payload->{recipient}); + $self->assert($ical =~ "METHOD:REQUEST"); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedulestatus b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedulestatus index e751f41dfc..3f6a08d635 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedulestatus +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedulestatus @@ -2,45 +2,52 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedulestatus - :min_version_3_4 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_4 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - title => 'test', - replyTo => { - imip => 'mailto:orga@local', - }, - participants => { - part1 => { - '@type' => 'Participant', - sendTo => { - imip => 'mailto:part1@local', - }, - roles => { - attendee => JSON::true, - }, - scheduleStatus => ['2.0', '2.4'], - }, - }, - start => '2021-01-01T01:00:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + title => 'test', + replyTo => { + imip => 'mailto:orga@local', + }, + participants => { + part1 => { + '@type' => 'Participant', + sendTo => { + imip => 'mailto:part1@local', + }, + roles => { + attendee => JSON::true, }, + scheduleStatus => [ '2.0', '2.4' ], + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event1'], - }, 'R2'], - ]); - $self->assert_deep_equals(['2.0', '2.4'], - $res->[1][1]{list}[0]{participants}{part1}{scheduleStatus}); + start => '2021-01-01T01:00:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event1'], + }, + 'R2' + ], + ]); + $self->assert_deep_equals([ '2.0', '2.4' ], + $res->[1][1]{list}[0]{participants}{part1}{scheduleStatus}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedulingmessages b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedulingmessages index 9423fb4ef1..205667c172 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedulingmessages +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_schedulingmessages @@ -2,84 +2,91 @@ use Cassandane::Tiny; sub test_calendarevent_set_schedulingmessages - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uidlocal', - title => "event1", - start => "2020-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - 'owner' => JSON::true, - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, - attendee1 => { - roles => { - 'attendee' => JSON::true, - }, - sendTo => { - imip => 'mailto:attendee1@example.com', - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uidlocal', + title => "event1", + start => "2020-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + 'owner' => JSON::true, + 'attendee' => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + attendee1 => { + roles => { + 'attendee' => JSON::true, }, + sendTo => { + imip => 'mailto:attendee1@example.com', + }, + }, }, - sendSchedulingMessages => JSON::false, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); + }, + }, + sendSchedulingMessages => JSON::false, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($imip); + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_null($imip); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => "updatedEvent1", - }, - }, - sendSchedulingMessages => JSON::true, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => "updatedEvent1", + }, + }, + sendSchedulingMessages => JSON::true, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - $data = $self->{instance}->getnotify(); - ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); + $data = $self->{instance}->getnotify(); + ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); - my $payload = decode_json($imip->{MESSAGE}); - my $ical = $payload->{ical}; + my $payload = decode_json($imip->{MESSAGE}); + my $ical = $payload->{ical}; - $self->assert_str_equals('attendee1@example.com', $payload->{recipient}); - $self->assert($ical =~ "METHOD:REQUEST"); + $self->assert_str_equals('attendee1@example.com', $payload->{recipient}); + $self->assert($ical =~ "METHOD:REQUEST"); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_sentby b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_sentby index 5b77c60204..0a1afe199b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_sentby +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_sentby @@ -2,31 +2,38 @@ use Cassandane::Tiny; sub test_calendarevent_set_sentby - :needs_component_httpd :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_httpd : needs_component_jmap : min_version_3_5 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uidlocal', - title => "event1", - start => "2020-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - sentBy => 'sender@example.net', - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event1', '#event2'], - properties => ['sentBy'], - }, 'R3'], - ]); - $self->assert_str_equals('sender@example.net', $res->[1][1]{list}[0]{sentBy}); + uid => 'event1uidlocal', + title => "event1", + start => "2020-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + sentBy => 'sender@example.net', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [ '#event1', '#event2' ], + properties => ['sentBy'], + }, + 'R3' + ], + ]); + $self->assert_str_equals('sender@example.net', $res->[1][1]{list}[0]{sentBy}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_shared b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_shared index 446ff94c11..74c62eac60 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_shared @@ -2,227 +2,293 @@ use Cassandane::Tiny; sub test_calendarevent_set_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - my ($maj, $min) = Cassandane::Instance->get_version(); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - - xlog $self, "create calendar"; - my $CalendarId1 = $mantalk->NewCalendar({name => 'Manifold Calendar'}); - $self->assert_not_null($CalendarId1); - - xlog $self, "share $CalendarId1 read-only to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId1", "cassandane" => 'lr') or die; - - my $event = { - calendarIds => { + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + my ($maj, $min) = Cassandane::Instance->get_version(); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + + xlog $self, "create calendar"; + my $CalendarId1 = $mantalk->NewCalendar({ name => 'Manifold Calendar' }); + $self->assert_not_null($CalendarId1); + + xlog $self, "share $CalendarId1 read-only to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId1", + "cassandane" => 'lr') + or die; + + my $event = { + calendarIds => { + $CalendarId1 => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "participants" => undef, + "alerts" => { + 'a465d37a-0041-4119-a1e0-0177aabcdf4a' => { + '@type' => 'Alert', + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", + }, + action => "email" + } + } + }; + + my $updatedEvent = { + calendarIds => { + $CalendarId1 => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo2", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "participants" => undef, + "alerts" => { + 'a465d37a-0041-4119-a1e0-0177aabcdf4a' => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", + }, + action => "email" + } + } + }; + + xlog $self, "create event (should fail)"; + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => 'manifold', + create => { "1" => $event } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{notCreated}{1}); + + xlog $self, "share $CalendarId1 read-writable to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId1", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "create event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => 'manifold', + create => { "1" => $event } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $id = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get calendar event $id"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'manifold', + ids => [$id] + }, + "R1" + ] ]); + my $ret = $res->[0][1]{list}[0]; + $self->assert_normalized_event_equals($event, $ret); + + xlog $self, "update event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => 'manifold', + update => { + $id => { + calendarIds => { $CalendarId1 => JSON::true, + }, + "title" => "foo2", }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "participants" => undef, - "alerts" => { - 'a465d37a-0041-4119-a1e0-0177aabcdf4a' => { - '@type' => 'Alert', - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - action => "email" - } - } - }; + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{updated}); - my $updatedEvent = { - calendarIds => { + xlog $self, "get calendar event $id"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + accountId => 'manifold', + ids => [$id] + }, + "R1" + ] ]); + $ret = $res->[0][1]{list}[0]; + $self->assert_normalized_event_equals($updatedEvent, $ret); + + xlog $self, "share $CalendarId1 read-only to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId1", + "cassandane" => 'lr') + or die; + + xlog $self, "update event (should fail)"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => 'manifold', + update => { + $id => { + calendarIds => { $CalendarId1 => JSON::true, + }, + "title" => "1(updated)", }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo2", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "participants" => undef, - "alerts" => { - 'a465d37a-0041-4119-a1e0-0177aabcdf4a' => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - action => "email" - } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{notUpdated}{$id}); + + xlog $self, "share calendar home read-writable to user"; + $admintalk->setacl("user.manifold.#calendars", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "create another calendar"; + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + accountId => 'manifold', + create => { + "2" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1 } - }; - - xlog $self, "create event (should fail)"; - my $res = $jmap->CallMethods([['CalendarEvent/set',{ - accountId => 'manifold', - create => {"1" => $event}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{notCreated}{1}); - - xlog $self, "share $CalendarId1 read-writable to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId1", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "create event"; - $res = $jmap->CallMethods([['CalendarEvent/set',{ - accountId => 'manifold', - create => {"1" => $event}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - my $id = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get calendar event $id"; - $res = $jmap->CallMethods([['CalendarEvent/get', { - accountId => 'manifold', - ids => [$id]}, - "R1"]]); - my $ret = $res->[0][1]{list}[0]; - $self->assert_normalized_event_equals($event, $ret); - - xlog $self, "update event"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - accountId => 'manifold', - update => { - $id => { - calendarIds => { - $CalendarId1 => JSON::true, - }, - "title" => "foo2", - }, - }}, "R1"]]); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "get calendar event $id"; - $res = $jmap->CallMethods([['CalendarEvent/get', { - accountId => 'manifold', - ids => [$id]}, - "R1"]]); - $ret = $res->[0][1]{list}[0]; - $self->assert_normalized_event_equals($updatedEvent, $ret); - - xlog $self, "share $CalendarId1 read-only to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId1", "cassandane" => 'lr') or die; - - xlog $self, "update event (should fail)"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - accountId => 'manifold', - update => { - $id => { - calendarIds => { - $CalendarId1 => JSON::true, - }, - "title" => "1(updated)", - }, - }}, "R1"]]); - $self->assert(exists $res->[0][1]{notUpdated}{$id}); - - xlog $self, "share calendar home read-writable to user"; - $admintalk->setacl("user.manifold.#calendars", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "create another calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - accountId => 'manifold', - create => { "2" => { - name => "foo", - color => "coral", - sortOrder => 2, - isVisible => \1 - }}}, "R1"] - ]); - my $CalendarId2 = $res->[0][1]{created}{"2"}{id}; - $self->assert_not_null($CalendarId2); - - xlog $self, "share $CalendarId1 read-writable to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId1", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "share $CalendarId2 read-only to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId2", "cassandane" => 'lr') or die; - - xlog $self, "move event (should fail)"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - accountId => 'manifold', - update => { - $id => { - calendarIds => { - $CalendarId2 => JSON::true, - }, - "title" => "1(updated)", - }, - }}, "R1"]]); - $self->assert(exists $res->[0][1]{notUpdated}{$id}); - - xlog $self, "share $CalendarId2 read-writable to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId2", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "move event"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - accountId => 'manifold', - update => { - $id => { - calendarIds => { - $CalendarId2 => JSON::true, - }, - "title" => "1(updated)", - }, - }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - xlog $self, "share $CalendarId2 read-only to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId2", "cassandane" => 'lr') or die; - - xlog $self, "destroy event (should fail)"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - accountId => 'manifold', - destroy => [ $id ], - }, "R1"]]); - $self->assert(exists $res->[0][1]{notDestroyed}{$id}); - - xlog $self, "share $CalendarId2 read-writable to user"; - $admintalk->setacl("user.manifold.#calendars.$CalendarId2", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "destroy event"; - $res = $jmap->CallMethods([['CalendarEvent/set', { - accountId => 'manifold', - destroy => [ $id ], - }, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + } + }, + "R1" + ] ]); + my $CalendarId2 = $res->[0][1]{created}{"2"}{id}; + $self->assert_not_null($CalendarId2); + + xlog $self, "share $CalendarId1 read-writable to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId1", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "share $CalendarId2 read-only to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId2", + "cassandane" => 'lr') + or die; + + xlog $self, "move event (should fail)"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => 'manifold', + update => { + $id => { + calendarIds => { + $CalendarId2 => JSON::true, + }, + "title" => "1(updated)", + }, + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{notUpdated}{$id}); + + xlog $self, "share $CalendarId2 read-writable to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId2", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "move event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => 'manifold', + update => { + $id => { + calendarIds => { + $CalendarId2 => JSON::true, + }, + "title" => "1(updated)", + }, + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + xlog $self, "share $CalendarId2 read-only to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId2", + "cassandane" => 'lr') + or die; + + xlog $self, "destroy event (should fail)"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => 'manifold', + destroy => [$id], + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{notDestroyed}{$id}); + + xlog $self, "share $CalendarId2 read-writable to user"; + $admintalk->setacl("user.manifold.#calendars.$CalendarId2", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "destroy event"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + accountId => 'manifold', + destroy => [$id], + }, + "R1" + ] ]); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_simple b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_simple index 09c9d2a3b9..7abf5f2f97 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_simple +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_simple @@ -2,35 +2,34 @@ use Cassandane::Tiny; sub test_calendarevent_set_simple - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "priority" => 9, - "locale" => "en", - "color" => "turquoise", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "privacy" => "secret", - "participants" => undef, - "alerts"=> undef, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "priority" => 9, + "locale" => "en", + "color" => "turquoise", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "privacy" => "secret", + "participants" => undef, + "alerts" => undef, + }; - my $ret = $self->createandget_event($event); - $self->assert_normalized_event_equals($event, $ret); - $self->assert_num_equals(42, $event->{sequence}); + my $ret = $self->createandget_event($event); + $self->assert_normalized_event_equals($event, $ret); + $self->assert_num_equals(42, $event->{sequence}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instance_floatingtz b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instance_floatingtz index 4c1cdb319c..cc3841de4f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instance_floatingtz +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instance_floatingtz @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_calendarevent_set_standalone_instance_floatingtz - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $ical = <<'EOF'; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN @@ -27,40 +26,48 @@ UID:889i-uid1@example.com END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => [ - 'recurrenceId', - 'recurrenceIdTimeZone', - ], - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{list}[0]{recurrenceId}); - $self->assert_null($res->[0][1]{list}[0]{recurrenceIdTimeZone}); - my $eventId = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'recurrenceId', 'recurrenceIdTimeZone', ], + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{list}[0]{recurrenceId}); + $self->assert_null($res->[0][1]{list}[0]{recurrenceIdTimeZone}); + my $eventId = $res->[0][1]{list}[0]{id}; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'xxx', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'xxx', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => [ - 'recurrenceId', - 'recurrenceIdTimeZone'], - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{list}[0]{recurrenceId}); - $self->assert_null($res->[0][1]{list}[0]{recurrenceIdTimeZone}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{list}[0]{recurrenceId}); + $self->assert_null($res->[0][1]{list}[0]{recurrenceIdTimeZone}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_create b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_create index c7bc5f1e9e..62b45714ce 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_create +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_create @@ -2,161 +2,198 @@ use Cassandane::Tiny; sub test_calendarevent_set_standalone_instances_create - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Get event state"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => [], - }, 'R2'], - ]); - my $state = $res->[0][1]{state}; + xlog "Get event state"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [], + }, + 'R2' + ], + ]); + my $state = $res->[0][1]{state}; - xlog "Create standalone instance"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-01-01T01:01:01', - recurrenceIdTimeZone => 'Europe/London', - }, + xlog "Create standalone instance"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#instance1'], - properties => ['start', 'timeZone', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R2'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R3'], - ]); - my $instance1Id = $res->[0][1]{created}{instance1}{id}; - $self->assert_not_null($instance1Id); - my $xhref1 = $res->[0][1]{created}{instance1}{'x-href'}; - $self->assert_not_null($xhref1); - $self->assert_str_equals('2021-01-01T11:11:11', - $res->[1][1]{list}[0]{start}); - $self->assert_str_equals('Europe/Berlin', - $res->[1][1]{list}[0]{timeZone}); - $self->assert_str_equals('2021-01-01T01:01:01', - $res->[1][1]{list}[0]{recurrenceId}); - $self->assert_str_equals('Europe/London', - $res->[1][1]{list}[0]{recurrenceIdTimeZone}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[1][1]{state}); - $self->assert_str_not_equals($state, $res->[2][1]{newState}); - $self->assert_deep_equals([$instance1Id], $res->[2][1]{created}); - $self->assert_deep_equals([], $res->[2][1]{updated}); - $self->assert_deep_equals([], $res->[2][1]{destroyed}); - $state = $res->[2][1]{newState}; + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-01-01T01:01:01', + recurrenceIdTimeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#instance1'], + properties => + [ 'start', 'timeZone', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R2' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R3' + ], + ]); + my $instance1Id = $res->[0][1]{created}{instance1}{id}; + $self->assert_not_null($instance1Id); + my $xhref1 = $res->[0][1]{created}{instance1}{'x-href'}; + $self->assert_not_null($xhref1); + $self->assert_str_equals('2021-01-01T11:11:11', $res->[1][1]{list}[0]{start}); + $self->assert_str_equals('Europe/Berlin', $res->[1][1]{list}[0]{timeZone}); + $self->assert_str_equals('2021-01-01T01:01:01', + $res->[1][1]{list}[0]{recurrenceId}); + $self->assert_str_equals('Europe/London', + $res->[1][1]{list}[0]{recurrenceIdTimeZone}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[1][1]{state}); + $self->assert_str_not_equals($state, $res->[2][1]{newState}); + $self->assert_deep_equals([$instance1Id], $res->[2][1]{created}); + $self->assert_deep_equals([], $res->[2][1]{updated}); + $self->assert_deep_equals([], $res->[2][1]{destroyed}); + $state = $res->[2][1]{newState}; - xlog "Can't create a new standalone instance with same recurrence id"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance2 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance2', - start => '2021-02-02T22:22:22', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-01-01T01:01:01', - recurrenceIdTimeZone => 'Europe/London', - }, + xlog "Can't create a new standalone instance with same recurrence id"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance2 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R2'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notCreated}{instance2}{type}); - $self->assert_deep_equals(['uid', 'recurrenceId'], - $res->[0][1]{notCreated}{instance2}{properties}); + '@type' => 'Event', + uid => 'event1uid', + title => 'instance2', + start => '2021-02-02T22:22:22', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-01-01T01:01:01', + recurrenceIdTimeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R2' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{instance2}{type}); + $self->assert_deep_equals([ 'uid', 'recurrenceId' ], + $res->[0][1]{notCreated}{instance2}{properties}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_str_equals($state, $res->[1][1]{newState}); - $self->assert_deep_equals([], $res->[1][1]{created}); - $self->assert_deep_equals([], $res->[1][1]{updated}); - $self->assert_deep_equals([], $res->[1][1]{destroyed}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_str_equals($state, $res->[1][1]{newState}); + $self->assert_deep_equals([], $res->[1][1]{created}); + $self->assert_deep_equals([], $res->[1][1]{updated}); + $self->assert_deep_equals([], $res->[1][1]{destroyed}); - xlog "Create standalone instance with same uid but different recurrence id"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance2 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance2', - start => '2021-02-02T02:02:02', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-02-02T02:02:02', - recurrenceIdTimeZone => 'Europe/London', - }, + xlog "Create standalone instance with same uid but different recurrence id"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance2 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#instance2'], - properties => ['start', 'timeZone', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R2'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R3'], - ]); - my $instance2Id = $res->[0][1]{created}{instance2}{id}; - $self->assert_not_null($instance2Id); - my $xhref2 = $res->[0][1]{created}{instance2}{'x-href'}; - $self->assert_not_null($xhref2); - $self->assert_str_equals('2021-02-02T02:02:02', - $res->[1][1]{list}[0]{start}); - $self->assert_str_equals('Europe/Berlin', - $res->[1][1]{list}[0]{timeZone}); - $self->assert_str_equals('2021-02-02T02:02:02', - $res->[1][1]{list}[0]{recurrenceId}); - $self->assert_str_equals('Europe/London', - $res->[1][1]{list}[0]{recurrenceIdTimeZone}); + '@type' => 'Event', + uid => 'event1uid', + title => 'instance2', + start => '2021-02-02T02:02:02', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-02-02T02:02:02', + recurrenceIdTimeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#instance2'], + properties => + [ 'start', 'timeZone', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R2' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R3' + ], + ]); + my $instance2Id = $res->[0][1]{created}{instance2}{id}; + $self->assert_not_null($instance2Id); + my $xhref2 = $res->[0][1]{created}{instance2}{'x-href'}; + $self->assert_not_null($xhref2); + $self->assert_str_equals('2021-02-02T02:02:02', $res->[1][1]{list}[0]{start}); + $self->assert_str_equals('Europe/Berlin', $res->[1][1]{list}[0]{timeZone}); + $self->assert_str_equals('2021-02-02T02:02:02', + $res->[1][1]{list}[0]{recurrenceId}); + $self->assert_str_equals('Europe/London', + $res->[1][1]{list}[0]{recurrenceIdTimeZone}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[1][1]{state}); - $self->assert_str_not_equals($state, $res->[2][1]{newState}); - $self->assert_deep_equals([$instance2Id], $res->[2][1]{created}); - $self->assert_deep_equals([], $res->[2][1]{updated}); - $self->assert_deep_equals([], $res->[2][1]{destroyed}); - $state = $res->[2][1]{newState}; + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[1][1]{state}); + $self->assert_str_not_equals($state, $res->[2][1]{newState}); + $self->assert_deep_equals([$instance2Id], $res->[2][1]{created}); + $self->assert_deep_equals([], $res->[2][1]{updated}); + $self->assert_deep_equals([], $res->[2][1]{destroyed}); + $state = $res->[2][1]{newState}; - xlog "Assert both events exist"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$instance1Id, $instance2Id], - properties => ['title', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{notFound}}); + xlog "Assert both events exist"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [ $instance1Id, $instance2Id ], + properties => [ 'title', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{notFound} }); - xlog "Assert CalDAV resource contains both instances"; - $res = $caldav->Request('GET', $xhref1); - $self->assert($res->{content} =~ m/SUMMARY:instance1/); - $self->assert($res->{content} =~ m/SUMMARY:instance2/); + xlog "Assert CalDAV resource contains both instances"; + $res = $caldav->Request('GET', $xhref1); + $self->assert($res->{content} =~ m/SUMMARY:instance1/); + $self->assert($res->{content} =~ m/SUMMARY:instance2/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_destroy b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_destroy index 65f0c56670..2003c03ecb 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_destroy +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_destroy @@ -2,126 +2,158 @@ use Cassandane::Tiny; sub test_calendarevent_set_standalone_instances_destroy - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create standalone instances"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-01-01T01:01:01', - recurrenceIdTimeZone => 'Europe/London', - }, - instance2 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance2', - start => '2021-02-02T02:02:02', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-02-02T02:02:02', - recurrenceIdTimeZone => 'Europe/London', - }, + xlog "Create standalone instances"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $instance1Id = $res->[0][1]{created}{instance1}{id}; - $self->assert_not_null($instance1Id); - my $instance2Id = $res->[0][1]{created}{instance2}{id}; - $self->assert_not_null($instance2Id); - my $xhref1 = $res->[0][1]{created}{instance1}{'x-href'}; - $self->assert_not_null($xhref1); - my $xhref2 = $res->[0][1]{created}{instance2}{'x-href'}; - $self->assert_not_null($xhref2); - $self->assert_str_equals($xhref1, $xhref2); - my $state = $res->[0][1]{newState}; + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-01-01T01:01:01', + recurrenceIdTimeZone => 'Europe/London', + }, + instance2 => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + uid => 'event1uid', + title => 'instance2', + start => '2021-02-02T02:02:02', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-02-02T02:02:02', + recurrenceIdTimeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + ]); + my $instance1Id = $res->[0][1]{created}{instance1}{id}; + $self->assert_not_null($instance1Id); + my $instance2Id = $res->[0][1]{created}{instance2}{id}; + $self->assert_not_null($instance2Id); + my $xhref1 = $res->[0][1]{created}{instance1}{'x-href'}; + $self->assert_not_null($xhref1); + my $xhref2 = $res->[0][1]{created}{instance2}{'x-href'}; + $self->assert_not_null($xhref2); + $self->assert_str_equals($xhref1, $xhref2); + my $state = $res->[0][1]{newState}; - xlog "Destroy first standalone instance"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [ $instance1Id ], - }, 'R1'], - ['CalendarEvent/get', { - ids => [$instance1Id], - properties => ['title', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R2'], - ['CalendarEvent/get', { - ids => [$instance2Id], - properties => ['title', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R3'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R4'], - ]); - $self->assert_deep_equals([$instance1Id], $res->[0][1]{destroyed}); - $self->assert_deep_equals([$instance1Id], $res->[1][1]{notFound}); - $self->assert_str_equals('instance2', $res->[2][1]{list}[0]{title}); + xlog "Destroy first standalone instance"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + destroy => [$instance1Id], + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$instance1Id], + properties => [ 'title', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => [$instance2Id], + properties => [ 'title', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R3' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R4' + ], + ]); + $self->assert_deep_equals([$instance1Id], $res->[0][1]{destroyed}); + $self->assert_deep_equals([$instance1Id], $res->[1][1]{notFound}); + $self->assert_str_equals('instance2', $res->[2][1]{list}[0]{title}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[1][1]{state}); - $self->assert_str_not_equals($state, $res->[2][1]{state}); - $self->assert_str_not_equals($state, $res->[3][1]{newState}); - $self->assert_deep_equals([], $res->[3][1]{created}); - $self->assert_deep_equals([], $res->[3][1]{updated}); - $self->assert_deep_equals([$instance1Id], $res->[3][1]{destroyed}); - $state = $res->[3][1]{newState}; + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[1][1]{state}); + $self->assert_str_not_equals($state, $res->[2][1]{state}); + $self->assert_str_not_equals($state, $res->[3][1]{newState}); + $self->assert_deep_equals([], $res->[3][1]{created}); + $self->assert_deep_equals([], $res->[3][1]{updated}); + $self->assert_deep_equals([$instance1Id], $res->[3][1]{destroyed}); + $state = $res->[3][1]{newState}; - xlog "Assert CalDAV resource still exists"; - $res = $caldav->Request('GET', $xhref1); - $self->assert(not $res->{content} =~ m/SUMMARY:instance1/); - $self->assert($res->{content} =~ m/SUMMARY:instance2/); + xlog "Assert CalDAV resource still exists"; + $res = $caldav->Request('GET', $xhref1); + $self->assert(not $res->{content} =~ m/SUMMARY:instance1/); + $self->assert($res->{content} =~ m/SUMMARY:instance2/); - xlog "Destroy second standalone instance"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [ $instance2Id ], - }, 'R1'], - ['CalendarEvent/get', { - ids => [$instance2Id], - properties => ['title', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R2'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R2'], - ]); - $self->assert_deep_equals([$instance2Id], $res->[0][1]{destroyed}); - $self->assert_deep_equals([$instance2Id], $res->[1][1]{notFound}); + xlog "Destroy second standalone instance"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + destroy => [$instance2Id], + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$instance2Id], + properties => [ 'title', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R2' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$instance2Id], $res->[0][1]{destroyed}); + $self->assert_deep_equals([$instance2Id], $res->[1][1]{notFound}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[1][1]{state}); - $self->assert_str_not_equals($state, $res->[2][1]{newState}); - $self->assert_deep_equals([], $res->[2][1]{created}); - $self->assert_deep_equals([], $res->[2][1]{updated}); - $self->assert_deep_equals([$instance2Id], $res->[2][1]{destroyed}); - $state = $res->[3][1]{newState}; + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[1][1]{state}); + $self->assert_str_not_equals($state, $res->[2][1]{newState}); + $self->assert_deep_equals([], $res->[2][1]{created}); + $self->assert_deep_equals([], $res->[2][1]{updated}); + $self->assert_deep_equals([$instance2Id], $res->[2][1]{destroyed}); + $state = $res->[3][1]{newState}; - xlog "Assert CalDAV resource is gone"; - # Can't use CalDAV talk for GET on non-existent URLs - my $xml = < EOF - $res = $caldav->Request('PROPFIND', 'Default', $xml, - 'Content-Type' => 'application/xml', - 'Depth' => '1' - ); - $self->assert_does_not_match(qr{event1uid}, $res); + $res = $caldav->Request( + 'PROPFIND', 'Default', $xml, + 'Content-Type' => 'application/xml', + 'Depth' => '1' + ); + $self->assert_does_not_match(qr{event1uid}, $res); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_move b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_move index 5514d3ddac..df236d2ebc 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_move +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_move @@ -2,99 +2,122 @@ use Cassandane::Tiny; sub test_calendarevent_set_standalone_instances_move - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create standalone instances"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-01-01T01:01:01', - recurrenceIdTimeZone => 'Europe/London', - }, - instance2 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance2', - start => '2021-02-02T02:02:02', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-02-02T02:02:02', - recurrenceIdTimeZone => 'Europe/London', - }, + xlog "Create standalone instances"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['Calendar/set', { - create => { - calendarA => { - name => 'A', - }, + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-01-01T01:01:01', + recurrenceIdTimeZone => 'Europe/London', + }, + instance2 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R2'], - ]); - my $instance1Id = $res->[0][1]{created}{instance1}{id}; - $self->assert_not_null($instance1Id); - my $instance2Id = $res->[0][1]{created}{instance2}{id}; - $self->assert_not_null($instance2Id); - my $state = $res->[0][1]{newState}; - my $calendarAId = $res->[1][1]{created}{calendarA}{id}; - $self->assert_not_null($calendarAId); + '@type' => 'Event', + uid => 'event1uid', + title => 'instance2', + start => '2021-02-02T02:02:02', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-02-02T02:02:02', + recurrenceIdTimeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + [ + 'Calendar/set', + { + create => { + calendarA => { + name => 'A', + }, + }, + }, + 'R2' + ], + ]); + my $instance1Id = $res->[0][1]{created}{instance1}{id}; + $self->assert_not_null($instance1Id); + my $instance2Id = $res->[0][1]{created}{instance2}{id}; + $self->assert_not_null($instance2Id); + my $state = $res->[0][1]{newState}; + my $calendarAId = $res->[1][1]{created}{calendarA}{id}; + $self->assert_not_null($calendarAId); - xlog "Move standalone instance to other calendar"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $instance1Id => { - calendarIds => { - $calendarAId => JSON::true, - }, - }, + xlog "Move standalone instance to other calendar"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $instance1Id => { + calendarIds => { + $calendarAId => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$instance1Id], - properties => ['calendarIds', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R2'], - ['CalendarEvent/get', { - ids => [$instance2Id], - properties => ['calendarIds', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R3'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R4'], - ]); - $self->assert(exists $res->[0][1]{updated}{$instance1Id}); - $self->assert_deep_equals({$calendarAId => JSON::true }, - $res->[1][1]{list}[0]{calendarIds}); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$instance1Id], + properties => [ 'calendarIds', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => [$instance2Id], + properties => [ 'calendarIds', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R3' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R4' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$instance1Id}); + $self->assert_deep_equals({ $calendarAId => JSON::true }, + $res->[1][1]{list}[0]{calendarIds}); - xlog "Moving one standalone instance also moves any other instances"; - $self->assert_deep_equals({$calendarAId => JSON::true }, - $res->[2][1]{list}[0]{calendarIds}); + xlog "Moving one standalone instance also moves any other instances"; + $self->assert_deep_equals({ $calendarAId => JSON::true }, + $res->[2][1]{list}[0]{calendarIds}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[1][1]{state}); - $self->assert_str_not_equals($state, $res->[2][1]{state}); - $self->assert_str_not_equals($state, $res->[3][1]{newState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[1][1]{state}); + $self->assert_str_not_equals($state, $res->[2][1]{state}); + $self->assert_str_not_equals($state, $res->[3][1]{newState}); - $self->assert_deep_equals([], $res->[3][1]{created}); - my @wantUpdated = sort ($instance1Id, $instance2Id); - my @haveUpdated = sort @{$res->[3][1]{updated}}; - $self->assert_deep_equals(\@wantUpdated, \@haveUpdated); - $self->assert_deep_equals([], $res->[3][1]{destroyed}); + $self->assert_deep_equals([], $res->[3][1]{created}); + my @wantUpdated = sort ($instance1Id, $instance2Id); + my @haveUpdated = sort @{ $res->[3][1]{updated} }; + $self->assert_deep_equals(\@wantUpdated, \@haveUpdated); + $self->assert_deep_equals([], $res->[3][1]{destroyed}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_to_main b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_to_main index 60488df797..8eeb6c0b73 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_to_main +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_to_main @@ -2,94 +2,116 @@ use Cassandane::Tiny; sub test_calendarevent_set_standalone_instances_to_main - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create standalone instance"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-01-01T01:01:01', - recurrenceIdTimeZone => 'Europe/London', - }, + xlog "Create standalone instance"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $instance1Id = $res->[0][1]{created}{instance1}{id}; - $self->assert_not_null($instance1Id); - my $state = $res->[0][1]{newState}; + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-01-01T01:01:01', + recurrenceIdTimeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + ]); + my $instance1Id = $res->[0][1]{created}{instance1}{id}; + $self->assert_not_null($instance1Id); + my $state = $res->[0][1]{newState}; - xlog "Can't convert a standalone instance to a main event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $instance1Id => { - recurrenceId => undef, - }, - }, - }, 'R1'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R2'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{$instance1Id}{type}); - $self->assert_deep_equals([ - # XXX invalidProperties doesn't deduplicate, - # but we'll only change this when we merged - # this feature branch - 'recurrenceId', 'recurrenceId', 'recurrenceIdTimeZone' - ], $res->[0][1]{notUpdated}{$instance1Id}{properties}); + xlog "Can't convert a standalone instance to a main event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $instance1Id => { + recurrenceId => undef, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R2' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$instance1Id}{type}); + $self->assert_deep_equals( + [ + # XXX invalidProperties doesn't deduplicate, + # but we'll only change this when we merged + # this feature branch + 'recurrenceId', 'recurrenceId', 'recurrenceIdTimeZone' + ], + $res->[0][1]{notUpdated}{$instance1Id}{properties} + ); - $self->assert_str_equals($state, $res->[1][1]{newState}); - $self->assert_deep_equals([], $res->[1][1]{created}); - $self->assert_deep_equals([], $res->[1][1]{updated}); - $self->assert_deep_equals([], $res->[1][1]{destroyed}); + $self->assert_str_equals($state, $res->[1][1]{newState}); + $self->assert_deep_equals([], $res->[1][1]{created}); + $self->assert_deep_equals([], $res->[1][1]{updated}); + $self->assert_deep_equals([], $res->[1][1]{destroyed}); - xlog "Create main event with the same uid"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'mainevent1', - start => '2020-12-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceRules => [{ - '@type' => 'RecurrenceRule', - frequency => 'monthly', - count => 3, - }], - }, + xlog "Create main event with the same uid"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R2'], - ]); - my $event1Id = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($event1Id); + '@type' => 'Event', + uid => 'event1uid', + title => 'mainevent1', + start => '2020-12-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceRules => [ { + '@type' => 'RecurrenceRule', + frequency => 'monthly', + count => 3, + } ], + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R2' + ], + ]); + my $event1Id = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($event1Id); - $self->assert_str_not_equals($state, $res->[1][1]{newState}); - $self->assert_deep_equals([$event1Id], $res->[1][1]{created}); - $self->assert_deep_equals([], $res->[1][1]{updated}); - $self->assert_deep_equals([$instance1Id], $res->[1][1]{destroyed}); + $self->assert_str_not_equals($state, $res->[1][1]{newState}); + $self->assert_deep_equals([$event1Id], $res->[1][1]{created}); + $self->assert_deep_equals([], $res->[1][1]{updated}); + $self->assert_deep_equals([$instance1Id], $res->[1][1]{destroyed}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_update b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_update index 56471e97fb..a42fa8824b 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_update +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_instances_update @@ -2,114 +2,141 @@ use Cassandane::Tiny; sub test_calendarevent_set_standalone_instances_update - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create standalone instances"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => '2021-01-01T11:11:11', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-01-01T01:01:01', - recurrenceIdTimeZone => 'Europe/London', - }, - instance2 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance2', - start => '2021-02-02T02:02:02', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => '2021-02-02T02:02:02', - recurrenceIdTimeZone => 'Europe/London', - }, + xlog "Create standalone instances"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance1 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $instance1Id = $res->[0][1]{created}{instance1}{id}; - $self->assert_not_null($instance1Id); - my $instance2Id = $res->[0][1]{created}{instance2}{id}; - $self->assert_not_null($instance2Id); - my $xhref1 = $res->[0][1]{created}{instance1}{'x-href'}; - $self->assert_not_null($xhref1); - my $xhref2 = $res->[0][1]{created}{instance2}{'x-href'}; - $self->assert_not_null($xhref2); - $self->assert_str_equals($xhref1, $xhref2); - my $state = $res->[0][1]{newState}; - - xlog "Update standalone instance"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $instance1Id => { - title => 'instance1Updated', - }, + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => '2021-01-01T11:11:11', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-01-01T01:01:01', + recurrenceIdTimeZone => 'Europe/London', + }, + instance2 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$instance1Id], - properties => ['title', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R2'], - ['CalendarEvent/get', { - ids => [$instance2Id], - properties => ['title', 'recurrenceId', 'recurrenceIdTimeZone'], - }, 'R3'], - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R4'], - ]); - $self->assert(exists $res->[0][1]{updated}{$instance1Id}); - $self->assert_str_equals('instance1Updated', $res->[1][1]{list}[0]{title}); - $self->assert_str_equals('instance2', $res->[2][1]{list}[0]{title}); + '@type' => 'Event', + uid => 'event1uid', + title => 'instance2', + start => '2021-02-02T02:02:02', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => '2021-02-02T02:02:02', + recurrenceIdTimeZone => 'Europe/London', + }, + }, + }, + 'R1' + ], + ]); + my $instance1Id = $res->[0][1]{created}{instance1}{id}; + $self->assert_not_null($instance1Id); + my $instance2Id = $res->[0][1]{created}{instance2}{id}; + $self->assert_not_null($instance2Id); + my $xhref1 = $res->[0][1]{created}{instance1}{'x-href'}; + $self->assert_not_null($xhref1); + my $xhref2 = $res->[0][1]{created}{instance2}{'x-href'}; + $self->assert_not_null($xhref2); + $self->assert_str_equals($xhref1, $xhref2); + my $state = $res->[0][1]{newState}; - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[1][1]{state}); - $self->assert_str_not_equals($state, $res->[2][1]{state}); - $self->assert_str_not_equals($state, $res->[3][1]{newState}); - $self->assert_deep_equals([], $res->[3][1]{created}); - $self->assert_deep_equals([$instance1Id], $res->[3][1]{updated}); - $self->assert_deep_equals([], $res->[3][1]{destroyed}); - $state = $res->[3][1]{newState}; + xlog "Update standalone instance"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $instance1Id => { + title => 'instance1Updated', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$instance1Id], + properties => [ 'title', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => [$instance2Id], + properties => [ 'title', 'recurrenceId', 'recurrenceIdTimeZone' ], + }, + 'R3' + ], + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R4' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$instance1Id}); + $self->assert_str_equals('instance1Updated', $res->[1][1]{list}[0]{title}); + $self->assert_str_equals('instance2', $res->[2][1]{list}[0]{title}); - xlog "Assert CalDAV resource contains both instances"; - $res = $caldav->Request('GET', $xhref1); - $self->assert($res->{content} =~ m/SUMMARY:instance1Updated/); - $self->assert($res->{content} =~ m/SUMMARY:instance2/); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[1][1]{state}); + $self->assert_str_not_equals($state, $res->[2][1]{state}); + $self->assert_str_not_equals($state, $res->[3][1]{newState}); + $self->assert_deep_equals([], $res->[3][1]{created}); + $self->assert_deep_equals([$instance1Id], $res->[3][1]{updated}); + $self->assert_deep_equals([], $res->[3][1]{destroyed}); + $state = $res->[3][1]{newState}; - xlog "Can't change the recurrenceId or recurrenceIdTimeZone property"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $instance1Id => { - recurrenceId => '2021-03-03T03:03:03', - }, - }, - }, 'R1'], - ['CalendarEvent/set', { - update => { - $instance1Id => { - recurrenceIdTimeZone => 'America/New_York', - }, - }, - }, 'R2'], - ]); - $self->assert_deep_equals(['recurrenceId'], - $res->[0][1]{notUpdated}{$instance1Id}{properties}); - $self->assert_deep_equals(['recurrenceIdTimeZone'], - $res->[1][1]{notUpdated}{$instance1Id}{properties}); + xlog "Assert CalDAV resource contains both instances"; + $res = $caldav->Request('GET', $xhref1); + $self->assert($res->{content} =~ m/SUMMARY:instance1Updated/); + $self->assert($res->{content} =~ m/SUMMARY:instance2/); + + xlog "Can't change the recurrenceId or recurrenceIdTimeZone property"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $instance1Id => { + recurrenceId => '2021-03-03T03:03:03', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + update => { + $instance1Id => { + recurrenceIdTimeZone => 'America/New_York', + }, + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals(['recurrenceId'], + $res->[0][1]{notUpdated}{$instance1Id}{properties}); + $self->assert_deep_equals(['recurrenceIdTimeZone'], + $res->[1][1]{notUpdated}{$instance1Id}{properties}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_itip b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_itip index bebd8d8045..e73584904c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_itip +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_standalone_itip @@ -2,238 +2,254 @@ use Cassandane::Tiny; sub test_calendarevent_set_standalone_itip - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $uid = 'event1uid', + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $uid = 'event1uid', my @details = ( - { start => '2021-01-01T01:01:01', recurid => '20210101T010101' }, - { start => '2022-02-02T02:02:02', recurid => '20220202T020202' }, - { start => '2022-03-03T03:03:03', recurid => '20220303T030303' }, + { start => '2021-01-01T01:01:01', recurid => '20210101T010101' }, + { start => '2022-02-02T02:02:02', recurid => '20220202T020202' }, + { start => '2022-03-03T03:03:03', recurid => '20220303T030303' }, ); - xlog "Clear notification cache"; - $self->{instance}->getnotify(); - - xlog "Create scheduled standalone instance"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance1 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => $uid, - title => 'instance1', - start => $details[0]->{start}, - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => $details[0]->{start}, - recurrenceIdTimeZone => 'Europe/London', - replyTo => { - imip => 'mailto:organizer@example.com', - }, - participants => { - cassandane => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - participationStatus => 'tentative', - expectReply => JSON::true, - }, - }, - }, + xlog "Clear notification cache"; + $self->{instance}->getnotify(); + + xlog "Create scheduled standalone instance"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance1 => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + uid => $uid, + title => 'instance1', + start => $details[0]->{start}, + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => $details[0]->{start}, + recurrenceIdTimeZone => 'Europe/London', + replyTo => { + imip => 'mailto:organizer@example.com', }, - }, 'R1'], - ]); - my $instance1Id = $res->[0][1]{created}{instance1}{id}; - $self->assert_not_null($instance1Id); - - xlog "Assert that iTIP notification is sent"; - my $data = $self->{instance}->getnotify(); - my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - my $notif_payload = decode_json($notif->{MESSAGE}); - my $itip = $notif_payload->{ical}; - my $ical = Data::ICal->new(data => $itip); - - my @vevents = grep { $_->ical_entry_type() eq 'VEVENT' } @{$ical->entries()}; - $self->assert_num_equals(1, scalar @vevents); - - my $recurid = $vevents[0]->property('RECURRENCE-ID'); - $self->assert_num_equals(1, scalar @{$recurid}); - $self->assert_str_equals($details[0]->{recurid}, $recurid->[0]->value()); - - my $attendees = $vevents[0]->property('ATTENDEE'); - $self->assert_num_equals(1, scalar @{$attendees}); - $self->assert_str_equals('mailto:cassandane@example.com', - $attendees->[0]->value()); - $self->assert_str_equals('TENTATIVE', - $attendees->[0]->parameters()->{'PARTSTAT'}); - - my $expect_id = encode_eventid($uid, $details[0]->{recurid}); - $self->assert_not_null($notif_payload->{id}); - $self->assert_str_equals($expect_id, $notif_payload->{id}); - $self->assert_str_equals('REPLY', $notif_payload->{method}); - - xlog "Clear notification cache"; - $self->{instance}->getnotify(); - - xlog "Create another standalone instance for that UID"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance2 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance1', - start => $details[1]->{start}, - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => $details[1]->{start}, - recurrenceIdTimeZone => 'Europe/London', - replyTo => { - imip => 'mailto:organizer@example.com', - }, - participants => { - cassandane => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - participationStatus => 'accepted', - expectReply => JSON::true, - }, - }, + participants => { + cassandane => { + roles => { + attendee => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', }, + participationStatus => 'tentative', + expectReply => JSON::true, + }, + }, + }, + }, + }, + 'R1' + ], + ]); + my $instance1Id = $res->[0][1]{created}{instance1}{id}; + $self->assert_not_null($instance1Id); + + xlog "Assert that iTIP notification is sent"; + my $data = $self->{instance}->getnotify(); + my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + my $notif_payload = decode_json($notif->{MESSAGE}); + my $itip = $notif_payload->{ical}; + my $ical = Data::ICal->new(data => $itip); + + my @vevents + = grep { $_->ical_entry_type() eq 'VEVENT' } @{ $ical->entries() }; + $self->assert_num_equals(1, scalar @vevents); + + my $recurid = $vevents[0]->property('RECURRENCE-ID'); + $self->assert_num_equals(1, scalar @{$recurid}); + $self->assert_str_equals($details[0]->{recurid}, $recurid->[0]->value()); + + my $attendees = $vevents[0]->property('ATTENDEE'); + $self->assert_num_equals(1, scalar @{$attendees}); + $self->assert_str_equals('mailto:cassandane@example.com', + $attendees->[0]->value()); + $self->assert_str_equals('TENTATIVE', + $attendees->[0]->parameters()->{'PARTSTAT'}); + + my $expect_id = encode_eventid($uid, $details[0]->{recurid}); + $self->assert_not_null($notif_payload->{id}); + $self->assert_str_equals($expect_id, $notif_payload->{id}); + $self->assert_str_equals('REPLY', $notif_payload->{method}); + + xlog "Clear notification cache"; + $self->{instance}->getnotify(); + + xlog "Create another standalone instance for that UID"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance2 => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + uid => 'event1uid', + title => 'instance1', + start => $details[1]->{start}, + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => $details[1]->{start}, + recurrenceIdTimeZone => 'Europe/London', + replyTo => { + imip => 'mailto:organizer@example.com', }, - }, 'R1'], - ]); - my $instance2Id = $res->[0][1]{created}{instance2}{id}; - $self->assert_not_null($instance2Id); - - xlog "Assert iTIP notification just gets sent for new instance"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - $notif_payload = decode_json($notif->{MESSAGE}); - $itip = $notif_payload->{ical}; - $ical = Data::ICal->new(data => $itip); - - @vevents = grep { $_->ical_entry_type() eq 'VEVENT' } @{$ical->entries()}; - $self->assert_num_equals(1, scalar @vevents); - - $recurid = $vevents[0]->property('RECURRENCE-ID'); - $self->assert_num_equals(1, scalar @{$recurid}); - $self->assert_str_equals($details[1]->{recurid}, $recurid->[0]->value()); - - $attendees = $vevents[0]->property('ATTENDEE'); - $self->assert_num_equals(1, scalar @{$attendees}); - $self->assert_str_equals('mailto:cassandane@example.com', - $attendees->[0]->value()); - $self->assert_str_equals('ACCEPTED', - $attendees->[0]->parameters()->{'PARTSTAT'}); - - $expect_id = encode_eventid($uid, $details[1]->{recurid}); - $self->assert_not_null($notif_payload->{id}); - $self->assert_str_equals($expect_id, $notif_payload->{id}); - $self->assert_str_equals('REPLY', $notif_payload->{method}); - - xlog "Clear notification cache"; - $self->{instance}->getnotify(); - - xlog "Update partstat in a standalone instance"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $instance2Id => { - 'participants/cassandane/participationStatus' => 'declined', + participants => { + cassandane => { + roles => { + attendee => JSON::true, }, + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + participationStatus => 'accepted', + expectReply => JSON::true, + }, + }, + }, + }, + }, + 'R1' + ], + ]); + my $instance2Id = $res->[0][1]{created}{instance2}{id}; + $self->assert_not_null($instance2Id); + + xlog "Assert iTIP notification just gets sent for new instance"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + $notif_payload = decode_json($notif->{MESSAGE}); + $itip = $notif_payload->{ical}; + $ical = Data::ICal->new(data => $itip); + + @vevents = grep { $_->ical_entry_type() eq 'VEVENT' } @{ $ical->entries() }; + $self->assert_num_equals(1, scalar @vevents); + + $recurid = $vevents[0]->property('RECURRENCE-ID'); + $self->assert_num_equals(1, scalar @{$recurid}); + $self->assert_str_equals($details[1]->{recurid}, $recurid->[0]->value()); + + $attendees = $vevents[0]->property('ATTENDEE'); + $self->assert_num_equals(1, scalar @{$attendees}); + $self->assert_str_equals('mailto:cassandane@example.com', + $attendees->[0]->value()); + $self->assert_str_equals('ACCEPTED', + $attendees->[0]->parameters()->{'PARTSTAT'}); + + $expect_id = encode_eventid($uid, $details[1]->{recurid}); + $self->assert_not_null($notif_payload->{id}); + $self->assert_str_equals($expect_id, $notif_payload->{id}); + $self->assert_str_equals('REPLY', $notif_payload->{method}); + + xlog "Clear notification cache"; + $self->{instance}->getnotify(); + + xlog "Update partstat in a standalone instance"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $instance2Id => { + 'participants/cassandane/participationStatus' => 'declined', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$instance2Id}); + + xlog "Assert iTIP notification only is sent for updated instance"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + $notif_payload = decode_json($notif->{MESSAGE}); + $itip = $notif_payload->{ical}; + $ical = Data::ICal->new(data => $itip); + + @vevents = grep { $_->ical_entry_type() eq 'VEVENT' } @{ $ical->entries() }; + $self->assert_num_equals(1, scalar @vevents); + + $recurid = $vevents[0]->property('RECURRENCE-ID'); + $self->assert_num_equals(1, scalar @{$recurid}); + $self->assert_str_equals($details[1]->{recurid}, $recurid->[0]->value()); + + $attendees = $vevents[0]->property('ATTENDEE'); + $self->assert_num_equals(1, scalar @{$attendees}); + $self->assert_str_equals('mailto:cassandane@example.com', + $attendees->[0]->value()); + $self->assert_str_equals('DECLINED', + $attendees->[0]->parameters()->{'PARTSTAT'}); + + $expect_id = encode_eventid($uid, $details[1]->{recurid}); + $self->assert_not_null($notif_payload->{id}); + $self->assert_str_equals($expect_id, $notif_payload->{id}); + $self->assert_str_equals('REPLY', $notif_payload->{method}); + + xlog "Clear notification cache"; + $self->{instance}->getnotify(); + + xlog "Create another standalone instance where PARTSTAT=NEEDS-ACTION"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + instance2 => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$instance2Id}); - - xlog "Assert iTIP notification only is sent for updated instance"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - $notif_payload = decode_json($notif->{MESSAGE}); - $itip = $notif_payload->{ical}; - $ical = Data::ICal->new(data => $itip); - - @vevents = grep { $_->ical_entry_type() eq 'VEVENT' } @{$ical->entries()}; - $self->assert_num_equals(1, scalar @vevents); - - $recurid = $vevents[0]->property('RECURRENCE-ID'); - $self->assert_num_equals(1, scalar @{$recurid}); - $self->assert_str_equals($details[1]->{recurid}, $recurid->[0]->value()); - - $attendees = $vevents[0]->property('ATTENDEE'); - $self->assert_num_equals(1, scalar @{$attendees}); - $self->assert_str_equals('mailto:cassandane@example.com', - $attendees->[0]->value()); - $self->assert_str_equals('DECLINED', - $attendees->[0]->parameters()->{'PARTSTAT'}); - - $expect_id = encode_eventid($uid, $details[1]->{recurid}); - $self->assert_not_null($notif_payload->{id}); - $self->assert_str_equals($expect_id, $notif_payload->{id}); - $self->assert_str_equals('REPLY', $notif_payload->{method}); - - xlog "Clear notification cache"; - $self->{instance}->getnotify(); - - xlog "Create another standalone instance where PARTSTAT=NEEDS-ACTION"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - instance2 => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => 'event1uid', - title => 'instance3', - start => $details[2]->{start}, - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceId => $details[2]->{start}, - recurrenceIdTimeZone => 'Europe/London', - replyTo => { - imip => 'mailto:organizer@example.com', - }, - participants => { - cassandane => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - participationStatus => 'needs-action', - expectReply => JSON::true, - }, - }, + '@type' => 'Event', + uid => 'event1uid', + title => 'instance3', + start => $details[2]->{start}, + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceId => $details[2]->{start}, + recurrenceIdTimeZone => 'Europe/London', + replyTo => { + imip => 'mailto:organizer@example.com', + }, + participants => { + cassandane => { + roles => { + attendee => JSON::true, + }, + sendTo => { + imip => 'mailto:cassandane@example.com', }, + participationStatus => 'needs-action', + expectReply => JSON::true, + }, }, - }, 'R1'], - ]); - my $instance3Id = $res->[0][1]{created}{instance2}{id}; - $self->assert_not_null($instance2Id); - - xlog "Assert no iTIP notification is sent"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_null($notif); + }, + }, + }, + 'R1' + ], + ]); + my $instance3Id = $res->[0][1]{created}{instance2}{id}; + $self->assert_not_null($instance2Id); + + xlog "Assert no iTIP notification is sent"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_null($notif); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_subseconds b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_subseconds index 05c22a072c..36184aeefb 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_subseconds +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_subseconds @@ -2,96 +2,95 @@ use Cassandane::Tiny; sub test_calendarevent_set_subseconds - :min_version_3_1 :max_version_3_4 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap { + my ($self) = @_; - # subseconds were deprecated in 3.5 but included as experimental in 3.4 + # subseconds were deprecated in 3.5 but included as experimental in 3.4 - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + uid => "58ADE31-custom-UID", + title => "subseconds", + start => "2011-12-04T04:05:06.78", + created => "2019-06-29T11:58:12.412Z", + updated => "2019-06-29T11:58:12.412Z", + duration => "PT5M3.45S", + timeZone => "Europe/Vienna", + recurrenceRules => [ { + '@type' => 'RecurrenceRule', + frequency => "daily", + until => '2011-12-10T04:05:06.78', + } ], + "replyTo" => { + "imip" => 'mailto:foo@local', + }, + "participants" => { + 'foo' => { + '@type' => 'Participant', + name => 'Foo', + email => 'foo@local', + roles => { + owner => JSON::true, + attendee => JSON::true, }, - uid => "58ADE31-custom-UID", - title => "subseconds", - start => "2011-12-04T04:05:06.78", - created => "2019-06-29T11:58:12.412Z", - updated => "2019-06-29T11:58:12.412Z", - duration=> "PT5M3.45S", - timeZone=> "Europe/Vienna", - recurrenceRules => [{ - '@type' => 'RecurrenceRule', - frequency => "daily", - until => '2011-12-10T04:05:06.78', - }], - "replyTo" => { - "imip" => 'mailto:foo@local', + sendTo => { + imip => 'mailto:foo@local', }, - "participants" => { - 'foo' => { - '@type' => 'Participant', - name => 'Foo', - email => 'foo@local', - roles => { - owner => JSON::true, - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:foo@local', - }, - scheduleSequence => 1, - scheduleUpdated => '2018-07-06T05:03:02.123Z', - }, + scheduleSequence => 1, + scheduleUpdated => '2018-07-06T05:03:02.123Z', + }, + }, + alerts => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M0.7S", }, - alerts => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M0.7S", - }, - acknowledged => "2015-11-07T08:57:00.523Z", - action => "display", - }, + acknowledged => "2015-11-07T08:57:00.523Z", + action => "display", + }, + }, + recurrenceOverrides => { + '2011-12-05T04:05:06.78' => { + title => "overridden event" + }, + '2011-12-06T04:05:06.78' => { + excluded => JSON::true + }, + '2011-12-07T11:00:00.99' => {}, + '2011-12-08T04:05:06.78' => { + title => "overridden event with DTEND", + duration => 'PT1H2.345S', + locations => { + endLocation => { + '@type' => 'Location', + name => 'end location in another timezone', + relativeTo => 'end', + timeZone => 'Europe/London', + } }, - recurrenceOverrides => { - '2011-12-05T04:05:06.78' => { - title => "overridden event" - }, - '2011-12-06T04:05:06.78' => { - excluded => JSON::true - }, - '2011-12-07T11:00:00.99' => {}, - '2011-12-08T04:05:06.78' => { - title => "overridden event with DTEND", - duration => 'PT1H2.345S', - locations => { - endLocation => { - '@type' => 'Location', - name => 'end location in another timezone', - relativeTo => 'end', - timeZone => 'Europe/London', - } - }, - }, - }, - }; + }, + }, + }; - my $ret = $self->createandget_event($event); + my $ret = $self->createandget_event($event); - # Known regresion: recurrenceRule.until - $self->assert_str_equals('2011-12-10T04:05:06', - $ret->{recurrenceRules}[0]{until}); - $ret->{recurrenceRules}[0]{until} = '2011-12-10T04:05:06.78'; + # Known regresion: recurrenceRule.until + $self->assert_str_equals('2011-12-10T04:05:06', + $ret->{recurrenceRules}[0]{until}); + $ret->{recurrenceRules}[0]{until} = '2011-12-10T04:05:06.78'; - # Known regression: participant.scheduleUpdated - $self->assert_str_equals('2018-07-06T05:03:02Z', - $ret->{participants}{foo}{scheduleUpdated}); - $ret->{participants}{foo}{scheduleUpdated} = '2018-07-06T05:03:02.123Z'; + # Known regression: participant.scheduleUpdated + $self->assert_str_equals('2018-07-06T05:03:02Z', + $ret->{participants}{foo}{scheduleUpdated}); + $ret->{participants}{foo}{scheduleUpdated} = '2018-07-06T05:03:02.123Z'; - $self->assert_str_equals($event->{created}, $ret->{created}); - $self->assert_str_equals($event->{updated}, $ret->{updated}); - $self->assert_normalized_event_equals($event, $ret); + $self->assert_str_equals($event->{created}, $ret->{created}); + $self->assert_str_equals($event->{updated}, $ret->{updated}); + $self->assert_normalized_event_equals($event, $ret); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_too_large b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_too_large index 1604ad2664..1a1eae2b95 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_too_large +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_too_large @@ -2,36 +2,49 @@ use Cassandane::Tiny; sub test_calendarevent_set_too_large - :min_version_3_5 :needs_component_jmap :iCalendarMaxSize10k -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap : iCalendarMaxSize10k { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog $self, "create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { - "1" => { - name => "A", color => "coral", sortOrder => 1, isVisible => JSON::true - } - }}, "R1"]]); - my $calid = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create calendar"; + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + "1" => { + name => "A", + color => "coral", + sortOrder => 1, + isVisible => JSON::true + } + } + }, + "R1" + ] ]); + my $calid = $res->[0][1]{created}{"1"}{id}; - xlog $self, "create event in calendar"; - $res = $jmap->CallMethods([['CalendarEvent/set', { create => { - "1" => { - "calendarIds" => { - $calid => JSON::true, - }, - "title" => "foo", - "description" => ('x' x 100000), - "freeBusyStatus" => "busy", - "showWithoutTime" => JSON::true, - "start" => "2015-10-06T00:00:00", - "duration" => "P1D", - "timeZone" => undef, - } - }}, "R1"]]); - $self->assert_str_equals('tooLarge', $res->[0][1]{notCreated}{1}{type}); + xlog $self, "create event in calendar"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => { + "calendarIds" => { + $calid => JSON::true, + }, + "title" => "foo", + "description" => ('x' x 100000), + "freeBusyStatus" => "busy", + "showWithoutTime" => JSON::true, + "start" => "2015-10-06T00:00:00", + "duration" => "P1D", + "timeZone" => undef, + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('tooLarge', $res->[0][1]{notCreated}{1}{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_type b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_type index d92c91150a..a7574f3381 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_type +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_type @@ -2,47 +2,54 @@ use Cassandane::Tiny; sub test_calendarevent_set_type - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "privacy" => "secret", - "participants" => undef, - "alerts"=> undef, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "privacy" => "secret", + "participants" => undef, + "alerts" => undef, + }; - # Setting no type is OK, we'll just assume jsevent - my $res = $jmap->CallMethods([['CalendarEvent/set', { - create => { - "1" => $event, - } - }, "R1"]]); - $self->assert_not_null($res->[0][1]{created}{"1"}); + # Setting no type is OK, we'll just assume jsevent + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => $event, + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}{"1"}); - # Setting any type other jsevent type is NOT OK - $event->{q{@type}} = 'jstask'; - $event->{uid} = '58ADE31-custom-UID-2'; - $res = $jmap->CallMethods([['CalendarEvent/set', { - create => { - "1" => $event, - } - }, "R1"]]); - $self->assert_not_null($res->[0][1]{notCreated}{"1"}); + # Setting any type other jsevent type is NOT OK + $event->{q{@type}} = 'jstask'; + $event->{uid} = '58ADE31-custom-UID-2'; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + "1" => $event, + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{notCreated}{"1"}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_uid b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_uid index f930345bf1..459da881f4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_uid +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_uid @@ -2,67 +2,74 @@ use Cassandane::Tiny; sub test_calendarevent_set_uid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Etc/UTC", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "privacy" => "secret", - "participants" => undef, - "alerts"=> undef, - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Etc/UTC", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "privacy" => "secret", + "participants" => undef, + "alerts" => undef, + }; - # An empty UID generates a random uid. - my $ret = $self->createandget_event($event); - my($filename, $dirs, $suffix) = fileparse($ret->{"x-href"}, ".ics"); - $self->assert_not_null($ret->{id}); - $self->assert_str_equals(encode_eventid($ret->{uid}), $ret->{id}); - $self->assert_str_equals(encode_eventid($filename), $ret->{id}); + # An empty UID generates a random uid. + my $ret = $self->createandget_event($event); + my ($filename, $dirs, $suffix) = fileparse($ret->{"x-href"}, ".ics"); + $self->assert_not_null($ret->{id}); + $self->assert_str_equals(encode_eventid($ret->{uid}), $ret->{id}); + $self->assert_str_equals(encode_eventid($filename), $ret->{id}); - # A sane UID maps to both the JMAP id and the DAV resource. - $event->{uid} = "458912982-some_UID"; - delete $event->{id}; - $ret = $self->createandget_event($event); - ($filename, $dirs, $suffix) = fileparse($ret->{"x-href"}, ".ics"); - $self->assert_str_equals($event->{uid}, $filename); - $self->assert_str_equals(encode_eventid($event->{uid}), $ret->{id}); + # A sane UID maps to both the JMAP id and the DAV resource. + $event->{uid} = "458912982-some_UID"; + delete $event->{id}; + $ret = $self->createandget_event($event); + ($filename, $dirs, $suffix) = fileparse($ret->{"x-href"}, ".ics"); + $self->assert_str_equals($event->{uid}, $filename); + $self->assert_str_equals(encode_eventid($event->{uid}), $ret->{id}); - # A non-pathsafe UID maps to the JMAP id but not the DAV resource. - $event->{uid} = "a/bogus/path#uid"; - delete $event->{id}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => $event, - }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); - $jmap->{CreatedIds} = undef; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$eventId], - }, 'R1'], - ]); - $ret = $res->[0][1]{list}[0]; - ($filename, $dirs, $suffix) = fileparse($ret->{"x-href"}, ".ics"); - $self->assert_not_null($filename); - $self->assert_str_not_equals($event->{uid}, $filename); - $self->assert_str_equals("EB-", substr($ret->{id}, 0, 3)); + # A non-pathsafe UID maps to the JMAP id but not the DAV resource. + $event->{uid} = "a/bogus/path#uid"; + delete $event->{id}; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => $event, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); + $jmap->{CreatedIds} = undef; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [$eventId], + }, + 'R1' + ], + ]); + $ret = $res->[0][1]{list}[0]; + ($filename, $dirs, $suffix) = fileparse($ret->{"x-href"}, ".ics"); + $self->assert_not_null($filename); + $self->assert_str_not_equals($event->{uid}, $filename); + $self->assert_str_equals("EB-", substr($ret->{id}, 0, 3)); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated index 59989f0310..50b199a903 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated @@ -2,41 +2,48 @@ use Cassandane::Tiny; sub test_calendarevent_set_updated - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $t = DateTime->now(); - $t->set_time_zone('Etc/UTC'); - my $start = $t->strftime('%Y-%m-%dT%H:%M:%S'); - $t->add(DateTime::Duration->new(days => -2)); - my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + my $t = DateTime->now(); + $t->set_time_zone('Etc/UTC'); + my $start = $t->strftime('%Y-%m-%dT%H:%M:%S'); + $t->add(DateTime::Duration->new(days => -2)); + my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - start => $start, - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - created => $past, - updated => $past, - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ '#event' ], - properties => ['created', 'updated', 'title'], - }, 'R2'], - ]); + start => $start, + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + created => $past, + updated => $past, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => [ 'created', 'updated', 'title' ], + }, + 'R2' + ], + ]); - $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); - my $updated = $res->[1][1]{list}[0]{updated}; - $self->assert($past lt $updated); - $self->assert_str_equals($updated, $res->[0][1]{created}{event}{updated}); + $self->assert_str_equals($past, $res->[1][1]{list}[0]{created}); + my $updated = $res->[1][1]{list}[0]{updated}; + $self->assert($past lt $updated); + $self->assert_str_equals($updated, $res->[0][1]{created}{event}{updated}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated_scheduled_not_source b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated_scheduled_not_source index d4121f8ee1..87a99f9d83 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated_scheduled_not_source +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated_scheduled_not_source @@ -2,87 +2,109 @@ use Cassandane::Tiny; sub test_calendarevent_set_updated_scheduled_not_source - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $t = DateTime->now(); - $t->set_time_zone('Etc/UTC'); - my $start = $t->strftime('%Y-%m-%dT%H:%M:%S'); - my $now= $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - $t->add(DateTime::Duration->new(days => -2)); - my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + my $t = DateTime->now(); + $t->set_time_zone('Etc/UTC'); + my $start = $t->strftime('%Y-%m-%dT%H:%M:%S'); + my $now = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + $t->add(DateTime::Duration->new(days => -2)); + my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - - xlog "Create event where cassandane is invitee"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - start => $start, - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - created => $past, - updated => $past, - replyTo => { - imip => 'mailto:someone@example.com', - }, - participants => { - cassandane => { - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - expectReply => JSON::true, - participationStatus => 'accepted', - }, - }, - }, + xlog "Create event where cassandane is invitee"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ '#event' ], - properties => ['updated'], - }, 'R2'], - ]); - $self->assert_str_equals($past, $res->[1][1]{list}[0]{updated}); - my $eventId = $res->[1][1]{list}[0]{id}; - - xlog "Change partstat of cassandane"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'participants/cassandane/participationStatus' => 'tentative', - }, + start => $start, + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + created => $past, + updated => $past, + replyTo => { + imip => 'mailto:someone@example.com', }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ '#event' ], - properties => ['updated'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_str_equals($past, $res->[1][1]{list}[0]{updated}); - - xlog "Client updates updated property themselves"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - updated => $now, + participants => { + cassandane => { + sendTo => { + imip => 'mailto:cassandane@example.com', }, + expectReply => JSON::true, + participationStatus => 'accepted', + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ '#event' ], - properties => ['updated'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_str_equals($now, $res->[1][1]{list}[0]{updated}); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => ['updated'], + }, + 'R2' + ], + ]); + $self->assert_str_equals($past, $res->[1][1]{list}[0]{updated}); + my $eventId = $res->[1][1]{list}[0]{id}; + + xlog "Change partstat of cassandane"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'participants/cassandane/participationStatus' => 'tentative', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => ['updated'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_str_equals($past, $res->[1][1]{list}[0]{updated}); + + xlog "Client updates updated property themselves"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + updated => $now, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => ['updated'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_str_equals($now, $res->[1][1]{list}[0]{updated}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated_scheduled_source b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated_scheduled_source index c54ea16429..a8cda45385 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated_scheduled_source +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_updated_scheduled_source @@ -2,96 +2,119 @@ use Cassandane::Tiny; sub test_calendarevent_set_updated_scheduled_source - :needs_component_httpd :needs_component_jmap :min_version_3_7 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_httpd : needs_component_jmap : min_version_3_7 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $t = DateTime->now(); - $t->set_time_zone('Etc/UTC'); - my $start = $t->strftime('%Y-%m-%dT%H:%M:%S'); - my $now= $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - $t->add(DateTime::Duration->new(days => -2)); - my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + my $t = DateTime->now(); + $t->set_time_zone('Etc/UTC'); + my $start = $t->strftime('%Y-%m-%dT%H:%M:%S'); + my $now = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); + $t->add(DateTime::Duration->new(days => -2)); + my $past = $t->strftime('%Y-%m-%dT%H:%M:%SZ'); - xlog "Create event where cassandane is owner"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - start => $start, - timeZone => 'Etc/UTC', - duration => 'PT1H', - title => 'event', - created => $past, - updated => $past, - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - someone => { - sendTo => { - imip => 'mailto:someone@example.com', - }, - expectReply => JSON::true, - participationStatus => 'needs-action', - }, - }, + xlog "Create event where cassandane is owner"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, + }, + start => $start, + timeZone => 'Etc/UTC', + duration => 'PT1H', + title => 'event', + created => $past, + updated => $past, + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + someone => { + sendTo => { + imip => 'mailto:someone@example.com', }, + expectReply => JSON::true, + participationStatus => 'needs-action', + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ '#event' ], - properties => ['updated'], - }, 'R2'], - ]); - my $updated = $res->[1][1]{list}[0]{updated}; - $self->assert($past lt $updated); - my $eventId = $res->[1][1]{list}[0]{id}; + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => ['updated'], + }, + 'R2' + ], + ]); + my $updated = $res->[1][1]{list}[0]{updated}; + $self->assert($past lt $updated); + my $eventId = $res->[1][1]{list}[0]{id}; - sleep(1); + sleep(1); - xlog "Invite someone else"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'participants/someoneelse' => { - sendTo => { - imip => 'mailto:someoneelse@example.com', - }, - expectReply => JSON::true, - participationStatus => 'needs-action', - }, - }, + xlog "Invite someone else"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'participants/someoneelse' => { + sendTo => { + imip => 'mailto:someoneelse@example.com', + }, + expectReply => JSON::true, + participationStatus => 'needs-action', }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ '#event' ], - properties => ['updated'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert($updated lt $res->[1][1]{list}[0]{updated}); - $updated = $res->[1][1]{list}[0]{updated}; + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => ['updated'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert($updated lt $res->[1][1]{list}[0]{updated}); + $updated = $res->[1][1]{list}[0]{updated}; - xlog "Client updates updated property themselves"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - updated => $past, - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [ '#event' ], - properties => ['updated'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_date_matches($updated, $res->[1][1]{list}[0]{updated}, 2); + xlog "Client updates updated property themselves"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + updated => $past, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event'], + properties => ['updated'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_date_matches($updated, $res->[1][1]{list}[0]{updated}, 2); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_utcstart b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_utcstart index 20f49671ab..f9fa4c989c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_utcstart +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_utcstart @@ -2,102 +2,125 @@ use Cassandane::Tiny; sub test_calendarevent_set_utcstart - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Assert event creation. - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - utcStart => "2019-12-10T23:30:00Z", - duration => "PT1H", - timeZone => "Australia/Melbourne", - }, - 2 => { - uid => 'eventuid2local', - calendarIds => { - Default => JSON::true, - }, - title => "event2", - utcStart => "2019-12-10T23:30:00Z", - duration => "PT1H", - timeZone => undef, # floating - }, + # Assert event creation. + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#1'], - properties => ['start', 'utcStart', 'utcEnd', 'timeZone', 'duration'], - }, 'R2'], - ['CalendarEvent/get', { - ids => ['#2'], - properties => ['start', 'utcStart', 'utcEnd', 'timeZone', 'duration'], - }, 'R3'] - ]); - my $eventId1 = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId1); - my $eventId2 = $res->[0][1]{created}{2}{id}; - $self->assert_not_null($eventId2); + title => "event1", + utcStart => "2019-12-10T23:30:00Z", + duration => "PT1H", + timeZone => "Australia/Melbourne", + }, + 2 => { + uid => 'eventuid2local', + calendarIds => { + Default => JSON::true, + }, + title => "event2", + utcStart => "2019-12-10T23:30:00Z", + duration => "PT1H", + timeZone => undef, # floating + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#1'], + properties => [ 'start', 'utcStart', 'utcEnd', 'timeZone', 'duration' ], + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => ['#2'], + properties => [ 'start', 'utcStart', 'utcEnd', 'timeZone', 'duration' ], + }, + 'R3' + ] + ]); + my $eventId1 = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId1); + my $eventId2 = $res->[0][1]{created}{2}{id}; + $self->assert_not_null($eventId2); - my $event1 = $res->[1][1]{list}[0]; - $self->assert_str_equals('2019-12-11T10:30:00', $event1->{start}); - $self->assert_str_equals('2019-12-10T23:30:00Z', $event1->{utcStart}); - $self->assert_str_equals('2019-12-11T00:30:00Z', $event1->{utcEnd}); - $self->assert_str_equals('Australia/Melbourne', $event1->{timeZone}); - $self->assert_str_equals('PT1H', $event1->{duration}); + my $event1 = $res->[1][1]{list}[0]; + $self->assert_str_equals('2019-12-11T10:30:00', $event1->{start}); + $self->assert_str_equals('2019-12-10T23:30:00Z', $event1->{utcStart}); + $self->assert_str_equals('2019-12-11T00:30:00Z', $event1->{utcEnd}); + $self->assert_str_equals('Australia/Melbourne', $event1->{timeZone}); + $self->assert_str_equals('PT1H', $event1->{duration}); - my $event2 = $res->[2][1]{list}[0]; - $self->assert_str_equals('2019-12-10T23:30:00', $event2->{start}); - $self->assert_str_equals('2019-12-10T23:30:00Z', $event2->{utcStart}); - $self->assert_str_equals('2019-12-11T00:30:00Z', $event2->{utcEnd}); - $self->assert_str_equals('Etc/UTC', $event2->{timeZone}); - $self->assert_str_equals('PT1H', $event2->{duration}); + my $event2 = $res->[2][1]{list}[0]; + $self->assert_str_equals('2019-12-10T23:30:00', $event2->{start}); + $self->assert_str_equals('2019-12-10T23:30:00Z', $event2->{utcStart}); + $self->assert_str_equals('2019-12-11T00:30:00Z', $event2->{utcEnd}); + $self->assert_str_equals('Etc/UTC', $event2->{timeZone}); + $self->assert_str_equals('PT1H', $event2->{duration}); - # Assert event updates. - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId1 => { - utcStart => "2019-12-11T01:30:00Z", - }, - $eventId2 => { - utcStart => "2019-12-10T11:30:00Z", - duration => 'PT30M', - timeZone => 'America/New_York', - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['start', 'utcStart', 'utcEnd', 'timeZone', 'duration'], - }, 'R2'], - ['CalendarEvent/get', { - ids => [$eventId2], - properties => ['start', 'utcStart', 'utcEnd', 'timeZone', 'duration'], - }, 'R3'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId1}); + # Assert event updates. + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId1 => { + utcStart => "2019-12-11T01:30:00Z", + }, + $eventId2 => { + utcStart => "2019-12-10T11:30:00Z", + duration => 'PT30M', + timeZone => 'America/New_York', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => [ 'start', 'utcStart', 'utcEnd', 'timeZone', 'duration' ], + }, + 'R2' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId2], + properties => [ 'start', 'utcStart', 'utcEnd', 'timeZone', 'duration' ], + }, + 'R3' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId1}); - $event1 = $res->[1][1]{list}[0]; - $self->assert_str_equals('2019-12-11T12:30:00', $event1->{start}); - $self->assert_str_equals('2019-12-11T01:30:00Z', $event1->{utcStart}); - $self->assert_str_equals('2019-12-11T02:30:00Z', $event1->{utcEnd}); - $self->assert_str_equals('Australia/Melbourne', $event1->{timeZone}); - $self->assert_str_equals('PT1H', $event1->{duration}); + $event1 = $res->[1][1]{list}[0]; + $self->assert_str_equals('2019-12-11T12:30:00', $event1->{start}); + $self->assert_str_equals('2019-12-11T01:30:00Z', $event1->{utcStart}); + $self->assert_str_equals('2019-12-11T02:30:00Z', $event1->{utcEnd}); + $self->assert_str_equals('Australia/Melbourne', $event1->{timeZone}); + $self->assert_str_equals('PT1H', $event1->{duration}); - $event2 = $res->[2][1]{list}[0]; - $self->assert_str_equals('2019-12-10T06:30:00', $event2->{start}); - $self->assert_str_equals('2019-12-10T11:30:00Z', $event2->{utcStart}); - $self->assert_str_equals('2019-12-10T12:00:00Z', $event2->{utcEnd}); - $self->assert_str_equals('America/New_York', $event2->{timeZone}); - $self->assert_str_equals('PT30M', $event2->{duration}); + $event2 = $res->[2][1]{list}[0]; + $self->assert_str_equals('2019-12-10T06:30:00', $event2->{start}); + $self->assert_str_equals('2019-12-10T11:30:00Z', $event2->{utcStart}); + $self->assert_str_equals('2019-12-10T12:00:00Z', $event2->{utcEnd}); + $self->assert_str_equals('America/New_York', $event2->{timeZone}); + $self->assert_str_equals('PT30M', $event2->{duration}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_utcstart_recur b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_utcstart_recur index 2188352f65..fafd838baf 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_utcstart_recur +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_utcstart_recur @@ -2,184 +2,233 @@ use Cassandane::Tiny; sub test_calendarevent_set_utcstart_recur - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $proplist = [ - 'start', - 'utcStart', - 'utcEnd', - 'timeZone', - 'duration', - 'recurrenceOverrides', - 'title' - ]; + my $jmap = $self->{jmap}; + my $proplist = [ + 'start', 'utcStart', 'utcEnd', 'timeZone', + 'duration', 'recurrenceOverrides', 'title' + ]; - # Assert event creation. - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - utcStart => "2019-12-10T23:30:00Z", - duration => "PT1H", - timeZone => "Australia/Melbourne", - recurrenceRules => [{ - frequency => 'daily', - count => 5, - }], - }, + # Assert event creation. + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#1'], - properties => $proplist, - }, 'R2'] - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); + title => "event1", + utcStart => "2019-12-10T23:30:00Z", + duration => "PT1H", + timeZone => "Australia/Melbourne", + recurrenceRules => [ { + frequency => 'daily', + count => 5, + } ], + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#1'], + properties => $proplist, + }, + 'R2' + ] + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); - my $event = $res->[1][1]{list}[0]; - $self->assert_str_equals('2019-12-11T10:30:00', $event->{start}); - $self->assert_str_equals('2019-12-10T23:30:00Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-11T00:30:00Z', $event->{utcEnd}); - $self->assert_str_equals('Australia/Melbourne', $event->{timeZone}); - $self->assert_str_equals('PT1H', $event->{duration}); + my $event = $res->[1][1]{list}[0]; + $self->assert_str_equals('2019-12-11T10:30:00', $event->{start}); + $self->assert_str_equals('2019-12-10T23:30:00Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-11T00:30:00Z', $event->{utcEnd}); + $self->assert_str_equals('Australia/Melbourne', $event->{timeZone}); + $self->assert_str_equals('PT1H', $event->{duration}); - # Updating utcStart on a recurring event with no overrides is OK. - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - utcStart => "2019-12-11T01:30:00Z", - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => $proplist, - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + # Updating utcStart on a recurring event with no overrides is OK. + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + utcStart => "2019-12-11T01:30:00Z", + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => $proplist, + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - $event = $res->[1][1]{list}[0]; - $self->assert_str_equals('2019-12-11T12:30:00', $event->{start}); - $self->assert_str_equals('2019-12-11T01:30:00Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-11T02:30:00Z', $event->{utcEnd}); - $self->assert_str_equals('Australia/Melbourne', $event->{timeZone}); - $self->assert_str_equals('PT1H', $event->{duration}); + $event = $res->[1][1]{list}[0]; + $self->assert_str_equals('2019-12-11T12:30:00', $event->{start}); + $self->assert_str_equals('2019-12-11T01:30:00Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-11T02:30:00Z', $event->{utcEnd}); + $self->assert_str_equals('Australia/Melbourne', $event->{timeZone}); + $self->assert_str_equals('PT1H', $event->{duration}); - # Updating utcStart on an expanded recurrence instance is OK. - my $eventInstanceId = encode_eventid('eventuid1local', '20191213T123000'); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventInstanceId => { - utcStart => "2019-12-13T03:30:00Z", - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventInstanceId], - properties => $proplist, - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventInstanceId}); + # Updating utcStart on an expanded recurrence instance is OK. + my $eventInstanceId = encode_eventid('eventuid1local', '20191213T123000'); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventInstanceId => { + utcStart => "2019-12-13T03:30:00Z", + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventInstanceId], + properties => $proplist, + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$eventInstanceId}); - $event = $res->[1][1]{list}[0]; - $self->assert_str_equals('2019-12-13T14:30:00', $event->{start}); - $self->assert_str_equals('2019-12-13T03:30:00Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-13T04:30:00Z', $event->{utcEnd}); - $self->assert_str_equals('Australia/Melbourne', $event->{timeZone}); - $self->assert_str_equals('PT1H', $event->{duration}); + $event = $res->[1][1]{list}[0]; + $self->assert_str_equals('2019-12-13T14:30:00', $event->{start}); + $self->assert_str_equals('2019-12-13T03:30:00Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-13T04:30:00Z', $event->{utcEnd}); + $self->assert_str_equals('Australia/Melbourne', $event->{timeZone}); + $self->assert_str_equals('PT1H', $event->{duration}); - # Now the event has a recurrenceOverride - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$eventId], - properties => $proplist, - }, 'R2'] - ]); - $event = $res->[0][1]{list}[0]; + # Now the event has a recurrenceOverride + $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => $proplist, + }, + 'R2' + ] ]); + $event = $res->[0][1]{list}[0]; - # Main event times are unchanged. - $self->assert_str_equals('2019-12-11T12:30:00', $event->{start}); - $self->assert_str_equals('2019-12-11T01:30:00Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-11T02:30:00Z', $event->{utcEnd}); - $self->assert_str_equals('Australia/Melbourne', $event->{timeZone}); - $self->assert_str_equals('PT1H', $event->{duration}); + # Main event times are unchanged. + $self->assert_str_equals('2019-12-11T12:30:00', $event->{start}); + $self->assert_str_equals('2019-12-11T01:30:00Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-11T02:30:00Z', $event->{utcEnd}); + $self->assert_str_equals('Australia/Melbourne', $event->{timeZone}); + $self->assert_str_equals('PT1H', $event->{duration}); - # Overriden instance times have changed. - my $override = $event->{recurrenceOverrides}{'2019-12-13T12:30:00'}; - $self->assert_str_equals('2019-12-13T14:30:00', $override->{start}); - $self->assert_str_equals('2019-12-13T03:30:00Z', $override->{utcStart}); - $self->assert_str_equals('2019-12-13T04:30:00Z', $override->{utcEnd}); + # Overriden instance times have changed. + my $override = $event->{recurrenceOverrides}{'2019-12-13T12:30:00'}; + $self->assert_str_equals('2019-12-13T14:30:00', $override->{start}); + $self->assert_str_equals('2019-12-13T03:30:00Z', $override->{utcStart}); + $self->assert_str_equals('2019-12-13T04:30:00Z', $override->{utcEnd}); - # It's OK to loop back a recurring event with overrides and UTC times. - $event->{title} = 'updated title'; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => $event, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => $proplist, - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_deep_equals($event, $res->[1][1]{list}[0]); + # It's OK to loop back a recurring event with overrides and UTC times. + $event->{title} = 'updated title'; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => $event, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => $proplist, + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_deep_equals($event, $res->[1][1]{list}[0]); - # But it is not OK to update UTC times in a recurring event with overrides. - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - utcStart => '2021-01-01T11:00:00Z', - }, - }, - }, 'R1'], - ['CalendarEvent/set', { - update => { - $eventId => { - recurrenceOverrides => { - '2019-12-13T12:30:00' => { - utcStart => '2021-01-01T11:00:00Z', - }, - }, - }, - }, - }, 'R2'], - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides/2019-12-13T12:30:00' => { - utcStart => '2021-01-01T11:00:00Z', - }, - }, + # But it is not OK to update UTC times in a recurring event with overrides. + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + utcStart => '2021-01-01T11:00:00Z', + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + recurrenceOverrides => { + '2019-12-13T12:30:00' => { + utcStart => '2021-01-01T11:00:00Z', + }, }, - }, 'R3'], - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides/2019-12-13T12:30:00/utcStart' => '2021-01-01T11:00:00Z', - }, + }, + }, + }, + 'R2' + ], + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides/2019-12-13T12:30:00' => { + utcStart => '2021-01-01T11:00:00Z', }, - }, 'R4'], - ['CalendarEvent/get', { - ids => [$eventId], - properties => $proplist, - }, 'R5'] - ]); - $self->assert_not_null($res->[0][1]{notUpdated}{$eventId}); - $self->assert_not_null($res->[1][1]{notUpdated}{$eventId}); - $self->assert_not_null($res->[2][1]{notUpdated}{$eventId}); - $self->assert_not_null($res->[3][1]{notUpdated}{$eventId}); + }, + }, + }, + 'R3' + ], + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides/2019-12-13T12:30:00/utcStart' => + '2021-01-01T11:00:00Z', + }, + }, + }, + 'R4' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => $proplist, + }, + 'R5' + ] + ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$eventId}); + $self->assert_not_null($res->[1][1]{notUpdated}{$eventId}); + $self->assert_not_null($res->[2][1]{notUpdated}{$eventId}); + $self->assert_not_null($res->[3][1]{notUpdated}{$eventId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_virtuallocation_xprop b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_virtuallocation_xprop index 682c2d3e5e..d14cb883ec 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_virtuallocation_xprop +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_virtuallocation_xprop @@ -2,43 +2,50 @@ use Cassandane::Tiny; sub test_calendarevent_set_virtuallocation_xprop - :needs_component_httpd :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_httpd : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $virtualLocations = { - vl1 => { - '@type' => 'VirtualLocation', - "example.com:foo" => "bar", - "example.com:bar" => { - "baz" => JSON::true, - }, - uri => 'https://example.com/v/e1ea21ce03a9', - }, - }; + my $virtualLocations = { + vl1 => { + '@type' => 'VirtualLocation', + "example.com:foo" => "bar", + "example.com:bar" => { + "baz" => JSON::true, + }, + uri => 'https://example.com/v/e1ea21ce03a9', + }, + }; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - '@type' => 'Event', - title => 'test', - start => '2024-04-29T09:00:00', - timeZone => 'Europe/Berlin', - virtualLocations => $virtualLocations, - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event1'], - properties => ['virtualLocations'], - }, 'R2'], - ]); + '@type' => 'Event', + title => 'test', + start => '2024-04-29T09:00:00', + timeZone => 'Europe/Berlin', + virtualLocations => $virtualLocations, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event1'], + properties => ['virtualLocations'], + }, + 'R2' + ], + ]); - $self->assert_deep_equals($virtualLocations, - $res->[1][1]{list}[0]{virtualLocations}); + $self->assert_deep_equals($virtualLocations, + $res->[1][1]{list}[0]{virtualLocations}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_writeown b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_writeown index fff2e6b927..fd2db96e07 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_writeown +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_writeown @@ -2,174 +2,189 @@ use Cassandane::Tiny; sub test_calendarevent_set_writeown - :needs_component_jmap :min_version_0_0 :max_version_0_0 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : needs_component_jmap : min_version_0_0 : max_version_0_0 { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Create sharee user"; - my $admin = $self->{adminstore}->get_client(); - $self->{instance}->create_user("sharee"); - my $service = $self->{instance}->get_service("http"); - my $shareeJmap = Mail::JMAPTalk->new( - user => 'sharee', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $shareeJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/calendars', - 'urn:ietf:params:jmap:calendars', - ]); + xlog "Create sharee user"; + my $admin = $self->{adminstore}->get_client(); + $self->{instance}->create_user("sharee"); + my $service = $self->{instance}->get_service("http"); + my $shareeJmap = Mail::JMAPTalk->new( + user => 'sharee', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $shareeJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'https://cyrusimap.org/ns/jmap/calendars', + 'urn:ietf:params:jmap:calendars', + ]); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - sharee => { - mayReadItems => JSON::true, - mayWriteOwn => JSON::true, - mayUpdatePrivate => JSON::true, - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + sharee => { + mayReadItems => JSON::true, + mayWriteOwn => JSON::true, + mayUpdatePrivate => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Create events"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventCassOwner => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - title => 'eventCassOwner', - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - part1 => { - '@type' => 'Participant', - sendTo => { - imip => 'mailto:part1@local', - }, - roles => { - attendee => JSON::true, - }, - }, - }, - start => '2021-01-01T01:00:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - }, - eventShareeOwner => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - title => 'eventShareeOwner', - replyTo => { - imip => 'mailto:sharee@example.com', - }, - participants => { - part1 => { - '@type' => 'Participant', - sendTo => { - imip => 'mailto:part1@local', - }, - roles => { - attendee => JSON::true, - }, - }, - }, - start => '2021-01-01T01:00:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - }, - eventNoOwner => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - title => 'eventNoOwner', - start => '2021-01-02T01:00:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - }, + xlog "Create events"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + eventCassOwner => { + calendarIds => { + 'Default' => JSON::true, }, - }, 'R1'], - ]); - my $eventCassOwner = $res->[0][1]{created}{eventCassOwner}{id}; - $self->assert_not_null($eventCassOwner); - my $eventShareeOwner = $res->[0][1]{created}{eventShareeOwner}{id}; - $self->assert_not_null($eventShareeOwner); - my $eventNoOwner = $res->[0][1]{created}{eventNoOwner}{id}; - $self->assert_not_null($eventNoOwner); - - xlog "Update private event properties as sharee"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventCassOwner => { - color => 'pink', - }, - $eventShareeOwner => { - color => 'pink', + '@type' => 'Event', + title => 'eventCassOwner', + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + part1 => { + '@type' => 'Participant', + sendTo => { + imip => 'mailto:part1@local', }, - $eventNoOwner => { - color => 'pink', + roles => { + attendee => JSON::true, }, + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventCassOwner}); - $self->assert(exists $res->[0][1]{updated}{$eventShareeOwner}); - $self->assert(exists $res->[0][1]{updated}{$eventNoOwner}); - - xlog "Update non-private event properties as sharee"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - update => { - $eventCassOwner => { - title => 'eventCassOwnerUpdated', - }, - $eventShareeOwner => { - title => 'eventShareeOwnerUpdated', + start => '2021-01-01T01:00:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + eventShareeOwner => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + title => 'eventShareeOwner', + replyTo => { + imip => 'mailto:sharee@example.com', + }, + participants => { + part1 => { + '@type' => 'Participant', + sendTo => { + imip => 'mailto:part1@local', }, - $eventNoOwner => { - title => 'eventNoOwnerUpdated', + roles => { + attendee => JSON::true, }, + }, }, - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', - $res->[0][1]{notUpdated}{$eventCassOwner}{type}); - $self->assert(exists $res->[0][1]{updated}{$eventShareeOwner}); - $self->assert(exists $res->[0][1]{updated}{$eventNoOwner}); + start => '2021-01-01T01:00:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + eventNoOwner => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + title => 'eventNoOwner', + start => '2021-01-02T01:00:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + ]); + my $eventCassOwner = $res->[0][1]{created}{eventCassOwner}{id}; + $self->assert_not_null($eventCassOwner); + my $eventShareeOwner = $res->[0][1]{created}{eventShareeOwner}{id}; + $self->assert_not_null($eventShareeOwner); + my $eventNoOwner = $res->[0][1]{created}{eventNoOwner}{id}; + $self->assert_not_null($eventNoOwner); + + xlog "Update private event properties as sharee"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventCassOwner => { + color => 'pink', + }, + $eventShareeOwner => { + color => 'pink', + }, + $eventNoOwner => { + color => 'pink', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventCassOwner}); + $self->assert(exists $res->[0][1]{updated}{$eventShareeOwner}); + $self->assert(exists $res->[0][1]{updated}{$eventNoOwner}); + + xlog "Update non-private event properties as sharee"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + update => { + $eventCassOwner => { + title => 'eventCassOwnerUpdated', + }, + $eventShareeOwner => { + title => 'eventShareeOwnerUpdated', + }, + $eventNoOwner => { + title => 'eventNoOwnerUpdated', + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$eventCassOwner}{type}); + $self->assert(exists $res->[0][1]{updated}{$eventShareeOwner}); + $self->assert(exists $res->[0][1]{updated}{$eventNoOwner}); - xlog "Destroy events as sharee"; - $res = $shareeJmap->CallMethods([ - ['CalendarEvent/set', { - accountId => 'cassandane', - destroy => [ - $eventCassOwner, - $eventShareeOwner, - $eventNoOwner, - ], - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', - $res->[0][1]{notDestroyed}{$eventCassOwner}{type}); - $self->assert(grep /$eventShareeOwner/, @{$res->[0][1]{destroyed}}); - $self->assert(grep /$eventNoOwner/, @{$res->[0][1]{destroyed}}); + xlog "Destroy events as sharee"; + $res = $shareeJmap->CallMethods([ + [ + 'CalendarEvent/set', + { + accountId => 'cassandane', + destroy => [ $eventCassOwner, $eventShareeOwner, $eventNoOwner, ], + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notDestroyed}{$eventCassOwner}{type}); + $self->assert(grep /$eventShareeOwner/, @{ $res->[0][1]{destroyed} }); + $self->assert(grep /$eventNoOwner/, @{ $res->[0][1]{destroyed} }); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_writeown_caldav b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_writeown_caldav index 363032a31f..d9639b63d7 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_writeown_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_set_writeown_caldav @@ -2,45 +2,48 @@ use Cassandane::Tiny; sub test_calendarevent_set_writeown_caldav - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create sharee user"; - my $admin = $self->{adminstore}->get_client(); - $self->{instance}->create_user("sharee"); - my $service = $self->{instance}->get_service("http"); - my $shareeCaldav = Net::CalDAVTalk->new( - user => "sharee", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + xlog "Create sharee user"; + my $admin = $self->{adminstore}->get_client(); + $self->{instance}->create_user("sharee"); + my $service = $self->{instance}->get_service("http"); + my $shareeCaldav = Net::CalDAVTalk->new( + user => "sharee", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - sharee => { - mayReadItems => JSON::true, - mayWriteOwn => JSON::true, - mayUpdatePrivate => JSON::true, - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + sharee => { + mayReadItems => JSON::true, + mayWriteOwn => JSON::true, + mayUpdatePrivate => JSON::true, + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Create event with cassandane owner"; - my $cassOwnerIcal = <<'EOF'; + xlog "Create event with cassandane owner"; + my $cassOwnerIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -58,12 +61,13 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $res = $caldav->Request('PUT', - '/dav/calendars/user/cassandane/Default/cassowner.ics', - $cassOwnerIcal, 'Content-Type' => 'text/calendar'); + $res + = $caldav->Request('PUT', + '/dav/calendars/user/cassandane/Default/cassowner.ics', + $cassOwnerIcal, 'Content-Type' => 'text/calendar'); - xlog "Create event with sharee owner"; - my $shareeOwnerIcal = <<'EOF'; + xlog "Create event with sharee owner"; + my $shareeOwnerIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -81,12 +85,12 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/sharee/cassandane.Default/shareeowner.ics', - $shareeOwnerIcal, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', + '/dav/calendars/user/sharee/cassandane.Default/shareeowner.ics', + $shareeOwnerIcal, 'Content-Type' => 'text/calendar'); - xlog "Create event with no owner"; - my $noOwnerIcal = <<'EOF'; + xlog "Create event with no owner"; + my $noOwnerIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -102,12 +106,12 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', - '/dav/calendars/user/sharee/cassandane.Default/noowner.ics', - $noOwnerIcal, 'Content-Type' => 'text/calendar'); + $caldav->Request('PUT', + '/dav/calendars/user/sharee/cassandane.Default/noowner.ics', + $noOwnerIcal, 'Content-Type' => 'text/calendar'); - xlog "Update event with sharee owner as sharee"; - $shareeOwnerIcal = <<'EOF'; + xlog "Update event with sharee owner as sharee"; + $shareeOwnerIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -125,12 +129,12 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $shareeCaldav->Request('PUT', - '/dav/calendars/user/sharee/cassandane.Default/shareeowner.ics', - $shareeOwnerIcal, 'Content-Type' => 'text/calendar'); + $shareeCaldav->Request('PUT', + '/dav/calendars/user/sharee/cassandane.Default/shareeowner.ics', + $shareeOwnerIcal, 'Content-Type' => 'text/calendar'); - xlog "Update event with no owner as sharee"; - $noOwnerIcal = <<'EOF'; + xlog "Update event with no owner as sharee"; + $noOwnerIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -146,12 +150,12 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $shareeCaldav->Request('PUT', - '/dav/calendars/user/sharee/cassandane.Default/noowner.ics', - $noOwnerIcal, 'Content-Type' => 'text/calendar'); + $shareeCaldav->Request('PUT', + '/dav/calendars/user/sharee/cassandane.Default/noowner.ics', + $noOwnerIcal, 'Content-Type' => 'text/calendar'); - xlog "Update per-user property as sharee"; - $cassOwnerIcal = <<'EOF'; + xlog "Update per-user property as sharee"; + $cassOwnerIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -170,12 +174,12 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $shareeCaldav->Request('PUT', - '/dav/calendars/user/sharee/cassandane.Default/cassowner.ics', - $cassOwnerIcal, 'Content-Type' => 'text/calendar'); + $shareeCaldav->Request('PUT', + '/dav/calendars/user/sharee/cassandane.Default/cassowner.ics', + $cassOwnerIcal, 'Content-Type' => 'text/calendar'); - xlog "Update property as sharee"; - $cassOwnerIcal = <<'EOF'; + xlog "Update property as sharee"; + $cassOwnerIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN @@ -193,37 +197,40 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - # annoyingly CalDAV talk aborts for HTTP status >= 400 - my $href = '/dav/calendars/user/sharee/cassandane.Default/cassowner.ics'; - my $rawResponse = $shareeCaldav->{ua}->request('PUT', - $shareeCaldav->request_url($href), { - content => $cassOwnerIcal, - headers => { - 'Content-Type' => 'text/calendar', - 'Authorization' => $shareeCaldav->auth_header(), - }, - }, - ); - $self->assert_num_equals(403, $rawResponse->{status}); + # annoyingly CalDAV talk aborts for HTTP status >= 400 + my $href = '/dav/calendars/user/sharee/cassandane.Default/cassowner.ics'; + my $rawResponse = $shareeCaldav->{ua}->request( + 'PUT', + $shareeCaldav->request_url($href), + { + content => $cassOwnerIcal, + headers => { + 'Content-Type' => 'text/calendar', + 'Authorization' => $shareeCaldav->auth_header(), + }, + }, + ); + $self->assert_num_equals(403, $rawResponse->{status}); - xlog "Delete event with sharee owner as sharee"; - $shareeCaldav->Request('DELETE', - '/dav/calendars/user/sharee/cassandane.Default/shareeowner.ics'); + xlog "Delete event with sharee owner as sharee"; + $shareeCaldav->Request('DELETE', + '/dav/calendars/user/sharee/cassandane.Default/shareeowner.ics'); - xlog "Delete event with no owner as sharee"; - $shareeCaldav->Request('DELETE', - '/dav/calendars/user/sharee/cassandane.Default/noowner.ics'); - - xlog "Delete event with cassandane owner as sharee"; - $href = '/dav/calendars/user/sharee/cassandane.Default/cassowner.ics'; - $rawResponse = $shareeCaldav->{ua}->request('DELETE', - $shareeCaldav->request_url($href), { - headers => { - 'Authorization' => $shareeCaldav->auth_header(), - }, - }, - ); - $self->assert_num_equals(403, $rawResponse->{status}); + xlog "Delete event with no owner as sharee"; + $shareeCaldav->Request('DELETE', + '/dav/calendars/user/sharee/cassandane.Default/noowner.ics'); + xlog "Delete event with cassandane owner as sharee"; + $href = '/dav/calendars/user/sharee/cassandane.Default/cassowner.ics'; + $rawResponse = $shareeCaldav->{ua}->request( + 'DELETE', + $shareeCaldav->request_url($href), + { + headers => { + 'Authorization' => $shareeCaldav->auth_header(), + }, + }, + ); + $self->assert_num_equals(403, $rawResponse->{status}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarevent_utcstart_customtz b/cassandane/tiny-tests/JMAPCalendars/calendarevent_utcstart_customtz index c7064a217b..edde89a632 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarevent_utcstart_customtz +++ b/cassandane/tiny-tests/JMAPCalendars/calendarevent_utcstart_customtz @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_calendarevent_utcstart_customtz - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - # Set custom calendar timezone. DST starts on December 1 at 2am. - my $CalendarId = $CalDAV->NewCalendar({name => 'mycalendar'}); - $self->assert_not_null($CalendarId); - my $proppatchXml = <NewCalendar({ name => 'mycalendar' }); + $self->assert_not_null($CalendarId); + my $proppatchXml = < @@ -45,59 +44,77 @@ END:VCALENDAR EOF - $CalDAV->Request('PROPPATCH', "/dav/calendars/user/cassandane/Default", - $proppatchXml, 'Content-Type' => 'text/xml'); + $CalDAV->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane/Default", + $proppatchXml, 'Content-Type' => 'text/xml' + ); - # Create floating time event. - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2019-11-30T23:30:00", - duration => "PT6H", - timeZone => undef, - }, + # Create floating time event. + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#1'], - properties => ['utcStart', 'utcEnd', 'timeZone'], - }, 'R2'] - ]); - my $eventId1 = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId1); - my $event = $res->[1][1]{list}[0]; - $self->assert_not_null($event); + title => "event1", + start => "2019-11-30T23:30:00", + duration => "PT6H", + timeZone => undef, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#1'], + properties => [ 'utcStart', 'utcEnd', 'timeZone' ], + }, + 'R2' + ] + ]); + my $eventId1 = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId1); + my $event = $res->[1][1]{list}[0]; + $self->assert_not_null($event); - # Floating event time falls back to custom calendar time zone. - $self->assert_str_equals('2019-12-01T07:30:00Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-01T12:30:00Z', $event->{utcEnd}); + # Floating event time falls back to custom calendar time zone. + $self->assert_str_equals('2019-12-01T07:30:00Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-01T12:30:00Z', $event->{utcEnd}); - # Assert event updates. - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId1 => { - utcStart => "2019-12-01T06:30:00Z", - }, - }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId1], - properties => ['start', 'utcStart', 'utcEnd', 'timeZone', 'duration'], - }, 'R2'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId1}); + # Assert event updates. + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId1 => { + utcStart => "2019-12-01T06:30:00Z", + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId1], + properties => [ 'start', 'utcStart', 'utcEnd', 'timeZone', 'duration' ], + }, + 'R2' + ] + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId1}); - $event = $res->[1][1]{list}[0]; - $self->assert_str_equals('2019-11-30T22:30:00', $event->{start}); - $self->assert_str_equals('2019-12-01T06:30:00Z', $event->{utcStart}); - $self->assert_str_equals('2019-12-01T11:30:00Z', $event->{utcEnd}); - $self->assert_null($event->{timeZone}); - $self->assert_str_equals('PT6H', $event->{duration}); + $event = $res->[1][1]{list}[0]; + $self->assert_str_equals('2019-11-30T22:30:00', $event->{start}); + $self->assert_str_equals('2019-12-01T06:30:00Z', $event->{utcStart}); + $self->assert_str_equals('2019-12-01T11:30:00Z', $event->{utcEnd}); + $self->assert_null($event->{timeZone}); + $self->assert_str_equals('PT6H', $event->{duration}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_aclcheck b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_aclcheck index b914f9fbb7..da19b5dedf 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_aclcheck +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_aclcheck @@ -2,110 +2,130 @@ use Cassandane::Tiny; sub test_calendareventnotification_aclcheck - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - sharedCalendar => { - name => 'sharedCalendar', - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayWriteOwn => JSON::true, - mayAdmin => JSON::false - }, - }, - }, - unsharedCalendar => { - name => 'unsharedCalendar', - }, + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + sharedCalendar => { + name => 'sharedCalendar', + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayWriteOwn => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ]); - my $sharedCalendarId = $res->[0][1]{created}{sharedCalendar}{id}; - $self->assert_not_null($sharedCalendarId); - my $unsharedCalendarId = $res->[0][1]{created}{unsharedCalendar}{id}; - $self->assert_not_null($unsharedCalendarId); + }, + unsharedCalendar => { + name => 'unsharedCalendar', + }, + }, + }, + 'R1' + ], + ]); + my $sharedCalendarId = $res->[0][1]{created}{sharedCalendar}{id}; + $self->assert_not_null($sharedCalendarId); + my $unsharedCalendarId = $res->[0][1]{created}{unsharedCalendar}{id}; + $self->assert_not_null($unsharedCalendarId); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_deep_equals([], $res->[0][1]{list}); - my $state = $res->[0][1]{state}; + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_deep_equals([], $res->[0][1]{list}); + my $state = $res->[0][1]{state}; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - sharedEvent => { - title => 'sharedEvent', - calendarIds => { - $sharedCalendarId => JSON::true, - }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', - }, - unsharedEvent => { - title => 'unsharedEvent', - calendarIds => { - $unsharedCalendarId => JSON::true, - }, - start => '2012-02-02T04:05:06', - duration => 'PT1H', - }, + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + sharedEvent => { + title => 'sharedEvent', + calendarIds => { + $sharedCalendarId => JSON::true, }, - }, 'R1'], - ]); - my $sharedEventId = $res->[0][1]{created}{sharedEvent}{id}; - $self->assert_not_null($sharedEventId); - my $unsharedEventId = $res->[0][1]{created}{unsharedEvent}{id}; - $self->assert_not_null($unsharedEventId); + start => '2011-01-01T04:05:06', + duration => 'PT1H', + }, + unsharedEvent => { + title => 'unsharedEvent', + calendarIds => { + $unsharedCalendarId => JSON::true, + }, + start => '2012-02-02T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + ]); + my $sharedEventId = $res->[0][1]{created}{sharedEvent}{id}; + $self->assert_not_null($sharedEventId); + my $unsharedEventId = $res->[0][1]{created}{unsharedEvent}{id}; + $self->assert_not_null($unsharedEventId); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - properties => ['calendarEventId'], - }, 'R1'], - ['CalendarEventNotification/query', { - accountId => 'cassandane', - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals($sharedEventId, $res->[0][1]{list}[0]{calendarEventId}); - my $notifId = $res->[0][1]{list}[0]{id}; - $self->assert_deep_equals([$notifId], $res->[1][1]{ids}); + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + properties => ['calendarEventId'], + }, + 'R1' + ], + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals($sharedEventId, + $res->[0][1]{list}[0]{calendarEventId}); + my $notifId = $res->[0][1]{list}[0]{id}; + $self->assert_deep_equals([$notifId], $res->[1][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_caldav b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_caldav index cb719c3c44..5b41d7de5d 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_caldav @@ -2,68 +2,75 @@ use Cassandane::Tiny; sub test_calendareventnotification_caldav - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admin = $self->{adminstore}->get_client(); - - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayWriteOwn => JSON::true, - mayAdmin => JSON::false - }, - }, - }, + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admin = $self->{adminstore}->get_client(); + + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); + + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayWriteOwn => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - - xlog "User creates an event"; - - my $ical = <assert(exists $res->[0][1]{updated}{Default}); + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + + xlog "User creates an event"; + + my $ical = <Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('created', $res->[0][1]{list}[0]{type}); - $self->assert_str_equals('cassandane', - $res->[0][1]{list}[0]{changedBy}{calendarPrincipalId}); - $self->assert_not_null($res->[0][1]{list}[0]{event}); - - xlog "User updates an event"; - - $ical = <Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('created', $res->[0][1]{list}[0]{type}); + $self->assert_str_equals('cassandane', + $res->[0][1]{list}[0]{changedBy}{calendarPrincipalId}); + $self->assert_not_null($res->[0][1]{list}[0]{event}); + + xlog "User updates an event"; + + $ical = <Request('PUT', - '/dav/calendars/user/cassandane/Default/test.ics', - $ical, 'Content-Type' => 'text/calendar'); - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); - my %notifs = map { $_->{type} => $_ } @{$res->[0][1]{list}}; - $self->assert_not_null($notifs{'updated'}->{event}); - $self->assert_not_null($notifs{'updated'}->{eventPatch}); - $self->assert_str_equals('cassandane', - $notifs{'updated'}->{changedBy}{calendarPrincipalId}); - - xlog "User deletes an event"; - - $caldav->Request('DELETE', - '/dav/calendars/user/cassandane/Default/test.ics'); - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('destroyed', $res->[0][1]{list}[0]{type}); - $self->assert_str_equals('cassandane', - $res->[0][1]{list}[0]{changedBy}{calendarPrincipalId}); - $self->assert_not_null($res->[0][1]{list}[0]{event}); - - xlog "iTIP handler creates an event"; - - $ical = <Request( + 'PUT', '/dav/calendars/user/cassandane/Default/test.ics', + $ical, 'Content-Type' => 'text/calendar' + ); + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); + my %notifs = map { $_->{type} => $_ } @{ $res->[0][1]{list} }; + $self->assert_not_null($notifs{'updated'}->{event}); + $self->assert_not_null($notifs{'updated'}->{eventPatch}); + $self->assert_str_equals('cassandane', + $notifs{'updated'}->{changedBy}{calendarPrincipalId}); + + xlog "User deletes an event"; + + $caldav->Request('DELETE', '/dav/calendars/user/cassandane/Default/test.ics'); + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('destroyed', $res->[0][1]{list}[0]{type}); + $self->assert_str_equals('cassandane', + $res->[0][1]{list}[0]{changedBy}{calendarPrincipalId}); + $self->assert_not_null($res->[0][1]{list}[0]{event}); + + xlog "iTIP handler creates an event"; + + $ical = <Request('PUT', - '/dav/calendars/user/cassandane/Default/testitip.ics', - $ical, 'Content-Type' => 'text/calendar', - 'Schedule-Sender-Address' => 'itipsender@local', - 'Schedule-Sender-Name' => '=?utf-8?q?iTIP_=E2=98=BA_Sender?=', - ); - - $res = $jmap->CallMethods([ - ['CalendarEventNotification/get', { - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('created', $res->[0][1]{list}[0]{type}); - $self->assert_str_equals('itipsender@local', - $res->[0][1]{list}[0]{changedBy}{email}); - $self->assert_str_equals("iTIP \N{WHITE SMILING FACE} Sender", # assert RFC0247 support - $res->[0][1]{list}[0]{changedBy}{name}); - $self->assert_null($res->[0][1]{list}[0]{changedBy}{calendarPrincipalId}); - - xlog "iTIP handler deletes an event"; - - $caldav->Request('DELETE', - '/dav/calendars/user/cassandane/Default/testitip.ics', - undef, - 'Schedule-Sender-Address' => 'itipdeleter@local', - 'Schedule-Sender-Name' => 'iTIP Deleter'); - - $res = $jmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('destroyed', $res->[0][1]{list}[0]{type}); - $self->assert_str_equals('itipdeleter@local', - $res->[0][1]{list}[0]{changedBy}{email}); - $self->assert_str_equals('iTIP Deleter', - $res->[0][1]{list}[0]{changedBy}{name}); - $self->assert_null($res->[0][1]{list}[0]{changedBy}{calendarPrincipalId}); + $caldav->Request( + 'PUT', + '/dav/calendars/user/cassandane/Default/testitip.ics', + $ical, + 'Content-Type' => 'text/calendar', + 'Schedule-Sender-Address' => 'itipsender@local', + 'Schedule-Sender-Name' => '=?utf-8?q?iTIP_=E2=98=BA_Sender?=', + ); + + $res = $jmap->CallMethods([ [ 'CalendarEventNotification/get', {}, 'R1' ], ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('created', $res->[0][1]{list}[0]{type}); + $self->assert_str_equals('itipsender@local', + $res->[0][1]{list}[0]{changedBy}{email}); + $self->assert_str_equals( + "iTIP \N{WHITE SMILING FACE} Sender", # assert RFC0247 support + $res->[0][1]{list}[0]{changedBy}{name} + ); + $self->assert_null($res->[0][1]{list}[0]{changedBy}{calendarPrincipalId}); + + xlog "iTIP handler deletes an event"; + + $caldav->Request( + 'DELETE', + '/dav/calendars/user/cassandane/Default/testitip.ics', + undef, + 'Schedule-Sender-Address' => 'itipdeleter@local', + 'Schedule-Sender-Name' => 'iTIP Deleter' + ); + + $res = $jmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('destroyed', $res->[0][1]{list}[0]{type}); + $self->assert_str_equals('itipdeleter@local', + $res->[0][1]{list}[0]{changedBy}{email}); + $self->assert_str_equals('iTIP Deleter', + $res->[0][1]{list}[0]{changedBy}{name}); + $self->assert_null($res->[0][1]{list}[0]{changedBy}{calendarPrincipalId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_changes b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_changes index 4d2fc03b2a..c02fee37ff 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_changes +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_changes @@ -2,83 +2,87 @@ use Cassandane::Tiny; sub test_calendareventnotification_changes - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - # Need to share calendar, otherwise no notification is created + # Need to share calendar, otherwise no notification is created - my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + my $admin = $self->{adminstore}->get_client(); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayWriteOwn => JSON::true, - mayAdmin => JSON::false - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayWriteOwn => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - $res = $jmap->CallMethods([ - ['CalendarEventNotification/get', { - }, 'R1'], - ]); - $self->assert_deep_equals([], $res->[0][1]{list}); - my $state = $res->[0][1]{state}; - $self->assert_not_null($state); + $res = $jmap->CallMethods([ [ 'CalendarEventNotification/get', {}, 'R1' ], ]); + $self->assert_deep_equals([], $res->[0][1]{list}); + my $state = $res->[0][1]{state}; + $self->assert_not_null($state); - $res = $jmap->CallMethods([ - ['CalendarEventNotification/changes', { - sinceState => $state, - }, 'R1'], - ]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $res = $jmap->CallMethods([ + [ + 'CalendarEventNotification/changes', + { + sinceState => $state, + }, + 'R1' + ], + ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); - xlog "create notification that cassandane will see"; + xlog "create notification that cassandane will see"; - my $ical = <Request('PUT', - '/dav/calendars/user/cassandane/Default/testitip.ics', - $ical, 'Content-Type' => 'text/calendar', - 'Schedule-Sender-Address' => 'itipsender@local', - 'Schedule-Sender-Name' => 'iTIP Sender', - ); + $caldav->Request( + 'PUT', + '/dav/calendars/user/cassandane/Default/testitip.ics', + $ical, + 'Content-Type' => 'text/calendar', + 'Schedule-Sender-Address' => 'itipsender@local', + 'Schedule-Sender-Name' => 'iTIP Sender', + ); - $res = $jmap->CallMethods([ - ['CalendarEventNotification/changes', { - sinceState => $state, - }, 'R1'], - ]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $res = $jmap->CallMethods([ + [ + 'CalendarEventNotification/changes', + { + sinceState => $state, + }, + 'R1' + ], + ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); - my $notifId = $res->[0][1]{created}[0]; - my $oldState = $state; - $state = $res->[0][1]{newState}; + my $notifId = $res->[0][1]{created}[0]; + my $oldState = $state; + $state = $res->[0][1]{newState}; - $res = $jmap->CallMethods([ - ['CalendarEventNotification/set', { - destroy => [$notifId], - }, 'R1'], - ]); - $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); + $res = $jmap->CallMethods([ + [ + 'CalendarEventNotification/set', + { + destroy => [$notifId], + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); - $res = $jmap->CallMethods([ - ['CalendarEventNotification/changes', { - sinceState => $state, - }, 'R1'], - ]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); + $res = $jmap->CallMethods([ + [ + 'CalendarEventNotification/changes', + { + sinceState => $state, + }, + 'R1' + ], + ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_changes_shared b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_changes_shared index 48294be102..067b82e06d 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_changes_shared +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_changes_shared @@ -2,73 +2,80 @@ use Cassandane::Tiny; sub test_calendareventnotification_changes_shared - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayWriteOwn => JSON::true, - mayAdmin => JSON::false - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayWriteOwn => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'] - ]); - my $state = $res->[0][1]{state}; - $self->assert_not_null($state); + $res = $manjmap->CallMethods([ [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ] ]); + my $state = $res->[0][1]{state}; + $self->assert_not_null($state); - # This should work, but it currently doesn't. - # At least we can check for the correct error. + # This should work, but it currently doesn't. + # At least we can check for the correct error. - $res = $jmap->CallMethods([ - ['CalendarEventNotification/queryChanges', { - accountId => 'cassandane', - sinceQueryState => $state, - }, 'R1'] - ]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + $res = $jmap->CallMethods([ [ + 'CalendarEventNotification/queryChanges', + { + accountId => 'cassandane', + sinceQueryState => $state, + }, + 'R1' + ] ]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_get b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_get index d2bd6b7fe0..6150d0adde 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_get +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_get @@ -2,134 +2,158 @@ use Cassandane::Tiny; sub test_calendareventnotification_get - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - xlog "Create event"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayWriteOwn => JSON::true, - mayAdmin => JSON::false - }, - }, - }, + xlog "Create event"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayWriteOwn => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ['CalendarEvent/set', { - create => { - event1 => { - title => 'event1', - calendarIds => { - Default => JSON::true, - }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', - }, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + event1 => { + title => 'event1', + calendarIds => { + Default => JSON::true, }, - }, 'R2'], - ['CalendarEventNotification/get', { - }, 'R3'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - my $eventId = $res->[1][1]{created}{event1}{id}; - $self->assert_not_null($eventId); - # Event creator is not notified. - $self->assert_num_equals(0, scalar @{$res->[2][1]{list}}); + start => '2011-01-01T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R2' + ], + [ 'CalendarEventNotification/get', {}, 'R3' ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + my $eventId = $res->[1][1]{created}{event1}{id}; + $self->assert_not_null($eventId); + # Event creator is not notified. + $self->assert_num_equals(0, scalar @{ $res->[2][1]{list} }); - # Event sharee is notified. - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals('created', $res->[0][1]{list}[0]{type}); - my $notif1 = $res->[0][1]{list}[0]{id}; + # Event sharee is notified. + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals('created', $res->[0][1]{list}[0]{type}); + my $notif1 = $res->[0][1]{list}[0]{id}; - xlog "Update event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'event1updated', - }, - }, - }, 'R1'], - ['CalendarEventNotification/get', { - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - # Event updater is not notified. - $self->assert_num_equals(0, scalar @{$res->[1][1]{list}}); - # Event sharee is notified. - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); + xlog "Update event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'event1updated', + }, + }, + }, + 'R1' + ], + [ 'CalendarEventNotification/get', {}, 'R2' ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + # Event updater is not notified. + $self->assert_num_equals(0, scalar @{ $res->[1][1]{list} }); + # Event sharee is notified. + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); - my %notifs = map { $_->{type} => $_ } @{$res->[0][1]{list}}; - $self->assert_str_equals($notif1, $notifs{created}{id}); - my $notif2 = $notifs{updated}{id}; - $self->assert_str_not_equals($notif2, $notif1); + my %notifs = map { $_->{type} => $_ } @{ $res->[0][1]{list} }; + $self->assert_str_equals($notif1, $notifs{created}{id}); + my $notif2 = $notifs{updated}{id}; + $self->assert_str_not_equals($notif2, $notif1); - xlog "Destroy event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [$eventId], - }, 'R1'], - ['CalendarEventNotification/get', { - }, 'R2'], - ]); - $self->assert_deep_equals([$eventId], $res->[0][1]{destroyed}); - # Event destroyer is not notified. - $self->assert_num_equals(0, scalar @{$res->[2][1]{list}}); + xlog "Destroy event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + destroy => [$eventId], + }, + 'R1' + ], + [ 'CalendarEventNotification/get', {}, 'R2' ], + ]); + $self->assert_deep_equals([$eventId], $res->[0][1]{destroyed}); + # Event destroyer is not notified. + $self->assert_num_equals(0, scalar @{ $res->[2][1]{list} }); - # Event sharee only sees destroy notification. - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_str_not_equals($notif1, $res->[0][1]{list}[0]{id}); - $self->assert_str_not_equals($notif2, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals('destroyed', $res->[0][1]{list}[0]{type}); + # Event sharee only sees destroy notification. + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_str_not_equals($notif1, $res->[0][1]{list}[0]{id}); + $self->assert_str_not_equals($notif2, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals('destroyed', $res->[0][1]{list}[0]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_get_no_sharee b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_get_no_sharee index e2579c4b51..3a614ff63c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_get_no_sharee +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_get_no_sharee @@ -2,60 +2,72 @@ use Cassandane::Tiny; sub test_calendareventnotification_get_no_sharee - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); - - $admin->create('user.cassandane.#jmapnotification') or die; - $admin->setacl('user.cassandane.#jmapnotification', - 'cassandane' => 'lrswipkxtecdan') or die; - - xlog "Create event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - title => 'event1', - calendarIds => { - Default => JSON::true, - }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', - }, - }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); - - $self->assert_num_equals(0, - $admin->message_count('user.cassandane.#jmapnotification')); - - xlog "Update event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - title => 'event1Updated', - }, + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); + + $admin->create('user.cassandane.#jmapnotification') or die; + $admin->setacl('user.cassandane.#jmapnotification', + 'cassandane' => 'lrswipkxtecdan') + or die; + + xlog "Create event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + title => 'event1', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - $self->assert_num_equals(0, - $admin->message_count('user.cassandane.#jmapnotification')); - - xlog "Destroy event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - destroy => [ $eventId ], - }, 'R1'], - ]); - $self->assert_deep_equals([$eventId], $res->[0][1]{destroyed}); - - $self->assert_num_equals(0, - $admin->message_count('user.cassandane.#jmapnotification')); + start => '2011-01-01T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); + + $self->assert_num_equals(0, + $admin->message_count('user.cassandane.#jmapnotification')); + + xlog "Update event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + title => 'event1Updated', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + $self->assert_num_equals(0, + $admin->message_count('user.cassandane.#jmapnotification')); + + xlog "Destroy event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + destroy => [$eventId], + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$eventId], $res->[0][1]{destroyed}); + + $self->assert_num_equals(0, + $admin->message_count('user.cassandane.#jmapnotification')); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_imip b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_imip index 4368c898e5..a997c470d3 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_imip +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_imip @@ -2,13 +2,14 @@ use Cassandane::Tiny; sub test_calendareventnotification_imip - :needs_component_sieve :needs_component_httpd :needs_component_jmap :min_version_3_5 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{jmap}; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -49,28 +50,28 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + xlog $self, "Deliver iMIP invite"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { properties => ['id'] }, 'R1'], - ['CalendarEventNotification/get', { }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); + my $res = $jmap->CallMethods([ + [ 'CalendarEvent/get', { properties => ['id'] }, 'R1' ], + [ 'CalendarEventNotification/get', {}, 'R2' ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); - $self->assert_str_equals('sender@example.net', - $res->[1][1]{list}[0]{changedBy}{email}); - $self->assert_str_equals('Sally Sender', - $res->[1][1]{list}[0]{changedBy}{name}); - $self->assert_str_equals('created', $res->[1][1]{list}[0]{type}); + $self->assert_str_equals('sender@example.net', + $res->[1][1]{list}[0]{changedBy}{email}); + $self->assert_str_equals('Sally Sender', + $res->[1][1]{list}[0]{changedBy}{name}); + $self->assert_str_equals('created', $res->[1][1]{list}[0]{type}); - my $state = $res->[1][1]{state}; - $self->assert_not_null($state); + my $state = $res->[1][1]{state}; + $self->assert_not_null($state); - # UPDATE + # UPDATE - $imip = <<'EOF'; + $imip = <<'EOF'; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -98,38 +99,46 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP update"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { properties => ['id'] }, 'R1'], - ['CalendarEventNotification/changes', { - sinceState => $state }, - 'R2'], - ['CalendarEventNotification/get', { - '#ids' => { - resultOf => 'R2', - name => 'CalendarEventNotification/changes', - path => '/created' - }, - }, 'R3'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - - $self->assert_num_equals(1, scalar @{$res->[2][1]{list}}); - - $self->assert_str_equals('sender@example.net', - $res->[2][1]{list}[0]{changedBy}{email}); - $self->assert_str_equals('Sally Sender', - $res->[2][1]{list}[0]{changedBy}{name}); - $self->assert_str_equals('updated', $res->[2][1]{list}[0]{type}); - - $state = $res->[2][1]{state}; - $self->assert_not_null($state); - - # DELETE - - $imip = <<'EOF'; + xlog $self, "Deliver iMIP update"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + + $res = $jmap->CallMethods([ + [ 'CalendarEvent/get', { properties => ['id'] }, 'R1' ], + [ + 'CalendarEventNotification/changes', + { + sinceState => $state + }, + 'R2' + ], + [ + 'CalendarEventNotification/get', + { + '#ids' => { + resultOf => 'R2', + name => 'CalendarEventNotification/changes', + path => '/created' + }, + }, + 'R3' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + + $self->assert_num_equals(1, scalar @{ $res->[2][1]{list} }); + + $self->assert_str_equals('sender@example.net', + $res->[2][1]{list}[0]{changedBy}{email}); + $self->assert_str_equals('Sally Sender', + $res->[2][1]{list}[0]{changedBy}{name}); + $self->assert_str_equals('updated', $res->[2][1]{list}[0]{type}); + + $state = $res->[2][1]{state}; + $self->assert_not_null($state); + + # DELETE + + $imip = <<'EOF'; Date: Thu, 23 Sep 2021 10:06:18 -0400 From: Sally Sender To: Cassandane @@ -157,30 +166,42 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP cancellation"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id'] - }, 'R1'], - ['CalendarEventNotification/changes', { - sinceState => $state }, - 'R2'], - ['CalendarEventNotification/get', { - '#ids' => { - resultOf => 'R2', - name => 'CalendarEventNotification/changes', - path => '/created' - }, - }, 'R3'], - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{list}}); - - $self->assert_str_equals('sender@example.net', - $res->[2][1]{list}[0]{changedBy}{email}); - $self->assert_str_equals('Sally Sender', - $res->[2][1]{list}[0]{changedBy}{name}); - $self->assert_str_equals('destroyed', $res->[2][1]{list}[0]{type}); + xlog $self, "Deliver iMIP cancellation"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => ['id'] + }, + 'R1' + ], + [ + 'CalendarEventNotification/changes', + { + sinceState => $state + }, + 'R2' + ], + [ + 'CalendarEventNotification/get', + { + '#ids' => { + resultOf => 'R2', + name => 'CalendarEventNotification/changes', + path => '/created' + }, + }, + 'R3' + ], + ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{list} }); + + $self->assert_str_equals('sender@example.net', + $res->[2][1]{list}[0]{changedBy}{email}); + $self->assert_str_equals('Sally Sender', + $res->[2][1]{list}[0]{changedBy}{name}); + $self->assert_str_equals('destroyed', $res->[2][1]{list}[0]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_prune b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_prune index 80317d3276..0ea783557c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_prune +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_prune @@ -2,127 +2,158 @@ use Cassandane::Tiny; sub test_calendareventnotification_prune - :min_version_3_9 :needs_component_jmap :JmapMaxCalendarEventNotifs -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : JmapMaxCalendarEventNotifs { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $jmap_max_calendareventnotifs = $self->{instance}->{ - config}->get('jmap_max_calendareventnotifs'); - $self->assert_not_null($jmap_max_calendareventnotifs); + my $jmap_max_calendareventnotifs + = $self->{instance}->{config}->get('jmap_max_calendareventnotifs'); + $self->assert_not_null($jmap_max_calendareventnotifs); - my ($manJmap) = $self->create_user('manifold'); - $manJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + my ($manJmap) = $self->create_user('manifold'); + $manJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - xlog $self, "Share calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayWriteOwn => JSON::true, - mayAdmin => JSON::false - }, - }, - }, + xlog $self, "Share calendar"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayWriteOwn => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog $self, "Create event notification"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - title => 'event1', - calendarIds => { - Default => JSON::true, - }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', - }, + xlog $self, "Create event notification"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + title => 'event1', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{created}{event1}); - - xlog $self, "Get event notification"; - $res = $manJmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - my $notif1Id = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($notif1Id); + start => '2011-01-01T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{event1}); - xlog $self, "Create maximum count of allowed notifications"; - for my $i (2 .. $jmap_max_calendareventnotifs) { - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - "event$i" => { - title => "event$i", - calendarIds => { - Default => JSON::true, - }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', - }, - }, - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{created}{"event$i"}); - } + xlog $self, "Get event notification"; + $res = $manJmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + my $notif1Id = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($notif1Id); - xlog $self, "Get event notifications"; - $res = $manJmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - properties => ['id'], - }, 'R1'], + xlog $self, "Create maximum count of allowed notifications"; + for my $i (2 .. $jmap_max_calendareventnotifs) { + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + "event$i" => { + title => "event$i", + calendarIds => { + Default => JSON::true, + }, + start => '2011-01-01T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], ]); - $self->assert_num_equals($jmap_max_calendareventnotifs, scalar @{$res->[0][1]{list}}); + $self->assert_not_null($res->[0][1]{created}{"event$i"}); + } - xlog $self, "Assert first event notification exists"; - $self->assert_equals(1, scalar grep { $_->{id} eq $notif1Id } @{$res->[0][1]{list}}); + xlog $self, "Get event notifications"; + $res = $manJmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + properties => ['id'], + }, + 'R1' + ], + ]); + $self->assert_num_equals($jmap_max_calendareventnotifs, + scalar @{ $res->[0][1]{list} }); - xlog $self, "Create one more event notification"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - eventX => { - title => 'eventX', - calendarIds => { - Default => JSON::true, - }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', - }, + xlog $self, "Assert first event notification exists"; + $self->assert_equals(1, + scalar grep { $_->{id} eq $notif1Id } @{ $res->[0][1]{list} }); + + xlog $self, "Create one more event notification"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + eventX => { + title => 'eventX', + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{created}{eventX}); + start => '2011-01-01T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{eventX}); - xlog $self, "Get event notifications"; - $res = $manJmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - properties => ['id'], - }, 'R1'], - ]); - $self->assert_num_equals($jmap_max_calendareventnotifs, scalar @{$res->[0][1]{list}}); + xlog $self, "Get event notifications"; + $res = $manJmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + properties => ['id'], + }, + 'R1' + ], + ]); + $self->assert_num_equals($jmap_max_calendareventnotifs, + scalar @{ $res->[0][1]{list} }); - xlog $self, "Assert first event notification does not exist"; - $self->assert_equals(0, scalar grep { $_->{id} eq $notif1Id } @{$res->[0][1]{list}}); + xlog $self, "Assert first event notification does not exist"; + $self->assert_equals(0, + scalar grep { $_->{id} eq $notif1Id } @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_query b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_query index dd02dbbe3b..52b6e4d780 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_query +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_query @@ -2,158 +2,197 @@ use Cassandane::Tiny; sub test_calendareventnotification_query - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayWriteOwn => JSON::true, - mayAdmin => JSON::false - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayWriteOwn => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - xlog "Create notifications"; + xlog "Create notifications"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - title => 'event1', - calendarIds => { - Default => JSON::true, - }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', - }, + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + title => 'event1', + calendarIds => { + Default => JSON::true, }, - }, 'R2'], - ]); - my $event1Id = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($event1Id); + start => '2011-01-01T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R2' + ], + ]); + my $event1Id = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($event1Id); - sleep(1); + sleep(1); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $event1Id => { - title => 'event1updated', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$event1Id}); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $event1Id => { + title => 'event1updated', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$event1Id}); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); - my %notifs = map { $_->{type} => $_ } @{$res->[0][1]{list}}; - my $notif1 = $notifs{created}; - $self->assert_not_null($notif1); - my $notif2 = $notifs{updated}; - $self->assert_not_null($notif2); + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); + my %notifs = map { $_->{type} => $_ } @{ $res->[0][1]{list} }; + my $notif1 = $notifs{created}; + $self->assert_not_null($notif1); + my $notif2 = $notifs{updated}; + $self->assert_not_null($notif2); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/query', { - accountId => 'cassandane', - filter => { - type => 'created', - }, - }, 'R1'], - ['CalendarEventNotification/query', { - accountId => 'cassandane', - filter => { - type => 'updated', - }, - }, 'R2'], - ['CalendarEventNotification/query', { - accountId => 'cassandane', - filter => { - before => $notif2->{created}, - }, - }, 'R3'], - ['CalendarEventNotification/query', { - accountId => 'cassandane', - filter => { - after => $notif2->{created}, - }, - }, 'R4'], - ]); - $self->assert_deep_equals([$notif1->{id}], $res->[0][1]{ids}); - $self->assert_deep_equals([$notif2->{id}], $res->[1][1]{ids}); - $self->assert_deep_equals([$notif1->{id}], $res->[2][1]{ids}); - $self->assert_deep_equals([$notif2->{id}], $res->[3][1]{ids}); + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + filter => { + type => 'created', + }, + }, + 'R1' + ], + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + filter => { + type => 'updated', + }, + }, + 'R2' + ], + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + filter => { + before => $notif2->{created}, + }, + }, + 'R3' + ], + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + filter => { + after => $notif2->{created}, + }, + }, + 'R4' + ], + ]); + $self->assert_deep_equals([ $notif1->{id} ], $res->[0][1]{ids}); + $self->assert_deep_equals([ $notif2->{id} ], $res->[1][1]{ids}); + $self->assert_deep_equals([ $notif1->{id} ], $res->[2][1]{ids}); + $self->assert_deep_equals([ $notif2->{id} ], $res->[3][1]{ids}); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event2 => { - title => 'event2', - calendarIds => { - Default => JSON::true, - }, - start => '2012-02-02T04:05:06', - duration => 'PT1H', - }, + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event2 => { + title => 'event2', + calendarIds => { + Default => JSON::true, }, - }, 'R2'], - ]); - my $event2Id = $res->[0][1]{created}{event2}{id}; - $self->assert_not_null($event2Id); + start => '2012-02-02T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R2' + ], + ]); + my $event2Id = $res->[0][1]{created}{event2}{id}; + $self->assert_not_null($event2Id); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/query', { - accountId => 'cassandane', - filter => { - calendarEventIds => [$event2Id], - }, - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_not_equals($notif1->{id}, $res->[0][1]{ids}[0]); - $self->assert_str_not_equals($notif2->{id}, $res->[0][1]{ids}[0]); + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + filter => { + calendarEventIds => [$event2Id], + }, + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_not_equals($notif1->{id}, $res->[0][1]{ids}[0]); + $self->assert_str_not_equals($notif2->{id}, $res->[0][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_querychanges b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_querychanges index 0d26a667ed..aa154c9057 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_querychanges +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_querychanges @@ -2,15 +2,16 @@ use Cassandane::Tiny; sub test_calendareventnotification_querychanges - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEventNotification/queryChanges', { - sinceQueryState => 'whatever', - }, 'R1'] - ]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + my $res = $jmap->CallMethods([ [ + 'CalendarEventNotification/queryChanges', + { + sinceQueryState => 'whatever', + }, + 'R1' + ] ]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_set b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_set index 5fb7371d2b..d75d0dcb57 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_set +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_set @@ -2,120 +2,137 @@ use Cassandane::Tiny; sub test_calendareventnotification_set - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - xlog "Create event"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayWriteOwn => JSON::true, - mayUpdatePrivate => JSON::true, - mayAdmin => JSON::false - }, - }, - }, + xlog "Create event"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayWriteOwn => JSON::true, + mayUpdatePrivate => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ['CalendarEvent/set', { - create => { - event1 => { - title => 'event1', - calendarIds => { - Default => JSON::true, - }, - start => '2011-01-01T04:05:06', - duration => 'PT1H', - }, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + event1 => { + title => 'event1', + calendarIds => { + Default => JSON::true, }, - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - my $eventId = $res->[1][1]{created}{event1}{id}; - $self->assert_not_null($eventId); + start => '2011-01-01T04:05:06', + duration => 'PT1H', + }, + }, + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + my $eventId = $res->[1][1]{created}{event1}{id}; + $self->assert_not_null($eventId); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R2'], - ]); + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R2' + ], + ]); - my $notif = $res->[0][1]{list}[0]; - my $notifId = $notif->{id}; - $self->assert_not_null($notifId); - delete($notif->{id}); + my $notif = $res->[0][1]{list}[0]; + my $notifId = $notif->{id}; + $self->assert_not_null($notifId); + delete($notif->{id}); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/set', { - accountId => 'cassandane', - create => { - newnotif => $notif, - }, - update => { - $notifId => $notif, - }, - }, "R1"] - ]); - $self->assert_str_equals('forbidden', - $res->[0][1]{notCreated}{newnotif}{type}); - $self->assert_str_equals('forbidden', - $res->[0][1]{notUpdated}{$notifId}{type}); - $self->assert_not_null($res->[0][1]{newState}); - my $state = $res->[0][1]{newState}; + $res = $manjmap->CallMethods([ [ + 'CalendarEventNotification/set', + { + accountId => 'cassandane', + create => { + newnotif => $notif, + }, + update => { + $notifId => $notif, + }, + }, + "R1" + ] ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notCreated}{newnotif}{type}); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$notifId}{type}); + $self->assert_not_null($res->[0][1]{newState}); + my $state = $res->[0][1]{newState}; - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/set', { - accountId => 'cassandane', - destroy => [$notifId, 'unknownId'], - }, "R1"] - ]); - $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); - $self->assert_str_equals('notFound', - $res->[0][1]{notDestroyed}{unknownId}{type}); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $res = $manjmap->CallMethods([ [ + 'CalendarEventNotification/set', + { + accountId => 'cassandane', + destroy => [ $notifId, 'unknownId' ], + }, + "R1" + ] ]); + $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); + $self->assert_str_equals('notFound', + $res->[0][1]{notDestroyed}{unknownId}{type}); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - ids => [$notifId], - }, 'R1'] - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_deep_equals([$notifId], $res->[0][1]{notFound}); + $res = $manjmap->CallMethods([ [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + ids => [$notifId], + }, + 'R1' + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_deep_equals([$notifId], $res->[0][1]{notFound}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_set_destroy b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_set_destroy index c647b79009..2d1a073b8a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_set_destroy +++ b/cassandane/tiny-tests/JMAPCalendars/calendareventnotification_set_destroy @@ -2,76 +2,80 @@ use Cassandane::Tiny; sub test_calendareventnotification_set_destroy - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admin = $self->{adminstore}->get_client(); - - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayUpdatePrivate => JSON::true, - mayWriteOwn => JSON::true, - mayAdmin => JSON::false - }, - }, - }, + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admin = $self->{adminstore}->get_client(); + + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); + + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + update => { + Default => { + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayUpdatePrivate => JSON::true, + mayWriteOwn => JSON::true, + mayAdmin => JSON::false + }, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - $res = $jmap->CallMethods([ - ['CalendarEventNotification/get', { - }, 'R1'], - ]); - my $cassState = $res->[0][1]{state}; - $self->assert_not_null($cassState); - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ]); - my $manState = $res->[0][1]{state}; - $self->assert_not_null($manState); - - xlog "create a notification that both cassandane and manifold will see"; - - my $ical = <assert(exists $res->[0][1]{updated}{Default}); + + $res = $jmap->CallMethods([ [ 'CalendarEventNotification/get', {}, 'R1' ], ]); + my $cassState = $res->[0][1]{state}; + $self->assert_not_null($cassState); + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + ]); + my $manState = $res->[0][1]{state}; + $self->assert_not_null($manState); + + xlog "create a notification that both cassandane and manifold will see"; + + my $ical = <Request('PUT', - '/dav/calendars/user/cassandane/Default/testitip.ics', - $ical, 'Content-Type' => 'text/calendar', - 'Schedule-Sender-Address' => 'itipsender@local', - 'Schedule-Sender-Name' => 'iTIP Sender', - ); - - xlog "fetch notifications"; - - $res = $jmap->CallMethods([ - ['CalendarEventNotification/get', { - }, 'R1'], - ['CalendarEventNotification/query', { - }, 'R2'], - ['CalendarEventNotification/changes', { - sinceState => $cassState, - }, 'R3'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{created}}); - $cassState = $res->[2][1]{newState}; - - my $notifId = $res->[1][1]{ids}[0]; - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ['CalendarEventNotification/query', { - accountId => 'cassandane', - }, 'R2'], - ['CalendarEventNotification/changes', { - accountId => 'cassandane', - sinceState => $manState, - }, 'R3'], - ]); - $self->{instance}->getsyslog(); # ignore seen.db DBERROR - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{created}}); - $manState = $res->[2][1]{newState}; - - xlog "destroy notification as cassandane user"; - - $res = $jmap->CallMethods([ - ['CalendarEventNotification/set', { - destroy => [$notifId], - }, 'R1'], - ]); - $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); - - xlog "refetch notifications"; - - $res = $jmap->CallMethods([ - ['CalendarEventNotification/get', { - }, 'R1'], - ['CalendarEventNotification/query', { - }, 'R2'], - ['CalendarEventNotification/changes', { - sinceState => $cassState, - }, 'R3'], - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{destroyed}}); - $cassState = $res->[2][1]{newState}; - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ['CalendarEventNotification/query', { - accountId => 'cassandane', - }, 'R2'], - ['CalendarEventNotification/changes', { - accountId => 'cassandane', - sinceState => $manState, - }, 'R3'], - ]); - $self->{instance}->getsyslog(); # ignore seen.db DBERROR - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{destroyed}}); - $manState = $res->[2][1]{newState}; - - xlog "destroy notification as sharee"; - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/set', { - accountId => 'cassandane', - destroy => [$notifId], - }, 'R1'], - ]); - $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); - - xlog "refetch notifications"; - - $res = $jmap->CallMethods([ - ['CalendarEventNotification/get', { - }, 'R1'], - ['CalendarEventNotification/query', { - }, 'R2'], - ['CalendarEventNotification/changes', { - sinceState => $cassState, - }, 'R3'], - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{created}}); - # XXX this should be 0 but we err on the safe side and report duplicate destroys - $self->assert_num_equals(1, scalar @{$res->[2][1]{destroyed}}); - $cassState = $res->[2][1]{newState}; - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ['CalendarEventNotification/query', { - accountId => 'cassandane', - }, 'R2'], - ['CalendarEventNotification/changes', { - accountId => 'cassandane', - sinceState => $manState, - }, 'R3'], - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{destroyed}}); - $manState = $res->[2][1]{newState}; - - $res = $jmap->CallMethods([ - ['CalendarEventNotification/get', { - }, 'R1'], - ['CalendarEventNotification/query', { - }, 'R2'], - ['CalendarEventNotification/changes', { - sinceState => $cassState, - }, 'R3'], - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{destroyed}}); - - $res = $manjmap->CallMethods([ - ['CalendarEventNotification/get', { - accountId => 'cassandane', - }, 'R1'], - ['CalendarEventNotification/query', { - accountId => 'cassandane', - }, 'R2'], - ['CalendarEventNotification/changes', { - accountId => 'cassandane', - sinceState => $manState, - }, 'R3'], - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[2][1]{destroyed}}); + $caldav->Request( + 'PUT', + '/dav/calendars/user/cassandane/Default/testitip.ics', + $ical, + 'Content-Type' => 'text/calendar', + 'Schedule-Sender-Address' => 'itipsender@local', + 'Schedule-Sender-Name' => 'iTIP Sender', + ); + + xlog "fetch notifications"; + + $res = $jmap->CallMethods([ + [ 'CalendarEventNotification/get', {}, 'R1' ], + [ 'CalendarEventNotification/query', {}, 'R2' ], + [ + 'CalendarEventNotification/changes', + { + sinceState => $cassState, + }, + 'R3' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{created} }); + $cassState = $res->[2][1]{newState}; + + my $notifId = $res->[1][1]{ids}[0]; + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + }, + 'R2' + ], + [ + 'CalendarEventNotification/changes', + { + accountId => 'cassandane', + sinceState => $manState, + }, + 'R3' + ], + ]); + $self->{instance}->getsyslog(); # ignore seen.db DBERROR + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{created} }); + $manState = $res->[2][1]{newState}; + + xlog "destroy notification as cassandane user"; + + $res = $jmap->CallMethods([ + [ + 'CalendarEventNotification/set', + { + destroy => [$notifId], + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); + + xlog "refetch notifications"; + + $res = $jmap->CallMethods([ + [ 'CalendarEventNotification/get', {}, 'R1' ], + [ 'CalendarEventNotification/query', {}, 'R2' ], + [ + 'CalendarEventNotification/changes', + { + sinceState => $cassState, + }, + 'R3' + ], + ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{destroyed} }); + $cassState = $res->[2][1]{newState}; + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + }, + 'R2' + ], + [ + 'CalendarEventNotification/changes', + { + accountId => 'cassandane', + sinceState => $manState, + }, + 'R3' + ], + ]); + $self->{instance}->getsyslog(); # ignore seen.db DBERROR + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{destroyed} }); + $manState = $res->[2][1]{newState}; + + xlog "destroy notification as sharee"; + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/set', + { + accountId => 'cassandane', + destroy => [$notifId], + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); + + xlog "refetch notifications"; + + $res = $jmap->CallMethods([ + [ 'CalendarEventNotification/get', {}, 'R1' ], + [ 'CalendarEventNotification/query', {}, 'R2' ], + [ + 'CalendarEventNotification/changes', + { + sinceState => $cassState, + }, + 'R3' + ], + ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{created} }); +# XXX this should be 0 but we err on the safe side and report duplicate destroys + $self->assert_num_equals(1, scalar @{ $res->[2][1]{destroyed} }); + $cassState = $res->[2][1]{newState}; + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + }, + 'R2' + ], + [ + 'CalendarEventNotification/changes', + { + accountId => 'cassandane', + sinceState => $manState, + }, + 'R3' + ], + ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{destroyed} }); + $manState = $res->[2][1]{newState}; + + $res = $jmap->CallMethods([ + [ 'CalendarEventNotification/get', {}, 'R1' ], + [ 'CalendarEventNotification/query', {}, 'R2' ], + [ + 'CalendarEventNotification/changes', + { + sinceState => $cassState, + }, + 'R3' + ], + ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{destroyed} }); + + $res = $manjmap->CallMethods([ + [ + 'CalendarEventNotification/get', + { + accountId => 'cassandane', + }, + 'R1' + ], + [ + 'CalendarEventNotification/query', + { + accountId => 'cassandane', + }, + 'R2' + ], + [ + 'CalendarEventNotification/changes', + { + accountId => 'cassandane', + sinceState => $manState, + }, + 'R3' + ], + ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[2][1]{destroyed} }); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_defaultcalendar b/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_defaultcalendar index 1944ea32f6..1c147f6740 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_defaultcalendar +++ b/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_defaultcalendar @@ -2,17 +2,17 @@ use Cassandane::Tiny; sub test_calendarpreferences_defaultcalendar - :min_version_3_7 :needs_component_jmap :needs_component_sieve - :CalDAVNoDefaultCalendar -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap : needs_component_sieve + : CalDAVNoDefaultCalendar { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admin = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admin = $self->{adminstore}->get_client(); - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <NewCalendar({ id => 'Default' }); - - my $res = $jmap->CallMethods([ - ['Calendar/get', { }, 'R1'], - ]); - $self->assert_str_equals('Default', $res->[0][1]{list}[0]{id}); - - xlog "No defaultCalendar set"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/get', { }, 'R1'], - ]); - $self->assert_null($res->[0][1]{list}[0]{defaultCalendarId}); - - xlog "Get CalendarEvent state"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { }, 'R1'], - ]); - $self->assert_deep_equals([], $res->[0][1]{list}); - my $state = $res->[0][1]{state}; - - xlog "Deliver message"; - $self->deliver_imip(); - - xlog "Message should go into hard-coded Default calendar"; - $res = $jmap->CallMethods([ - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/changes', - path => '/created' - }, - properties => ['calendarIds'], - }, 'R2'], - ]); - $self->assert_deep_equals({ - Default => JSON::true - }, $res->[1][1]{list}[0]{calendarIds}); - $state = $res->[1][1]{state}; - - xlog "Create calendars A, B and C"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendarA => { - name => 'A', - }, - calendarB => { - name => 'B', - }, - calendarC => { - name => 'C', - }, - }, - }, 'R1'], - ]); - my $calendarA = $res->[0][1]{created}{calendarA}{id}; - $self->assert_not_null($calendarA); - my $calendarB = $res->[0][1]{created}{calendarB}{id}; - $self->assert_not_null($calendarB); - my $calendarC = $res->[0][1]{created}{calendarC}{id}; - $self->assert_not_null($calendarC); - - xlog "Make calendar C read-only to owner"; - $admin->setacl("user.cassandane.#calendars.$calendarC", cassandane => 'lrs') or die; - - xlog "Set calendarA as default"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/set', { - update => { - singleton => { - defaultCalendarId => $calendarA, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{singleton}); - - xlog "Deliver message"; - $self->deliver_imip(); - - xlog "Message should go into calendar A"; - $res = $jmap->CallMethods([ - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/changes', - path => '/created' - }, - properties => ['calendarIds'], - }, 'R2'], - ]); - $self->assert_deep_equals({ - $calendarA => JSON::true - }, $res->[1][1]{list}[0]{calendarIds}); - $state = $res->[1][1]{state}; - - xlog "Destroying calendar A picks Default as new default"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - destroy => [$calendarA], - onDestroyRemoveEvents => JSON::true, - }, 'R1'], - ['CalendarPreferences/get', { - }, 'R2'], - ]); - $self->assert_deep_equals([$calendarA], $res->[0][1]{destroyed}); - $self->assert_str_equals('Default', $res->[1][1]{list}[0]{defaultCalendarId}); - - xlog "Can set defaultCalendarId to null, but new one gets picked immediately"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/set', { - update => { - singleton => { - defaultCalendarId => undef, - }, - }, - }, 'R1'], - ['CalendarPreferences/get', { - }, 'R2'], - ]); - $self->assert_str_equals($res->[0][1]{updated}{singleton}{defaultCalendarId}, - $res->[1][1]{list}[0]{defaultCalendarId}); - - xlog "Destroy special calendar Default, new default is calendar B"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - destroy => [ 'Default' ], - onDestroyRemoveEvents => JSON::true, - }, 'R1'], - ['CalendarPreferences/get', { - }, 'R2'], - ]); - $self->assert_deep_equals(['Default'], $res->[0][1]{destroyed}); - $self->assert_str_equals($calendarB, $res->[1][1]{list}[0]{defaultCalendarId}); - - xlog "Get CalendarEvent state"; - $res = $jmap->CallMethods([ - ['Calendar/get', { - properties => ['id'], - }, 'R0'], - ['CalendarEvent/get', { - properties => ['id', 'calendarIds'], - }, 'R1'], - ['Calendar/get', { - properties => ['id'], - }, 'R2'], - ]); - $state = $res->[1][1]{state}; - - xlog "Deliver message"; - $self->deliver_imip(); - - xlog "Message should go into writable calendar B"; - $res = $jmap->CallMethods([ - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/changes', - path => '/created' - }, - properties => ['calendarIds'], - }, 'R2'], - ]); - $self->assert_deep_equals({ - $calendarB => JSON::true - }, $res->[1][1]{list}[0]{calendarIds}); - $state = $res->[1][1]{state}; - - xlog "Destroy calendar B"; - $res = $jmap->CallMethods([ - ['Calendar/set', { - destroy => [ $calendarB ], - onDestroyRemoveEvents => JSON::true, - }, 'R1'], - ['CalendarPreferences/get', { - }, 'R2'], - ]); - $self->assert_deep_equals([$calendarB], $res->[0][1]{destroyed}); - - xlog "Read-only calendar C does not get picked as default"; - $self->assert_null($res->[1][1]{list}[0]{defaultCalendarId}); - - xlog "Cannot set read-only calendar as default calendar"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/set', { - update => { - singleton => { - defaultCalendarId => $calendarC, - }, - }, - }, 'R1'], - ['CalendarPreferences/get', { - }, 'R2'], - ]); - $self->assert_deep_equals(['defaultCalendarId'], - $res->[0][1]{notUpdated}{singleton}{properties}); - $self->assert_null($res->[1][1]{list}[0]{defaultCalendarId}); + ); + + xlog "Create special-named Default calendar"; + $caldav->NewCalendar({ id => 'Default' }); + + my $res = $jmap->CallMethods([ [ 'Calendar/get', {}, 'R1' ], ]); + $self->assert_str_equals('Default', $res->[0][1]{list}[0]{id}); + + xlog "No defaultCalendar set"; + $res = $jmap->CallMethods([ [ 'CalendarPreferences/get', {}, 'R1' ], ]); + $self->assert_null($res->[0][1]{list}[0]{defaultCalendarId}); + + xlog "Get CalendarEvent state"; + $res = $jmap->CallMethods([ [ 'CalendarEvent/get', {}, 'R1' ], ]); + $self->assert_deep_equals([], $res->[0][1]{list}); + my $state = $res->[0][1]{state}; + + xlog "Deliver message"; + $self->deliver_imip(); + + xlog "Message should go into hard-coded Default calendar"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/changes', + path => '/created' + }, + properties => ['calendarIds'], + }, + 'R2' + ], + ]); + $self->assert_deep_equals( + { + Default => JSON::true + }, + $res->[1][1]{list}[0]{calendarIds} + ); + $state = $res->[1][1]{state}; + + xlog "Create calendars A, B and C"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + calendarA => { + name => 'A', + }, + calendarB => { + name => 'B', + }, + calendarC => { + name => 'C', + }, + }, + }, + 'R1' + ], + ]); + my $calendarA = $res->[0][1]{created}{calendarA}{id}; + $self->assert_not_null($calendarA); + my $calendarB = $res->[0][1]{created}{calendarB}{id}; + $self->assert_not_null($calendarB); + my $calendarC = $res->[0][1]{created}{calendarC}{id}; + $self->assert_not_null($calendarC); + + xlog "Make calendar C read-only to owner"; + $admin->setacl("user.cassandane.#calendars.$calendarC", cassandane => 'lrs') + or die; + + xlog "Set calendarA as default"; + $res = $jmap->CallMethods([ + [ + 'CalendarPreferences/set', + { + update => { + singleton => { + defaultCalendarId => $calendarA, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{singleton}); + + xlog "Deliver message"; + $self->deliver_imip(); + + xlog "Message should go into calendar A"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/changes', + path => '/created' + }, + properties => ['calendarIds'], + }, + 'R2' + ], + ]); + $self->assert_deep_equals( + { + $calendarA => JSON::true + }, + $res->[1][1]{list}[0]{calendarIds} + ); + $state = $res->[1][1]{state}; + + xlog "Destroying calendar A picks Default as new default"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + destroy => [$calendarA], + onDestroyRemoveEvents => JSON::true, + }, + 'R1' + ], + [ 'CalendarPreferences/get', {}, 'R2' ], + ]); + $self->assert_deep_equals([$calendarA], $res->[0][1]{destroyed}); + $self->assert_str_equals('Default', $res->[1][1]{list}[0]{defaultCalendarId}); + + xlog "Can set defaultCalendarId to null, but new one gets picked immediately"; + $res = $jmap->CallMethods([ + [ + 'CalendarPreferences/set', + { + update => { + singleton => { + defaultCalendarId => undef, + }, + }, + }, + 'R1' + ], + [ 'CalendarPreferences/get', {}, 'R2' ], + ]); + $self->assert_str_equals($res->[0][1]{updated}{singleton}{defaultCalendarId}, + $res->[1][1]{list}[0]{defaultCalendarId}); + + xlog "Destroy special calendar Default, new default is calendar B"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + destroy => ['Default'], + onDestroyRemoveEvents => JSON::true, + }, + 'R1' + ], + [ 'CalendarPreferences/get', {}, 'R2' ], + ]); + $self->assert_deep_equals(['Default'], $res->[0][1]{destroyed}); + $self->assert_str_equals($calendarB, + $res->[1][1]{list}[0]{defaultCalendarId}); + + xlog "Get CalendarEvent state"; + $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + properties => ['id'], + }, + 'R0' + ], + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'calendarIds' ], + }, + 'R1' + ], + [ + 'Calendar/get', + { + properties => ['id'], + }, + 'R2' + ], + ]); + $state = $res->[1][1]{state}; + + xlog "Deliver message"; + $self->deliver_imip(); + + xlog "Message should go into writable calendar B"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/changes', + path => '/created' + }, + properties => ['calendarIds'], + }, + 'R2' + ], + ]); + $self->assert_deep_equals( + { + $calendarB => JSON::true + }, + $res->[1][1]{list}[0]{calendarIds} + ); + $state = $res->[1][1]{state}; + + xlog "Destroy calendar B"; + $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + destroy => [$calendarB], + onDestroyRemoveEvents => JSON::true, + }, + 'R1' + ], + [ 'CalendarPreferences/get', {}, 'R2' ], + ]); + $self->assert_deep_equals([$calendarB], $res->[0][1]{destroyed}); + + xlog "Read-only calendar C does not get picked as default"; + $self->assert_null($res->[1][1]{list}[0]{defaultCalendarId}); + + xlog "Cannot set read-only calendar as default calendar"; + $res = $jmap->CallMethods([ + [ + 'CalendarPreferences/set', + { + update => { + singleton => { + defaultCalendarId => $calendarC, + }, + }, + }, + 'R1' + ], + [ 'CalendarPreferences/get', {}, 'R2' ], + ]); + $self->assert_deep_equals(['defaultCalendarId'], + $res->[0][1]{notUpdated}{singleton}{properties}); + $self->assert_null($res->[1][1]{list}[0]{defaultCalendarId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_participantidentity b/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_participantidentity index 44cc4adbf9..2359d044b8 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_participantidentity +++ b/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_participantidentity @@ -2,51 +2,55 @@ use Cassandane::Tiny; sub test_calendarpreferences_participantidentity - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "No defaultParticipantIdentityId set"; - my $res = $jmap->CallMethods([ - ['CalendarPreferences/get', { }, 'R1'], - ]); - $self->assert_null($res->[0][1]{list}[0]{defaultParticipantIdentityId}); + xlog "No defaultParticipantIdentityId set"; + my $res = $jmap->CallMethods([ [ 'CalendarPreferences/get', {}, 'R1' ], ]); + $self->assert_null($res->[0][1]{list}[0]{defaultParticipantIdentityId}); - xlog 'Cyrus selects owner participant'; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - participants => { - someone => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - }, - }, + xlog 'Cyrus selects owner participant'; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2020-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + participants => { + someone => { + roles => { + attendee => JSON::true, + }, + sendTo => { + imip => 'mailto:someone@example.com', }, + }, }, - }, 'R1'], - ]); - $self->assert_deep_equals({ - imip => 'mailto:cassandane@example.com', - }, $res->[0][1]{created}{event1}{replyTo}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals( + { + imip => 'mailto:cassandane@example.com', + }, + $res->[0][1]{created}{event1}{replyTo} + ); - xlog "Set scheduling addresses via CalDAV"; - my $xml = <<'EOF'; + xlog "Set scheduling addresses via CalDAV"; + my $xml = <<'EOF'; @@ -60,71 +64,85 @@ sub test_calendarpreferences_participantidentity EOF - $caldav->Request('PROPPATCH', "/dav/principals/user/cassandane", - $xml, 'Content-Type' => 'text/xml'); + $caldav->Request( + 'PROPPATCH', "/dav/principals/user/cassandane", + $xml, 'Content-Type' => 'text/xml' + ); - xlog "No defaultParticipantIdentityId set"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/get', { }, 'R1'], - ]); - $self->assert_null($res->[0][1]{list}[0]{defaultParticipantIdentityId}); + xlog "No defaultParticipantIdentityId set"; + $res = $jmap->CallMethods([ [ 'CalendarPreferences/get', {}, 'R1' ], ]); + $self->assert_null($res->[0][1]{list}[0]{defaultParticipantIdentityId}); - xlog "Get participant identities"; - $res = $jmap->CallMethods([ - ['ParticipantIdentity/get', { }, 'R1'], - ]); - my $participantId = (grep {$_->{sendTo}{imip} eq 'mailto:alias2@example.com'} - @{$res->[0][1]{list}})[0]{id}; - $self->assert_not_null($participantId); + xlog "Get participant identities"; + $res = $jmap->CallMethods([ [ 'ParticipantIdentity/get', {}, 'R1' ], ]); + my $participantId + = (grep { $_->{sendTo}{imip} eq 'mailto:alias2@example.com' } + @{ $res->[0][1]{list} })[0]{id}; + $self->assert_not_null($participantId); - xlog "Set participant identity as default"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/set', { - update => { - singleton => { - defaultParticipantIdentityId => $participantId, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{singleton}); + xlog "Set participant identity as default"; + $res = $jmap->CallMethods([ + [ + 'CalendarPreferences/set', + { + update => { + singleton => { + defaultParticipantIdentityId => $participantId, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{singleton}); - xlog 'Cyrus uses default participant'; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event2 => { - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2020-01-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - participants => { - someone => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - }, - }, + xlog 'Cyrus uses default participant'; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event2 => { + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2020-01-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + participants => { + someone => { + roles => { + attendee => JSON::true, + }, + sendTo => { + imip => 'mailto:someone@example.com', }, + }, }, - }, 'R1'], - ['CalendarEvent/get', { - ids => ['#event2'], - properties => ['replyTo'], - }, 'R2'], - ]); - $self->assert_deep_equals({ - imip => 'mailto:alias2@example.com', - }, $res->[0][1]{created}{event2}{replyTo}); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => ['#event2'], + properties => ['replyTo'], + }, + 'R2' + ], + ]); + $self->assert_deep_equals( + { + imip => 'mailto:alias2@example.com', + }, + $res->[0][1]{created}{event2}{replyTo} + ); - xlog "Updated scheduling addresses keep default participant"; - $xml = <<'EOF'; + xlog "Updated scheduling addresses keep default participant"; + $xml = <<'EOF'; @@ -139,17 +157,17 @@ EOF EOF - $caldav->Request('PROPPATCH', "/dav/principals/user/cassandane", - $xml, 'Content-Type' => 'text/xml'); + $caldav->Request( + 'PROPPATCH', "/dav/principals/user/cassandane", + $xml, 'Content-Type' => 'text/xml' + ); - $res = $jmap->CallMethods([ - ['CalendarPreferences/get', { }, 'R1'] - ]); - $self->assert_str_equals($participantId, - $res->[0][1]{list}[0]{defaultParticipantIdentityId}); + $res = $jmap->CallMethods([ [ 'CalendarPreferences/get', {}, 'R1' ] ]); + $self->assert_str_equals($participantId, + $res->[0][1]{list}[0]{defaultParticipantIdentityId}); - xlog "Removed default scheduling address reset default id"; - $xml = <<'EOF'; + xlog "Removed default scheduling address reset default id"; + $xml = <<'EOF'; @@ -162,11 +180,11 @@ EOF EOF - $caldav->Request('PROPPATCH', "/dav/principals/user/cassandane", - $xml, 'Content-Type' => 'text/xml'); + $caldav->Request( + 'PROPPATCH', "/dav/principals/user/cassandane", + $xml, 'Content-Type' => 'text/xml' + ); - $res = $jmap->CallMethods([ - ['CalendarPreferences/get', { }, 'R1'] - ]); - $self->assert_null($res->[0][1]{list}[0]{defaultParticipantIdentityId}); + $res = $jmap->CallMethods([ [ 'CalendarPreferences/get', {}, 'R1' ] ]); + $self->assert_null($res->[0][1]{list}[0]{defaultParticipantIdentityId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_set b/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_set index 1c96622b57..3653ba192c 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_set +++ b/cassandane/tiny-tests/JMAPCalendars/calendarpreferences_set @@ -2,101 +2,137 @@ use Cassandane::Tiny; sub test_calendarpreferences_set - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - # omit debug properties, so don't use debug extension - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'urn:ietf:params:jmap:calendars:preferences', - 'https://cyrusimap.org/ns/jmap/calendars' - ]; + # omit debug properties, so don't use debug extension + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'urn:ietf:params:jmap:calendars:preferences', + 'https://cyrusimap.org/ns/jmap/calendars' + ]; - xlog "Create calendar"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - calendar => { - name => 'Test', - }, - } - }, 'R1'], - ], $using); - my $calendarId = $res->[0][1]{created}{calendar}{id}; - $self->assert_not_null($calendarId); + xlog "Create calendar"; + my $res = $jmap->CallMethods( + [ + [ + 'Calendar/set', + { + create => { + calendar => { + name => 'Test', + }, + } + }, + 'R1' + ], + ], + $using + ); + my $calendarId = $res->[0][1]{created}{calendar}{id}; + $self->assert_not_null($calendarId); - xlog "Fetch participant identities"; - $res = $jmap->CallMethods([ - ['ParticipantIdentity/get', { - }, 'R1'], - ], $using); - my $participantId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($participantId); + xlog "Fetch participant identities"; + $res + = $jmap->CallMethods([ [ 'ParticipantIdentity/get', {}, 'R1' ], ], $using); + my $participantId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($participantId); - xlog "Fetch preferences"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/get', { - }, 'R1'], - ], $using); - $self->assert_deep_equals([{ - id => 'singleton', - defaultCalendarId => undef, - defaultParticipantIdentityId => undef, - }], $res->[0][1]{list}); - my $state = $res->[0][1]{state}; + xlog "Fetch preferences"; + $res + = $jmap->CallMethods([ [ 'CalendarPreferences/get', {}, 'R1' ], ], $using); + $self->assert_deep_equals( + [ { + id => 'singleton', + defaultCalendarId => undef, + defaultParticipantIdentityId => undef, + } ], + $res->[0][1]{list} + ); + my $state = $res->[0][1]{state}; - xlog "Set preferences"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/set', { - update => { - singleton => { - defaultCalendarId => $calendarId, - defaultParticipantIdentityId => $participantId, - }, + xlog "Set preferences"; + $res = $jmap->CallMethods( + [ + [ + 'CalendarPreferences/set', + { + update => { + singleton => { + defaultCalendarId => $calendarId, + defaultParticipantIdentityId => $participantId, }, - }, 'R1'], - ], $using); - $self->assert(exists $res->[0][1]{updated}{singleton}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert(exists $res->[0][1]{updated}{singleton}); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); - xlog "Fetch preferences by id"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/get', { - ids => ['singleton'], - }, 'R1'], - ], $using); - $self->assert_deep_equals([{ - id => 'singleton', - defaultCalendarId => $calendarId, - defaultParticipantIdentityId => $participantId, - }], $res->[0][1]{list}); + xlog "Fetch preferences by id"; + $res = $jmap->CallMethods( + [ + [ + 'CalendarPreferences/get', + { + ids => ['singleton'], + }, + 'R1' + ], + ], + $using + ); + $self->assert_deep_equals( + [ { + id => 'singleton', + defaultCalendarId => $calendarId, + defaultParticipantIdentityId => $participantId, + } ], + $res->[0][1]{list} + ); - xlog "Unset preferences"; - $res = $jmap->CallMethods([ - ['CalendarPreferences/set', { - update => { - singleton => { - defaultCalendarId => undef, - defaultParticipantIdentityId => undef, - }, + xlog "Unset preferences"; + $res = $jmap->CallMethods( + [ + [ + 'CalendarPreferences/set', + { + update => { + singleton => { + defaultCalendarId => undef, + defaultParticipantIdentityId => undef, }, - }, 'R1'], - ['CalendarPreferences/get', { - ids => ['singleton'], - }, 'R2'], - ], $using); - xlog "Setting defaultCalendarId to null assigns a new default calendar"; - $self->assert_not_null($res->[0][1]{updated}{singleton}{defaultCalendarId}); - $self->assert_deep_equals([{ - id => 'singleton', - defaultCalendarId => $res->[0][1]{updated}{singleton}{defaultCalendarId}, - defaultParticipantIdentityId => undef, - }], $res->[1][1]{list}); + }, + }, + 'R1' + ], + [ + 'CalendarPreferences/get', + { + ids => ['singleton'], + }, + 'R2' + ], + ], + $using + ); + xlog "Setting defaultCalendarId to null assigns a new default calendar"; + $self->assert_not_null($res->[0][1]{updated}{singleton}{defaultCalendarId}); + $self->assert_deep_equals( + [ { + id => 'singleton', + defaultCalendarId => $res->[0][1]{updated}{singleton}{defaultCalendarId}, + defaultParticipantIdentityId => undef, + } ], + $res->[1][1]{list} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_changes b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_changes index ea381fd267..5ece707617 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_changes +++ b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_changes @@ -2,14 +2,10 @@ use Cassandane::Tiny; sub test_calendarprincipal_changes - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Principal/changes', { - }, 'R1'] - ]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + my $res = $jmap->CallMethods([ [ 'Principal/changes', {}, 'R1' ] ]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_get b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_get index da6d1e96cd..229ad22a6f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_get +++ b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_get @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_calendarprincipal_get - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $CalDAV = $self->{caldav}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $CalDAV = $self->{caldav}; - # Set timezone - my $proppatchXml = < @@ -51,11 +50,13 @@ END:VCALENDAR EOF - $CalDAV->Request('PROPPATCH', "/dav/calendars/user/cassandane", - $proppatchXml, 'Content-Type' => 'text/xml'); + $CalDAV->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane", + $proppatchXml, 'Content-Type' => 'text/xml' + ); - # Set description - $proppatchXml = < @@ -65,11 +66,13 @@ EOF EOF - $CalDAV->Request('PROPPATCH', "/dav/calendars/user/cassandane", - $proppatchXml, 'Content-Type' => 'text/xml'); + $CalDAV->Request( + 'PROPPATCH', "/dav/calendars/user/cassandane", + $proppatchXml, 'Content-Type' => 'text/xml' + ); - # Set name - $proppatchXml = < @@ -79,31 +82,37 @@ EOF EOF - $CalDAV->Request('PROPPATCH', "/dav/principals/user/cassandane", - $proppatchXml, 'Content-Type' => 'text/xml'); + $CalDAV->Request( + 'PROPPATCH', "/dav/principals/user/cassandane", + $proppatchXml, 'Content-Type' => 'text/xml' + ); + my $res = $jmap->CallMethods([ [ + 'Principal/get', + { + ids => [ 'cassandane', 'nope' ], + }, + 'R1' + ] ]); + my $p = $res->[0][1]{list}[0]; - my $res = $jmap->CallMethods([ - ['Principal/get', { - ids => ['cassandane', 'nope'], - }, 'R1'] - ]); - my $p = $res->[0][1]{list}[0]; - - $self->assert_not_null($p->{account}); - delete ($p->{account}); - $self->assert_deep_equals({ - id => 'cassandane', - name => 'Cassandane User', - description => 'A description', - email => 'cassandane@example.com', - type => 'individual', - timeZone => 'Europe/Berlin', - mayGetAvailability => JSON::true, - accountId => 'cassandane', - sendTo => { - imip => 'mailto:cassandane@example.com', - }, - }, $p); - $self->assert_deep_equals(['nope'], $res->[0][1]{notFound}); + $self->assert_not_null($p->{account}); + delete($p->{account}); + $self->assert_deep_equals( + { + id => 'cassandane', + name => 'Cassandane User', + description => 'A description', + email => 'cassandane@example.com', + type => 'individual', + timeZone => 'Europe/Berlin', + mayGetAvailability => JSON::true, + accountId => 'cassandane', + sendTo => { + imip => 'mailto:cassandane@example.com', + }, + }, + $p + ); + $self->assert_deep_equals(['nope'], $res->[0][1]{notFound}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_getavailability_merged b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_getavailability_merged index a84aa878c2..f2408ea012 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_getavailability_merged +++ b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_getavailability_merged @@ -2,140 +2,157 @@ use Cassandane::Tiny; sub test_calendarprincipal_getavailability_merged - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - # 09:00 to 10:30: Two events adjacent to each other. - 'event-0900-1000' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-0900-1000", - start => "2020-08-01T09:00:00", - timeZone => "Etc/UTC", - duration => "PT1H", - }, - 'event-1000-1030' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-1000-1030", - start => "2020-08-01T10:00:00", - timeZone => "Etc/UTC", - duration => "PT30M", - }, - # 05:00 to 08:00: One event completely overlapping the other. - 'event-0500-0800' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-0500-0800", - start => "2020-08-01T05:00:00", - timeZone => "Etc/UTC", - duration => "PT3H", - }, - 'event-0600-0700' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-06:00-07:00", - start => "2020-08-01T06:00:00", - timeZone => "Etc/UTC", - duration => "PT1H", - }, - # 01:00 to 03:00: One event partially overlapping the other. - 'event-0100-0200' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-0100-0200", - start => "2020-08-01T01:00:00", - timeZone => "Etc/UTC", - duration => "PT1H", - }, - 'event-0130-0300' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-0130-0300", - start => "2020-08-01T01:30:00", - timeZone => "Etc/UTC", - duration => "PT1H30M", - }, - # 12:00 to 13:30: Overlapping events with differing busyStatus. - 'event-1200-1300-tentative' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-1200-1300-tentative", - start => "2020-08-01T12:00:00", - timeZone => "Etc/UTC", - duration => "PT1H", - status => 'tentative', - }, - 'event-1200-1330-confirmed' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-1200-1330-confirmed", - start => "2020-08-01T12:00:00", - timeZone => "Etc/UTC", - duration => "PT1H30M", - status => 'confirmed', - }, - 'event-1200-1230-unavailable' => { - calendarIds => { - Default => JSON::true, - }, - title => "event-1200-1330-unavailable", - start => "2020-08-01T12:00:00", - timeZone => "Etc/UTC", - duration => "PT30M", - }, + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + # 09:00 to 10:30: Two events adjacent to each other. + 'event-0900-1000' => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['Principal/getAvailability', { - id => 'cassandane', - utcStart => '2020-08-01T00:00:00Z', - utcEnd => '2020-09-01T00:00:00Z', - }, 'R2'], - ]); - $self->assert_num_equals(9, scalar keys %{$res->[0][1]{created}}); + title => "event-0900-1000", + start => "2020-08-01T09:00:00", + timeZone => "Etc/UTC", + duration => "PT1H", + }, + 'event-1000-1030' => { + calendarIds => { + Default => JSON::true, + }, + title => "event-1000-1030", + start => "2020-08-01T10:00:00", + timeZone => "Etc/UTC", + duration => "PT30M", + }, + # 05:00 to 08:00: One event completely overlapping the other. + 'event-0500-0800' => { + calendarIds => { + Default => JSON::true, + }, + title => "event-0500-0800", + start => "2020-08-01T05:00:00", + timeZone => "Etc/UTC", + duration => "PT3H", + }, + 'event-0600-0700' => { + calendarIds => { + Default => JSON::true, + }, + title => "event-06:00-07:00", + start => "2020-08-01T06:00:00", + timeZone => "Etc/UTC", + duration => "PT1H", + }, + # 01:00 to 03:00: One event partially overlapping the other. + 'event-0100-0200' => { + calendarIds => { + Default => JSON::true, + }, + title => "event-0100-0200", + start => "2020-08-01T01:00:00", + timeZone => "Etc/UTC", + duration => "PT1H", + }, + 'event-0130-0300' => { + calendarIds => { + Default => JSON::true, + }, + title => "event-0130-0300", + start => "2020-08-01T01:30:00", + timeZone => "Etc/UTC", + duration => "PT1H30M", + }, + # 12:00 to 13:30: Overlapping events with differing busyStatus. + 'event-1200-1300-tentative' => { + calendarIds => { + Default => JSON::true, + }, + title => "event-1200-1300-tentative", + start => "2020-08-01T12:00:00", + timeZone => "Etc/UTC", + duration => "PT1H", + status => 'tentative', + }, + 'event-1200-1330-confirmed' => { + calendarIds => { + Default => JSON::true, + }, + title => "event-1200-1330-confirmed", + start => "2020-08-01T12:00:00", + timeZone => "Etc/UTC", + duration => "PT1H30M", + status => 'confirmed', + }, + 'event-1200-1230-unavailable' => { + calendarIds => { + Default => JSON::true, + }, + title => "event-1200-1330-unavailable", + start => "2020-08-01T12:00:00", + timeZone => "Etc/UTC", + duration => "PT30M", + }, + }, + }, + 'R1' + ], + [ + 'Principal/getAvailability', + { + id => 'cassandane', + utcStart => '2020-08-01T00:00:00Z', + utcEnd => '2020-09-01T00:00:00Z', + }, + 'R2' + ], + ]); + $self->assert_num_equals(9, scalar keys %{ $res->[0][1]{created} }); - $self->assert_deep_equals([{ - utcStart => "2020-08-01T01:00:00Z", - utcEnd => "2020-08-01T03:00:00Z", + $self->assert_deep_equals( + [ + { + utcStart => "2020-08-01T01:00:00Z", + utcEnd => "2020-08-01T03:00:00Z", busyStatus => 'unavailable', - event => undef, - }, { - utcStart => "2020-08-01T05:00:00Z", - utcEnd => "2020-08-01T08:00:00Z", + event => undef, + }, + { + utcStart => "2020-08-01T05:00:00Z", + utcEnd => "2020-08-01T08:00:00Z", busyStatus => 'unavailable', - event => undef, - }, { - utcStart => "2020-08-01T09:00:00Z", - utcEnd => "2020-08-01T10:30:00Z", + event => undef, + }, + { + utcStart => "2020-08-01T09:00:00Z", + utcEnd => "2020-08-01T10:30:00Z", busyStatus => 'unavailable', - event => undef, - }, { - utcStart => "2020-08-01T12:00:00Z", - utcEnd => "2020-08-01T13:30:00Z", + event => undef, + }, + { + utcStart => "2020-08-01T12:00:00Z", + utcEnd => "2020-08-01T13:30:00Z", busyStatus => 'confirmed', - event => undef, - }, { - utcStart => "2020-08-01T12:00:00Z", - utcEnd => "2020-08-01T12:30:00Z", + event => undef, + }, + { + utcStart => "2020-08-01T12:00:00Z", + utcEnd => "2020-08-01T12:30:00Z", busyStatus => 'unavailable', - event => undef, - }, { - utcStart => "2020-08-01T12:00:00Z", - utcEnd => "2020-08-01T13:00:00Z", + event => undef, + }, + { + utcStart => "2020-08-01T12:00:00Z", + utcEnd => "2020-08-01T13:00:00Z", busyStatus => 'tentative', - event => undef, - }], $res->[1][1]{list}); + event => undef, + } + ], + $res->[1][1]{list} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_getavailability_showdetails b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_getavailability_showdetails index 22a6cca156..129ac8a9a0 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_getavailability_showdetails +++ b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_getavailability_showdetails @@ -2,148 +2,169 @@ use Cassandane::Tiny; sub test_calendarprincipal_getavailability_showdetails - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - invisible => { - name => 'invisibleCalendar', - includeInAvailability => 'none', - }, - }, - }, 'R1'], - ]); - my $invisibleCalendarId = $res->[0][1]{created}{invisible}{id}; - $self->assert_not_null($invisibleCalendarId); + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + invisible => { + name => 'invisibleCalendar', + includeInAvailability => 'none', + }, + }, + }, + 'R1' + ], + ]); + my $invisibleCalendarId = $res->[0][1]{created}{invisible}{id}; + $self->assert_not_null($invisibleCalendarId); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event1uid', - title => "event1", - start => "2020-07-01T09:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - status => 'confirmed', - recurrenceRules => [{ - frequency => 'weekly', - count => 12, - }], - recurrenceOverrides => { - "2020-08-26T09:00:00" => { - start => "2020-08-26T13:00:00", - }, - }, - }, - event2 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event2uid', - title => "event2", - start => "2020-08-07T11:00:00", - timeZone => "Europe/Vienna", - duration => "PT3H", - }, - event3 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event3uid', - title => "event3", - start => "2020-08-10T13:00:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - freeBusyStatus => 'free', - }, - event4 => { - calendarIds => { - Default => JSON::true, - }, - uid => 'event4uid', - title => "event4", - start => "2020-08-12T09:30:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - status => 'tentative', - }, - event5 => { - calendarIds => { - $invisibleCalendarId => JSON::true, - }, - uid => 'event5uid', - title => "event5", - start => "2020-08-14T15:30:00", - timeZone => "Europe/Vienna", - duration => "PT1H", - }, + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event1uid', + title => "event1", + start => "2020-07-01T09:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + status => 'confirmed', + recurrenceRules => [ { + frequency => 'weekly', + count => 12, + } ], + recurrenceOverrides => { + "2020-08-26T09:00:00" => { + start => "2020-08-26T13:00:00", + }, + }, + }, + event2 => { + calendarIds => { + Default => JSON::true, }, - }, 'R1'], - ['Principal/getAvailability', { - id => 'cassandane', - utcStart => '2020-08-01T00:00:00Z', - utcEnd => '2020-09-01T00:00:00Z', - showDetails => JSON::true, - eventProperties => ['start', 'title'], - }, 'R2'], - ]); - $self->assert_num_equals(5, scalar keys %{$res->[0][1]{created}}); + uid => 'event2uid', + title => "event2", + start => "2020-08-07T11:00:00", + timeZone => "Europe/Vienna", + duration => "PT3H", + }, + event3 => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event3uid', + title => "event3", + start => "2020-08-10T13:00:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + freeBusyStatus => 'free', + }, + event4 => { + calendarIds => { + Default => JSON::true, + }, + uid => 'event4uid', + title => "event4", + start => "2020-08-12T09:30:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + status => 'tentative', + }, + event5 => { + calendarIds => { + $invisibleCalendarId => JSON::true, + }, + uid => 'event5uid', + title => "event5", + start => "2020-08-14T15:30:00", + timeZone => "Europe/Vienna", + duration => "PT1H", + }, + }, + }, + 'R1' + ], + [ + 'Principal/getAvailability', + { + id => 'cassandane', + utcStart => '2020-08-01T00:00:00Z', + utcEnd => '2020-09-01T00:00:00Z', + showDetails => JSON::true, + eventProperties => [ 'start', 'title' ], + }, + 'R2' + ], + ]); + $self->assert_num_equals(5, scalar keys %{ $res->[0][1]{created} }); - $self->assert_deep_equals([{ - utcStart => "2020-08-05T07:00:00Z", - utcEnd => "2020-08-05T08:00:00Z", + $self->assert_deep_equals( + [ + { + utcStart => "2020-08-05T07:00:00Z", + utcEnd => "2020-08-05T08:00:00Z", busyStatus => 'confirmed', - event => { - start => "2020-08-05T09:00:00", - title => 'event1', + event => { + start => "2020-08-05T09:00:00", + title => 'event1', }, - }, { - utcStart => "2020-08-07T09:00:00Z", - utcEnd => "2020-08-07T12:00:00Z", + }, + { + utcStart => "2020-08-07T09:00:00Z", + utcEnd => "2020-08-07T12:00:00Z", busyStatus => 'unavailable', - event => { - start => "2020-08-07T11:00:00", - title => 'event2', + event => { + start => "2020-08-07T11:00:00", + title => 'event2', }, - }, { - utcStart => "2020-08-12T07:00:00Z", - utcEnd => "2020-08-12T08:00:00Z", + }, + { + utcStart => "2020-08-12T07:00:00Z", + utcEnd => "2020-08-12T08:00:00Z", busyStatus => 'confirmed', - event => { - start => "2020-08-12T09:00:00", - title => 'event1', + event => { + start => "2020-08-12T09:00:00", + title => 'event1', }, - }, { - utcStart => "2020-08-12T07:30:00Z", - utcEnd => "2020-08-12T08:30:00Z", + }, + { + utcStart => "2020-08-12T07:30:00Z", + utcEnd => "2020-08-12T08:30:00Z", busyStatus => 'tentative', - event => { - start => "2020-08-12T09:30:00", - title => 'event4', + event => { + start => "2020-08-12T09:30:00", + title => 'event4', }, - }, { - utcStart => "2020-08-19T07:00:00Z", - utcEnd => "2020-08-19T08:00:00Z", + }, + { + utcStart => "2020-08-19T07:00:00Z", + utcEnd => "2020-08-19T08:00:00Z", busyStatus => 'confirmed', - event => { - start => "2020-08-19T09:00:00", - title => 'event1', + event => { + start => "2020-08-19T09:00:00", + title => 'event1', }, - }, { - utcStart => "2020-08-26T11:00:00Z", - utcEnd => "2020-08-26T12:00:00Z", + }, + { + utcStart => "2020-08-26T11:00:00Z", + utcEnd => "2020-08-26T12:00:00Z", busyStatus => 'confirmed', - event => { - start => "2020-08-26T13:00:00", - title => 'event1', + event => { + start => "2020-08-26T13:00:00", + title => 'event1', }, - }], $res->[1][1]{list}); + } + ], + $res->[1][1]{list} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_query b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_query index 35bd463b1a..e9b2713d0a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_query +++ b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_query @@ -2,54 +2,66 @@ use Cassandane::Tiny; sub test_calendarprincipal_query - :min_version_3_3 :needs_component_jmap :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_3 : needs_component_jmap : JMAPExtensions : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user("manifold"); - # Trigger creation of default calendar - my $http = $self->{instance}->get_service("http"); - Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - $admintalk->setacl("user.manifold", "cassandane", "lr") or die; - $admintalk->setacl("user.manifold.#calendars", "cassandane", "lr") or die; - $admintalk->setacl("user.manifold.#calendars.Default", "cassandane" => 'lr') or die; + $self->{instance}->create_user("manifold"); + # Trigger creation of default calendar + my $http = $self->{instance}->get_service("http"); + Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + $admintalk->setacl("user.manifold", "cassandane", "lr") or die; + $admintalk->setacl("user.manifold.#calendars", "cassandane", "lr") or die; + $admintalk->setacl("user.manifold.#calendars.Default", "cassandane" => 'lr') + or die; - xlog "test filters"; - my $res = $jmap->CallMethods([ - ['Principal/query', { - filter => { - name => 'Test', - email => 'cassandane@example.com', - text => 'User', - }, - }, 'R1'], - ]); - $self->assert_deep_equals(['cassandane'], $res->[0][1]{ids}); + xlog "test filters"; + my $res = $jmap->CallMethods([ + [ + 'Principal/query', + { + filter => { + name => 'Test', + email => 'cassandane@example.com', + text => 'User', + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals(['cassandane'], $res->[0][1]{ids}); - xlog "test sorting"; - $res = $jmap->CallMethods([ - ['Principal/query', { - sort => [{ - property => 'id', - }], - }, 'R1'], - ['Principal/query', { - sort => [{ - property => 'id', - isAscending => JSON::false, - }], - }, 'R2'], - ]); - $self->assert_deep_equals(['cassandane', 'manifold'], $res->[0][1]{ids}); - $self->assert_deep_equals(['manifold', 'cassandane'], $res->[1][1]{ids}); + xlog "test sorting"; + $res = $jmap->CallMethods([ + [ + 'Principal/query', + { + sort => [ { + property => 'id', + } ], + }, + 'R1' + ], + [ + 'Principal/query', + { + sort => [ { + property => 'id', + isAscending => JSON::false, + } ], + }, + 'R2' + ], + ]); + $self->assert_deep_equals([ 'cassandane', 'manifold' ], $res->[0][1]{ids}); + $self->assert_deep_equals([ 'manifold', 'cassandane' ], $res->[1][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_querychanges b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_querychanges index 2f11613ca3..2a86f2b0d9 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_querychanges +++ b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_querychanges @@ -2,15 +2,16 @@ use Cassandane::Tiny; sub test_calendarprincipal_querychanges - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Principal/queryChanges', { - sinceQueryState => 'whatever', - }, 'R1'] - ]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + my $res = $jmap->CallMethods([ [ + 'Principal/queryChanges', + { + sinceQueryState => 'whatever', + }, + 'R1' + ] ]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_set b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_set index ccd73b92d4..4ccae06d6f 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_set +++ b/cassandane/tiny-tests/JMAPCalendars/calendarprincipal_set @@ -2,78 +2,95 @@ use Cassandane::Tiny; sub test_calendarprincipal_set - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Principal/set', { - create => { - principal1 => { - timeZone => 'America/New_York', - }, - }, - update => { - cassandane => { - name => 'Xyz', - }, - principal2 => { - timeZone => 'Europe/Berlin', - }, - }, - destroy => ['principal3'], - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'Principal/set', + { + create => { + principal1 => { + timeZone => 'America/New_York', + }, + }, + update => { + cassandane => { + name => 'Xyz', + }, + principal2 => { + timeZone => 'Europe/Berlin', + }, + }, + destroy => ['principal3'], + }, + 'R1' + ] ]); - $self->assert_str_equals('forbidden', - $res->[0][1]{notCreated}{principal1}{type}); - $self->assert_str_equals('forbidden', - $res->[0][1]{notUpdated}{principal2}{type}); - $self->assert_str_equals('forbidden', - $res->[0][1]{notDestroyed}{principal3}{type}); + $self->assert_str_equals('forbidden', + $res->[0][1]{notCreated}{principal1}{type}); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{principal2}{type}); + $self->assert_str_equals('forbidden', + $res->[0][1]{notDestroyed}{principal3}{type}); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{cassandane}{type}); - $self->assert_deep_equals(['name'], - $res->[0][1]{notUpdated}{cassandane}{properties}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{cassandane}{type}); + $self->assert_deep_equals(['name'], + $res->[0][1]{notUpdated}{cassandane}{properties}); - $res = $jmap->CallMethods([ - ['Principal/get', { - ids => ['cassandane'], - properties => ['timeZone'], - }, 'R1'], - ['Principal/set', { - update => { - cassandane => { - timeZone => 'Australia/Melbourne', - }, - }, - }, 'R2'], - ['Principal/get', { - ids => ['cassandane'], - properties => ['timeZone'], - }, 'R3'] - ]); - $self->assert_null($res->[0][1]{list}[0]{timeZone}); - $self->assert_deep_equals({}, $res->[1][1]{updated}{cassandane}); - $self->assert_str_equals('Australia/Melbourne', - $res->[2][1]{list}[0]{timeZone}); + $res = $jmap->CallMethods([ + [ + 'Principal/get', + { + ids => ['cassandane'], + properties => ['timeZone'], + }, + 'R1' + ], + [ + 'Principal/set', + { + update => { + cassandane => { + timeZone => 'Australia/Melbourne', + }, + }, + }, + 'R2' + ], + [ + 'Principal/get', + { + ids => ['cassandane'], + properties => ['timeZone'], + }, + 'R3' + ] + ]); + $self->assert_null($res->[0][1]{list}[0]{timeZone}); + $self->assert_deep_equals({}, $res->[1][1]{updated}{cassandane}); + $self->assert_str_equals('Australia/Melbourne', + $res->[2][1]{list}[0]{timeZone}); - $self->assert_not_null($res->[1][1]{oldState}); - $self->assert_not_null($res->[1][1]{newState}); - $self->assert_str_not_equals($res->[1][1]{oldState}, $res->[1][1]{newState}); + $self->assert_not_null($res->[1][1]{oldState}); + $self->assert_not_null($res->[1][1]{newState}); + $self->assert_str_not_equals($res->[1][1]{oldState}, $res->[1][1]{newState}); - my $oldState = $res->[1][1]{oldState}; - $res = $jmap->CallMethods([ - ['Principal/set', { - ifInState => $oldState, - update => { - cassandane => { - timeZone => 'Asia/Tokyo', - }, - }, - }, 'R1'], - ]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + my $oldState = $res->[1][1]{oldState}; + $res = $jmap->CallMethods([ + [ + 'Principal/set', + { + ifInState => $oldState, + update => { + cassandane => { + timeZone => 'Asia/Tokyo', + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_changes b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_changes index dc5a9177b4..53b1aa185a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_changes +++ b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_changes @@ -2,134 +2,144 @@ use Cassandane::Tiny; sub test_calendarsharenotification_changes - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Create sharee - my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + # Create sharee + my $admin = $self->{adminstore}->get_client(); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $manjmap->CallMethods([ - ['ShareNotification/get', { - }, 'R1'] - ]); - my $state = $res->[0][1]{state}; - $self->assert_not_null($state); + my $res = $manjmap->CallMethods([ [ 'ShareNotification/get', {}, 'R1' ] ]); + my $state = $res->[0][1]{state}; + $self->assert_not_null($state); - $res = $manjmap->CallMethods([ - ['ShareNotification/changes', { - sinceState => $state, - }, 'R1'] - ]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $res = $manjmap->CallMethods([ [ + 'ShareNotification/changes', + { + sinceState => $state, + }, + 'R1' + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - "shareWith/manifold" => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - }, - }, - }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + update => { + Default => { + "shareWith/manifold" => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + }, + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - $res = $manjmap->CallMethods([ - ['ShareNotification/changes', { - sinceState => $state, - }, 'R1'] - ]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - my $notifId = $res->[0][1]{created}[0]; - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]{newState}; + $res = $manjmap->CallMethods([ [ + 'ShareNotification/changes', + { + sinceState => $state, + }, + 'R1' + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + my $notifId = $res->[0][1]{created}[0]; + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]{newState}; - $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - 1 => { - name => 'someCalendar', - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - }, - }, - }, + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + 1 => { + name => 'someCalendar', + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{created}{1}); + }, + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{created}{1}); - $res = $manjmap->CallMethods([ - ['ShareNotification/set', { - destroy => [$notifId], - }, "R1"] - ]); - $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); + $res = $manjmap->CallMethods([ [ + 'ShareNotification/set', + { + destroy => [$notifId], + }, + "R1" + ] ]); + $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); - $res = $manjmap->CallMethods([ - ['ShareNotification/changes', { - sinceState => $state, - maxChanges => 1, - }, 'R1'] - ]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]{newState}; + $res = $manjmap->CallMethods([ [ + 'ShareNotification/changes', + { + sinceState => $state, + maxChanges => 1, + }, + 'R1' + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]{newState}; - $res = $manjmap->CallMethods([ - ['ShareNotification/changes', { - sinceState => $state, - }, 'R1'] - ]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); + $res = $manjmap->CallMethods([ [ + 'ShareNotification/changes', + { + sinceState => $state, + }, + 'R1' + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_get b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_get index d40c9d6ebe..02ab8c6486 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_get +++ b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_get @@ -2,104 +2,104 @@ use Cassandane::Tiny; sub test_calendarsharenotification_get - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Create sharee - my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + # Create sharee + my $admin = $self->{adminstore}->get_client(); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $manjmap->CallMethods([ - ['ShareNotification/get', { - }, 'R1'] - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - my $state = $res->[0][1]{state}; + my $res = $manjmap->CallMethods([ [ 'ShareNotification/get', {}, 'R1' ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + my $state = $res->[0][1]{state}; - $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - name => 'myname', - "shareWith/manifold" => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - }, - }, - }, - }, "R1"] - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); - - $res = $manjmap->CallMethods([ - ['ShareNotification/get', { - }, 'R1'] - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - - my $notif = $res->[0][1]{list}[0]; - # Assert dynamically generated values. - my $notifId = $notif->{id}; - $self->assert_not_null($notifId); - delete($notif->{id}); - $self->assert_not_null($notif->{created}); - delete($notif->{created}); - # Assert remaining values. - $self->assert_deep_equals({ - changedBy => { - name => 'Test User', - email => 'cassandane@example.com', - principalId => 'cassandane', - }, - objectType => 'Calendar', - objectAccountId => 'cassandane', - objectId => 'Default', - oldRights => undef, - newRights => { + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + update => { + Default => { + name => 'myname', + "shareWith/manifold" => { mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - mayWriteAll => JSON::false, - mayRSVP => JSON::false, - mayDelete => JSON::false, - mayAdmin => JSON::false, - mayUpdatePrivate => JSON::false, - mayWriteOwn => JSON::false, + mayReadItems => JSON::true, + }, }, - }, $notif); + }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{Default}); + + $res = $manjmap->CallMethods([ [ 'ShareNotification/get', {}, 'R1' ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + + my $notif = $res->[0][1]{list}[0]; + # Assert dynamically generated values. + my $notifId = $notif->{id}; + $self->assert_not_null($notifId); + delete($notif->{id}); + $self->assert_not_null($notif->{created}); + delete($notif->{created}); + # Assert remaining values. + $self->assert_deep_equals( + { + changedBy => { + name => 'Test User', + email => 'cassandane@example.com', + principalId => 'cassandane', + }, + objectType => 'Calendar', + objectAccountId => 'cassandane', + objectId => 'Default', + oldRights => undef, + newRights => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + mayWriteAll => JSON::false, + mayRSVP => JSON::false, + mayDelete => JSON::false, + mayAdmin => JSON::false, + mayUpdatePrivate => JSON::false, + mayWriteOwn => JSON::false, + }, + }, + $notif + ); - $res = $manjmap->CallMethods([ - ['ShareNotification/get', { - ids => [$notifId, 'nope'], - }, 'R1'] - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_deep_equals(['nope'], $res->[0][1]{notFound}); - $self->assert_str_not_equals($state, $res->[0][1]{state}); + $res = $manjmap->CallMethods([ [ + 'ShareNotification/get', + { + ids => [ $notifId, 'nope' ], + }, + 'R1' + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_deep_equals(['nope'], $res->[0][1]{notFound}); + $self->assert_str_not_equals($state, $res->[0][1]{state}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_query b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_query index 3fe14c3730..b82c4f112a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_query +++ b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_query @@ -2,131 +2,158 @@ use Cassandane::Tiny; sub test_calendarsharenotification_query - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Create sharee - my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + # Create sharee + my $admin = $self->{adminstore}->get_client(); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - A => { - name => 'A', - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - }, - }, - }, + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + A => { + name => 'A', + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, }, - }, 'R1'] - ]); - $self->assert_not_null($res->[0][1]{created}{A}); + }, + }, + }, + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{created}{A}); - sleep(1); + sleep(1); - $res = $jmap->CallMethods([ - ['Calendar/set', { - create => { - B => { - name => 'B', - shareWith => { - manifold => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - }, - }, - }, + $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + create => { + B => { + name => 'B', + shareWith => { + manifold => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, }, - }, 'R1'] - ]); - $self->assert_not_null($res->[0][1]{created}{B}); + }, + }, + }, + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{created}{B}); - $res = $manjmap->CallMethods([ - ['ShareNotification/query', { - }, 'R1'], - ['ShareNotification/query', { - sort => [{ - property => 'created', - isAscending => JSON::false, - }], - }, 'R2'], - ['ShareNotification/get', { - properties => ['created'], - }, 'R3'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[1][1]{ids}}); + $res = $manjmap->CallMethods([ + [ 'ShareNotification/query', {}, 'R1' ], + [ + 'ShareNotification/query', + { + sort => [ { + property => 'created', + isAscending => JSON::false, + } ], + }, + 'R2' + ], + [ + 'ShareNotification/get', + { + properties => ['created'], + }, + 'R3' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[1][1]{ids} }); - my %notifTimestamps = map { $_->{id} => $_->{created} } @{$res->[2][1]{list}}; - $self->assert($notifTimestamps{$res->[0][1]{ids}[0]} lt - $notifTimestamps{$res->[0][1]{ids}[1]}); - $self->assert($notifTimestamps{$res->[1][1]{ids}[0]} gt - $notifTimestamps{$res->[1][1]{ids}[1]}); + my %notifTimestamps + = map { $_->{id} => $_->{created} } @{ $res->[2][1]{list} }; + $self->assert($notifTimestamps{ $res->[0][1]{ids}[0] } + lt $notifTimestamps{ $res->[0][1]{ids}[1] }); + $self->assert($notifTimestamps{ $res->[1][1]{ids}[0] } + gt $notifTimestamps{ $res->[1][1]{ids}[1] }); - my $notifIdT1 = $res->[0][1]{ids}[0]; - my $timestampT1 = $notifTimestamps{$notifIdT1}; + my $notifIdT1 = $res->[0][1]{ids}[0]; + my $timestampT1 = $notifTimestamps{$notifIdT1}; - my $notifIdT2 = $res->[0][1]{ids}[1]; - my $timestampT2 = $notifTimestamps{$notifIdT2}; + my $notifIdT2 = $res->[0][1]{ids}[1]; + my $timestampT2 = $notifTimestamps{$notifIdT2}; - $res = $manjmap->CallMethods([ - ['ShareNotification/query', { - filter => { - before => $timestampT2, - }, - }, 'R1'], - ['ShareNotification/query', { - filter => { - after => $timestampT2, - }, - }, 'R2'], - ['ShareNotification/query', { - position => 1, - }, 'R3'], - ['ShareNotification/query', { - anchor => $notifIdT2, - anchorOffset => -1, - limit => 1, - }, 'R3'], - ]); - $self->assert_deep_equals([$notifIdT1], $res->[0][1]{ids}); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_deep_equals([$notifIdT2], $res->[1][1]{ids}); - $self->assert_num_equals(1, $res->[1][1]{total}); - $self->assert_deep_equals([$notifIdT2], $res->[2][1]{ids}); - $self->assert_num_equals(2, $res->[2][1]{total}); - $self->assert_deep_equals([$notifIdT1], $res->[3][1]{ids}); - $self->assert_num_equals(2, $res->[2][1]{total}); + $res = $manjmap->CallMethods([ + [ + 'ShareNotification/query', + { + filter => { + before => $timestampT2, + }, + }, + 'R1' + ], + [ + 'ShareNotification/query', + { + filter => { + after => $timestampT2, + }, + }, + 'R2' + ], + [ + 'ShareNotification/query', + { + position => 1, + }, + 'R3' + ], + [ + 'ShareNotification/query', + { + anchor => $notifIdT2, + anchorOffset => -1, + limit => 1, + }, + 'R3' + ], + ]); + $self->assert_deep_equals([$notifIdT1], $res->[0][1]{ids}); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_deep_equals([$notifIdT2], $res->[1][1]{ids}); + $self->assert_num_equals(1, $res->[1][1]{total}); + $self->assert_deep_equals([$notifIdT2], $res->[2][1]{ids}); + $self->assert_num_equals(2, $res->[2][1]{total}); + $self->assert_deep_equals([$notifIdT1], $res->[3][1]{ids}); + $self->assert_num_equals(2, $res->[2][1]{total}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_querychanges b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_querychanges index 52681e53bd..82846b3a44 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_querychanges +++ b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_querychanges @@ -2,15 +2,16 @@ use Cassandane::Tiny; sub test_calendarsharenotification_querychanges - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['ShareNotification/queryChanges', { - sinceQueryState => 'whatever', - }, 'R1'] - ]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + my $res = $jmap->CallMethods([ [ + 'ShareNotification/queryChanges', + { + sinceQueryState => 'whatever', + }, + 'R1' + ] ]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_set b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_set index c950590aa7..f3ebf4cd14 100644 --- a/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_set +++ b/cassandane/tiny-tests/JMAPCalendars/calendarsharenotification_set @@ -2,97 +2,101 @@ use Cassandane::Tiny; sub test_calendarsharenotification_set - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Create sharee - my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $mantalk = Net::CalDAVTalk->new( - user => "manifold", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + # Create sharee + my $admin = $self->{adminstore}->get_client(); + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $mantalk = Net::CalDAVTalk->new( + user => "manifold", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $jmap->CallMethods([ - ['Calendar/set', { - update => { - Default => { - name => 'myname', - "shareWith/manifold" => { - mayReadFreeBusy => JSON::true, - mayReadItems => JSON::true, - }, - }, - }, - }, "R1"] - ]); - $self->assert(exists $res->[0][1]{updated}{Default}); + my $res = $jmap->CallMethods([ [ + 'Calendar/set', + { + update => { + Default => { + name => 'myname', + "shareWith/manifold" => { + mayReadFreeBusy => JSON::true, + mayReadItems => JSON::true, + }, + }, + }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{Default}); - $res = $manjmap->CallMethods([ - ['ShareNotification/get', { - }, 'R1'] - ]); - my $notif = $res->[0][1]{list}[0]; - my $notifId = $notif->{id}; - $self->assert_not_null($notifId); - delete($notif->{id}); + $res = $manjmap->CallMethods([ [ 'ShareNotification/get', {}, 'R1' ] ]); + my $notif = $res->[0][1]{list}[0]; + my $notifId = $notif->{id}; + $self->assert_not_null($notifId); + delete($notif->{id}); - $res = $manjmap->CallMethods([ - ['ShareNotification/set', { - create => { - newnotif => $notif, - }, - update => { - $notifId => $notif, - }, - }, "R1"] - ]); - $self->assert_str_equals('forbidden', - $res->[0][1]{notCreated}{newnotif}{type}); - $self->assert_str_equals('forbidden', - $res->[0][1]{notUpdated}{$notifId}{type}); - $self->assert_not_null($res->[0][1]{newState}); - my $state = $res->[0][1]{newState}; + $res = $manjmap->CallMethods([ [ + 'ShareNotification/set', + { + create => { + newnotif => $notif, + }, + update => { + $notifId => $notif, + }, + }, + "R1" + ] ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notCreated}{newnotif}{type}); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{$notifId}{type}); + $self->assert_not_null($res->[0][1]{newState}); + my $state = $res->[0][1]{newState}; - $res = $manjmap->CallMethods([ - ['ShareNotification/set', { - destroy => [$notifId, 'unknownId'], - }, "R1"] - ]); - $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); - $self->assert_str_equals('notFound', - $res->[0][1]{notDestroyed}{unknownId}{type}); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $res = $manjmap->CallMethods([ [ + 'ShareNotification/set', + { + destroy => [ $notifId, 'unknownId' ], + }, + "R1" + ] ]); + $self->assert_deep_equals([$notifId], $res->[0][1]{destroyed}); + $self->assert_str_equals('notFound', + $res->[0][1]{notDestroyed}{unknownId}{type}); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $res = $manjmap->CallMethods([ - ['ShareNotification/get', { - ids => [$notifId], - }, 'R1'] - ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_deep_equals([$notifId], $res->[0][1]{notFound}); + $res = $manjmap->CallMethods([ [ + 'ShareNotification/get', + { + ids => [$notifId], + }, + 'R1' + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_deep_equals([$notifId], $res->[0][1]{notFound}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/crasher20191227 b/cassandane/tiny-tests/JMAPCalendars/crasher20191227 index b9e8c2cee1..90bdd66567 100644 --- a/cassandane/tiny-tests/JMAPCalendars/crasher20191227 +++ b/cassandane/tiny-tests/JMAPCalendars/crasher20191227 @@ -2,65 +2,68 @@ use Cassandane::Tiny; sub test_crasher20191227 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; + my $jmap = $self->{jmap}; + my $calid = "Default"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event1 => { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "title", - "description"=> "description", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT2H", - "timeZone" => "Europe/London", - "showWithoutTime"=> JSON::false, - recurrenceRules => [{ - frequency => 'weekly', - }], - recurrenceOverrides => { - '2015-11-14T09:00:00' => { - title => 'foo', - }, - }, - "freeBusyStatus"=> "busy", - "status" => "confirmed", - "alerts" => { - alert1 => { - trigger => { - '@type' => 'OffsetTrigger', - relativeTo => "start", - offset => "-PT5M", - }, - acknowledged => "2015-11-07T08:57:00Z", - action => "email", - }, - }, - "useDefaultAlerts" => JSON::true, - }, + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + create => { + event1 => { + calendarIds => { + $calid => JSON::true, + }, + "title" => "title", + "description" => "description", + "start" => "2015-11-07T09:00:00", + "duration" => "PT2H", + "timeZone" => "Europe/London", + "showWithoutTime" => JSON::false, + recurrenceRules => [ { + frequency => 'weekly', + } ], + recurrenceOverrides => { + '2015-11-14T09:00:00' => { + title => 'foo', }, - }, 'R1'] - ]); - my $eventId = $res->[0][1]{created}{event1}{id}; - $self->assert_not_null($eventId); - - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'recurrenceOverrides/2015-11-14T09:00:00' => { - alerts => undef, - } - }, + }, + "freeBusyStatus" => "busy", + "status" => "confirmed", + "alerts" => { + alert1 => { + trigger => { + '@type' => 'OffsetTrigger', + relativeTo => "start", + offset => "-PT5M", + }, + acknowledged => "2015-11-07T08:57:00Z", + action => "email", }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + }, + "useDefaultAlerts" => JSON::true, + }, + }, + }, + 'R1' + ] ]); + my $eventId = $res->[0][1]{created}{event1}{id}; + $self->assert_not_null($eventId); + + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'recurrenceOverrides/2015-11-14T09:00:00' => { + alerts => undef, + } + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/itip_ignore_invalid_timezone b/cassandane/tiny-tests/JMAPCalendars/itip_ignore_invalid_timezone index 79a24af51c..f4c19b5ddc 100644 --- a/cassandane/tiny-tests/JMAPCalendars/itip_ignore_invalid_timezone +++ b/cassandane/tiny-tests/JMAPCalendars/itip_ignore_invalid_timezone @@ -3,40 +3,46 @@ use Cassandane::Tiny; use Data::UUID; sub test_itip_ignore_invalid_timezone - :min_version_3_9 :needs_component_jmap :needs_component_sieve -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_component_sieve { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'UTC', # can handle as 'Etc/UTC' - wantAdded => 1, - }, { - tzid => 'Europe/Vienna', # already is IANA name - wantAdded => 1, - }, { - tzid => 'Pacific Standard Time', # can map to IANA name - wantAdded => 1, - }, { - tzid => 'Foo', # can't map to IANA, reject - wantAdded => 0, - }); + my @testCases = ( + { + tzid => 'UTC', # can handle as 'Etc/UTC' + wantAdded => 1, + }, + { + tzid => 'Europe/Vienna', # already is IANA name + wantAdded => 1, + }, + { + tzid => 'Pacific Standard Time', # can map to IANA name + wantAdded => 1, + }, + { + tzid => 'Foo', # can't map to IANA, reject + wantAdded => 0, + } + ); - for my $tc (@testCases) { + for my $tc (@testCases) { - my $uid = (Data::UUID->new)->create_str(); + my $uid = (Data::UUID->new)->create_str(); - xlog $self, "Send iMIP message with invalid VTIMEZONE having name $tc->{tzid}"; - my $imip = <<"EOF"; + xlog $self, + "Send iMIP message with invalid VTIMEZONE having name $tc->{tzid}"; + my $imip = <<"EOF"; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -66,18 +72,20 @@ END:VEVENT END:VCALENDAR EOF - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [encode_eventid($uid)] - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'CalendarEvent/get', + { + ids => [ encode_eventid($uid) ] + }, + 'R1' + ] ]); - if ($tc->{wantAdded}) { - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - } else { - $self->assert_num_equals(1, scalar @{$res->[0][1]{notFound}}); - } + if ($tc->{wantAdded}) { + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + } else { + $self->assert_num_equals(1, scalar @{ $res->[0][1]{notFound} }); } + } } diff --git a/cassandane/tiny-tests/JMAPCalendars/itip_remove_privacy_property b/cassandane/tiny-tests/JMAPCalendars/itip_remove_privacy_property index b1427795c3..e506388ee5 100644 --- a/cassandane/tiny-tests/JMAPCalendars/itip_remove_privacy_property +++ b/cassandane/tiny-tests/JMAPCalendars/itip_remove_privacy_property @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_itip_remove_privacy_property - :min_version_3_7 :needs_component_jmap :needs_component_sieve -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{jmap}; + my $caldav = $self->{caldav}; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -47,51 +47,59 @@ END:VEVENT END:VCALENDAR EOF - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - - xlog $self, "Assert privacy property is reset to default"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'participants', 'privacy', 'x-href'], - }, 'R1'], - ]); - my $eventId = $res->[0][1]{list}[0]{id}; - $self->assert_null($res->[0][1]{list}[0]->{privacy}); - my $xhref = $res->[0][1]{list}[0]{'x-href'}; - $self->assert_not_null($xhref); - - xlog $self, "Assert privacy property is not present in iCalendar"; - my $caldavResponse = $caldav->Request('GET', $xhref); - $self->assert($caldavResponse->{content} !~ /X-JMAP-PRIVACY:PRIVATE/); - - xlog $self, "Clear notifications"; - $self->{instance}->getnotify(); - - xlog $self, "Set privacy to 'secret' and accept invitation"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'participants/cassandane/participationStatus' => 'accepted', - 'privacy' => 'secret', - }, - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - - xlog $self, "Assert that iMIP message is sent"; - my $data = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($imip); - - xlog $self, "Assert privacy property is not present in iTIP REPLY"; - my $msg = decode_json($imip->{MESSAGE}); - $self->assert($msg->{ical} !~ /X-JMAP-PRIVACY:PRIVATE/); - - xlog $self, "Organizer updates invite with recurrence override"; - - my $imip = <<'EOF'; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + + xlog $self, "Assert privacy property is reset to default"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'participants', 'privacy', 'x-href' ], + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{list}[0]{id}; + $self->assert_null($res->[0][1]{list}[0]->{privacy}); + my $xhref = $res->[0][1]{list}[0]{'x-href'}; + $self->assert_not_null($xhref); + + xlog $self, "Assert privacy property is not present in iCalendar"; + my $caldavResponse = $caldav->Request('GET', $xhref); + $self->assert($caldavResponse->{content} !~ /X-JMAP-PRIVACY:PRIVATE/); + + xlog $self, "Clear notifications"; + $self->{instance}->getnotify(); + + xlog $self, "Set privacy to 'secret' and accept invitation"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'participants/cassandane/participationStatus' => 'accepted', + 'privacy' => 'secret', + }, + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + + xlog $self, "Assert that iMIP message is sent"; + my $data = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($imip); + + xlog $self, "Assert privacy property is not present in iTIP REPLY"; + my $msg = decode_json($imip->{MESSAGE}); + $self->assert($msg->{ical} !~ /X-JMAP-PRIVACY:PRIVATE/); + + xlog $self, "Organizer updates invite with recurrence override"; + + my $imip = <<'EOF'; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Sally Sender To: Cassandane @@ -134,18 +142,23 @@ END:VEVENT END:VCALENDAR EOF - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - - xlog $self, "Assert user-set privacy property is preserved"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [$eventId], - properties => ['id', 'privacy', 'recurrenceOverrides'], - }, 'R1'], - ]); - $self->assert_str_equals('secret', $res->[0][1]{list}[0]->{privacy}); - - xlog $self, "Assert privacy property on override matches main event"; - $self->assert_null($res->[0][1]{list}[0]->{ - recurrenceOverrides}{'2021-09-24T15:30:00'}{privacy}); + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + + xlog $self, "Assert user-set privacy property is preserved"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => [ 'id', 'privacy', 'recurrenceOverrides' ], + }, + 'R1' + ], + ]); + $self->assert_str_equals('secret', $res->[0][1]{list}[0]->{privacy}); + + xlog $self, "Assert privacy property on override matches main event"; + $self->assert_null( + $res->[0][1]{list}[0]->{recurrenceOverrides}{'2021-09-24T15:30:00'}{privacy} + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/itip_request_tzid_change b/cassandane/tiny-tests/JMAPCalendars/itip_request_tzid_change index 1152976aff..3086474c2a 100644 --- a/cassandane/tiny-tests/JMAPCalendars/itip_request_tzid_change +++ b/cassandane/tiny-tests/JMAPCalendars/itip_request_tzid_change @@ -2,84 +2,91 @@ use Cassandane::Tiny; sub test_itip_request_tzid_change - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $uid = 'event1uid', + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $uid = 'event1uid', xlog "Clear notifications"; - $self->{instance}->getnotify(); + $self->{instance}->getnotify(); - xlog "Create scheduled event"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - event => { - calendarIds => { - 'Default' => JSON::true, - }, - '@type' => 'Event', - uid => $uid, - title => 'event', - start => '2021-01-01T15:30:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', - recurrenceRules => [{ - frequency => 'daily', - count => 30, - }], - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - cassandane => { - roles => { - attendee => JSON::true, - }, - sendTo => { - imip => 'mailto:someone@example.com', - }, - participationStatus => 'needs-action', - expectReply => JSON::true, - }, - }, + xlog "Create scheduled event"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + event => { + calendarIds => { + 'Default' => JSON::true, + }, + '@type' => 'Event', + uid => $uid, + title => 'event', + start => '2021-01-01T15:30:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + recurrenceRules => [ { + frequency => 'daily', + count => 30, + } ], + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + cassandane => { + roles => { + attendee => JSON::true, + }, + sendTo => { + imip => 'mailto:someone@example.com', }, + participationStatus => 'needs-action', + expectReply => JSON::true, + }, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{event}{id}; - $self->assert_not_null($eventId); + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{event}{id}; + $self->assert_not_null($eventId); - xlog "Assert that iTIP notification is sent"; - my $data = $self->{instance}->getnotify(); - my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - my $notif_payload = decode_json($notif->{MESSAGE}); - my $expect_id = encode_eventid($uid); - $self->assert_str_equals($expect_id, $notif_payload->{id}); - $self->assert_str_equals('REQUEST', $notif_payload->{method}); + xlog "Assert that iTIP notification is sent"; + my $data = $self->{instance}->getnotify(); + my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + my $notif_payload = decode_json($notif->{MESSAGE}); + my $expect_id = encode_eventid($uid); + $self->assert_str_equals($expect_id, $notif_payload->{id}); + $self->assert_str_equals('REQUEST', $notif_payload->{method}); - xlog "Clear notifications"; - $self->{instance}->getnotify(); + xlog "Clear notifications"; + $self->{instance}->getnotify(); - xlog "Update time zone of scheduled event"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - timeZone => 'America/New_York', - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog "Update time zone of scheduled event"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + timeZone => 'America/New_York', + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog "Assert that iTIP notification is sent"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - $notif_payload = decode_json($notif->{MESSAGE}); - $self->assert_str_equals($expect_id, $notif_payload->{id}); - $self->assert_str_equals('REQUEST', $notif_payload->{method}); + xlog "Assert that iTIP notification is sent"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + $notif_payload = decode_json($notif->{MESSAGE}); + $self->assert_str_equals($expect_id, $notif_payload->{id}); + $self->assert_str_equals('REQUEST', $notif_payload->{method}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/itip_rsvp_organizer_change b/cassandane/tiny-tests/JMAPCalendars/itip_rsvp_organizer_change index 2306114936..6af41d3304 100644 --- a/cassandane/tiny-tests/JMAPCalendars/itip_rsvp_organizer_change +++ b/cassandane/tiny-tests/JMAPCalendars/itip_rsvp_organizer_change @@ -2,22 +2,22 @@ use Cassandane::Tiny; sub test_itip_rsvp_organizer_change - :min_version_3_7 :needs_component_jmap :needs_component_sieve -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $uid = '7e017102-0caf-490a-bbdf-422141d34e75'; + : min_version_3_7 : needs_component_jmap : needs_component_sieve { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $uid = '7e017102-0caf-490a-bbdf-422141d34e75'; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -44,70 +44,89 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + xlog $self, "Deliver iMIP invite"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - xlog "Clear notifications"; - $self->{instance}->getnotify(); + xlog "Clear notifications"; + $self->{instance}->getnotify(); - xlog "Accept invitation in JMAP"; - my $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - properties => ['id', 'participants'], - }, 'R1'], - ]); - my $eventId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($eventId); - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - 'participants/cassandane/participationStatus' => 'accepted', - }, - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); + xlog "Accept invitation in JMAP"; + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + properties => [ 'id', 'participants' ], + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($eventId); + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + 'participants/cassandane/participationStatus' => 'accepted', + }, + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); - xlog "Assert that iTIP notification is sent"; - my $data = $self->{instance}->getnotify(); - my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - my $notif_payload = decode_json($notif->{MESSAGE}); - my $expect_id = encode_eventid($uid); - $self->assert_str_equals($expect_id, $notif_payload->{id}); - $self->assert_str_equals('REPLY', $notif_payload->{method}); + xlog "Assert that iTIP notification is sent"; + my $data = $self->{instance}->getnotify(); + my ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + my $notif_payload = decode_json($notif->{MESSAGE}); + my $expect_id = encode_eventid($uid); + $self->assert_str_equals($expect_id, $notif_payload->{id}); + $self->assert_str_equals('REPLY', $notif_payload->{method}); - xlog "Clear notifications"; - $self->{instance}->getnotify(); + xlog "Clear notifications"; + $self->{instance}->getnotify(); - xlog "Change organizer in JMAP"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - replyTo => { - imip => 'mailto:organizerB@example.net', - }, - 'participants/organizerA' => undef, - }, - } - }, 'R1'], - ['CalendarEvent/get', { - ids => [ $eventId ], - properties => [ 'replyTo' ], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $self->assert_deep_equals({ - imip => 'mailto:organizerB@example.net', - }, $res->[1][1]{list}[0]{replyTo}); + xlog "Change organizer in JMAP"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + replyTo => { + imip => 'mailto:organizerB@example.net', + }, + 'participants/organizerA' => undef, + }, + } + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + properties => ['replyTo'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $self->assert_deep_equals( + { + imip => 'mailto:organizerB@example.net', + }, + $res->[1][1]{list}[0]{replyTo} + ); - xlog "Assert that iTIP notification is sent"; - $data = $self->{instance}->getnotify(); - ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; - $self->assert_not_null($notif); - $notif_payload = decode_json($notif->{MESSAGE}); - $self->assert_str_equals($expect_id, $notif_payload->{id}); - $self->assert_str_equals('REPLY', $notif_payload->{method}); + xlog "Assert that iTIP notification is sent"; + $data = $self->{instance}->getnotify(); + ($notif) = grep { $_->{METHOD} eq 'imip' } @$data; + $self->assert_not_null($notif); + $notif_payload = decode_json($notif->{MESSAGE}); + $self->assert_str_equals($expect_id, $notif_payload->{id}); + $self->assert_str_equals('REPLY', $notif_payload->{method}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/misc_creationids b/cassandane/tiny-tests/JMAPCalendars/misc_creationids index 5870f6d9f2..e1e5d0fc51 100644 --- a/cassandane/tiny-tests/JMAPCalendars/misc_creationids +++ b/cassandane/tiny-tests/JMAPCalendars/misc_creationids @@ -2,38 +2,54 @@ use Cassandane::Tiny; sub test_misc_creationids - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create and get calendar and event"; - my $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "c1" => { - name => "foo", - color => "coral", + xlog $self, "create and get calendar and event"; + my $res = $jmap->CallMethods([ + [ + 'Calendar/set', + { + create => { + "c1" => { + name => "foo", + color => "coral", sortOrder => 2, isVisible => \1, - }}}, 'R1'], - ['CalendarEvent/set', { create => { "e1" => { + } + } + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + "e1" => { calendarIds => { - '#c1' => JSON::true, + '#c1' => JSON::true, }, - "title" => "bar", - "description" => "description", - "freeBusyStatus" => "busy", + "title" => "bar", + "description" => "description", + "freeBusyStatus" => "busy", "showWithoutTime" => JSON::true, - "start" => "2015-10-06T00:00:00", - }}}, "R2"], - ['CalendarEvent/get', {ids => ["#e1"]}, "R3"], - ['Calendar/get', {ids => ["#c1"]}, "R4"], - ]); - my $event = $res->[2][1]{list}[0]; - $self->assert_str_equals("bar", $event->{title}); + "start" => "2015-10-06T00:00:00", + } + } + }, + "R2" + ], + [ 'CalendarEvent/get', { ids => ["#e1"] }, "R3" ], + [ 'Calendar/get', { ids => ["#c1"] }, "R4" ], + ]); + my $event = $res->[2][1]{list}[0]; + $self->assert_str_equals("bar", $event->{title}); - my $calendar = $res->[3][1]{list}[0]; - $self->assert_str_equals("foo", $calendar->{name}); + my $calendar = $res->[3][1]{list}[0]; + $self->assert_str_equals("foo", $calendar->{name}); - $self->assert_deep_equals({$calendar->{id} => JSON::true}, $event->{calendarIds}); + $self->assert_deep_equals({ $calendar->{id} => JSON::true }, + $event->{calendarIds}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/misc_timezone_expansion b/cassandane/tiny-tests/JMAPCalendars/misc_timezone_expansion index d2515f5e55..ae23f071d4 100644 --- a/cassandane/tiny-tests/JMAPCalendars/misc_timezone_expansion +++ b/cassandane/tiny-tests/JMAPCalendars/misc_timezone_expansion @@ -2,42 +2,42 @@ use Cassandane::Tiny; sub test_misc_timezone_expansion - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "uid" => "58ADE31-custom-UID", - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT5M", - "sequence"=> 42, - "timeZone"=> "Europe/Vienna", - "showWithoutTime"=> JSON::false, - "locale" => "en", - "status" => "tentative", - "description"=> "", - "freeBusyStatus"=> "busy", - "privacy" => "secret", - "participants" => undef, - "alerts"=> undef, - "recurrenceRules" => [{ - frequency => "weekly", - }], - }; + my $jmap = $self->{jmap}; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "uid" => "58ADE31-custom-UID", + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT5M", + "sequence" => 42, + "timeZone" => "Europe/Vienna", + "showWithoutTime" => JSON::false, + "locale" => "en", + "status" => "tentative", + "description" => "", + "freeBusyStatus" => "busy", + "privacy" => "secret", + "participants" => undef, + "alerts" => undef, + "recurrenceRules" => [ { + frequency => "weekly", + } ], + }; - my $ret = $self->createandget_event($event); + my $ret = $self->createandget_event($event); - my $CalDAV = $self->{caldav}; - $ret = $CalDAV->Request('GET', $ret->{"x-href"}, undef, 'CalDAV-Timezones' => 'T'); + my $CalDAV = $self->{caldav}; + $ret = $CalDAV->Request('GET', $ret->{"x-href"}, undef, + 'CalDAV-Timezones' => 'T'); - # Assert that we get two RRULEs, one for DST and one for leaving DST - $ret->{content} =~ /.*(BEGIN:VTIMEZONE\r\n.*END:VTIMEZONE).*/s; - my $rrulecount = () = $1 =~ /RRULE/gi; - $self->assert_num_equals(2, $rrulecount); + # Assert that we get two RRULEs, one for DST and one for leaving DST + $ret->{content} =~ /.*(BEGIN:VTIMEZONE\r\n.*END:VTIMEZONE).*/s; + my $rrulecount = () = $1 =~ /RRULE/gi; + $self->assert_num_equals(2, $rrulecount); } diff --git a/cassandane/tiny-tests/JMAPCalendars/no_shared_calendar b/cassandane/tiny-tests/JMAPCalendars/no_shared_calendar index d239e7a920..d2061ab4a6 100644 --- a/cassandane/tiny-tests/JMAPCalendars/no_shared_calendar +++ b/cassandane/tiny-tests/JMAPCalendars/no_shared_calendar @@ -2,87 +2,110 @@ use Cassandane::Tiny; sub test_no_shared_calendar - :min_version_3_5 :needs_component_jmap :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap : JMAPExtensions : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create other user"; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user.other'); - $admintalk->setacl('user.other', admin => 'lrswipkxtecdan') or die; - $admintalk->setacl('user.other', other => 'lrswipkxtecdn') or die; + xlog $self, "create other user"; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user.other'); + $admintalk->setacl('user.other', admin => 'lrswipkxtecdan') or die; + $admintalk->setacl('user.other', other => 'lrswipkxtecdn') or die; - my $service = $self->{instance}->get_service("http"); - my $otherJmap = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $otherJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + my $service = $self->{instance}->get_service("http"); + my $otherJmap = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $otherJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + ]); - my $res = $otherJmap->CallMethods([ - ['Calendar/get', { - properties => ['id'], - }, 'R1'], - ]); - my $otherCalendarId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($otherCalendarId); - $admintalk->setacl('user.other.#calendars', cassandane => 'lr') or die; + my $res = $otherJmap->CallMethods([ + [ + 'Calendar/get', + { + properties => ['id'], + }, + 'R1' + ], + ]); + my $otherCalendarId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($otherCalendarId); + $admintalk->setacl('user.other.#calendars', cassandane => 'lr') or die; - $res = $jmap->ua->get($jmap->uri(), { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }); - $self->assert_str_equals('200', $res->{status}); - my $session = eval { decode_json($res->{content}) }; - my $capabilities = $session->{accounts}{other}{accountCapabilities}; - $self->assert_not_null($capabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); + $res = $jmap->ua->get( + $jmap->uri(), + { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + } + ); + $self->assert_str_equals('200', $res->{status}); + my $session = eval { decode_json($res->{content}) }; + my $capabilities = $session->{accounts}{other}{accountCapabilities}; + $self->assert_not_null( + $capabilities->{'https://cyrusimap.org/ns/jmap/calendars'}); - $res = $jmap->CallMethods([ - ['Calendar/get', { - accountId => 'other', - }, 'R1'], - ['Calendar/changes', { - accountId => 'other', - sinceState => '0', - }, 'R2'], - ['Calendar/set', { - accountId => 'other', - create => { - calendar1 => { - name => 'test', - }, - }, - update => { - $otherCalendarId => { - name => 'test', - }, - }, - destroy => [$otherCalendarId], - }, 'R3'], - ['CalendarEvent/get', { - accountId => 'other', - }, 'R4'], - ]); - $self->assert_deep_equals([], $res->[0][1]{list}); - $self->assert_deep_equals([], $res->[1][1]{created}); - $self->assert_deep_equals([], $res->[1][1]{updated}); - $self->assert_deep_equals([], $res->[1][1]{destroyed}); - $self->assert_str_equals('accountReadOnly', - $res->[2][1]{notCreated}{calendar1}{type}); - $self->assert_str_equals('notFound', - $res->[2][1]{notUpdated}{$otherCalendarId}{type}); - $self->assert_str_equals('notFound', - $res->[2][1]{notDestroyed}{$otherCalendarId}{type}); - $self->assert_deep_equals([], $res->[3][1]{list}); + $res = $jmap->CallMethods([ + [ + 'Calendar/get', + { + accountId => 'other', + }, + 'R1' + ], + [ + 'Calendar/changes', + { + accountId => 'other', + sinceState => '0', + }, + 'R2' + ], + [ + 'Calendar/set', + { + accountId => 'other', + create => { + calendar1 => { + name => 'test', + }, + }, + update => { + $otherCalendarId => { + name => 'test', + }, + }, + destroy => [$otherCalendarId], + }, + 'R3' + ], + [ + 'CalendarEvent/get', + { + accountId => 'other', + }, + 'R4' + ], + ]); + $self->assert_deep_equals([], $res->[0][1]{list}); + $self->assert_deep_equals([], $res->[1][1]{created}); + $self->assert_deep_equals([], $res->[1][1]{updated}); + $self->assert_deep_equals([], $res->[1][1]{destroyed}); + $self->assert_str_equals('accountReadOnly', + $res->[2][1]{notCreated}{calendar1}{type}); + $self->assert_str_equals('notFound', + $res->[2][1]{notUpdated}{$otherCalendarId}{type}); + $self->assert_str_equals('notFound', + $res->[2][1]{notDestroyed}{$otherCalendarId}{type}); + $self->assert_deep_equals([], $res->[3][1]{list}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/participantidentity_changes b/cassandane/tiny-tests/JMAPCalendars/participantidentity_changes index 6fa6af328f..26d520d9ec 100644 --- a/cassandane/tiny-tests/JMAPCalendars/participantidentity_changes +++ b/cassandane/tiny-tests/JMAPCalendars/participantidentity_changes @@ -2,15 +2,16 @@ use Cassandane::Tiny; sub test_participantidentity_changes - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['ParticipantIdentity/changes', { - sinceState => '0', - }, 'R1'] - ]); - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + my $res = $jmap->CallMethods([ [ + 'ParticipantIdentity/changes', + { + sinceState => '0', + }, + 'R1' + ] ]); + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/participantidentity_get b/cassandane/tiny-tests/JMAPCalendars/participantidentity_get index 2c2832d1a4..1cd86a5de8 100644 --- a/cassandane/tiny-tests/JMAPCalendars/participantidentity_get +++ b/cassandane/tiny-tests/JMAPCalendars/participantidentity_get @@ -2,52 +2,60 @@ use Cassandane::Tiny; sub test_participantidentity_get - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $res = $jmap->CallMethods([ - ['ParticipantIdentity/get', { - }, 'R1'], - ]); + my $res = $jmap->CallMethods([ [ 'ParticipantIdentity/get', {}, 'R1' ], ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_deep_equals({ - imip => 'mailto:cassandane@example.com', - }, $res->[0][1]{list}[0]{sendTo}); - my $partId1 = $res->[0][1]{list}[0]{id}; + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_deep_equals( + { + imip => 'mailto:cassandane@example.com', + }, + $res->[0][1]{list}[0]{sendTo} + ); + my $partId1 = $res->[0][1]{list}[0]{id}; - $caldav->Request( - 'PROPPATCH', - '', - x('D:propertyupdate', $caldav->NS(), - x('D:set', - x('D:prop', - x('C:calendar-user-address-set', - x('D:href', 'mailto:cassandane@example.com'), - x('D:href', 'mailto:foo@local'), - ) + $caldav->Request( + 'PROPPATCH', + '', + x( + 'D:propertyupdate', + $caldav->NS(), + x( + 'D:set', + x( + 'D:prop', + x( + 'C:calendar-user-address-set', + x('D:href', 'mailto:cassandane@example.com'), + x('D:href', 'mailto:foo@local'), ) ) ) - ); + ) + ); - $res = $jmap->CallMethods([ - ['ParticipantIdentity/get', { - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); + $res = $jmap->CallMethods([ [ 'ParticipantIdentity/get', {}, 'R1' ], ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); - $res = $jmap->CallMethods([ - ['ParticipantIdentity/get', { - ids => [$partId1, 'nope'], - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); - $self->assert_deep_equals({ - imip => 'mailto:cassandane@example.com', - }, $res->[0][1]{list}[0]{sendTo}); - $self->assert_deep_equals(['nope'], $res->[0][1]{notFound}); + $res = $jmap->CallMethods([ + [ + 'ParticipantIdentity/get', + { + ids => [ $partId1, 'nope' ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); + $self->assert_deep_equals( + { + imip => 'mailto:cassandane@example.com', + }, + $res->[0][1]{list}[0]{sendTo} + ); + $self->assert_deep_equals(['nope'], $res->[0][1]{notFound}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/participantidentity_set b/cassandane/tiny-tests/JMAPCalendars/participantidentity_set index d3ed1af41e..92370c88b5 100644 --- a/cassandane/tiny-tests/JMAPCalendars/participantidentity_set +++ b/cassandane/tiny-tests/JMAPCalendars/participantidentity_set @@ -2,33 +2,34 @@ use Cassandane::Tiny; sub test_participantidentity_set - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['ParticipantIdentity/set', { - create => { - partid1 => { - sendTo => { - imip => 'mailto:foo@local', - }, - }, - }, - update => { - partid2 => { - name => 'bar', - }, - }, - destroy => ['partid3'], - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'ParticipantIdentity/set', + { + create => { + partid1 => { + sendTo => { + imip => 'mailto:foo@local', + }, + }, + }, + update => { + partid2 => { + name => 'bar', + }, + }, + destroy => ['partid3'], + }, + 'R1' + ] ]); - $self->assert_str_equals('forbidden', - $res->[0][1]{notCreated}{partid1}{type}); - $self->assert_str_equals('forbidden', - $res->[0][1]{notUpdated}{partid2}{type}); - $self->assert_str_equals('forbidden', - $res->[0][1]{notDestroyed}{partid3}{type}); + $self->assert_str_equals('forbidden', + $res->[0][1]{notCreated}{partid1}{type}); + $self->assert_str_equals('forbidden', + $res->[0][1]{notUpdated}{partid2}{type}); + $self->assert_str_equals('forbidden', + $res->[0][1]{notDestroyed}{partid3}{type}); } diff --git a/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_binary_itip_caldav b/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_binary_itip_caldav index 9b850c7b92..74406f2d17 100644 --- a/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_binary_itip_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_binary_itip_caldav @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_rewrite_webdav_attachment_binary_itip_caldav - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + my $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -27,27 +26,30 @@ SEQUENCE:0 END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $rawIcal, - 'Content-Type' => 'text/calendar'); - my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; + $caldav->Request('PUT', 'Default/test.ics', $rawIcal, + 'Content-Type' => 'text/calendar'); + my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; - xlog "Add attachment via CalDAV"; - my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; - my $res = $caldav->ua->post($url, { - headers => { - 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment;filename=test', - 'Prefer' => 'return=representation', - 'Authorization' => $caldav->auth_header(), - }, - content => 'someblob', - }); - $self->assert_str_equals('201', $res->{status}); + xlog "Add attachment via CalDAV"; + my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; + my $res = $caldav->ua->post( + $url, + { + headers => { + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment;filename=test', + 'Prefer' => 'return=representation', + 'Authorization' => $caldav->auth_header(), + }, + content => 'someblob', + } + ); + $self->assert_str_equals('201', $res->{status}); - # Now we have a blob "someblob" (c29tZWJsb2I=) in managed attachments. + # Now we have a blob "someblob" (c29tZWJsb2I=) in managed attachments. - xlog "Create event via CalDAV"; - $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -67,23 +69,25 @@ SEQUENCE:1 END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $rawIcal, - 'Schedule-Sender-Address' => 'attendee@local', - 'Content-Type' => 'text/calendar'); + $caldav->Request( + 'PUT', 'Default/test.ics', $rawIcal, + 'Schedule-Sender-Address' => 'attendee@local', + 'Content-Type' => 'text/calendar' + ); - my $caldavResponse = $caldav->Request('GET', $eventHref); - my $ical = Data::ICal->new(data => $caldavResponse->{content}); - my %entries = map { $_->ical_entry_type() => $_ } @{$ical->entries()}; - my $event = $entries{'VEVENT'}; - $self->assert_not_null($event); + my $caldavResponse = $caldav->Request('GET', $eventHref); + my $ical = Data::ICal->new(data => $caldavResponse->{content}); + my %entries = map { $_->ical_entry_type() => $_ } @{ $ical->entries() }; + my $event = $entries{'VEVENT'}; + $self->assert_not_null($event); - xlog "Assert BINARY ATTACH got rewritten to managed attachment URI"; - my $attach = $event->property('ATTACH'); - $self->assert_num_equals(1, scalar @{$attach}); - $self->assert_not_null($attach->[0]->parameters()->{'MANAGED-ID'}); - $self->assert_null($attach->[0]->parameters()->{VALUE}); - my $webdavAttachURI = - $self->{instance}->{config}->get('webdav_attachments_baseurl') . - '/dav/calendars/user/cassandane/Attachments/'; - $self->assert($attach->[0]->value() =~ /^$webdavAttachURI.+/); + xlog "Assert BINARY ATTACH got rewritten to managed attachment URI"; + my $attach = $event->property('ATTACH'); + $self->assert_num_equals(1, scalar @{$attach}); + $self->assert_not_null($attach->[0]->parameters()->{'MANAGED-ID'}); + $self->assert_null($attach->[0]->parameters()->{VALUE}); + my $webdavAttachURI + = $self->{instance}->{config}->get('webdav_attachments_baseurl') + . '/dav/calendars/user/cassandane/Attachments/'; + $self->assert($attach->[0]->value() =~ /^$webdavAttachURI.+/); } diff --git a/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_url_itip_caldav b/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_url_itip_caldav index 8615a12a32..1ef896cf95 100644 --- a/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_url_itip_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_url_itip_caldav @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_rewrite_webdav_attachment_url_itip_caldav - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + my $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -27,25 +26,28 @@ SEQUENCE:0 END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $rawIcal, - 'Content-Type' => 'text/calendar'); - my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; + $caldav->Request('PUT', 'Default/test.ics', $rawIcal, + 'Content-Type' => 'text/calendar'); + my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog "Add attachment via CalDAV"; - my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; - my $res = $caldav->ua->post($url, { - headers => { - 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment;filename=test', - 'Prefer' => 'return=representation', - 'Authorization' => $caldav->auth_header(), - }, - content => 'someblob', - }); - $self->assert_str_equals('201', $res->{status}); + xlog "Add attachment via CalDAV"; + my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; + my $res = $caldav->ua->post( + $url, + { + headers => { + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment;filename=test', + 'Prefer' => 'return=representation', + 'Authorization' => $caldav->auth_header(), + }, + content => 'someblob', + } + ); + $self->assert_str_equals('201', $res->{status}); - $self->assert_rewrite_webdav_attachment_url_itip($eventHref); + $self->assert_rewrite_webdav_attachment_url_itip($eventHref); } diff --git a/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_url_itip_jmap b/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_url_itip_jmap index e16788531d..5b68361ef7 100644 --- a/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_url_itip_jmap +++ b/cassandane/tiny-tests/JMAPCalendars/rewrite_webdav_attachment_url_itip_jmap @@ -2,65 +2,68 @@ use Cassandane::Tiny; sub test_rewrite_webdav_attachment_url_itip_jmap - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Upload blob via JMAP"; - my $res = $jmap->Upload('someblob', "application/octet-stream"); - my $blobId = $res->{blobId}; - $self->assert_not_null($blobId); + xlog "Upload blob via JMAP"; + my $res = $jmap->Upload('someblob', "application/octet-stream"); + my $blobId = $res->{blobId}; + $self->assert_not_null($blobId); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog "Create event with a Link.blobId"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - create => { - 1 => { - uid => 'eventuid1local', - calendarIds => { - Default => JSON::true, - }, - title => "event1", - start => "2019-12-10T23:30:00", - duration => "PT1H", - timeZone => "Australia/Melbourne", - links => { - link1 => { - rel => 'enclosure', - blobId => $blobId, - contentType => 'image/jpg', - }, - }, - replyTo => { - imip => 'mailto:cassandane@example.com', - }, - participants => { - part1 => { - '@type' => 'Participant', - sendTo => { - imip => 'mailto:part1@local', - }, - roles => { - attendee => JSON::true, - }, - }, - }, - start => '2021-01-01T01:00:00', - timeZone => 'Europe/Berlin', - duration => 'PT1H', + xlog "Create event with a Link.blobId"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + create => { + 1 => { + uid => 'eventuid1local', + calendarIds => { + Default => JSON::true, + }, + title => "event1", + start => "2019-12-10T23:30:00", + duration => "PT1H", + timeZone => "Australia/Melbourne", + links => { + link1 => { + rel => 'enclosure', + blobId => $blobId, + contentType => 'image/jpg', + }, + }, + replyTo => { + imip => 'mailto:cassandane@example.com', + }, + participants => { + part1 => { + '@type' => 'Participant', + sendTo => { + imip => 'mailto:part1@local', + }, + roles => { + attendee => JSON::true, }, + }, }, - }, 'R1'], - ]); - my $eventId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($eventId); - my $eventHref = $res->[0][1]{created}{1}{'x-href'}; - $self->assert_not_null($eventHref); + start => '2021-01-01T01:00:00', + timeZone => 'Europe/Berlin', + duration => 'PT1H', + }, + }, + }, + 'R1' + ], + ]); + my $eventId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($eventId); + my $eventHref = $res->[0][1]{created}{1}{'x-href'}; + $self->assert_not_null($eventHref); - $self->assert_rewrite_webdav_attachment_url_itip($eventHref); + $self->assert_rewrite_webdav_attachment_url_itip($eventHref); } diff --git a/cassandane/tiny-tests/JMAPCalendars/rscale_in_jmap_hidden_in_caldav b/cassandane/tiny-tests/JMAPCalendars/rscale_in_jmap_hidden_in_caldav index fc700e73e4..5305f2b4fe 100644 --- a/cassandane/tiny-tests/JMAPCalendars/rscale_in_jmap_hidden_in_caldav +++ b/cassandane/tiny-tests/JMAPCalendars/rscale_in_jmap_hidden_in_caldav @@ -2,87 +2,94 @@ use Cassandane::Tiny; sub test_rscale_in_jmap_hidden_in_caldav - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admin = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admin = $self->{adminstore}->get_client(); - my $calid = "Default"; - my $event = { - calendarIds => { - $calid => JSON::true, - }, - "title"=> "foo", - "start"=> "2015-11-07T09:00:00", - "duration"=> "PT1H", - "timeZone" => "Europe/London", - "locations" => { - "loc1" => { - "timeZone" => "Europe/Berlin", - "relativeTo" => "end", - }, - }, - "showWithoutTime"=> JSON::false, - "description"=> "", - "freeBusyStatus"=> "busy", - "prodId" => "foo", - "recurrenceRules" => [{ - "frequency" => "monthly", - count => 12, - }], - }; + my $calid = "Default"; + my $event = { + calendarIds => { + $calid => JSON::true, + }, + "title" => "foo", + "start" => "2015-11-07T09:00:00", + "duration" => "PT1H", + "timeZone" => "Europe/London", + "locations" => { + "loc1" => { + "timeZone" => "Europe/Berlin", + "relativeTo" => "end", + }, + }, + "showWithoutTime" => JSON::false, + "description" => "", + "freeBusyStatus" => "busy", + "prodId" => "foo", + "recurrenceRules" => [ { + "frequency" => "monthly", + count => 12, + } ], + }; - my $ret = $self->createandget_event($event); - $self->assert_normalized_event_equals($event, $ret); - my $eventId = $ret->{id}; + my $ret = $self->createandget_event($event); + $self->assert_normalized_event_equals($event, $ret); + my $eventId = $ret->{id}; - # Overide one event, this causes rscale to get added - my $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $eventId => { - "recurrenceOverrides" => { - "2015-12-07T09:00:00" => { - excluded => JSON::true, - } - }, - }, + # Overide one event, this causes rscale to get added + my $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $eventId => { + "recurrenceOverrides" => { + "2015-12-07T09:00:00" => { + excluded => JSON::true, + } }, - }, 'R1'], - ['CalendarEvent/get', { - ids => [$eventId], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$eventId}); - $ret = $res->[1][1]{list}[0]; - $self->assert_not_null($ret); + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + ids => [$eventId], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$eventId}); + $ret = $res->[1][1]{list}[0]; + $self->assert_not_null($ret); - # rscale should now be in jmap - $self->assert_deep_equals([ - { - '@type' => 'RecurrenceRule', - count => 12, - firstDayOfWeek => 'mo', - frequency => 'monthly', - interval => 1, - rscale => 'gregorian', - skip => 'omit' - }], - $ret->{recurrenceRules}, - ); + # rscale should now be in jmap + $self->assert_deep_equals( + [ { + '@type' => 'RecurrenceRule', + count => 12, + firstDayOfWeek => 'mo', + frequency => 'monthly', + interval => 1, + rscale => 'gregorian', + skip => 'omit' + } ], + $ret->{recurrenceRules}, + ); - # FIXME Net-CalDAV talk needs to update - # Make sure we have no rscale through caldav, most clients can't - # handle it - my $events = $caldav->GetEvents("$calid"); - $self->assert_deep_equals( - { - count => 12, - frequency => 'monthly', - }, - $events->[0]->{recurrenceRule}, - ); + # FIXME Net-CalDAV talk needs to update + # Make sure we have no rscale through caldav, most clients can't + # handle it + my $events = $caldav->GetEvents("$calid"); + $self->assert_deep_equals( + { + count => 12, + frequency => 'monthly', + }, + $events->[0]->{recurrenceRule}, + ); } diff --git a/cassandane/tiny-tests/JMAPCalendars/session_capability_isrfc b/cassandane/tiny-tests/JMAPCalendars/session_capability_isrfc index c4ed90fcf0..efbfbff97e 100644 --- a/cassandane/tiny-tests/JMAPCalendars/session_capability_isrfc +++ b/cassandane/tiny-tests/JMAPCalendars/session_capability_isrfc @@ -2,26 +2,25 @@ use Cassandane::Tiny; sub test_session_capability_isrfc - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - my $session = eval { decode_json($RawResponse->{content}) }; - $self->assert_not_null($session); + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + my $session = eval { decode_json($RawResponse->{content}) }; + $self->assert_not_null($session); - $self->assert_deep_equals( - $session->{capabilities}{'https://cyrusimap.org/ns/jmap/calendars'}, - { isRFC => JSON::true }); + $self->assert_deep_equals( + $session->{capabilities}{'https://cyrusimap.org/ns/jmap/calendars'}, + { isRFC => JSON::true }); } diff --git a/cassandane/tiny-tests/JMAPContacts/account_get_capabilities b/cassandane/tiny-tests/JMAPContacts/account_get_capabilities index fab5765717..3066d50918 100644 --- a/cassandane/tiny-tests/JMAPContacts/account_get_capabilities +++ b/cassandane/tiny-tests/JMAPContacts/account_get_capabilities @@ -2,33 +2,33 @@ use Cassandane::Tiny; sub test_account_get_capabilities - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $http = $self->{instance}->get_service("http"); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $http = $self->{instance}->get_service("http"); + my $admintalk = $self->{adminstore}->get_client(); - xlog "Get session object"; + xlog "Get session object"; - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - }, - content => '', - }; - my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert_str_equals('200', $RawResponse->{status}); - my $session = eval { decode_json($RawResponse->{content}) }; - $self->assert_not_null($session); + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => '', + }; + my $RawResponse = $jmap->ua->get($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert_str_equals('200', $RawResponse->{status}); + my $session = eval { decode_json($RawResponse->{content}) }; + $self->assert_not_null($session); - my $capas = $session->{accounts}{cassandane}{accountCapabilities}{'urn:ietf:params:jmap:contacts'}; - $self->assert_not_null($capas); + my $capas = $session->{accounts}{cassandane}{accountCapabilities} + {'urn:ietf:params:jmap:contacts'}; + $self->assert_not_null($capas); - $self->assert_equals(JSON::true, $capas->{mayCreateAddressBook}); + $self->assert_equals(JSON::true, $capas->{mayCreateAddressBook}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_changes b/cassandane/tiny-tests/JMAPContacts/addressbook_changes index a3a7006401..85a4f82ce5 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_changes +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_changes @@ -2,68 +2,84 @@ use Cassandane::Tiny; sub test_addressbook_changes - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - xlog $self, "get current state"; - my $res = $jmap->CallMethods([['AddressBook/get', {ids => undef}, "R1"]]); - my $state = $res->[0][1]{state}; + xlog $self, "get current state"; + my $res + = $jmap->CallMethods([ [ 'AddressBook/get', { ids => undef }, "R1" ] ]); + my $state = $res->[0][1]{state}; - xlog $self, "create addressbooks"; - my $id1 = $carddav->NewAddressBook("foo"); - my $id2 = $carddav->NewAddressBook("bar"); + xlog $self, "create addressbooks"; + my $id1 = $carddav->NewAddressBook("foo"); + my $id2 = $carddav->NewAddressBook("bar"); - xlog $self, "get addressbook updates"; - $res = $jmap->CallMethods([['AddressBook/changes', { - "sinceState" => $state - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $state = $res->[0][1]{newState}; + xlog $self, "get addressbook updates"; + $res = $jmap->CallMethods([ [ + 'AddressBook/changes', + { + "sinceState" => $state + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $state = $res->[0][1]{newState}; - xlog $self, "get addressbook updates without changes"; - $res = $jmap->CallMethods([['AddressBook/changes', { - "sinceState" => $state - }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals(0, scalar @{$res->[0][1]{destroyed}}); + xlog $self, "get addressbook updates without changes"; + $res = $jmap->CallMethods([ [ + 'AddressBook/changes', + { + "sinceState" => $state + }, + "R1" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals(0, scalar @{ $res->[0][1]{destroyed} }); - my $basepath = $carddav->{basepath}; - xlog $self, "update name of addressbook $id1, destroy addressbook $id2"; - $carddav->UpdateAddressBook($basepath . "/" . $id1, name => "foo (upd)"); - $carddav->DeleteAddressBook($basepath . "/" . $id2); + my $basepath = $carddav->{basepath}; + xlog $self, "update name of addressbook $id1, destroy addressbook $id2"; + $carddav->UpdateAddressBook($basepath . "/" . $id1, name => "foo (upd)"); + $carddav->DeleteAddressBook($basepath . "/" . $id2); - xlog $self, "get addressbook updates"; - $res = $jmap->CallMethods([['AddressBook/changes', { - "sinceState" => $state - }, "R1"]]); - $self->assert_str_equals("AddressBook/changes", $res->[0][0]); - $self->assert_str_equals("R1", $res->[0][2]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{destroyed}[0]); - $state = $res->[0][1]{newState}; + xlog $self, "get addressbook updates"; + $res = $jmap->CallMethods([ [ + 'AddressBook/changes', + { + "sinceState" => $state + }, + "R1" + ] ]); + $self->assert_str_equals("AddressBook/changes", $res->[0][0]); + $self->assert_str_equals("R1", $res->[0][2]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id1, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{destroyed}[0]); + $state = $res->[0][1]{newState}; - xlog $self, "get empty addressbook updates"; - $res = $jmap->CallMethods([['AddressBook/changes', { - "sinceState" => $state - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); + xlog $self, "get empty addressbook updates"; + $res = $jmap->CallMethods([ [ + 'AddressBook/changes', + { + "sinceState" => $state + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_get b/cassandane/tiny-tests/JMAPContacts/addressbook_get index 718c7497ff..87d07e18db 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_get +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_get @@ -2,44 +2,46 @@ use Cassandane::Tiny; sub test_addressbook_get - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - my $id = $carddav->NewAddressBook("bookname"); - my $unknownId = "foo"; + my $id = $carddav->NewAddressBook("bookname"); + my $unknownId = "foo"; - xlog $self, "get existing addressbook"; - my $res = $jmap->CallMethods([['AddressBook/get', {ids => [$id]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('AddressBook/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{list}})); - $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); + xlog $self, "get existing addressbook"; + my $res + = $jmap->CallMethods([ [ 'AddressBook/get', { ids => [$id] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('AddressBook/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{list} })); + $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); - xlog $self, "get existing addressbook with select properties"; - $res = $jmap->CallMethods([['AddressBook/get', { ids => [$id], properties => ["name"] }, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{list}})); - $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals("bookname", $res->[0][1]{list}[0]{name}); - $self->assert_null($res->[0][1]{list}[0]{isSubscribed}); + xlog $self, "get existing addressbook with select properties"; + $res = $jmap->CallMethods( + [ [ 'AddressBook/get', { ids => [$id], properties => ["name"] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{list} })); + $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals("bookname", $res->[0][1]{list}[0]{name}); + $self->assert_null($res->[0][1]{list}[0]{isSubscribed}); - xlog $self, "get unknown addressbook"; - $res = $jmap->CallMethods([['AddressBook/get', {ids => [$unknownId]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(0, scalar(@{$res->[0][1]{list}})); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{notFound}})); - $self->assert_str_equals($unknownId, $res->[0][1]{notFound}[0]); + xlog $self, "get unknown addressbook"; + $res = $jmap->CallMethods( + [ [ 'AddressBook/get', { ids => [$unknownId] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(0, scalar(@{ $res->[0][1]{list} })); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{notFound} })); + $self->assert_str_equals($unknownId, $res->[0][1]{notFound}[0]); - xlog $self, "get all addressbooks"; - $res = $jmap->CallMethods([['AddressBook/get', {ids => undef}, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(2, scalar(@{$res->[0][1]{list}})); - $res = $jmap->CallMethods([['AddressBook/get', {}, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(2, scalar(@{$res->[0][1]{list}})); + xlog $self, "get all addressbooks"; + $res = $jmap->CallMethods([ [ 'AddressBook/get', { ids => undef }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(2, scalar(@{ $res->[0][1]{list} })); + $res = $jmap->CallMethods([ [ 'AddressBook/get', {}, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(2, scalar(@{ $res->[0][1]{list} })); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_get_default b/cassandane/tiny-tests/JMAPContacts/addressbook_get_default index c2e50f2670..a43db52f87 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_get_default +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_get_default @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_addressbook_get_default - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # XXX - A previous CardDAV test might have created the default - # addressbook already. To make this test self-sufficient, we need - # to create a test user just for this test. How? - xlog $self, "get default addressbook"; - my $res = $jmap->CallMethods([['AddressBook/get', {ids => ["Default"]}, "R1"]]); - $self->assert_str_equals("Default", $res->[0][1]{list}[0]{id}); + # XXX - A previous CardDAV test might have created the default + # addressbook already. To make this test self-sufficient, we need + # to create a test user just for this test. How? + xlog $self, "get default addressbook"; + my $res = $jmap->CallMethods( + [ [ 'AddressBook/get', { ids => ["Default"] }, "R1" ] ]); + $self->assert_str_equals("Default", $res->[0][1]{list}[0]{id}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_get_shared b/cassandane/tiny-tests/JMAPContacts/addressbook_get_shared index b76928168d..27223e3352 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_get_shared +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_get_shared @@ -2,68 +2,81 @@ use Cassandane::Tiny; sub test_addressbook_get_shared - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "create addressbook"; - my $ABookId = $mantalk->NewAddressBook('Manifold Addressbook'); - $self->assert_not_null($ABookId); + xlog $self, "create addressbook"; + my $ABookId = $mantalk->NewAddressBook('Manifold Addressbook'); + $self->assert_not_null($ABookId); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.$ABookId", "cassandane" => 'lr') or die; + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.$ABookId", + "cassandane" => 'lr') + or die; - xlog $self, "get addressbook"; - my $res = $jmap->CallMethods([['AddressBook/get', {accountId => 'manifold'}, "R1"]]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert_str_equals("Manifold Addressbook", $res->[0][1]{list}[0]->{name}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]->{myRights}->{mayRead}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]->{myRights}{mayWrite}); - my $id = $res->[0][1]{list}[0]->{id}; + xlog $self, "get addressbook"; + my $res = $jmap->CallMethods( + [ [ 'AddressBook/get', { accountId => 'manifold' }, "R1" ] ]); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert_str_equals("Manifold Addressbook", + $res->[0][1]{list}[0]->{name}); + $self->assert_equals(JSON::true, + $res->[0][1]{list}[0]->{myRights}->{mayRead}); + $self->assert_equals(JSON::false, + $res->[0][1]{list}[0]->{myRights}{mayWrite}); + my $id = $res->[0][1]{list}[0]->{id}; - xlog $self, "refetch addressbook"; - $res = $jmap->CallMethods([['AddressBook/get', {accountId => 'manifold', ids => [$id]}, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{list}[0]->{id}); + xlog $self, "refetch addressbook"; + $res = $jmap->CallMethods( + [ [ 'AddressBook/get', { accountId => 'manifold', ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{list}[0]->{id}); - xlog $self, "create another shared addressbook"; - my $ABookId2 = $mantalk->NewAddressBook('Manifold Addressbook 2'); - $self->assert_not_null($ABookId2); - $admintalk->setacl("user.manifold.#addressbooks.$ABookId2", "cassandane" => 'lr') or die; + xlog $self, "create another shared addressbook"; + my $ABookId2 = $mantalk->NewAddressBook('Manifold Addressbook 2'); + $self->assert_not_null($ABookId2); + $admintalk->setacl("user.manifold.#addressbooks.$ABookId2", + "cassandane" => 'lr') + or die; - xlog $self, "remove access rights to addressbook"; - $admintalk->setacl("user.manifold.#addressbooks.$ABookId", "cassandane" => '') or die; + xlog $self, "remove access rights to addressbook"; + $admintalk->setacl("user.manifold.#addressbooks.$ABookId", "cassandane" => '') + or die; - xlog $self, "refetch addressbook (should fail)"; - $res = $jmap->CallMethods([['AddressBook/get', {accountId => 'manifold', ids => [$id]}, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); + xlog $self, "refetch addressbook (should fail)"; + $res = $jmap->CallMethods( + [ [ 'AddressBook/get', { accountId => 'manifold', ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); - xlog $self, "remove access rights to all shared addressbooks"; - $admintalk->setacl("user.manifold.#addressbooks.$ABookId2", "cassandane" => '') or die; + xlog $self, "remove access rights to all shared addressbooks"; + $admintalk->setacl("user.manifold.#addressbooks.$ABookId2", + "cassandane" => '') + or die; - xlog $self, "refetch addressbook (should fail)"; - $res = $jmap->CallMethods([['AddressBook/get', {accountId => 'manifold', ids => [$id]}, "R1"]]); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); + xlog $self, "refetch addressbook (should fail)"; + $res = $jmap->CallMethods( + [ [ 'AddressBook/get', { accountId => 'manifold', ids => [$id] }, "R1" ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set b/cassandane/tiny-tests/JMAPContacts/addressbook_set index 47f4beeee2..adbe6955ce 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set @@ -2,56 +2,68 @@ use Cassandane::Tiny; sub test_addressbook_set - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "create addressbook"; - my $res = $jmap->CallMethods([ - ['AddressBook/set', { create => { "1" => { - name => "foo" - }}}, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('AddressBook/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{created}); - - my $id = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get addressbook $id"; - $res = $jmap->CallMethods([['AddressBook/get', {ids => [$id]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{list}})); - $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals('foo', $res->[0][1]{list}[0]{name}); - - xlog $self, "update addressbook $id"; - $res = $jmap->CallMethods([ - ['AddressBook/set', {update => {"$id" => { - name => "bar" - }}}, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert(exists $res->[0][1]{updated}{$id}); - - xlog $self, "get addressbook $id"; - $res = $jmap->CallMethods([['AddressBook/get', {ids => [$id]}, "R1"]]); - $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); - - xlog $self, "destroy addressbook $id"; - $res = $jmap->CallMethods([['AddressBook/set', {destroy => ["$id"]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{destroyed}); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); - - xlog $self, "get addressbook $id"; - $res = $jmap->CallMethods([['AddressBook/get', {ids => [$id]}, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "create addressbook"; + my $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + create => { + "1" => { + name => "foo" + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('AddressBook/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{created}); + + my $id = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get addressbook $id"; + $res = $jmap->CallMethods([ [ 'AddressBook/get', { ids => [$id] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{list} })); + $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals('foo', $res->[0][1]{list}[0]{name}); + + xlog $self, "update addressbook $id"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + update => { + "$id" => { + name => "bar" + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert(exists $res->[0][1]{updated}{$id}); + + xlog $self, "get addressbook $id"; + $res = $jmap->CallMethods([ [ 'AddressBook/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); + + xlog $self, "destroy addressbook $id"; + $res = $jmap->CallMethods( + [ [ 'AddressBook/set', { destroy => ["$id"] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{destroyed}); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + + xlog $self, "get addressbook $id"; + $res = $jmap->CallMethods([ [ 'AddressBook/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_badname b/cassandane/tiny-tests/JMAPContacts/addressbook_set_badname index 286f4fc596..fd11304359 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_badname +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_badname @@ -2,24 +2,30 @@ use Cassandane::Tiny; sub test_addressbook_set_badname - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create addressbook with excessively long name"; - # Exceed the maximum allowed 256 byte length by 1. - my $badname = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt risus quis urna aliquam sollicitudin. Pellentesque aliquet nisl ut neque viverra pellentesque. Donec tincidunt eros at ante malesuada porta. Nam sapien arcu, vehicula non posuere."; + xlog $self, "create addressbook with excessively long name"; + # Exceed the maximum allowed 256 byte length by 1. + my $badname + = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt risus quis urna aliquam sollicitudin. Pellentesque aliquet nisl ut neque viverra pellentesque. Donec tincidunt eros at ante malesuada porta. Nam sapien arcu, vehicula non posuere."; - my $res = $jmap->CallMethods([ - ['AddressBook/set', { create => { "1" => { - name => $badname - }}}, "R1"] - ]); - $self->assert_not_null($res); - my $errType = $res->[0][1]{notCreated}{"1"}{type}; - my $errProp = $res->[0][1]{notCreated}{"1"}{properties}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals(["name"], $errProp); + my $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + create => { + "1" => { + name => $badname + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + my $errType = $res->[0][1]{notCreated}{"1"}{type}; + my $errProp = $res->[0][1]{notCreated}{"1"}{properties}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(["name"], $errProp); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_contents b/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_contents index 6716d6cd71..eea466302c 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_contents +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_contents @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_addressbook_set_destroy_contents - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - xlog "Create addressbook and contact"; - my $abookId = $carddav->NewAddressBook("foo"); + xlog "Create addressbook and contact"; + my $abookId = $carddav->NewAddressBook("foo"); - my $card = <new_fromstring($card); - my $cardId = basename($carddav->NewContact($abookId, $VCard), '.vcf'); + my $VCard = Net::CardDAVTalk::VCard->new_fromstring($card); + my $cardId = basename($carddav->NewContact($abookId, $VCard), '.vcf'); - xlog "Destroy addressbook (with and without onDestroyRemoveContents)"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - destroy => [$abookId], - }, 'R1'], -# XXX Change to ContactCard/get once implemented - ['Contact/get', { - ids => [$cardId], - properties => ['id'], - }, 'R2'], - ['AddressBook/set', { - destroy => [$abookId], - onDestroyRemoveContents => JSON::true, - }, 'R3'], - ['Contact/get', { - ids => [$cardId], - properties => ['id'], - }, 'R2'], - ]); - $self->assert_str_equals('addressBookHasContents', - $res->[0][1]{notDestroyed}{$abookId}{type}); - $self->assert_str_equals($cardId, $res->[1][1]{list}[0]{id}); - $self->assert_deep_equals([$abookId], $res->[2][1]{destroyed}); - $self->assert_deep_equals([$cardId], $res->[3][1]{notFound}); + xlog "Destroy addressbook (with and without onDestroyRemoveContents)"; + $res = $jmap->CallMethods([ + [ + 'AddressBook/set', + { + destroy => [$abookId], + }, + 'R1' + ], + # XXX Change to ContactCard/get once implemented + [ + 'Contact/get', + { + ids => [$cardId], + properties => ['id'], + }, + 'R2' + ], + [ + 'AddressBook/set', + { + destroy => [$abookId], + onDestroyRemoveContents => JSON::true, + }, + 'R3' + ], + [ + 'Contact/get', + { + ids => [$cardId], + properties => ['id'], + }, + 'R2' + ], + ]); + $self->assert_str_equals('addressBookHasContents', + $res->[0][1]{notDestroyed}{$abookId}{type}); + $self->assert_str_equals($cardId, $res->[1][1]{list}[0]{id}); + $self->assert_deep_equals([$abookId], $res->[2][1]{destroyed}); + $self->assert_deep_equals([$cardId], $res->[3][1]{notFound}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_default b/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_default index 57f4201f42..21d50a64b1 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_default +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_destroy_default @@ -2,21 +2,24 @@ use Cassandane::Tiny; sub test_addressbook_set_destroy_default - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - my $defaultId = 'Default'; + my $defaultId = 'Default'; - xlog "Attempt to destroy default addressbook"; - my $res = $jmap->CallMethods([ - ['AddressBook/set', { - destroy => [$defaultId], - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', - $res->[0][1]{notDestroyed}{$defaultId}{type}); + xlog "Attempt to destroy default addressbook"; + my $res = $jmap->CallMethods([ + [ + 'AddressBook/set', + { + destroy => [$defaultId], + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', + $res->[0][1]{notDestroyed}{$defaultId}{type}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_error b/cassandane/tiny-tests/JMAPContacts/addressbook_set_error index 0de1dd2bf8..1879a1cee9 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_error +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_error @@ -2,74 +2,100 @@ use Cassandane::Tiny; sub test_addressbook_set_error - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create addressbook with missing mandatory attributes"; - my $res = $jmap->CallMethods([ - ['AddressBook/set', { create => { "1" => {}}}, "R1"] - ]); - $self->assert_not_null($res); - my $errType = $res->[0][1]{notCreated}{"1"}{type}; - my $errProp = $res->[0][1]{notCreated}{"1"}{properties}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals([ "name" ], $errProp); + xlog $self, "create addressbook with missing mandatory attributes"; + my $res = $jmap->CallMethods([ + [ 'AddressBook/set', { create => { "1" => {} } }, "R1" ] ]); + $self->assert_not_null($res); + my $errType = $res->[0][1]{notCreated}{"1"}{type}; + my $errProp = $res->[0][1]{notCreated}{"1"}{properties}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(["name"], $errProp); - xlog $self, "create addressbook with invalid optional attributes"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { create => { "1" => { - name => "foo", - myRights => { - mayRead => \0, mayWrite => \0, - mayDelete => \0 - } - }}}, "R1"] - ]); - $errType = $res->[0][1]{notCreated}{"1"}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals(['myRights'], $res->[0][1]{notCreated}{"1"}{properties}); + xlog $self, "create addressbook with invalid optional attributes"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + create => { + "1" => { + name => "foo", + myRights => { + mayRead => \0, + mayWrite => \0, + mayDelete => \0 + } + } + } + }, + "R1" + ] ]); + $errType = $res->[0][1]{notCreated}{"1"}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(['myRights'], + $res->[0][1]{notCreated}{"1"}{properties}); - xlog $self, "update unknown addressbook"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { update => { "unknown" => { - name => "foo" - }}}, "R1"] - ]); - $errType = $res->[0][1]{notUpdated}{"unknown"}{type}; - $self->assert_str_equals("notFound", $errType); + xlog $self, "update unknown addressbook"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + update => { + "unknown" => { + name => "foo" + } + } + }, + "R1" + ] ]); + $errType = $res->[0][1]{notUpdated}{"unknown"}{type}; + $self->assert_str_equals("notFound", $errType); - xlog $self, "create addressbook"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { create => { "1" => { - name => "foo" - }}}, "R1"] - ]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create addressbook"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + create => { + "1" => { + name => "foo" + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "update addressbook with immutable optional attributes"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { update => { $id => { - myRights => { - mayRead => \0, mayWrite => \0, - mayDelete => \0 - } - }}}, "R1"] - ]); - $errType = $res->[0][1]{notUpdated}{$id}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals(['myRights'], $res->[0][1]{notUpdated}{$id}{properties}); + xlog $self, "update addressbook with immutable optional attributes"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + update => { + $id => { + myRights => { + mayRead => \0, + mayWrite => \0, + mayDelete => \0 + } + } + } + }, + "R1" + ] ]); + $errType = $res->[0][1]{notUpdated}{$id}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(['myRights'], + $res->[0][1]{notUpdated}{$id}{properties}); - xlog $self, "destroy unknown addressbook"; - $res = $jmap->CallMethods([ - ['AddressBook/set', {destroy => ["unknown"]}, "R1"] - ]); - $errType = $res->[0][1]{notDestroyed}{"unknown"}{type}; - $self->assert_str_equals("notFound", $errType); + xlog $self, "destroy unknown addressbook"; + $res = $jmap->CallMethods([ + [ 'AddressBook/set', { destroy => ["unknown"] }, "R1" ] ]); + $errType = $res->[0][1]{notDestroyed}{"unknown"}{type}; + $self->assert_str_equals("notFound", $errType); - xlog $self, "destroy addressbook $id"; - $res = $jmap->CallMethods([['AddressBook/set', {destroy => ["$id"]}, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy addressbook $id"; + $res = $jmap->CallMethods( + [ [ 'AddressBook/set', { destroy => ["$id"] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_issubscribed b/cassandane/tiny-tests/JMAPContacts/addressbook_set_issubscribed index 3085f459e1..fff1c63248 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_issubscribed +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_issubscribed @@ -2,44 +2,59 @@ use Cassandane::Tiny; sub test_addressbook_set_issubscribed - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Create addressbook - my $res = $jmap->CallMethods([ - ['AddressBook/set', { - create => { - '1' => { - name => 'A' - } - }, - }, 'R1'], - ['AddressBook/get', { - ids => ['#1'], - properties => ['isSubscribed'] - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{created}{1}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); - my $id = $res->[0][1]{created}{"1"}{id}; + # Create addressbook + my $res = $jmap->CallMethods([ + [ + 'AddressBook/set', + { + create => { + '1' => { + name => 'A' + } + }, + }, + 'R1' + ], + [ + 'AddressBook/get', + { + ids => ['#1'], + properties => ['isSubscribed'] + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{created}{1}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); + my $id = $res->[0][1]{created}{"1"}{id}; - # Can't unsubscribe own addressbooks - $res = $jmap->CallMethods([ - ['AddressBook/set', - { update => { - $id => { - isSubscribed => JSON::false, - } - } - }, "R1"], - ['AddressBook/get', { - ids => [$id], - properties => ['isSubscribed'] - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{notUpdated}{$id}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); + # Can't unsubscribe own addressbooks + $res = $jmap->CallMethods([ + [ + 'AddressBook/set', + { + update => { + $id => { + isSubscribed => JSON::false, + } + } + }, + "R1" + ], + [ + 'AddressBook/get', + { + ids => [$id], + properties => ['isSubscribed'] + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$id}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_issubscribed_shared b/cassandane/tiny-tests/JMAPContacts/addressbook_set_issubscribed_shared index 9a2ff4e1b6..8d6e382c53 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_issubscribed_shared +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_issubscribed_shared @@ -2,58 +2,70 @@ use Cassandane::Tiny; sub test_addressbook_set_issubscribed_shared - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.other"); + xlog $self, "create shared account"; + $admintalk->create("user.other"); - $admintalk->setacl("user.other", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.other", other => 'lrswipkxtecdn'); + $admintalk->setacl("user.other", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.other", other => 'lrswipkxtecdn'); - xlog $self, "create and share default addressbook"; - my $othertalk = Net::CardDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - $admintalk->setacl('user.other.#addressbooks.Default', "cassandane" => 'lr') or die; + xlog $self, "create and share default addressbook"; + my $othertalk = Net::CardDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + $admintalk->setacl('user.other.#addressbooks.Default', "cassandane" => 'lr') + or die; - # Get addressbook - my $res = $jmap->CallMethods([ - ['AddressBook/get', { - accountId => 'other', - properties => ['isSubscribed'] - }, 'R!'], - ]); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isSubscribed}); - my $id = $res->[0][1]{list}[0]{id}; + # Get addressbook + my $res = $jmap->CallMethods([ + [ + 'AddressBook/get', + { + accountId => 'other', + properties => ['isSubscribed'] + }, + 'R!' + ], + ]); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isSubscribed}); + my $id = $res->[0][1]{list}[0]{id}; - # Toggle isSubscribed on read-only shared addressbook - $res = $jmap->CallMethods([ - ['AddressBook/set', { - accountId => 'other', - update => { - $id => { - isSubscribed => JSON::true, - } - } - }, "R1"], - ['AddressBook/get', { - accountId => 'other', - ids => [$id], - properties => ['isSubscribed'] - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$id}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); + # Toggle isSubscribed on read-only shared addressbook + $res = $jmap->CallMethods([ + [ + 'AddressBook/set', + { + accountId => 'other', + update => { + $id => { + isSubscribed => JSON::true, + } + } + }, + "R1" + ], + [ + 'AddressBook/get', + { + accountId => 'other', + ids => [$id], + properties => ['isSubscribed'] + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{isSubscribed}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_shared b/cassandane/tiny-tests/JMAPContacts/addressbook_set_shared index f689e19396..fed988c0eb 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_shared +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_shared @@ -2,84 +2,116 @@ use Cassandane::Tiny; sub test_addressbook_set_shared - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); + my $service = $self->{instance}->get_service("http"); + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - # Call CardDAV once to create manifold's addressbook home #addressbooks - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + # Call CardDAV once to create manifold's addressbook home #addressbooks + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "share addressbook home read-only to user"; - $admintalk->setacl("user.manifold.#addressbooks", cassandane => 'lr') or die; + xlog $self, "share addressbook home read-only to user"; + $admintalk->setacl("user.manifold.#addressbooks", cassandane => 'lr') or die; - xlog $self, "create addressbook (should fail)"; - my $res = $jmap->CallMethods([ - ['AddressBook/set', { - accountId => 'manifold', - create => { "1" => { - name => "foo" - }}}, "R1"] - ]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert_str_equals("accountReadOnly", $res->[0][1]{notCreated}{1}{type}); + xlog $self, "create addressbook (should fail)"; + my $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + accountId => 'manifold', + create => { + "1" => { + name => "foo" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert_str_equals("accountReadOnly", + $res->[0][1]{notCreated}{1}{type}); - xlog $self, "share addressbook home read-writable to user"; - $admintalk->setacl("user.manifold.#addressbooks", cassandane => 'lrswipkxtecdn') or die; + xlog $self, "share addressbook home read-writable to user"; + $admintalk->setacl("user.manifold.#addressbooks", + cassandane => 'lrswipkxtecdn') + or die; - xlog $self, "create addressbook"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - accountId => 'manifold', - create => { "1" => { - name => "foo" - }}}, "R1"] - ]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - my $AddressBookId = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($AddressBookId); + xlog $self, "create addressbook"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + accountId => 'manifold', + create => { + "1" => { + name => "foo" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + my $AddressBookId = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($AddressBookId); - xlog $self, "share addressbook read-only to user"; - $admintalk->setacl("user.manifold.#addressbooks.$AddressBookId", "cassandane" => 'lr') or die; + xlog $self, "share addressbook read-only to user"; + $admintalk->setacl("user.manifold.#addressbooks.$AddressBookId", + "cassandane" => 'lr') + or die; - xlog $self, "update addressbook"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - accountId => 'manifold', - update => {$AddressBookId => { - name => "bar" - }}}, "R1"] - ]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert(exists $res->[0][1]{updated}{$AddressBookId}); + xlog $self, "update addressbook"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + accountId => 'manifold', + update => { + $AddressBookId => { + name => "bar" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert(exists $res->[0][1]{updated}{$AddressBookId}); - xlog $self, "destroy addressbook $AddressBookId (should fail)"; - $res = $jmap->CallMethods([['AddressBook/set', {accountId => 'manifold', destroy => [$AddressBookId]}, "R1"]]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert_str_equals("accountReadOnly", $res->[0][1]{notDestroyed}{$AddressBookId}{type}); + xlog $self, "destroy addressbook $AddressBookId (should fail)"; + $res = $jmap->CallMethods( + [ [ + 'AddressBook/set', + { accountId => 'manifold', destroy => [$AddressBookId] }, "R1" + ] ] + ); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert_str_equals("accountReadOnly", + $res->[0][1]{notDestroyed}{$AddressBookId}{type}); - xlog $self, "share read-writable to user"; - $admintalk->setacl("user.manifold.#addressbooks.$AddressBookId", "cassandane" => 'lrswipkxtecdn') or die; + xlog $self, "share read-writable to user"; + $admintalk->setacl("user.manifold.#addressbooks.$AddressBookId", + "cassandane" => 'lrswipkxtecdn') + or die; - xlog $self, "destroy addressbook $AddressBookId"; - $res = $jmap->CallMethods([['AddressBook/set', {accountId => 'manifold', destroy => [$AddressBookId]}, "R1"]]); - $self->assert_str_equals('manifold', $res->[0][1]{accountId}); - $self->assert_str_equals($AddressBookId, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy addressbook $AddressBookId"; + $res = $jmap->CallMethods( + [ [ + 'AddressBook/set', + { accountId => 'manifold', destroy => [$AddressBookId] }, "R1" + ] ] + ); + $self->assert_str_equals('manifold', $res->[0][1]{accountId}); + $self->assert_str_equals($AddressBookId, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_sharewith b/cassandane/tiny-tests/JMAPContacts/addressbook_set_sharewith index a731bff023..3146e44e00 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_sharewith +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_sharewith @@ -2,141 +2,158 @@ use Cassandane::Tiny; sub test_addressbook_set_sharewith - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); - - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.master"); - - my $mastalk = Net::CardDAVTalk->new( - user => "master", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.master", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.master", master => 'lrswipkxtecdn'); - - xlog $self, "create addressbook"; - my $AddressBookId = $mastalk->NewAddressBook('Shared', name => 'Shared AddressBook'); - $self->assert_not_null($AddressBookId); - - xlog $self, "share to user with permission to share"; - $admintalk->setacl("user.master.#addressbooks.$AddressBookId", "cassandane" => 'lrswipkxtecdan') or die; - - xlog $self, "create third account"; - $admintalk->create("user.manifold"); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - - xlog $self, "and a forth"; - $admintalk->create("user.paraphrase"); - - $admintalk->setacl("user.paraphrase", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.paraphrase", paraphrase => 'lrswipkxtecdn'); - - # Call CardDAV once to create manifold's addressbook home #addressbooks - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - # Call CardDAV once to create paraphrase's addressbook home #addressbooks - my $partalk = Net::CardDAVTalk->new( - user => "paraphrase", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "sharee gives third user access to shared addressbook"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - accountId => 'master', - update => { "$AddressBookId" => { - "shareWith/manifold" => { - mayRead => JSON::true - }, - "shareWith/paraphrase" => { - mayRead => JSON::true, - mayWrite => JSON::true, - }, - }}}, "R1"] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('AddressBook/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "check ACL on JMAP upload folder"; - $acl = $admintalk->getacl("user.master.#jmap"); - %map = @$acl; - $self->assert_str_equals('lrswitedn', $map{cassandane}); - $self->assert_str_equals('lrw', $map{manifold}); - $self->assert_str_equals('lrswitedn', $map{paraphrase}); - - xlog $self, "Update sharewith just for manifold"; - $jmap->CallMethods([ - ['AddressBook/set', { - accountId => 'master', - update => { "$AddressBookId" => { - "shareWith/manifold/mayWrite" => JSON::true, - }}}, "R1"] - ]); - - xlog $self, "check ACL on JMAP upload folder"; - $acl = $admintalk->getacl("user.master.#jmap"); - %map = @$acl; - $self->assert_str_equals('lrswitedn', $map{cassandane}); - $self->assert_str_equals('lrswitedn', $map{manifold}); - $self->assert_str_equals('lrswitedn', $map{paraphrase}); - - xlog $self, "Remove the access for paraphrase"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - accountId => 'master', - update => { "$AddressBookId" => { - "shareWith/paraphrase" => undef, - }}}, "R1"] - ]); - - $self->assert_not_null($res); - $self->assert_str_equals('AddressBook/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "check ACL"; - $acl = $admintalk->getacl("user.master.#addressbooks.$AddressBookId"); - %map = @$acl; - $self->assert_str_equals('lrswipkxtecdan', $map{cassandane}); - $self->assert_str_equals('lrswitedn', $map{manifold}); - $self->assert_null($map{paraphrase}); - - xlog $self, "check ACL on JMAP upload folder"; - $acl = $admintalk->getacl("user.master.#jmap"); - %map = @$acl; - $self->assert_str_equals('lrswitedn', $map{cassandane}); - $self->assert_str_equals('lrswitedn', $map{manifold}); - $self->assert_null($map{paraphrase}); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); + + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.master"); + + my $mastalk = Net::CardDAVTalk->new( + user => "master", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.master", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.master", master => 'lrswipkxtecdn'); + + xlog $self, "create addressbook"; + my $AddressBookId + = $mastalk->NewAddressBook('Shared', name => 'Shared AddressBook'); + $self->assert_not_null($AddressBookId); + + xlog $self, "share to user with permission to share"; + $admintalk->setacl("user.master.#addressbooks.$AddressBookId", + "cassandane" => 'lrswipkxtecdan') + or die; + + xlog $self, "create third account"; + $admintalk->create("user.manifold"); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + + xlog $self, "and a forth"; + $admintalk->create("user.paraphrase"); + + $admintalk->setacl("user.paraphrase", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.paraphrase", paraphrase => 'lrswipkxtecdn'); + + # Call CardDAV once to create manifold's addressbook home #addressbooks + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + # Call CardDAV once to create paraphrase's addressbook home #addressbooks + my $partalk = Net::CardDAVTalk->new( + user => "paraphrase", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "sharee gives third user access to shared addressbook"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + accountId => 'master', + update => { + "$AddressBookId" => { + "shareWith/manifold" => { + mayRead => JSON::true + }, + "shareWith/paraphrase" => { + mayRead => JSON::true, + mayWrite => JSON::true, + }, + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('AddressBook/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "check ACL on JMAP upload folder"; + $acl = $admintalk->getacl("user.master.#jmap"); + %map = @$acl; + $self->assert_str_equals('lrswitedn', $map{cassandane}); + $self->assert_str_equals('lrw', $map{manifold}); + $self->assert_str_equals('lrswitedn', $map{paraphrase}); + + xlog $self, "Update sharewith just for manifold"; + $jmap->CallMethods([ [ + 'AddressBook/set', + { + accountId => 'master', + update => { + "$AddressBookId" => { + "shareWith/manifold/mayWrite" => JSON::true, + } + } + }, + "R1" + ] ]); + + xlog $self, "check ACL on JMAP upload folder"; + $acl = $admintalk->getacl("user.master.#jmap"); + %map = @$acl; + $self->assert_str_equals('lrswitedn', $map{cassandane}); + $self->assert_str_equals('lrswitedn', $map{manifold}); + $self->assert_str_equals('lrswitedn', $map{paraphrase}); + + xlog $self, "Remove the access for paraphrase"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + accountId => 'master', + update => { + "$AddressBookId" => { + "shareWith/paraphrase" => undef, + } + } + }, + "R1" + ] ]); + + $self->assert_not_null($res); + $self->assert_str_equals('AddressBook/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "check ACL"; + $acl = $admintalk->getacl("user.master.#addressbooks.$AddressBookId"); + %map = @$acl; + $self->assert_str_equals('lrswipkxtecdan', $map{cassandane}); + $self->assert_str_equals('lrswitedn', $map{manifold}); + $self->assert_null($map{paraphrase}); + + xlog $self, "check ACL on JMAP upload folder"; + $acl = $admintalk->getacl("user.master.#jmap"); + %map = @$acl; + $self->assert_str_equals('lrswitedn', $map{cassandane}); + $self->assert_str_equals('lrswitedn', $map{manifold}); + $self->assert_null($map{paraphrase}); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_sharewith_acl b/cassandane/tiny-tests/JMAPContacts/addressbook_set_sharewith_acl index b7032097ba..706b9af88d 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_sharewith_acl +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_sharewith_acl @@ -2,83 +2,103 @@ use Cassandane::Tiny; sub test_addressbook_set_sharewith_acl - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.test1"); + $admin->create("user.test1"); - my $res = $jmap->CallMethods([ - ['AddressBook/set', { - create => { - '1' => { - name => 'A', - } - }, - }, 'R1'], - ]); - my $addressbookId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($addressbookId); - - my @testCases = ({ - rights => { - mayRead => JSON::true, - }, - acl => 'lrw', - }, { - rights => { - mayWrite => JSON::true, - }, - acl => 'switedn', - wantRights => { - mayWrite => JSON::true, - }, - }, { - rights => { - mayAdmin => JSON::true, - }, - acl => 'wa', - }, { - rights => { - mayDelete => JSON::true, + my $res = $jmap->CallMethods([ + [ + 'AddressBook/set', + { + create => { + '1' => { + name => 'A', + } }, - acl => 'wxc', - }); + }, + 'R1' + ], + ]); + my $addressbookId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($addressbookId); - foreach(@testCases) { + my @testCases = ( + { + rights => { + mayRead => JSON::true, + }, + acl => 'lrw', + }, + { + rights => { + mayWrite => JSON::true, + }, + acl => 'switedn', + wantRights => { + mayWrite => JSON::true, + }, + }, + { + rights => { + mayAdmin => JSON::true, + }, + acl => 'wa', + }, + { + rights => { + mayDelete => JSON::true, + }, + acl => 'wxc', + } + ); - xlog "Run test for acl $_->{acl}"; + foreach (@testCases) { - $res = $jmap->CallMethods([ - ['AddressBook/set', { - update => { - $addressbookId => { - shareWith => { - test1 => $_->{rights}, - }, - }, - }, - }, 'R1'], - ['AddressBook/get', { - ids => [$addressbookId], - properties => ['shareWith'], - }, 'R2'], - ]); + xlog "Run test for acl $_->{acl}"; - $_->{wantRights} ||= $_->{rights}; + $res = $jmap->CallMethods([ + [ + 'AddressBook/set', + { + update => { + $addressbookId => { + shareWith => { + test1 => $_->{rights}, + }, + }, + }, + }, + 'R1' + ], + [ + 'AddressBook/get', + { + ids => [$addressbookId], + properties => ['shareWith'], + }, + 'R2' + ], + ]); - my %mergedrights = (( - mayRead => JSON::false, - mayWrite => JSON::false, - mayAdmin => JSON::false, - mayDelete => JSON::false, - ), %{$_->{wantRights}}); + $_->{wantRights} ||= $_->{rights}; - $self->assert_deep_equals(\%mergedrights, - $res->[1][1]{list}[0]{shareWith}{test1}); - my %acl = @{$admin->getacl("user.cassandane.#addressbooks.$addressbookId")}; - $self->assert_str_equals($_->{acl}, $acl{test1}); - } + my %mergedrights = ( + ( + mayRead => JSON::false, + mayWrite => JSON::false, + mayAdmin => JSON::false, + mayDelete => JSON::false, + ), + %{ $_->{wantRights} } + ); + + $self->assert_deep_equals(\%mergedrights, + $res->[1][1]{list}[0]{shareWith}{test1}); + my %acl + = @{ $admin->getacl("user.cassandane.#addressbooks.$addressbookId") }; + $self->assert_str_equals($_->{acl}, $acl{test1}); + } } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_state b/cassandane/tiny-tests/JMAPContacts/addressbook_set_state index 1966dfb572..48bc5948bf 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_state +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_state @@ -2,99 +2,116 @@ use Cassandane::Tiny; sub test_addressbook_set_state - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with invalid state token"; - my $res = $jmap->CallMethods([ - ['AddressBook/set', { - ifInState => "badstate", - create => { "1" => { name => "foo" }} - }, "R1"] - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + xlog $self, "create with invalid state token"; + my $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + ifInState => "badstate", + create => { "1" => { name => "foo" } } + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "create with wrong state token"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - ifInState => "987654321", - create => { "1" => { name => "foo" }} - }, "R1"] - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + xlog $self, "create with wrong state token"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + ifInState => "987654321", + create => { "1" => { name => "foo" } } + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "create addressbook"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { create => { "1" => { - name => "foo" - }}}, "R1"] - ]); - $self->assert_not_null($res); + xlog $self, "create addressbook"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + create => { + "1" => { + name => "foo" + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); - my $id = $res->[0][1]{created}{"1"}{id}; - my $state = $res->[0][1]{newState}; + my $id = $res->[0][1]{created}{"1"}{id}; + my $state = $res->[0][1]{newState}; - xlog $self, "update addressbook $id with current state"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - ifInState => $state, - update => {"$id" => {name => "bar"}} - }, "R1"] - ]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); + xlog $self, "update addressbook $id with current state"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + ifInState => $state, + update => { "$id" => { name => "bar" } } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); - my $oldState = $state; - $state = $res->[0][1]{newState}; + my $oldState = $state; + $state = $res->[0][1]{newState}; - xlog $self, "setAddressBook noops must keep state"; - $res = $jmap->CallMethods([ - ['AddressBook/set', {}, "R1"], - ['AddressBook/set', {}, "R2"], - ['AddressBook/set', {}, "R3"] - ]); - $self->assert_not_null($res->[0][1]{newState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); + xlog $self, "setAddressBook noops must keep state"; + $res = $jmap->CallMethods([ + [ 'AddressBook/set', {}, "R1" ], + [ 'AddressBook/set', {}, "R2" ], + [ 'AddressBook/set', {}, "R3" ] + ]); + $self->assert_not_null($res->[0][1]{newState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); - xlog $self, "update addressbook $id with expired state"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - ifInState => $oldState, - update => {"$id" => {name => "baz"}} - }, "R1"] - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals("stateMismatch", $res->[0][1]{type}); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "update addressbook $id with expired state"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + ifInState => $oldState, + update => { "$id" => { name => "baz" } } + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals("stateMismatch", $res->[0][1]{type}); + $self->assert_str_equals('R1', $res->[0][2]); - xlog $self, "get addressbook $id to make sure state didn't change"; - $res = $jmap->CallMethods([['AddressBook/get', {ids => [$id]}, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]{state}); - $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); + xlog $self, "get addressbook $id to make sure state didn't change"; + $res = $jmap->CallMethods([ [ 'AddressBook/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]{state}); + $self->assert_str_equals('bar', $res->[0][1]{list}[0]{name}); - xlog $self, "destroy addressbook $id with expired state"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - ifInState => $oldState, - destroy => [$id] - }, "R1"] - ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals("stateMismatch", $res->[0][1]{type}); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "destroy addressbook $id with expired state"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + ifInState => $oldState, + destroy => [$id] + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals("stateMismatch", $res->[0][1]{type}); + $self->assert_str_equals('R1', $res->[0][2]); - xlog $self, "destroy addressbook $id with current state"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { - ifInState => $state, - destroy => [$id] - }, "R1"] - ]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy addressbook $id with current state"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + ifInState => $state, + destroy => [$id] + }, + "R1" + ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/addressbook_set_unknown_addressbookright b/cassandane/tiny-tests/JMAPContacts/addressbook_set_unknown_addressbookright index 5c6529f17d..e1778b1cef 100644 --- a/cassandane/tiny-tests/JMAPContacts/addressbook_set_unknown_addressbookright +++ b/cassandane/tiny-tests/JMAPContacts/addressbook_set_unknown_addressbookright @@ -2,29 +2,34 @@ use Cassandane::Tiny; sub test_addressbook_set_unknown_addressbookright - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['AddressBook/set', { - update => { - Default => { - shareWith => { - sharee => { - unknownAddressBookRight => JSON::true, - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'AddressBook/set', + { + update => { + Default => { + shareWith => { + sharee => { + unknownAddressBookRight => JSON::true, + }, }, - }, 'R1'], - ]); + }, + }, + }, + 'R1' + ], + ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{Default}{type}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{Default}{type}); - $self->assert_deep_equals(['shareWith/sharee/unknownAddressBookRight'], - $res->[0][1]{notUpdated}{Default}{properties}); + $self->assert_deep_equals( + ['shareWith/sharee/unknownAddressBookRight'], + $res->[0][1]{notUpdated}{Default}{properties} + ); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_changes b/cassandane/tiny-tests/JMAPContacts/card_changes index 866c99fbb6..2f768cb620 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_changes +++ b/cassandane/tiny-tests/JMAPContacts/card_changes @@ -2,132 +2,175 @@ use Cassandane::Tiny; sub test_card_changes - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; -# Update to ContactCard/[get|set] once implemented - xlog $self, "get contacts"; - my $res = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - my $state = $res->[0][1]{state}; + # Update to ContactCard/[get|set] once implemented + xlog $self, "get contacts"; + my $res = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + my $state = $res->[0][1]{state}; - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $state, - addressbookId => "Default", - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $state, + addressbookId => "Default", + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - xlog $self, "create contact 1"; - $res = $jmap->CallMethods([['Contact/set', {create => {"1" => {firstName => "first", lastName => "last"}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create contact 1"; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { create => { "1" => { firstName => "first", lastName => "last" } } }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $oldState = $state; - $state = $res->[0][1]{newState}; + my $oldState = $state; + $state = $res->[0][1]{newState}; - xlog $self, "create contact 2"; - $res = $jmap->CallMethods([['Contact/set', {create => {"2" => {firstName => "second", lastName => "prev"}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id2 = $res->[0][1]{created}{"2"}{id}; + xlog $self, "create contact 2"; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { create => { "2" => { firstName => "second", lastName => "prev" } } }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id2 = $res->[0][1]{created}{"2"}{id}; - xlog $self, "get contact updates (since last change)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; + xlog $self, "get contact updates (since last change)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; - xlog $self, "get contact updates (in bulk)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $oldState - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); + xlog $self, "get contact updates (in bulk)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $oldState + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); - xlog $self, "get contact updates from initial state (maxChanges=1)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $oldState, - maxChanges => 1 - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $interimState = $res->[0][1]{newState}; + xlog $self, "get contact updates from initial state (maxChanges=1)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $oldState, + maxChanges => 1 + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + my $interimState = $res->[0][1]{newState}; - xlog $self, "get contact updates from interim state (maxChanges=10)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $interimState, - maxChanges => 10 - }, "R2"]]); - $self->assert_str_equals($interimState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; + xlog $self, "get contact updates from interim state (maxChanges=10)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $interimState, + maxChanges => 10 + }, + "R2" + ] ]); + $self->assert_str_equals($interimState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; - xlog $self, "destroy contact 1, update contact 2"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$id1], - update => {$id2 => {firstName => "foo"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "destroy contact 1, update contact 2"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$id1], + update => { $id2 => { firstName => "foo" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - xlog $self, "destroy contact 2"; - $res = $jmap->CallMethods([['Contact/set', {destroy => [$id2]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "destroy contact 2"; + $res = $jmap->CallMethods([ [ 'Contact/set', { destroy => [$id2] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_changes_shared b/cassandane/tiny-tests/JMAPContacts/card_changes_shared index 012d5dfc51..3af300d3ce 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_changes_shared +++ b/cassandane/tiny-tests/JMAPContacts/card_changes_shared @@ -2,169 +2,215 @@ use Cassandane::Tiny; sub test_card_changes_shared - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrswipkxtecdn') or die; - -# Update to ContactCard/[get|set] once implemented - xlog $self, "get contacts"; - my $res = $jmap->CallMethods([['Contact/get', { accountId => 'manifold' }, "R2"]]); - my $state = $res->[0][1]{state}; - - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - - xlog $self, "create contact 1"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => {"1" => {firstName => "first", lastName => "last"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - - my $oldState = $state; - $state = $res->[0][1]{newState}; - - xlog $self, "create contact 2"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => {"2" => {firstName => "second", lastName => "prev"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id2 = $res->[0][1]{created}{"2"}{id}; - - xlog $self, "get contact updates (since last change)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "get contact updates (in bulk)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $oldState - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get contact updates from initial state (maxChanges=1)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $oldState, - maxChanges => 1 - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $interimState = $res->[0][1]{newState}; - - xlog $self, "get contact updates from interim state (maxChanges=10)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $interimState, - maxChanges => 10 - }, "R2"]]); - $self->assert_str_equals($interimState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "destroy contact 1, update contact 2"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - destroy => [$id1], - update => {$id2 => {firstName => "foo"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - - xlog $self, "destroy contact 2"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - destroy => [$id2] - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + # Update to ContactCard/[get|set] once implemented + xlog $self, "get contacts"; + my $res = $jmap->CallMethods( + [ [ 'Contact/get', { accountId => 'manifold' }, "R2" ] ]); + my $state = $res->[0][1]{state}; + + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + + xlog $self, "create contact 1"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { "1" => { firstName => "first", lastName => "last" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + + my $oldState = $state; + $state = $res->[0][1]{newState}; + + xlog $self, "create contact 2"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { "2" => { firstName => "second", lastName => "prev" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id2 = $res->[0][1]{created}{"2"}{id}; + + xlog $self, "get contact updates (since last change)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "get contact updates (in bulk)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $oldState + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get contact updates from initial state (maxChanges=1)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $oldState, + maxChanges => 1 + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + my $interimState = $res->[0][1]{newState}; + + xlog $self, "get contact updates from interim state (maxChanges=10)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $interimState, + maxChanges => 10 + }, + "R2" + ] ]); + $self->assert_str_equals($interimState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "destroy contact 1, update contact 2"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + destroy => [$id1], + update => { $id2 => { firstName => "foo" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + + xlog $self, "destroy contact 2"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + destroy => [$id2] + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_copy b/cassandane/tiny-tests/JMAPContacts/card_copy index 99544f3ab6..30db80ff45 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_copy +++ b/cassandane/tiny-tests/JMAPContacts/card_copy @@ -6,117 +6,134 @@ use Cassandane::Tiny; # sub test_card_copy - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared accounts"; - $admintalk->create("user.other"); - $admintalk->create("user.other2"); - $admintalk->create("user.other3"); - -# my $carddav = Net::CardDAVTalk->new( -# user => 'cassandane', -# password => 'pass', -# host => $service->host(), -# port => $service->port(), -# scheme => 'http', -# url => '/', -# expandurl => 1, -# ); - - my $othercarddav = Net::CardDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $other2carddav = Net::CardDAVTalk->new( - user => "other2", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $other3carddav = Net::CardDAVTalk->new( - user => "other3", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "share addressbooks"; - $admintalk->setacl("user.other.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; - $admintalk->setacl("user.other2.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; - $admintalk->setacl("user.other3.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; - - # avatar - xlog $self, "upload avatar"; - my $data = "some photo"; - my $res = $jmap->Upload($data, "image/jpeg"); - my $blobid = $res->{blobId}; - - my $card = { - "addressBookId" => "Default", - name => { full => "foo bar" }, -# "avatar" => { -# "blobId" => $blobid, -# "size" => 10, -# "type" => "image/jpeg", -# "name" => JSON::null -# } - }; - - xlog $self, "create card"; - $res = $jmap->CallMethods([['ContactCard/set',{ - create => {"1" => $card}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - my $cardId = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "copy card $cardId w/o changes"; - $res = $jmap->CallMethods([['ContactCard/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 1 => { - id => $cardId, - addressBookId => "Default", - }, + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared accounts"; + $admintalk->create("user.other"); + $admintalk->create("user.other2"); + $admintalk->create("user.other3"); + + # my $carddav = Net::CardDAVTalk->new( + # user => 'cassandane', + # password => 'pass', + # host => $service->host(), + # port => $service->port(), + # scheme => 'http', + # url => '/', + # expandurl => 1, + # ); + + my $othercarddav = Net::CardDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $other2carddav = Net::CardDAVTalk->new( + user => "other2", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $other3carddav = Net::CardDAVTalk->new( + user => "other3", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "share addressbooks"; + $admintalk->setacl("user.other.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + $admintalk->setacl("user.other2.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + $admintalk->setacl("user.other3.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + # avatar + xlog $self, "upload avatar"; + my $data = "some photo"; + my $res = $jmap->Upload($data, "image/jpeg"); + my $blobid = $res->{blobId}; + + my $card = { + "addressBookId" => "Default", + name => { full => "foo bar" }, + # "avatar" => { + # "blobId" => $blobid, + # "size" => 10, + # "type" => "image/jpeg", + # "name" => JSON::null + # } + }; + + xlog $self, "create card"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { "1" => $card } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $cardId = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "copy card $cardId w/o changes"; + $res = $jmap->CallMethods([ [ + 'ContactCard/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $cardId, + addressBookId => "Default", }, + }, }, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - my $copiedCardId = $res->[0][1]{created}{"1"}{id}; - - $res = $jmap->CallMethods([ - ['ContactCard/get', { - accountId => 'other', - ids => [$copiedCardId], - }, 'R1'], - ['ContactCard/get', { - accountId => undef, - ids => [$cardId], - }, 'R2'], - ]); - $self->assert_str_equals('foo bar', $res->[0][1]{list}[0]{name}{full}); + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $copiedCardId = $res->[0][1]{created}{"1"}{id}; + + $res = $jmap->CallMethods([ + [ + 'ContactCard/get', + { + accountId => 'other', + ids => [$copiedCardId], + }, + 'R1' + ], + [ + 'ContactCard/get', + { + accountId => undef, + ids => [$cardId], + }, + 'R2' + ], + ]); + $self->assert_str_equals('foo bar', $res->[0][1]{list}[0]{name}{full}); # my $blob = $jmap->Download({ accept => 'image/jpeg' }, # 'other', $res->[0][1]{list}[0]{avatar}{blobId}); # $self->assert_str_equals('image/jpeg', @@ -124,7 +141,7 @@ sub test_card_copy # $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); # $self->assert_equals($data, $blob->{content}); - $self->assert_str_equals('foo bar', $res->[1][1]{list}[0]{name}{full}); + $self->assert_str_equals('foo bar', $res->[1][1]{list}[0]{name}{full}); # $blob = $jmap->Download({ accept => 'image/jpeg' }, # 'cassandane', $res->[1][1]{list}[0]{avatar}{blobId}); # $self->assert_str_equals('image/jpeg', @@ -132,98 +149,118 @@ sub test_card_copy # $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); # $self->assert_equals($data, $blob->{content}); - xlog $self, "move card $cardId with changes"; - $res = $jmap->CallMethods([['ContactCard/copy', { - fromAccountId => 'cassandane', - accountId => 'other2', - create => { - 1 => { - id => $cardId, - addressBookId => "Default", -# avatar => JSON::null, - nicknames => { n1 => { '@type' => 'Nickname', name => "xxxxx" } } - }, - } + xlog $self, "move card $cardId with changes"; + $res = $jmap->CallMethods([ [ + 'ContactCard/copy', + { + fromAccountId => 'cassandane', + accountId => 'other2', + create => { + 1 => { + id => $cardId, + addressBookId => "Default", + # avatar => JSON::null, + nicknames => { n1 => { '@type' => 'Nickname', name => "xxxxx" } } + }, + } }, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - $copiedCardId = $res->[0][1]{created}{"1"}{id}; - - $res = $jmap->CallMethods([ - ['ContactCard/get', { - accountId => 'other2', - ids => [$copiedCardId], - }, 'R1'], - ['ContactCard/get', { - accountId => undef, - ids => [$cardId], - }, 'R2'], - ]); - $self->assert_str_equals('foo bar', $res->[0][1]{list}[0]{name}{full}); - $self->assert_str_equals('xxxxx', $res->[0][1]{list}[0]{nicknames}{n1}{name}); -# $self->assert_null($res->[0][1]{list}[0]{avatar}); -return; - my $other3Jmap = Mail::JMAPTalk->new( - user => 'other3', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $other3Jmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - - # avatar - xlog $self, "upload avatar for other3"; - $data = "some other photo"; - $res = $other3Jmap->Upload($data, "image/jpeg"); - $blobid = $res->{blobId}; - - $admintalk->setacl("user.other3.#jmap", - "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "move card $cardId with different avatar"; - $res = $jmap->CallMethods([['Contact/copy', { - fromAccountId => 'cassandane', - accountId => 'other3', - create => { - 1 => { - id => $cardId, - addressbookId => "Default", - avatar => { - blobId => "$blobid", - size => 16, - type => "image/jpeg", - name => JSON::null - } - }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + $copiedCardId = $res->[0][1]{created}{"1"}{id}; + + $res = $jmap->CallMethods([ + [ + 'ContactCard/get', + { + accountId => 'other2', + ids => [$copiedCardId], + }, + 'R1' + ], + [ + 'ContactCard/get', + { + accountId => undef, + ids => [$cardId], + }, + 'R2' + ], + ]); + $self->assert_str_equals('foo bar', $res->[0][1]{list}[0]{name}{full}); + $self->assert_str_equals('xxxxx', $res->[0][1]{list}[0]{nicknames}{n1}{name}); + # $self->assert_null($res->[0][1]{list}[0]{avatar}); + return; + my $other3Jmap = Mail::JMAPTalk->new( + user => 'other3', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $other3Jmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'https://cyrusimap.org/ns/jmap/calendars', + ]); + + # avatar + xlog $self, "upload avatar for other3"; + $data = "some other photo"; + $res = $other3Jmap->Upload($data, "image/jpeg"); + $blobid = $res->{blobId}; + + $admintalk->setacl("user.other3.#jmap", "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "move card $cardId with different avatar"; + $res = $jmap->CallMethods([ [ + 'Contact/copy', + { + fromAccountId => 'cassandane', + accountId => 'other3', + create => { + 1 => { + id => $cardId, + addressbookId => "Default", + avatar => { + blobId => "$blobid", + size => 16, + type => "image/jpeg", + name => JSON::null + } }, - onSuccessDestroyOriginal => JSON::true, + }, + onSuccessDestroyOriginal => JSON::true, }, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - $copiedCardId = $res->[0][1]{created}{"1"}{id}; - - $res = $jmap->CallMethods([ - ['Contact/get', { - accountId => 'other3', - ids => [$copiedCardId], - }, 'R1'], - ['Contact/get', { - accountId => undef, - ids => [$cardId], - }, 'R2'], - ]); - $self->assert_str_equals('foo', $res->[0][1]{list}[0]{firstName}); - $blob = $jmap->Download({ accept => 'image/jpeg' }, - 'other3', $res->[0][1]{list}[0]{avatar}{blobId}); - $self->assert_str_equals('image/jpeg', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_equals($data, $blob->{content}); - - $self->assert_str_equals($cardId, $res->[1][1]{notFound}[0]); + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + $copiedCardId = $res->[0][1]{created}{"1"}{id}; + + $res = $jmap->CallMethods([ + [ + 'Contact/get', + { + accountId => 'other3', + ids => [$copiedCardId], + }, + 'R1' + ], + [ + 'Contact/get', + { + accountId => undef, + ids => [$cardId], + }, + 'R2' + ], + ]); + $self->assert_str_equals('foo', $res->[0][1]{list}[0]{firstName}); + $blob = $jmap->Download({ accept => 'image/jpeg' }, + 'other3', $res->[0][1]{list}[0]{avatar}{blobId}); + $self->assert_str_equals('image/jpeg', $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_equals($data, $blob->{content}); + + $self->assert_str_equals($cardId, $res->[1][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_copy_state b/cassandane/tiny-tests/JMAPContacts/card_copy_state index 6a242a4191..c6bf2ffcac 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_copy_state +++ b/cassandane/tiny-tests/JMAPContacts/card_copy_state @@ -2,90 +2,105 @@ use Cassandane::Tiny; sub test_card_copy_state - :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.other"); + xlog $self, "create shared account"; + $admintalk->create("user.other"); - my $othercarddav = Net::CardDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $othercarddav = Net::CardDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "share addressbook"; - $admintalk->setacl("user.other.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; + xlog $self, "share addressbook"; + $admintalk->setacl("user.other.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; - my $card = { - "addressBookId" => "Default", - name => { full => "foo bar" }, - }; + my $card = { + "addressBookId" => "Default", + name => { full => "foo bar" }, + }; - xlog $self, "create card"; - $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => {"1" => $card} - }, "R1"], - ['ContactCard/get', { - accountId => 'other', - ids => ['foo'], # Just fetching current state for 'other' - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}); - my $cardId = $res->[0][1]{created}{"1"}{id}; - my $fromState = $res->[0][1]->{newState}; - $self->assert_not_null($fromState); - my $state = $res->[1][1]->{state}; - $self->assert_not_null($state); + xlog $self, "create card"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + create => { "1" => $card } + }, + "R1" + ], + [ + 'ContactCard/get', + { + accountId => 'other', + ids => ['foo'], # Just fetching current state for 'other' + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}); + my $cardId = $res->[0][1]{created}{"1"}{id}; + my $fromState = $res->[0][1]->{newState}; + $self->assert_not_null($fromState); + my $state = $res->[1][1]->{state}; + $self->assert_not_null($state); - xlog $self, "move card"; - $res = $jmap->CallMethods([ - ['ContactCard/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - ifFromInState => $fromState, - ifInState => $state, - create => { - 1 => { - id => $cardId, - addressBookId => "Default", - }, - }, - onSuccessDestroyOriginal => JSON::true, - destroyFromIfInState => $fromState, - }, "R1"], - ['ContactCard/get', { - accountId => 'other', - ids => ['#1'], - properties => ['name'], - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}); - my $oldState = $res->[0][1]->{oldState}; - $self->assert_str_equals($oldState, $state); - my $newState = $res->[0][1]->{newState}; - $self->assert_not_null($newState); - $self->assert_str_equals('ContactCard/set', $res->[1][0]); - $self->assert_str_equals($cardId, $res->[1][1]{destroyed}[0]); - $self->assert_str_equals('foo bar', $res->[2][1]{list}[0]{name}{full}); + xlog $self, "move card"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + ifFromInState => $fromState, + ifInState => $state, + create => { + 1 => { + id => $cardId, + addressBookId => "Default", + }, + }, + onSuccessDestroyOriginal => JSON::true, + destroyFromIfInState => $fromState, + }, + "R1" + ], + [ + 'ContactCard/get', + { + accountId => 'other', + ids => ['#1'], + properties => ['name'], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}); + my $oldState = $res->[0][1]->{oldState}; + $self->assert_str_equals($oldState, $state); + my $newState = $res->[0][1]->{newState}; + $self->assert_not_null($newState); + $self->assert_str_equals('ContactCard/set', $res->[1][0]); + $self->assert_str_equals($cardId, $res->[1][1]{destroyed}[0]); + $self->assert_str_equals('foo bar', $res->[2][1]{list}[0]{name}{full}); - # Is the blobId downloadable? - my $blob = $jmap->Download({ accept => 'text/vcard' }, - 'other', - $res->[0][1]{created}{"1"}{'cyrusimap.org:blobId'}); - $self->assert_str_equals('text/vcard; version=4.0', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_matches(qr/\r\nFN:foo bar\r\n/, $blob->{content}); + # Is the blobId downloadable? + my $blob = $jmap->Download({ accept => 'text/vcard' }, + 'other', $res->[0][1]{created}{"1"}{'cyrusimap.org:blobId'}); + $self->assert_str_equals('text/vcard; version=4.0', + $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_matches(qr/\r\nFN:foo bar\r\n/, $blob->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_apple_countrycode b/cassandane/tiny-tests/JMAPContacts/card_get_apple_countrycode index eced91c190..bca2a488ce 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_apple_countrycode +++ b/cassandane/tiny-tests/JMAPContacts/card_get_apple_countrycode @@ -2,26 +2,25 @@ use Cassandane::Tiny; sub test_card_get_apple_countrycode - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/test.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - properties => ['addresses'] - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'ContactCard/get', + { + properties => ['addresses'] + }, + 'R1' + ] ]); - $self->assert_str_equals('us', $res->[0][1]{list}[0]{addresses}{A1}{countryCode}); - $self->assert_str_equals('xyz', $res->[0][1]{list}[0]{addresses}{A1}{label}); - $self->assert_str_equals('de', $res->[0][1]{list}[0]{addresses}{A2}{countryCode}); + $self->assert_str_equals('us', + $res->[0][1]{list}[0]{addresses}{A1}{countryCode}); + $self->assert_str_equals('xyz', $res->[0][1]{list}[0]{addresses}{A1}{label}); + $self->assert_str_equals('de', + $res->[0][1]{list}[0]{addresses}{A2}{countryCode}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_disable_uri_as_blobid b/cassandane/tiny-tests/JMAPContacts/card_get_disable_uri_as_blobid index 386fc084e8..deca10d3a6 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_disable_uri_as_blobid +++ b/cassandane/tiny-tests/JMAPContacts/card_get_disable_uri_as_blobid @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_card_get_disable_uri_as_blobid - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - my $vcard = <<'EOF'; + my $vcard = <<'EOF'; BEGIN:VCARD VERSION:4.0 UID:85b5d651-1cde-43d9-901d-7059d67807f9 @@ -17,23 +16,31 @@ PHOTO;PROP-ID=photo1:data:image/x-bla;base64,YmxhYmxhCg= CREATED:20230823T133154Z END:VCARD EOF - $vcard =~ s/\r?\n/\r\n/gs; - $carddav->Request('PUT', 'Default/test.vcf', $vcard, - 'Content-Type' => 'text/vcard'); + $vcard =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', 'Default/test.vcf', $vcard, + 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - properties => ['media'], - }, 'R1'], - ['ContactCard/get', { - properties => ['media'], - disableUriAsBlobId => JSON::true, - }, 'R2'], - ]); + my $res = $jmap->CallMethods([ + [ + 'ContactCard/get', + { + properties => ['media'], + }, + 'R1' + ], + [ + 'ContactCard/get', + { + properties => ['media'], + disableUriAsBlobId => JSON::true, + }, + 'R2' + ], + ]); - $self->assert_not_null($res->[0][1]{list}[0]{media}{photo1}{blobId}); - $self->assert_null($res->[0][1]{list}[0]{media}{photo1}{uri}); + $self->assert_not_null($res->[0][1]{list}[0]{media}{photo1}{blobId}); + $self->assert_null($res->[0][1]{list}[0]{media}{photo1}{uri}); - $self->assert_null($res->[1][1]{list}[0]{media}{photo1}{blobId}); - $self->assert_not_null($res->[1][1]{list}[0]{media}{photo1}{uri}); + $self->assert_null($res->[1][1]{list}[0]{media}{photo1}{blobId}); + $self->assert_not_null($res->[1][1]{list}[0]{media}{photo1}{uri}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_localizations b/cassandane/tiny-tests/JMAPContacts/card_get_localizations index acc8dc4605..78431d4801 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_localizations +++ b/cassandane/tiny-tests/JMAPContacts/card_get_localizations @@ -3,29 +3,28 @@ use Cassandane::Tiny; use utf8; sub test_card_get_localizations - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - # Sample card from RFC 6350 - # Second N suffix removed due to vparse bug - # PROP-IDs added so we can easily compare the results - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/test.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $want_jscard = { - '@type' => 'Card', - version => '1.0', - addressBookId => 'Default', - 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, - kind => 'individual', - language => 'es', - vCardProps => [ - [ 'version', {}, 'text', '4.0' ] - ], - name => { - full => 'Gabriel García Márquez', - components => [ - { 'kind' => 'surname', 'value' => 'Márquez' }, - { 'kind' => 'given', 'value' => 'Gabriel' }, - { 'kind' => 'given2', 'value' => 'García' }, - ], - }, - titles => { - t1 => { - 'name' => 'Novelista' - } - }, - speakToAs => { - grammaticalGender => 'neuter', - pronouns => { - k19 => { - pronouns => 'él' - } - } - }, - addresses => { - addr1 => { - components => [ - { kind => 'locality', value =>'Tokio' } - ] - } - }, - localizations => { - en => { - 'titles/t1/name' => 'Novelist', - 'speakToAs/grammaticalGender' => 'masculine', - 'addresses/addr1/components/0/value' => 'Tokyo' - }, - fr => { - 'titles/t1/name' => 'Écrivain', - 'speakToAs/pronouns/k19/pronouns' => 'il' - }, - de => { - 'speakToAs/pronouns/k19/pronouns' => 'er' - }, - it => { - 'speakToAs/pronouns/k19/pronouns' => 'lui' - }, - jp => { - 'name/full' => 'ガブリエル・ガルシア・マルケス', - 'name/components/0/value' => 'マルケス', - 'name/components/1/value' => 'ガブリエル', - 'name/components/2/value' => 'ガルシア', - 'addresses/addr1/components/0/value' => '東京' - } + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); + + my $want_jscard = { + '@type' => 'Card', + version => '1.0', + addressBookId => 'Default', + 'cyrusimap.org:href' => $carddav->fullpath() . $href, + id => $id, + uid => $id, + kind => 'individual', + language => 'es', + vCardProps => [ [ 'version', {}, 'text', '4.0' ] ], + name => { + full => 'Gabriel García Márquez', + components => [ + { 'kind' => 'surname', 'value' => 'Márquez' }, + { 'kind' => 'given', 'value' => 'Gabriel' }, + { 'kind' => 'given2', 'value' => 'García' }, + ], + }, + titles => { + t1 => { + 'name' => 'Novelista' + } + }, + speakToAs => { + grammaticalGender => 'neuter', + pronouns => { + k19 => { + pronouns => 'él' } - }; + } + }, + addresses => { + addr1 => { + components => [ { kind => 'locality', value => 'Tokio' } ] + } + }, + localizations => { + en => { + 'titles/t1/name' => 'Novelist', + 'speakToAs/grammaticalGender' => 'masculine', + 'addresses/addr1/components/0/value' => 'Tokyo' + }, + fr => { + 'titles/t1/name' => 'Écrivain', + 'speakToAs/pronouns/k19/pronouns' => 'il' + }, + de => { + 'speakToAs/pronouns/k19/pronouns' => 'er' + }, + it => { + 'speakToAs/pronouns/k19/pronouns' => 'lui' + }, + jp => { + 'name/full' => 'ガブリエル・ガルシア・マルケス', + 'name/components/0/value' => 'マルケス', + 'name/components/1/value' => 'ガブリエル', + 'name/components/2/value' => 'ガルシア', + 'addresses/addr1/components/0/value' => '東京' + } + } + }; - - my $have_jscard = $res->[0][1]{list}[0]; + my $have_jscard = $res->[0][1]{list}[0]; - # Delete generated fields - delete $have_jscard->{blobId}; - delete $have_jscard->{'cyrusimap.org:blobId'}; - delete $have_jscard->{'cyrusimap.org:size'}; + # Delete generated fields + delete $have_jscard->{blobId}; + delete $have_jscard->{'cyrusimap.org:blobId'}; + delete $have_jscard->{'cyrusimap.org:size'}; - # Normalize and compare cards - normalize_jscard($want_jscard); - normalize_jscard($have_jscard); + # Normalize and compare cards + normalize_jscard($want_jscard); + normalize_jscard($have_jscard); - $self->assert_deep_equals($want_jscard, $have_jscard); + $self->assert_deep_equals($want_jscard, $have_jscard); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_ordered_phonetics b/cassandane/tiny-tests/JMAPContacts/card_get_ordered_phonetics index 2f3c1e5ca7..1d33751cd3 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_ordered_phonetics +++ b/cassandane/tiny-tests/JMAPContacts/card_get_ordered_phonetics @@ -3,26 +3,25 @@ use Cassandane::Tiny; use utf8; sub test_card_get_ordered_phonetics - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/test.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $want_jscard = { - '@type' => 'Card', - version => '1.0', - addressBookId => 'Default', - 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, - created => '2023-08-24T14:36:19Z', - vCardProps => [ - [ 'version', {}, 'text', '4.0' ] - ], - name => { - phoneticSystem => 'ipa', - isOrdered => JSON::true, - components => [ - { kind => 'given', value => 'John' , phonetic => "/d͡ʒɑn/" }, - { kind => 'surname', value => 'Smith', phonetic => "/smɪθ/" } - ] - }, - }; + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); - my $have_jscard = $res->[0][1]{list}[0]; + my $want_jscard = { + '@type' => 'Card', + version => '1.0', + addressBookId => 'Default', + 'cyrusimap.org:href' => $carddav->fullpath() . $href, + id => $id, + uid => $id, + created => '2023-08-24T14:36:19Z', + vCardProps => [ [ 'version', {}, 'text', '4.0' ] ], + name => { + phoneticSystem => 'ipa', + isOrdered => JSON::true, + components => [ + { kind => 'given', value => 'John', phonetic => "/d͡ʒɑn/" }, + { kind => 'surname', value => 'Smith', phonetic => "/smɪθ/" } + ] + }, + }; - # Delete generated fields - delete $have_jscard->{blobId}; - delete $have_jscard->{'cyrusimap.org:blobId'}; - delete $have_jscard->{'cyrusimap.org:size'}; + my $have_jscard = $res->[0][1]{list}[0]; - # Normalize and compare cards - normalize_jscard($want_jscard); - normalize_jscard($have_jscard); + # Delete generated fields + delete $have_jscard->{blobId}; + delete $have_jscard->{'cyrusimap.org:blobId'}; + delete $have_jscard->{'cyrusimap.org:size'}; -warn Dumper($want_jscard); -warn Dumper($have_jscard); - $self->assert_deep_equals($want_jscard, $have_jscard); + # Normalize and compare cards + normalize_jscard($want_jscard); + normalize_jscard($have_jscard); + + warn Dumper($want_jscard); + warn Dumper($have_jscard); + $self->assert_deep_equals($want_jscard, $have_jscard); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_phonetics b/cassandane/tiny-tests/JMAPContacts/card_get_phonetics index 04cc8a49c0..dddcbaa5d2 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_phonetics +++ b/cassandane/tiny-tests/JMAPContacts/card_get_phonetics @@ -3,26 +3,25 @@ use Cassandane::Tiny; use utf8; sub test_card_get_phonetics - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/test.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $want_jscard = { - '@type' => 'Card', - version => '1.0', - addressBookId => 'Default', - 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, - language => 'zho-Hant', - vCardProps => [ - [ 'version', {}, 'text', '4.0' ] - ], - name => { - full => '孫中山文逸仙', - components => [ - { kind => 'surname', value => '孫' }, - { kind => 'given', value => '中山' }, - { kind => 'given2', value => '文' }, - { kind => 'given2', value => '逸仙' } - ] - }, - localizations => { - yue => { - "name/phoneticSystem" => "jyut", - "name/phoneticScript" => "Latn", - "name/components/0/phonetic" => "syun1", - "name/components/1/phonetic" => "zung1saan1", - "name/components/2/phonetic" => "man4", - "name/components/3/phonetic" => "jat6sin1" - } - } - }; + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); - my $have_jscard = $res->[0][1]{list}[0]; + my $want_jscard = { + '@type' => 'Card', + version => '1.0', + addressBookId => 'Default', + 'cyrusimap.org:href' => $carddav->fullpath() . $href, + id => $id, + uid => $id, + language => 'zho-Hant', + vCardProps => [ [ 'version', {}, 'text', '4.0' ] ], + name => { + full => '孫中山文逸仙', + components => [ + { kind => 'surname', value => '孫' }, + { kind => 'given', value => '中山' }, + { kind => 'given2', value => '文' }, + { kind => 'given2', value => '逸仙' } + ] + }, + localizations => { + yue => { + "name/phoneticSystem" => "jyut", + "name/phoneticScript" => "Latn", + "name/components/0/phonetic" => "syun1", + "name/components/1/phonetic" => "zung1saan1", + "name/components/2/phonetic" => "man4", + "name/components/3/phonetic" => "jat6sin1" + } + } + }; - # Delete generated fields - delete $have_jscard->{blobId}; - delete $have_jscard->{'cyrusimap.org:blobId'}; - delete $have_jscard->{'cyrusimap.org:size'}; + my $have_jscard = $res->[0][1]{list}[0]; - # Normalize and compare cards - normalize_jscard($want_jscard); - normalize_jscard($have_jscard); + # Delete generated fields + delete $have_jscard->{blobId}; + delete $have_jscard->{'cyrusimap.org:blobId'}; + delete $have_jscard->{'cyrusimap.org:size'}; - $self->assert_deep_equals($want_jscard, $have_jscard); + # Normalize and compare cards + normalize_jscard($want_jscard); + normalize_jscard($have_jscard); + + $self->assert_deep_equals($want_jscard, $have_jscard); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_v3 b/cassandane/tiny-tests/JMAPContacts/card_get_v3 index 1be84b9c5c..a880497d5e 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_v3 +++ b/cassandane/tiny-tests/JMAPContacts/card_get_v3 @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_card_get_v3 - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - # PROP-IDs added so we can easily compare the results - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/test.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); - my $want_jscard = { - '@type' => 'Card', - version => '1.0', - addressBookId => 'Default', - 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, - updated => '2008-04-24T19:52:43Z', - vCardProps => [ - [ 'version', {}, 'text', '3.0' ] + my $want_jscard = { + '@type' => 'Card', + version => '1.0', + addressBookId => 'Default', + 'cyrusimap.org:href' => $carddav->fullpath() . $href, + id => $id, + uid => $id, + updated => '2008-04-24T19:52:43Z', + vCardProps => [ [ 'version', {}, 'text', '3.0' ] ], + name => { + full => 'Forrest Gump', + components => [ + { 'kind' => 'surname', 'value' => 'Gump' }, + { 'kind' => 'given', 'value' => 'Forrest' }, + { 'kind' => 'title', 'value' => 'Mr.' }, + ] + }, + anniversaries => { + A1 => { + 'kind' => 'birth', + 'date' => { 'year' => 1944, 'month' => 6, 'day' => 7 } + } + }, + organizations => { + O1 => { + name => 'Bubba Gump Shrimp Co.', + units => [ { name => 'foo' } ] + } + }, + titles => { + T1 => { + 'name' => 'Shrimp Man' + } + }, + addresses => { + A1 => { + 'isOrdered' => JSON::true, + 'components' => [ + { kind => 'name', value => '1501 Broadway' }, + { kind => 'locality', value => 'New York' }, + { kind => 'region', value => 'NY' }, + { kind => 'postcode', value => '10036' }, + { kind => 'country', value => 'USA' } ], - name => { - full => 'Forrest Gump', - components => [ - { 'kind' => 'surname', 'value' => 'Gump' }, - { 'kind' => 'given', 'value' => 'Forrest' }, - { 'kind' => 'title', 'value' => 'Mr.' }, - ] - }, - anniversaries => { - A1 => { - 'kind' => 'birth', - 'date' => { 'year' => 1944, 'month' => 6, 'day' => 7 } - } - }, - organizations => { - O1 => { - name => 'Bubba Gump Shrimp Co.', - units => [ - { name => 'foo' } - ] - } - }, - titles => { - T1 => { - 'name' => 'Shrimp Man' - } - }, - addresses => { - A1 => { - 'isOrdered' => JSON::true, - 'components' => [ - { kind => 'name', value => '1501 Broadway' }, - { kind => 'locality', value => 'New York' }, - { kind => 'region', value => 'NY' }, - { kind => 'postcode', value => '10036' }, - { kind => 'country', value => 'USA' } - ], - 'coordinates' => 'geo:40.7571383482188,-73.98695548990568', - 'timeZone' => 'Etc/GMT+5' - } - }, - media => { - P1 => { - kind => 'photo', - mediaType => 'image/jpeg' - } - } - }; + 'coordinates' => 'geo:40.7571383482188,-73.98695548990568', + 'timeZone' => 'Etc/GMT+5' + } + }, + media => { + P1 => { + kind => 'photo', + mediaType => 'image/jpeg' + } + } + }; - - my $have_jscard = $res->[0][1]{list}[0]; + my $have_jscard = $res->[0][1]{list}[0]; - # Get media blob id before we delete it - my $blobid = $res->[0][1]{list}[0]{media}{P1}{blobId}; - $self->assert_not_null($blobid); + # Get media blob id before we delete it + my $blobid = $res->[0][1]{list}[0]{media}{P1}{blobId}; + $self->assert_not_null($blobid); - # Delete generated fields - delete $have_jscard->{blobId}; - delete $have_jscard->{media}{P1}{blobId}; - delete $have_jscard->{'cyrusimap.org:blobId'}; - delete $have_jscard->{'cyrusimap.org:size'}; + # Delete generated fields + delete $have_jscard->{blobId}; + delete $have_jscard->{media}{P1}{blobId}; + delete $have_jscard->{'cyrusimap.org:blobId'}; + delete $have_jscard->{'cyrusimap.org:size'}; - # Normalize and compare cards - normalize_jscard($want_jscard); - normalize_jscard($have_jscard); - $self->assert_deep_equals($want_jscard, $have_jscard); + # Normalize and compare cards + normalize_jscard($want_jscard); + normalize_jscard($have_jscard); + $self->assert_deep_equals($want_jscard, $have_jscard); - $res = $jmap->Download('cassandane', $blobid); + $res = $jmap->Download('cassandane', $blobid); - $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); - $self->assert_str_equals('some photo', $res->{content}); + $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); + $self->assert_str_equals('some photo', $res->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_get_v4 b/cassandane/tiny-tests/JMAPContacts/card_get_v4 index 154e1c8397..0c05b52f64 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_get_v4 +++ b/cassandane/tiny-tests/JMAPContacts/card_get_v4 @@ -2,29 +2,28 @@ use Cassandane::Tiny; sub test_card_get_v4 - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - # Sample card from RFC 6350 - # Second N suffix removed due to vparse bug - # PROP-IDs added so we can easily compare the results - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/test.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); - my $want_jscard = { - '@type' => 'Card', - version => '1.0', - addressBookId => 'Default', - 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, - kind => 'individual', - updated => '2023-04-22T19:46:39Z', - vCardProps => [ - [ 'version', {}, 'text', '4.0' ], - [ 'gender', {}, 'text', 'M' ], + my $want_jscard = { + '@type' => 'Card', + version => '1.0', + addressBookId => 'Default', + 'cyrusimap.org:href' => $carddav->fullpath() . $href, + id => $id, + uid => $id, + kind => 'individual', + updated => '2023-04-22T19:46:39Z', + vCardProps => + [ [ 'version', {}, 'text', '4.0' ], [ 'gender', {}, 'text', 'M' ], ], + name => { + full => 'Simon Perreault', + components => [ + { 'kind' => 'surname', 'value' => 'Perreault' }, + { 'kind' => 'given', 'value' => 'Simon' }, + { 'kind' => 'credential', 'value' => 'ing. jr' }, + ], + foo => 'bar' + }, + anniversaries => { + A1 => { + 'kind' => 'birth', + 'date' => { 'month' => 2, 'day' => 3 } + }, + A2 => { + 'kind' => 'wedding', + 'date' => { '@type' => 'Timestamp', 'utc' => '2009-08-08T19:30:00Z' } + }, + }, + preferredLanguages => { + L2 => { + 'language' => 'en', + 'pref' => 2 + }, + L1 => { + 'language' => 'fr', + 'pref' => 1 + }, + }, + organizations => { + O1 => { + 'name' => 'Viagenie', + foo => {} + } + }, + addresses => { + A1 => { + 'components' => [ + { kind => 'apartment', value => 'Suite D2-630' }, + { kind => 'name', value => '2875 Laurier' }, + { kind => 'locality', value => 'Quebec' }, + { kind => 'region', value => 'QC' }, + { kind => 'postcode', value => 'G1V 2M2' }, + { kind => 'country', value => 'Canada' } ], - name => { - full => 'Simon Perreault', - components => [ - { 'kind' => 'surname', 'value' => 'Perreault' }, - { 'kind' => 'given', 'value' => 'Simon' }, - { 'kind' => 'credential', 'value' => 'ing. jr' }, - ], - foo => 'bar' + 'contexts' => { 'work' => JSON::true }, + 'coordinates' => 'geo:46.772673,-71.282945', + 'timeZone' => 'America/Montreal' + }, + A2 => { + full => 'Somewhere', + components => [ { kind => 'foo', value => 'bar' } ] + } + }, + phones => { + P1 => { + number => "tel:+1-418-656-9254;ext=102", + contexts => { + work => JSON::true }, - anniversaries => { - A1 => { - 'kind' => 'birth', - 'date' => { 'month' => 2, 'day' => 3 } - }, - A2 => { - 'kind' => 'wedding', - 'date' => { '@type' => 'Timestamp', 'utc' => '2009-08-08T19:30:00Z' } - }, + features => { + voice => JSON::true, + foo => JSON::true }, - preferredLanguages => { - L2 => { - 'language' => 'en', - 'pref' => 2 - }, - L1 => { - 'language' => 'fr', - 'pref' => 1 - }, - }, - organizations => { - O1 => { - 'name' => 'Viagenie', - foo => {} - } - }, - addresses => { - A1 => { - 'components' => [ - { kind => 'apartment', value => 'Suite D2-630' }, - { kind => 'name', value => '2875 Laurier' }, - { kind => 'locality', value => 'Quebec' }, - { kind => 'region', value => 'QC' }, - { kind => 'postcode', value => 'G1V 2M2' }, - { kind => 'country', value => 'Canada' } - ], - 'contexts' => { 'work' => JSON::true }, - 'coordinates' => 'geo:46.772673,-71.282945', - 'timeZone' => 'America/Montreal' - }, - A2 => { - full => 'Somewhere', - components => [ - { kind => 'foo', value => 'bar' } - ] - } - }, - phones => { - P1 => { - number => "tel:+1-418-656-9254;ext=102", - contexts => { - work => JSON::true - }, - features => { - voice => JSON::true, - foo => JSON::true - }, - pref => 1 - }, - P2 => { - 'number' => 'tel:+1-418-262-6501', - 'contexts' => { 'work' => JSON::true }, - 'features' => { 'cell' => JSON::true, 'voice' => JSON::true, - 'video' => JSON::true, 'text' => JSON::true } - }, - }, - emails => { - E1 => { - 'address' => 'simon.perreault@viagenie.ca', - 'contexts' => { 'work' => JSON::true } - }, - }, - cryptoKeys => { - K1 => { - 'uri' => 'http://www.viagenie.ca/simon.perreault/simon.asc', - 'contexts' => { 'work' => JSON::true } - }, - }, - links => { - L1 => { - 'uri' => 'http://nomis80.org', - 'contexts' => { 'private' => JSON::true } - }, - }, - onlineServices => { - 'OS1' => { - 'uri' => 'xmpp:simon@example.com', - 'vCardName' => 'impp', - 'pref' => 1 - }, - 'OS2' => { - 'user' => 'Simon P.', - 'uri' => 'https://example.com/@simon', - 'service' => 'Mastodon' - } - }, - media => { - L1 => { - 'kind' => 'logo', - 'mediaType' => 'image/png', - 'uri' => 'http://example.org/logo.png' - }, - P1 => { - kind => 'photo', - mediaType => 'image/jpeg', - }, - S1 => { - kind => 'sound', - mediaType => 'audio/mpeg', - }, - }, - }; - + pref => 1 + }, + P2 => { + 'number' => 'tel:+1-418-262-6501', + 'contexts' => { 'work' => JSON::true }, + 'features' => { + 'cell' => JSON::true, + 'voice' => JSON::true, + 'video' => JSON::true, + 'text' => JSON::true + } + }, + }, + emails => { + E1 => { + 'address' => 'simon.perreault@viagenie.ca', + 'contexts' => { 'work' => JSON::true } + }, + }, + cryptoKeys => { + K1 => { + 'uri' => 'http://www.viagenie.ca/simon.perreault/simon.asc', + 'contexts' => { 'work' => JSON::true } + }, + }, + links => { + L1 => { + 'uri' => 'http://nomis80.org', + 'contexts' => { 'private' => JSON::true } + }, + }, + onlineServices => { + 'OS1' => { + 'uri' => 'xmpp:simon@example.com', + 'vCardName' => 'impp', + 'pref' => 1 + }, + 'OS2' => { + 'user' => 'Simon P.', + 'uri' => 'https://example.com/@simon', + 'service' => 'Mastodon' + } + }, + media => { + L1 => { + 'kind' => 'logo', + 'mediaType' => 'image/png', + 'uri' => 'http://example.org/logo.png' + }, + P1 => { + kind => 'photo', + mediaType => 'image/jpeg', + }, + S1 => { + kind => 'sound', + mediaType => 'audio/mpeg', + }, + }, + }; - my $have_jscard = $res->[0][1]{list}[0]; + my $have_jscard = $res->[0][1]{list}[0]; - # Get media blob ids before we delete them - my $p_blobid = $have_jscard->{media}{P1}{blobId}; - $self->assert_not_null($p_blobid); - my $s_blobid = $have_jscard->{media}{S1}{blobId}; - $self->assert_not_null($s_blobid); + # Get media blob ids before we delete them + my $p_blobid = $have_jscard->{media}{P1}{blobId}; + $self->assert_not_null($p_blobid); + my $s_blobid = $have_jscard->{media}{S1}{blobId}; + $self->assert_not_null($s_blobid); - # Delete generated fields - delete $have_jscard->{blobId}; - delete $have_jscard->{media}{P1}{blobId}; - delete $have_jscard->{media}{S1}{blobId}; - delete $have_jscard->{'cyrusimap.org:blobId'}; - delete $have_jscard->{'cyrusimap.org:size'}; + # Delete generated fields + delete $have_jscard->{blobId}; + delete $have_jscard->{media}{P1}{blobId}; + delete $have_jscard->{media}{S1}{blobId}; + delete $have_jscard->{'cyrusimap.org:blobId'}; + delete $have_jscard->{'cyrusimap.org:size'}; - # Normalize and compare cards - normalize_jscard($want_jscard); - normalize_jscard($have_jscard); - $self->assert_deep_equals($want_jscard, $have_jscard); + # Normalize and compare cards + normalize_jscard($want_jscard); + normalize_jscard($have_jscard); + $self->assert_deep_equals($want_jscard, $have_jscard); - $res = $jmap->Download('cassandane', $p_blobid); + $res = $jmap->Download('cassandane', $p_blobid); - $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); - $self->assert_str_equals('some photo', $res->{content}); + $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); + $self->assert_str_equals('some photo', $res->{content}); - $res = $jmap->Download('cassandane', $s_blobid); + $res = $jmap->Download('cassandane', $s_blobid); - $self->assert_str_equals('audio/mpeg', $res->{headers}{'content-type'}); - $self->assert_str_equals('some sound', $res->{content}); + $self->assert_str_equals('audio/mpeg', $res->{headers}{'content-type'}); + $self->assert_str_equals('some sound', $res->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_parse b/cassandane/tiny-tests/JMAPContacts/card_parse index b32ed0186a..9c2ca9cbb0 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_parse +++ b/cassandane/tiny-tests/JMAPContacts/card_parse @@ -2,26 +2,25 @@ use Cassandane::Tiny; sub test_card_parse - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - # PROP-IDs added so we can easily compare the results - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $card = <Upload($card, "text/vcard"); - my $blobId = $res->{blobId}; + xlog $self, "upload vCard"; + my $res = $jmap->Upload($card, "text/vcard"); + my $blobId = $res->{blobId}; - my $res = $jmap->CallMethods([ - ['ContactCard/parse', { - blobIds => [ $blobId ], - properties => [ "\@type", "uid", "name", "media", "vCardProps" ] - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'ContactCard/parse', + { + blobIds => [$blobId], + properties => [ "\@type", "uid", "name", "media", "vCardProps" ] + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{parsed}{$blobId}); - $self->assert_str_equals('Card', $res->[0][1]{parsed}{$blobId}{'@type'}); - $self->assert_str_equals($id, $res->[0][1]{parsed}{$blobId}{uid}); - $self->assert_deep_equals([ - [ 'version', {}, 'text', '3.0' ] - ], $res->[0][1]{parsed}{$blobId}{vCardProps}); - $self->assert_str_equals('Forrest Gump', $res->[0][1]{parsed}{$blobId}{name}{full}); + $self->assert_not_null($res->[0][1]{parsed}{$blobId}); + $self->assert_str_equals('Card', $res->[0][1]{parsed}{$blobId}{'@type'}); + $self->assert_str_equals($id, $res->[0][1]{parsed}{$blobId}{uid}); + $self->assert_deep_equals( + [ [ 'version', {}, 'text', '3.0' ] ], + $res->[0][1]{parsed}{$blobId}{vCardProps} + ); + $self->assert_str_equals('Forrest Gump', + $res->[0][1]{parsed}{$blobId}{name}{full}); - $self->assert_null($res->[0][1]{parsed}{$blobId}{version}); - $self->assert_null($res->[0][1]{parsed}{$blobId}{updated}); - $self->assert_null($res->[0][1]{parsed}{$blobId}{anniversaries}); - $self->assert_null($res->[0][1]{parsed}{$blobId}{organizations}); - $self->assert_null($res->[0][1]{parsed}{$blobId}{titles}); + $self->assert_null($res->[0][1]{parsed}{$blobId}{version}); + $self->assert_null($res->[0][1]{parsed}{$blobId}{updated}); + $self->assert_null($res->[0][1]{parsed}{$blobId}{anniversaries}); + $self->assert_null($res->[0][1]{parsed}{$blobId}{organizations}); + $self->assert_null($res->[0][1]{parsed}{$blobId}{titles}); - $self->assert_str_equals('photo', - $res->[0][1]{parsed}{$blobId}{media}{P1}{kind}); - $self->assert_str_equals('image/jpeg', - $res->[0][1]{parsed}{$blobId}{media}{P1}{mediaType}); + $self->assert_str_equals('photo', + $res->[0][1]{parsed}{$blobId}{media}{P1}{kind}); + $self->assert_str_equals('image/jpeg', + $res->[0][1]{parsed}{$blobId}{media}{P1}{mediaType}); - my $blobid = $res->[0][1]{parsed}{$blobId}{media}{P1}{blobId}; - $self->assert_not_null($blobid); + my $blobid = $res->[0][1]{parsed}{$blobId}{media}{P1}{blobId}; + $self->assert_not_null($blobid); - $res = $jmap->Download('cassandane', $blobid); + $res = $jmap->Download('cassandane', $blobid); - $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); - $self->assert_str_equals('some photo', $res->{content}); + $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); + $self->assert_str_equals('some photo', $res->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_parse_disable_uri_as_blobid b/cassandane/tiny-tests/JMAPContacts/card_parse_disable_uri_as_blobid index a1b126dc44..d77d9ea9da 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_parse_disable_uri_as_blobid +++ b/cassandane/tiny-tests/JMAPContacts/card_parse_disable_uri_as_blobid @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_card_parse_disable_uri_as_blobid - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - my $vcard = <<'EOF'; + my $vcard = <<'EOF'; BEGIN:VCARD VERSION:4.0 UID:85b5d651-1cde-43d9-901d-7059d67807f9 @@ -17,29 +16,33 @@ PHOTO;PROP-ID=photo1:data:image/x-bla;base64,YmxhYmxhCg= CREATED:20230823T133154Z END:VCARD EOF - $vcard =~ s/\r?\n/\r\n/gs; + $vcard =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($vcard, "text/vcard"); - my $blobId = $data->{blobId}; - $self->assert_not_null($blobId); + my $data = $jmap->Upload($vcard, "text/vcard"); + my $blobId = $data->{blobId}; + $self->assert_not_null($blobId); - $res = $jmap->CallMethods([ - ['ContactCard/parse', { - blobIds => [$blobId], - }, 'R1'], - ['ContactCard/parse', { - blobIds => [$blobId], - disableUriAsBlobId => JSON::true, - }, 'R2'], - ]); + $res = $jmap->CallMethods([ + [ + 'ContactCard/parse', + { + blobIds => [$blobId], + }, + 'R1' + ], + [ + 'ContactCard/parse', + { + blobIds => [$blobId], + disableUriAsBlobId => JSON::true, + }, + 'R2' + ], + ]); - $self->assert_not_null($res->[0][1]{parsed}{$blobId}{ - media}{photo1}{blobId}); - $self->assert_null($res->[0][1]{parsed}{$blobId}{ - media}{photo1}{uri}); + $self->assert_not_null($res->[0][1]{parsed}{$blobId}{media}{photo1}{blobId}); + $self->assert_null($res->[0][1]{parsed}{$blobId}{media}{photo1}{uri}); - $self->assert_null($res->[1][1]{parsed}{$blobId}{ - media}{photo1}{blobId}); - $self->assert_not_null($res->[1][1]{parsed}{$blobId}{ - media}{photo1}{uri}); + $self->assert_null($res->[1][1]{parsed}{$blobId}{media}{photo1}{blobId}); + $self->assert_not_null($res->[1][1]{parsed}{$blobId}{media}{photo1}{uri}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_query b/cassandane/tiny-tests/JMAPContacts/card_query index 5b4a1b0b1f..69e71c2619 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_query +++ b/cassandane/tiny-tests/JMAPContacts/card_query @@ -2,382 +2,480 @@ use Cassandane::Tiny; sub test_card_query - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create cards"; - my $res = $jmap->CallMethods([['ContactCard/set', { - create => { - "1" => { - name => { - isOrdered => JSON::false, - components => [ - { - kind => "given", - value => "foo" - }, - { - kind => "surname", - value => "last" - }, - ], - sortAs => { - surname => 'aaa' - } - }, - nicknames => { - 'n1' => { - name => "foo" - } + xlog $self, "create cards"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + name => { + isOrdered => JSON::false, + components => [ + { + kind => "given", + value => "foo" + }, + { + kind => "surname", + value => "last" + }, + ], + sortAs => { + surname => 'aaa' + } + }, + nicknames => { + 'n1' => { + name => "foo" + } + }, + emails => { + 'e1' => { + contexts => { + private => JSON::true + }, + address => "foo\@example.com" + } + }, + personalInfo => { + 'p1' => { + kind => 'hobby', + value => 'reading' + } + } + }, + "2" => { + name => { + isOrdered => JSON::false, + components => [ + { + kind => "given", + value => "bar" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + emails => { + 'e1' => { + contexts => { + work => JSON::true + }, + address => "bar\@bar.org" + }, + 'e2' => { + contexts => { + other => JSON::true + }, + address => "me\@example.com" + } + }, + addresses => { + 'a1' => { + contexts => { + private => JSON::true + }, + isOrdered => JSON::false, + components => [ + { + kind => "name", + value => "Some Lane" }, - emails => { - 'e1' => { - contexts => { - private => JSON::true - }, - address => "foo\@example.com" - } + { + kind => "number", + value => "24" }, - personalInfo => { - 'p1' => { - kind => 'hobby', - value => 'reading' - } - } - }, - "2" => { - name => { - isOrdered => JSON::false, - components => [ - { - kind => "given", - value => "bar" - }, - { - kind => "surname", - value => "last" - }, - ] + { + kind => "locality", + value => "SomeWhere City" }, - emails => { - 'e1' => { - contexts => { - work => JSON::true - }, - address => "bar\@bar.org" - }, - 'e2' => { - contexts => { - other => JSON::true - }, - address => "me\@example.com" - } + { + kind => "region", + value => "" }, - addresses => { - 'a1' => { - contexts => { - private => JSON::true - }, - isOrdered => JSON::false, - components => [ - { - kind => "name", - value => "Some Lane" - }, - { - kind => "number", - value => "24" - }, - { - kind => "locality", - value => "SomeWhere City" - }, - { - kind => "region", - value => "" - }, - { - kind => "postcode", - value => "1234" - } - ] - } + { + kind => "postcode", + value => "1234" } - }, - "3" => { - name => { - isOrdered => JSON::false, - components => [ - { - kind => "given", - value => "baz" - }, - { - kind => "surname", - value => "last" - }, - ] + ] + } + } + }, + "3" => { + name => { + isOrdered => JSON::false, + components => [ + { + kind => "given", + value => "baz" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + addresses => { + 'a1' => { + contexts => { + private => JSON::true + }, + isOrdered => JSON::false, + components => [ + { + kind => "name", + value => "Some Lane" }, - addresses => { - 'a1' => { - contexts => { - private => JSON::true - }, - isOrdered => JSON::false, - components => [ - { - kind => "name", - value => "Some Lane" - }, - { - kind => "number", - value => "24" - }, - { - kind => "locality", - value => "SomeWhere City" - }, - { - kind => "region", - value => "" - }, - { - kind => "postcode", - value => "1234" - }, - { - kind => "country", - value => "Someinistan" - } - ] - } + { + kind => "number", + value => "24" }, - personalInfo => { - 'p1' => { - kind => 'interest', - value => 'r&b music' - } - } - }, - "4" => { - name => { - isOrdered => JSON::false, - components => [ - { - kind => "given", - value => "bam" - }, - { - kind => "surname", - value => "last" - }, - ] + { + kind => "locality", + value => "SomeWhere City" }, - nicknames => { - 'n1' => { - name => "bam" - } + { + kind => "region", + value => "" }, - notes => { - 'n1' => { - note => "hello" - } - } - }, - "5" => { - kind => 'org', - name => { - full => 'My Org' + { + kind => "postcode", + value => "1234" + }, + { + kind => "country", + value => "Someinistan" } + ] + } + }, + personalInfo => { + 'p1' => { + kind => 'interest', + value => 'r&b music' + } + } + }, + "4" => { + name => { + isOrdered => JSON::false, + components => [ + { + kind => "given", + value => "bam" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + nicknames => { + 'n1' => { + name => "bam" } + }, + notes => { + 'n1' => { + note => "hello" + } + } + }, + "5" => { + kind => 'org', + name => { + full => 'My Org' + } } - }, "R1"]]); + } + }, + "R1" + ] ]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; - my $id3 = $res->[0][1]{created}{"3"}{id}; - my $id4 = $res->[0][1]{created}{"4"}{id}; + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; + my $id3 = $res->[0][1]{created}{"3"}{id}; + my $id4 = $res->[0][1]{created}{"4"}{id}; - xlog $self, "create card groups"; - $res = $jmap->CallMethods([['ContactCard/set', {create => { - "1" => { kind => 'group', - name => { full => "group1" }, - members => { $id1 => JSON::true, $id2 => JSON::true } - }, - "2" => { kind => 'group', - name => { full => "group2" }, - members => { $id3 => JSON::true } - }, - "3" => { kind => 'group', - name => { full => "group3" }, - members => { $id4 => JSON::true } - } - }}, "R1"]]); + xlog $self, "create card groups"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + kind => 'group', + name => { full => "group1" }, + members => { $id1 => JSON::true, $id2 => JSON::true } + }, + "2" => { + kind => 'group', + name => { full => "group2" }, + members => { $id3 => JSON::true } + }, + "3" => { + kind => 'group', + name => { full => "group3" }, + members => { $id4 => JSON::true } + } + } + }, + "R1" + ] ]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $group1 = $res->[0][1]{created}{"1"}{id}; - my $group2 = $res->[0][1]{created}{"2"}{id}; - my $group3 = $res->[0][1]{created}{"3"}{id}; + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $group1 = $res->[0][1]{created}{"1"}{id}; + my $group2 = $res->[0][1]{created}{"2"}{id}; + my $group3 = $res->[0][1]{created}{"3"}{id}; - xlog $self, "get unfiltered card list"; - $res = $jmap->CallMethods([ ['ContactCard/query', { }, "R1"] ]); + xlog $self, "get unfiltered card list"; + $res = $jmap->CallMethods([ [ 'ContactCard/query', {}, "R1" ] ]); - $self->assert_num_equals(8, $res->[0][1]{total}); - $self->assert_num_equals(8, scalar @{$res->[0][1]{ids}}); + $self->assert_num_equals(8, $res->[0][1]{total}); + $self->assert_num_equals(8, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by kind"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => 'individual'} - }, "R1"] ]); + xlog $self, "filter by kind"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { kind => 'individual' } + }, + "R1" + ] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by kind"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => 'org'} - }, "R1"] ]); + xlog $self, "filter by kind"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { kind => 'org' } + }, + "R1" + ] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by name (fullName)"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { name => "foo" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "filter by name (fullName)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { name => "foo" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - xlog $self, "filter by name (fullName)"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { name => "last" } - }, "R1"] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by name (fullName)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { name => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by name/given"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { 'name/given' => "foo" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "filter by name/given"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { 'name/given' => "foo" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - xlog $self, "filter by name/surname"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { 'name/surname' => "last" } - }, "R1"] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by name/surname"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { 'name/surname' => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by name/given and name/surname (one filter)"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { 'name/given' => "bam", 'name/surname' => "last" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); + xlog $self, "filter by name/given and name/surname (one filter)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { 'name/given' => "bam", 'name/surname' => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); - xlog $self, "filter by name/given and name/surname (AND filter)"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { operator => "AND", conditions => [{ - 'name/surname' => "last" - }, { - 'name/given' => "baz" - }]} - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); + xlog $self, "filter by name/given and name/surname (AND filter)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { + operator => "AND", + conditions => [ + { + 'name/surname' => "last" + }, + { + 'name/given' => "baz" + } + ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); - xlog $self, "filter by name/given (OR filter)"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { operator => "OR", conditions => [{ - 'name/given' => "bar" - }, { - 'name/given' => "baz" - }]} - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by name/given (OR filter)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { + operator => "OR", + conditions => [ + { + 'name/given' => "bar" + }, + { + 'name/given' => "baz" + } + ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by text"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { text => "some" } - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by text"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { text => "some" } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by nickName"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { nickName => "foo" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "filter by nickName"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { nickName => "foo" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - xlog $self, "filter by email"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { email => "example.com" } - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by email"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { email => "example.com" } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by hobby"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { hobby => "reading" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by hobby"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { hobby => "reading" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by note"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { note => "hello" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by note"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { note => "hello" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by inCardGroup"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { inCardGroup => [$group1, $group3] } - }, "R1"] ]); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by inCardGroup"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { inCardGroup => [ $group1, $group3 ] } + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by inCardGroup and name/given"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { inCardGroup => [$group1, $group3], - 'name/given' => "foo" } - }, "R1"] ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "filter by inCardGroup and name/given"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { + inCardGroup => [ $group1, $group3 ], + 'name/given' => "foo" + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - xlog $self, "sort by name/given"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => 'individual'}, - sort => [ { property => "name/given" } ] - }, "R1"] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); - $self->assert_str_equals($id2, $res->[0][1]{ids}[1]); - $self->assert_str_equals($id3, $res->[0][1]{ids}[2]); - $self->assert_str_equals($id1, $res->[0][1]{ids}[3]); + xlog $self, "sort by name/given"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { kind => 'individual' }, + sort => [ { property => "name/given" } ] + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); + $self->assert_str_equals($id2, $res->[0][1]{ids}[1]); + $self->assert_str_equals($id3, $res->[0][1]{ids}[2]); + $self->assert_str_equals($id1, $res->[0][1]{ids}[3]); - xlog $self, "sort by name/surname"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => 'individual'}, - sort => [ { property => "name/surname" } ] - }, "R1"] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "sort by name/surname"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { kind => 'individual' }, + sort => [ { property => "name/surname" } ] + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_query_multi_sort b/cassandane/tiny-tests/JMAPContacts/card_query_multi_sort index f78e2d932d..8e1d8de015 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_query_multi_sort +++ b/cassandane/tiny-tests/JMAPContacts/card_query_multi_sort @@ -2,79 +2,84 @@ use Cassandane::Tiny; sub test_card_query_multi_sort - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create cards"; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - card1 => { - uid => 'XXX-UID-1', - organizations => { - 'o1' => { - name => 'companyB' - } - } - }, - card2 => { - uid => 'XXX-UID-2', - organizations => { - 'o1' => { - name => 'companyA' - } - } - }, - card3 => { - uid => 'XXX-UID-3', - organizations => { - 'o1' => { - name => 'companyB' - } - } - }, - card4 => { - uid => 'XXX-UID-4', - organizations => { - 'o1' => { - name => 'companyC' - } - } - }, - }, - }, 'R1'], - ]); - my $cardId1 = $res->[0][1]{created}{card1}{id}; - $self->assert_not_null($cardId1); + xlog $self, "create cards"; + my $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + create => { + card1 => { + uid => 'XXX-UID-1', + organizations => { + 'o1' => { + name => 'companyB' + } + } + }, + card2 => { + uid => 'XXX-UID-2', + organizations => { + 'o1' => { + name => 'companyA' + } + } + }, + card3 => { + uid => 'XXX-UID-3', + organizations => { + 'o1' => { + name => 'companyB' + } + } + }, + card4 => { + uid => 'XXX-UID-4', + organizations => { + 'o1' => { + name => 'companyC' + } + } + }, + }, + }, + 'R1' + ], + ]); + my $cardId1 = $res->[0][1]{created}{card1}{id}; + $self->assert_not_null($cardId1); - my $cardId2 = $res->[0][1]{created}{card2}{id}; - $self->assert_not_null($cardId2); + my $cardId2 = $res->[0][1]{created}{card2}{id}; + $self->assert_not_null($cardId2); - my $cardId3 = $res->[0][1]{created}{card3}{id}; - $self->assert_not_null($cardId3); + my $cardId3 = $res->[0][1]{created}{card3}{id}; + $self->assert_not_null($cardId3); - my $cardId4 = $res->[0][1]{created}{card4}{id}; - $self->assert_not_null($cardId4); + my $cardId4 = $res->[0][1]{created}{card4}{id}; + $self->assert_not_null($cardId4); - xlog $self, "sort by multi-dimensional comparator"; - $res = $jmap->CallMethods([ - ['ContactCard/query', { - sort => [{ - property => 'organization', - }, { - property => 'uid', - isAscending => JSON::false, - }], - }, 'R2'], - ]); - $self->assert_deep_equals([ - $cardId2, - $cardId3, - $cardId1, - $cardId4, - ], $res->[0][1]{ids} - ); + xlog $self, "sort by multi-dimensional comparator"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/query', + { + sort => [ + { + property => 'organization', + }, + { + property => 'uid', + isAscending => JSON::false, + } + ], + }, + 'R2' + ], + ]); + $self->assert_deep_equals([ $cardId2, $cardId3, $cardId1, $cardId4, ], + $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_query_shared b/cassandane/tiny-tests/JMAPContacts/card_query_shared index a859151445..df93e850c4 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_query_shared +++ b/cassandane/tiny-tests/JMAPContacts/card_query_shared @@ -2,439 +2,443 @@ use Cassandane::Tiny; sub test_card_query_shared - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrswipkxtecdn') or die; + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; - xlog $self, "create cards"; - my $res = $jmap->CallMethods([ [ - 'ContactCard/set', - { - accountId => 'manifold', - create => { - card1 => { - name => { - isOrdered => JSON::false, - components => [ - { - kind => "given", - value => "given1" - }, - { - kind => "surname", - value => "last" - }, - ], - sortAs => { surname => 'aaa' } - }, - nicknames => { 'n1' => { name => "nick1" } }, - emails => { - 'e1' => { - contexts => { private => JSON::true }, - address => "card1\@example.com" - } - }, - personalInfo => { - 'p1' => { - kind => 'hobby', - value => 'reading' - } - } + xlog $self, "create cards"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + accountId => 'manifold', + create => { + card1 => { + name => { + isOrdered => JSON::false, + components => [ + { + kind => "given", + value => "given1" + }, + { + kind => "surname", + value => "last" + }, + ], + sortAs => { surname => 'aaa' } + }, + nicknames => { 'n1' => { name => "nick1" } }, + emails => { + 'e1' => { + contexts => { private => JSON::true }, + address => "card1\@example.com" + } + }, + personalInfo => { + 'p1' => { + kind => 'hobby', + value => 'reading' + } + } + }, + card2 => { + name => { + isOrdered => JSON::false, + components => [ + { + kind => "given", + value => "given2" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + emails => { + 'e1' => { + contexts => { work => JSON::true }, + address => "card2\@bar.org" + }, + 'e2' => { + contexts => { other => JSON::true }, + address => "me\@example.com" + } + }, + addresses => { + 'a1' => { + contexts => { private => JSON::true }, + isOrdered => JSON::false, + components => [ + { + kind => "name", + value => "Some Lane" }, - card2 => { - name => { - isOrdered => JSON::false, - components => [ - { - kind => "given", - value => "given2" - }, - { - kind => "surname", - value => "last" - }, - ] - }, - emails => { - 'e1' => { - contexts => { work => JSON::true }, - address => "card2\@bar.org" - }, - 'e2' => { - contexts => { other => JSON::true }, - address => "me\@example.com" - } - }, - addresses => { - 'a1' => { - contexts => { private => JSON::true }, - isOrdered => JSON::false, - components => [ - { - kind => "name", - value => "Some Lane" - }, - { - kind => "number", - value => "24" - }, - { - kind => "locality", - value => "SomeWhere City" - }, - { - kind => "region", - value => "" - }, - { - kind => "postcode", - value => "1234" - } - ] - } - } + { + kind => "number", + value => "24" }, - card3 => { - name => { - isOrdered => JSON::false, - components => [ - { - kind => "given", - value => "given3" - }, - { - kind => "surname", - value => "last" - }, - ] - }, - addresses => { - 'a1' => { - contexts => { private => JSON::true }, - isOrdered => JSON::false, - components => [ - { - kind => "name", - value => "Some Lane" - }, - { - kind => "number", - value => "24" - }, - { - kind => "locality", - value => "SomeWhere City" - }, - { - kind => "region", - value => "" - }, - { - kind => "postcode", - value => "1234" - }, - { - kind => "country", - value => "Someinistan" - } - ] - } - }, - personalInfo => { - 'p1' => { - kind => 'interest', - value => 'r&b music' - } - } + { + kind => "locality", + value => "SomeWhere City" }, - card4 => { - name => { - isOrdered => JSON::false, - components => [ - { - kind => "given", - value => "given4" - }, - { - kind => "surname", - value => "last" - }, - ] - }, - nicknames => { 'n1' => { name => "bam" } }, - notes => { 'n1' => { note => "hello" } } + { + kind => "region", + value => "" + }, + { + kind => "postcode", + value => "1234" } + ] } + } }, - "R1" - ] ]); - - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"card1"}{id}; - my $id2 = $res->[0][1]{created}{"card2"}{id}; - my $id3 = $res->[0][1]{created}{"card3"}{id}; - my $id4 = $res->[0][1]{created}{"card4"}{id}; - - xlog $self, "create card groups"; - $res = $jmap->CallMethods([ [ - 'ContactCard/set', - { - accountId => 'manifold', - create => { - group1 => { - kind => 'group', - name => { full => "group1" }, - members => { $id1 => JSON::true, $id2 => JSON::true } + card3 => { + name => { + isOrdered => JSON::false, + components => [ + { + kind => "given", + value => "given3" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + addresses => { + 'a1' => { + contexts => { private => JSON::true }, + isOrdered => JSON::false, + components => [ + { + kind => "name", + value => "Some Lane" + }, + { + kind => "number", + value => "24" }, - group2 => { - kind => 'group', - name => { full => "group2" }, - members => { $id3 => JSON::true } + { + kind => "locality", + value => "SomeWhere City" }, - group3 => { - kind => 'group', - name => { full => "group3" }, - members => { $id4 => JSON::true } + { + kind => "region", + value => "" + }, + { + kind => "postcode", + value => "1234" + }, + { + kind => "country", + value => "Someinistan" } + ] } + }, + personalInfo => { + 'p1' => { + kind => 'interest', + value => 'r&b music' + } + } }, - "R1" - ] ]); + card4 => { + name => { + isOrdered => JSON::false, + components => [ + { + kind => "given", + value => "given4" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + nicknames => { 'n1' => { name => "bam" } }, + notes => { 'n1' => { note => "hello" } } + } + } + }, + "R1" + ] ]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $group1 = $res->[0][1]{created}{"group1"}{id}; - my $group2 = $res->[0][1]{created}{"group2"}{id}; - my $group3 = $res->[0][1]{created}{"group3"}{id}; + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"card1"}{id}; + my $id2 = $res->[0][1]{created}{"card2"}{id}; + my $id3 = $res->[0][1]{created}{"card3"}{id}; + my $id4 = $res->[0][1]{created}{"card4"}{id}; - xlog $self, "get unfiltered card list"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', + xlog $self, "create card groups"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + accountId => 'manifold', + create => { + group1 => { + kind => 'group', + name => { full => "group1" }, + members => { $id1 => JSON::true, $id2 => JSON::true } + }, + group2 => { + kind => 'group', + name => { full => "group2" }, + members => { $id3 => JSON::true } }, - "R1" - ] ]); + group3 => { + kind => 'group', + name => { full => "group3" }, + members => { $id4 => JSON::true } + } + } + }, + "R1" + ] ]); - $self->assert_num_equals(7, $res->[0][1]{total}); - $self->assert_num_equals(7, scalar @{ $res->[0][1]{ids} }); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $group1 = $res->[0][1]{created}{"group1"}{id}; + my $group2 = $res->[0][1]{created}{"group2"}{id}; + my $group3 = $res->[0][1]{created}{"group3"}{id}; - xlog $self, "filter by name (fullName)"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { name => "given1" } - }, - "R1" - ] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "get unfiltered card list"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + }, + "R1" + ] ]); - xlog $self, "filter by name (fullName)"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { name => "last" } - }, - "R1" - ] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(7, $res->[0][1]{total}); + $self->assert_num_equals(7, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by name/given"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { 'name/given' => "given1" } - }, - "R1" - ] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "filter by name (fullName)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { name => "given1" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - xlog $self, "filter by name/surname"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { 'name/surname' => "last" } - }, - "R1" - ] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + xlog $self, "filter by name (fullName)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { name => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by name/given and name/surname (one filter)"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { 'name/given' => "given4", 'name/surname' => "last" } - }, - "R1" - ] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); + xlog $self, "filter by name/given"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { 'name/given' => "given1" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - xlog $self, "filter by name/given and name/surname (AND filter)"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => - { operator => "AND", conditions => [ { 'name/surname' => "last" }, { 'name/given' => "given3" } ] } - }, - "R1" - ] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); + xlog $self, "filter by name/surname"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { 'name/surname' => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by name/given (OR filter)"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => - { operator => "OR", conditions => [ { 'name/given' => "given2" }, { 'name/given' => "given3" } ] } - }, - "R1" - ] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + xlog $self, "filter by name/given and name/surname (one filter)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { 'name/given' => "given4", 'name/surname' => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); - xlog $self, "filter by text"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { text => "some" } - }, - "R1" - ] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + xlog $self, "filter by name/given and name/surname (AND filter)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { + operator => "AND", + conditions => + [ { 'name/surname' => "last" }, { 'name/given' => "given3" } ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); - xlog $self, "filter by nickName"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { nickName => "nick1" } - }, - "R1" - ] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "filter by name/given (OR filter)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { + operator => "OR", + conditions => + [ { 'name/given' => "given2" }, { 'name/given' => "given3" } ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by email"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { email => "example.com" } - }, - "R1" - ] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + xlog $self, "filter by text"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { text => "some" } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by hobby"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { hobby => "reading" } - }, - "R1" - ] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + xlog $self, "filter by nickName"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { nickName => "nick1" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - xlog $self, "filter by note"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { note => "hello" } - }, - "R1" - ] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + xlog $self, "filter by email"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { email => "example.com" } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by inCardGroup"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { inCardGroup => [ $group1, $group3 ] } - }, - "R1" - ] ]); - $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + xlog $self, "filter by hobby"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { hobby => "reading" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by inCardGroup and name/given"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - accountId => 'manifold', - filter => { - inCardGroup => [ $group1, $group3 ], - 'name/given' => "given1" - } - }, - "R1" - ] ]); - $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + xlog $self, "filter by note"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { note => "hello" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); - xlog $self, "sort by name/given"; - $res = $jmap->CallMethods([ [ - 'ContactCard/query', - { - filter => { name => "last" }, - accountId => 'manifold', - sort => [ { property => "name/given" } ] - }, - "R1" - ] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_deep_equals( - [ $id1, $id2, $id3, $id4 ], - $res->[0][1]{ids} - ); + xlog $self, "filter by inCardGroup"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { inCardGroup => [ $group1, $group3 ] } + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by inCardGroup and name/given"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + accountId => 'manifold', + filter => { + inCardGroup => [ $group1, $group3 ], + 'name/given' => "given1" + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + + xlog $self, "sort by name/given"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { name => "last" }, + accountId => 'manifold', + sort => [ { property => "name/given" } ] + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_deep_equals([ $id1, $id2, $id3, $id4 ], $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_query_text b/cassandane/tiny-tests/JMAPContacts/card_query_text index 6a7795ff58..65829cc097 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_query_text +++ b/cassandane/tiny-tests/JMAPContacts/card_query_text @@ -2,141 +2,190 @@ use Cassandane::Tiny; sub test_card_query_text - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create cards"; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - card1 => { - notes => { - 'n1' => { - note => 'cats and dogs' - } - } - }, - card2 => { - notes => { - 'n1' => { - note => 'hats and bats' - } - } - }, - }, - }, 'R1'], - ]); - my $cardId1 = $res->[0][1]{created}{card1}{id}; - $self->assert_not_null($cardId1); - my $cardId2 = $res->[0][1]{created}{card2}{id}; - $self->assert_not_null($cardId2); + xlog $self, "create cards"; + my $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + create => { + card1 => { + notes => { + 'n1' => { + note => 'cats and dogs' + } + } + }, + card2 => { + notes => { + 'n1' => { + note => 'hats and bats' + } + } + }, + }, + }, + 'R1' + ], + ]); + my $cardId1 = $res->[0][1]{created}{card1}{id}; + $self->assert_not_null($cardId1); + my $cardId2 = $res->[0][1]{created}{card2}{id}; + $self->assert_not_null($cardId2); - xlog "Query with loose terms"; - $res = $jmap->CallMethods([ - ['ContactCard/query', { - filter => { - note => "cats dogs", - }, - }, 'R1'], - ['ContactCard/query', { - filter => { - operator => 'NOT', - conditions => [{ - note => 'cats dogs', - }], - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); + xlog "Query with loose terms"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/query', + { + filter => { + note => "cats dogs", + }, + }, + 'R1' + ], + [ + 'ContactCard/query', + { + filter => { + operator => 'NOT', + conditions => [ { + note => 'cats dogs', + } ], + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); - xlog "Query with phrase"; - $res = $jmap->CallMethods([ - ['ContactCard/query', { - filter => { - note => "'cats and dogs'", - }, - }, 'R1'], - ['ContactCard/query', { - filter => { - operator => 'NOT', - conditions => [{ - note => "'cats and dogs'", - }], - }, - }, 'R1'], - ]); - $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); + xlog "Query with phrase"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/query', + { + filter => { + note => "'cats and dogs'", + }, + }, + 'R1' + ], + [ + 'ContactCard/query', + { + filter => { + operator => 'NOT', + conditions => [ { + note => "'cats and dogs'", + } ], + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); - xlog "Query with both phrase and loose terms"; - $res = $jmap->CallMethods([ - ['ContactCard/query', { - filter => { - note => "cats 'cats and dogs' dogs", - }, - }, 'R1'], - ['ContactCard/query', { - filter => { - operator => 'NOT', - conditions => [{ - note => "cats 'cats and dogs' dogs", - }], - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); + xlog "Query with both phrase and loose terms"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/query', + { + filter => { + note => "cats 'cats and dogs' dogs", + }, + }, + 'R1' + ], + [ + 'ContactCard/query', + { + filter => { + operator => 'NOT', + conditions => [ { + note => "cats 'cats and dogs' dogs", + } ], + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); - xlog "Query text"; - $res = $jmap->CallMethods([ - ['ContactCard/query', { - filter => { - text => "cats dogs", - }, - }, 'R1'], - ['ContactCard/query', { - filter => { - operator => 'NOT', - conditions => [{ - text => "cats dogs", - }], - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); + xlog "Query text"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/query', + { + filter => { + text => "cats dogs", + }, + }, + 'R1' + ], + [ + 'ContactCard/query', + { + filter => { + operator => 'NOT', + conditions => [ { + text => "cats dogs", + } ], + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); - xlog "Query text and notes"; - $res = $jmap->CallMethods([ - ['ContactCard/query', { - filter => { - operator => 'AND', - conditions => [{ - text => "cats", - }, { - note => "dogs", - }], + xlog "Query text and notes"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/query', + { + filter => { + operator => 'AND', + conditions => [ + { + text => "cats", }, - }, 'R1'], - ['ContactCard/query', { + { + note => "dogs", + } + ], + }, + }, + 'R1' + ], + [ + 'ContactCard/query', + { - filter => { - operator => 'NOT', - conditions => [{ - operator => 'AND', - conditions => [{ - text => "cats", - }, { - note => "dogs", - }], - }], - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); + filter => { + operator => 'NOT', + conditions => [ { + operator => 'AND', + conditions => [ + { + text => "cats", + }, + { + note => "dogs", + } + ], + } ], + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$cardId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$cardId2], $res->[1][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_query_windowing b/cassandane/tiny-tests/JMAPContacts/card_query_windowing index eef73fdc44..01b52d3f77 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_query_windowing +++ b/cassandane/tiny-tests/JMAPContacts/card_query_windowing @@ -2,124 +2,127 @@ use Cassandane::Tiny; sub test_card_query_windowing - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create cards"; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - card1 => { - uid => 'XXX-UID-1', - organizations => { - 'o1' => { - name => 'companyB' - } - } - }, - card2 => { - uid => 'XXX-UID-2', - organizations => { - 'o1' => { - name => 'companyA' - } - } - }, - card3 => { - uid => 'XXX-UID-3', - organizations => { - 'o1' => { - name => 'companyB' - } - } - }, - card4 => { - uid => 'XXX-UID-4', - organizations => { - 'o1' => { - name => 'companyC' - } - } - }, - }, - }, 'R1'], - ]); - my $cardId1 = $res->[0][1]{created}{card1}{id}; - $self->assert_not_null($cardId1); + xlog $self, "create cards"; + my $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + create => { + card1 => { + uid => 'XXX-UID-1', + organizations => { + 'o1' => { + name => 'companyB' + } + } + }, + card2 => { + uid => 'XXX-UID-2', + organizations => { + 'o1' => { + name => 'companyA' + } + } + }, + card3 => { + uid => 'XXX-UID-3', + organizations => { + 'o1' => { + name => 'companyB' + } + } + }, + card4 => { + uid => 'XXX-UID-4', + organizations => { + 'o1' => { + name => 'companyC' + } + } + }, + }, + }, + 'R1' + ], + ]); + my $cardId1 = $res->[0][1]{created}{card1}{id}; + $self->assert_not_null($cardId1); - my $cardId2 = $res->[0][1]{created}{card2}{id}; - $self->assert_not_null($cardId2); + my $cardId2 = $res->[0][1]{created}{card2}{id}; + $self->assert_not_null($cardId2); - my $cardId3 = $res->[0][1]{created}{card3}{id}; - $self->assert_not_null($cardId3); + my $cardId3 = $res->[0][1]{created}{card3}{id}; + $self->assert_not_null($cardId3); - my $cardId4 = $res->[0][1]{created}{card4}{id}; - $self->assert_not_null($cardId4); + my $cardId4 = $res->[0][1]{created}{card4}{id}; + $self->assert_not_null($cardId4); - xlog $self, "run query with windowing"; - $res = $jmap->CallMethods([ - ['ContactCard/query', { - sort => [{ - property => 'uid', - }], - limit => 2, - }, 'R1'], - ['ContactCard/query', { - sort => [{ - property => 'uid', - }], - limit => 2, - position => 2, - }, 'R2'], - ['ContactCard/query', { - sort => [{ - property => 'uid', - }], - anchor => $cardId3, - anchorOffset => -1, - limit => 2, - }, 'R3'], - ['ContactCard/query', { - sort => [{ - property => 'uid', - }], - limit => 2, - position => -2, - }, 'R4'], - ]); - # Request 1 - $self->assert_deep_equals([ - $cardId1, - $cardId2, - ], $res->[0][1]{ids} - ); - $self->assert_num_equals(0, $res->[0][1]{position}); - $self->assert_num_equals(4, $res->[0][1]{total}); - # Request 2 - $self->assert_deep_equals([ - $cardId3, - $cardId4, - ], $res->[1][1]{ids} - ); - $self->assert_num_equals(2, $res->[1][1]{position}); - $self->assert_num_equals(4, $res->[1][1]{total}); - # Request 3 - $self->assert_deep_equals([ - $cardId2, - $cardId3, - ], $res->[2][1]{ids} - ); - $self->assert_num_equals(1, $res->[2][1]{position}); - $self->assert_num_equals(4, $res->[2][1]{total}); - # Request 4 - $self->assert_deep_equals([ - $cardId3, - $cardId4, - ], $res->[3][1]{ids} - ); - $self->assert_num_equals(2, $res->[3][1]{position}); - $self->assert_num_equals(4, $res->[3][1]{total}); + xlog $self, "run query with windowing"; + $res = $jmap->CallMethods([ + [ + 'ContactCard/query', + { + sort => [ { + property => 'uid', + } ], + limit => 2, + }, + 'R1' + ], + [ + 'ContactCard/query', + { + sort => [ { + property => 'uid', + } ], + limit => 2, + position => 2, + }, + 'R2' + ], + [ + 'ContactCard/query', + { + sort => [ { + property => 'uid', + } ], + anchor => $cardId3, + anchorOffset => -1, + limit => 2, + }, + 'R3' + ], + [ + 'ContactCard/query', + { + sort => [ { + property => 'uid', + } ], + limit => 2, + position => -2, + }, + 'R4' + ], + ]); + # Request 1 + $self->assert_deep_equals([ $cardId1, $cardId2, ], $res->[0][1]{ids}); + $self->assert_num_equals(0, $res->[0][1]{position}); + $self->assert_num_equals(4, $res->[0][1]{total}); + # Request 2 + $self->assert_deep_equals([ $cardId3, $cardId4, ], $res->[1][1]{ids}); + $self->assert_num_equals(2, $res->[1][1]{position}); + $self->assert_num_equals(4, $res->[1][1]{total}); + # Request 3 + $self->assert_deep_equals([ $cardId2, $cardId3, ], $res->[2][1]{ids}); + $self->assert_num_equals(1, $res->[2][1]{position}); + $self->assert_num_equals(4, $res->[2][1]{total}); + # Request 4 + $self->assert_deep_equals([ $cardId3, $cardId4, ], $res->[3][1]{ids}); + $self->assert_num_equals(2, $res->[3][1]{position}); + $self->assert_num_equals(4, $res->[3][1]{total}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_addresses b/cassandane/tiny-tests/JMAPContacts/card_set_create_addresses index fdaa5cf6ba..b532c1b861 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_addresses +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_addresses @@ -2,132 +2,139 @@ use Cassandane::Tiny; sub test_card_set_create_addresses - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'John Doe' }, - addresses => { - k23 => { - '@type' => 'Address', - contexts => { - work => JSON::true - }, - full => "54321 Oak St\nReston\nVA\n20190\nUSA", - isOrdered => JSON::true, - defaultSeparator => "\n", - components => [ - { - kind => 'number', - value => '54321' - }, - { - kind => 'separator', - value => " " - }, - { - kind => 'name', - value => 'Oak St' - }, - { - kind => 'locality', - value => 'Reston' - }, - { - kind => 'separator', - value => ', ' - }, - { - kind => 'region', - value => 'VA' - }, - { - kind => 'postcode', - value => '20190' - }, - { - kind => 'country', - value => 'USA' - } - ], - countryCode => 'US' - }, - k24 => { - contexts => { - private => JSON::true - }, - full => "12345 Elm St\nReston\nVA\n20190\nUSA", - isOrdered => JSON::false, - components => [ - { - kind => 'number', - value => '12345' - }, - { - '@type' => 'Address', - kind => 'name', - value => 'Elm St' - }, - { - kind => 'locality', - value => 'Reston' - }, - { - kind => 'region', - value => 'VA' - }, - { - kind => 'postcode', - value => '20190' - }, - { - kind => 'country', - value => 'USA' - } - ], - countryCode => 'US', - timeZone => 'America/New_York' - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'John Doe' }, + addresses => { + k23 => { + '@type' => 'Address', + contexts => { + work => JSON::true + }, + full => "54321 Oak St\nReston\nVA\n20190\nUSA", + isOrdered => JSON::true, + defaultSeparator => "\n", + components => [ + { + kind => 'number', + value => '54321' + }, + { + kind => 'separator', + value => " " + }, + { + kind => 'name', + value => 'Oak St' + }, + { + kind => 'locality', + value => 'Reston' + }, + { + kind => 'separator', + value => ', ' + }, + { + kind => 'region', + value => 'VA' + }, + { + kind => 'postcode', + value => '20190' + }, + { + kind => 'country', + value => 'USA' } + ], + countryCode => 'US' + }, + k24 => { + contexts => { + private => JSON::true + }, + full => "12345 Elm St\nReston\nVA\n20190\nUSA", + isOrdered => JSON::false, + components => [ + { + kind => 'number', + value => '12345' + }, + { + '@type' => 'Address', + kind => 'name', + value => 'Elm St' + }, + { + kind => 'locality', + value => 'Reston' + }, + { + kind => 'region', + value => 'VA' + }, + { + kind => 'postcode', + value => '20190' + }, + { + kind => 'country', + value => 'USA' + } + ], + countryCode => 'US', + timeZone => 'America/New_York' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); - $self->assert_not_null($res->[0][1]{created}{1}{created}); + $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}{created}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/ADR;JSCOMPS="s,\^n;10;s, ;11;3;s,\\, ;4;5;6";PROP-ID=k23;TYPE=WORK;CC=US;LABEL=54321 Oak St\^nReston\^nVA\^n20190\^nUSA:;;54321,Oak St;Reston;VA;20190;USA;;;;54321;Oak St;;;;;;/, $card); - $self->assert_matches(qr/ADR;PROP-ID=k24;TYPE=HOME;TZ=America\/New_York;CC=US;LABEL=12345 Elm St\^nReston\^nVA\^n20190\^nUSA:;;12345,Elm St;Reston;VA;20190;USA;;;;12345;Elm St;;;;;;/, $card); - $self->assert_matches(qr/CREATED:/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr/ADR;JSCOMPS="s,\^n;10;s, ;11;3;s,\\, ;4;5;6";PROP-ID=k23;TYPE=WORK;CC=US;LABEL=54321 Oak St\^nReston\^nVA\^n20190\^nUSA:;;54321,Oak St;Reston;VA;20190;USA;;;;54321;Oak St;;;;;;/, + $card + ); + $self->assert_matches( + qr/ADR;PROP-ID=k24;TYPE=HOME;TZ=America\/New_York;CC=US;LABEL=12345 Elm St\^nReston\^nVA\^n20190\^nUSA:;;12345,Elm St;Reston;VA;20190;USA;;;;12345;Elm St;;;;;;/, + $card + ); + $self->assert_matches(qr/CREATED:/, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_anniversaries b/cassandane/tiny-tests/JMAPContacts/card_set_create_anniversaries index b45bc49125..3980744529 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_anniversaries +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_anniversaries @@ -2,85 +2,89 @@ use Cassandane::Tiny; sub test_card_set_create_anniversaries - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - anniversaries => { - k8 => { - '@type' => 'Anniversary', - kind => 'birth', - date => { - year => 1953, - month => 4, - day => 15 - } - }, - k9 => { - '@type' => 'Anniversary', - kind => 'death', - date => { - '@type' => 'Timestamp', - utc => '2019-10-15T23:10:00Z' - }, - place => { - '@type' => 'Address', - full => - '4445 Tree Street\nNew England, ND 58647\nUSA' - } - }, - k10 => { - '@type' => 'Anniversary', - kind => 'wedding', - date => { - '@type' => 'PartialDate', - year => 1975 - }, - place => { - full => 'Somewhere' - } - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + anniversaries => { + k8 => { + '@type' => 'Anniversary', + kind => 'birth', + date => { + year => 1953, + month => 4, + day => 15 + } + }, + k9 => { + '@type' => 'Anniversary', + kind => 'death', + date => { + '@type' => 'Timestamp', + utc => '2019-10-15T23:10:00Z' + }, + place => { + '@type' => 'Address', + full => '4445 Tree Street\nNew England, ND 58647\nUSA' + } + }, + k10 => { + '@type' => 'Anniversary', + kind => 'wedding', + date => { + '@type' => 'PartialDate', + year => 1975 + }, + place => { + full => 'Somewhere' + } } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|BDAY;VALUE=DATE;PROP-ID=k8:19530415|, $card); - $self->assert_matches(qr|DEATHDATE;VALUE=TIMESTAMP;PROP-ID=k9:20191015T231000Z|, $card); - $self->assert_matches(qr|DEATHPLACE;PROP-ID=k9:4445 Tree Street\\nNew England\\, ND 58647\\nUSA|, $card); - $self->assert_matches(qr|ANNIVERSARY;VALUE=DATE;PROP-ID=k10:1975|, $card); - $self->assert_matches(qr|JSPROP;JSPTR=anniversaries/k10/place:|, $card); + $self->assert_matches(qr|BDAY;VALUE=DATE;PROP-ID=k8:19530415|, $card); + $self->assert_matches( + qr|DEATHDATE;VALUE=TIMESTAMP;PROP-ID=k9:20191015T231000Z|, $card); + $self->assert_matches( + qr|DEATHPLACE;PROP-ID=k9:4445 Tree Street\\nNew England\\, ND 58647\\nUSA|, + $card + ); + $self->assert_matches(qr|ANNIVERSARY;VALUE=DATE;PROP-ID=k10:1975|, $card); + $self->assert_matches(qr|JSPROP;JSPTR=anniversaries/k10/place:|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_basic b/cassandane/tiny-tests/JMAPContacts/card_set_create_basic index 5282cc0fc2..1461119b53 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_basic +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_basic @@ -2,74 +2,81 @@ use Cassandane::Tiny; sub test_card_set_create_basic - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "create addressbook"; - my $res = $jmap->CallMethods([ - ['AddressBook/set', { create => { "1" => { - name => "foo" - }}}, "R1"] - ]); + xlog $self, "create addressbook"; + my $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + create => { + "1" => { + name => "foo" + } + } + }, + "R1" + ] ]); - my $abookid = $res->[0][1]{created}{"1"}{id}; + my $abookid = $res->[0][1]{created}{"1"}{id}; - my $now = DateTime->now(); - my $created = $now->strftime('%Y-%m-%dT%H:%M:%SZ'); - my $prodid = '-//Example Corp.//CardDAV Client//EN'; - my $name = 'Mr. John Q. Public, Esq.'; - my $id = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $now = DateTime->now(); + my $created = $now->strftime('%Y-%m-%dT%H:%M:%SZ'); + my $prodid = '-//Example Corp.//CardDAV Client//EN'; + my $name = 'Mr. John Q. Public, Esq.'; + my $id = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - addressBookId => $abookid, - uid => $id, - prodId => $prodid, - kind => 'individual', - created => $created, - name => { full => $name } - } - } - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + addressBookId => $abookid, + uid => $id, + prodId => $prodid, + kind => 'individual', + created => $created, + name => { full => $name } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); - $self->assert_not_null($res->[0][1]{created}{1}{id}); + $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}{id}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $name =~ s/,/\\\\,/gs; # escape commas + $name =~ s/,/\\\\,/gs; # escape commas - $created = $now->strftime('%Y%m%dT%H%M%SZ'); # vCard doesn't use separators + $created = $now->strftime('%Y%m%dT%H%M%SZ'); # vCard doesn't use separators - $self->assert_matches(qr/VERSION:4.0/, $card); - $self->assert_matches(qr/KIND:INDIVIDUAL/, $card); - $self->assert_matches(qr/UID:$id/, $card); - $self->assert_matches(qr/PRODID:$prodid/, $card); - $self->assert_matches(qr/CREATED:$created/, $card); - $self->assert_matches(qr/FN:$name/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches(qr/VERSION:4.0/, $card); + $self->assert_matches(qr/KIND:INDIVIDUAL/, $card); + $self->assert_matches(qr/UID:$id/, $card); + $self->assert_matches(qr/PRODID:$prodid/, $card); + $self->assert_matches(qr/CREATED:$created/, $card); + $self->assert_matches(qr/FN:$name/, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_calendars b/cassandane/tiny-tests/JMAPContacts/card_set_create_calendars index 04b1778ef6..2d29c4be47 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_calendars +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_calendars @@ -2,76 +2,86 @@ use Cassandane::Tiny; sub test_card_set_create_calendars - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - calendars => { - 'CAL-1' => { - '@type' => 'CalendarResource', - kind => 'calendar', - uri => 'https://cal.example.com/calA', - pref => 1 - }, - 'CAL-2' => { - '@type' => 'CalendarResource', - kind => 'calendar', - uri => 'https://ftp.example.com/calA.ics', - mediaType => 'text/calendar' - }, - 'FBURL-1' => { - '@type' => 'CalendarResource', - kind => 'freeBusy', - uri => 'https://www.example.com/busy/janedoe', - pref => 1 - }, - 'FBURL-2' => { - '@type' => 'CalendarResource', - kind => 'freeBusy', - uri => 'https://example.com/busy/project-a.ifb', - mediaType => 'text/calendar' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + calendars => { + 'CAL-1' => { + '@type' => 'CalendarResource', + kind => 'calendar', + uri => 'https://cal.example.com/calA', + pref => 1 + }, + 'CAL-2' => { + '@type' => 'CalendarResource', + kind => 'calendar', + uri => 'https://ftp.example.com/calA.ics', + mediaType => 'text/calendar' + }, + 'FBURL-1' => { + '@type' => 'CalendarResource', + kind => 'freeBusy', + uri => 'https://www.example.com/busy/janedoe', + pref => 1 + }, + 'FBURL-2' => { + '@type' => 'CalendarResource', + kind => 'freeBusy', + uri => 'https://example.com/busy/project-a.ifb', + mediaType => 'text/calendar' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|CALURI;PROP-ID=CAL-1;PREF=1:https://cal.example.com/calA|, $card); - $self->assert_matches(qr|CALURI;PROP-ID=CAL-2;MEDIATYPE=text/calendar:https://ftp.example.com/calA.ics|, $card); - $self->assert_matches(qr|FBURL;PROP-ID=FBURL-1;PREF=1:https://www.example.com/busy/janedoe|, $card); - $self->assert_matches(qr|FBURL;PROP-ID=FBURL-2;MEDIATYPE=text/calendar:https://example.com/busy/project-a.ifb|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr|CALURI;PROP-ID=CAL-1;PREF=1:https://cal.example.com/calA|, $card); + $self->assert_matches( + qr|CALURI;PROP-ID=CAL-2;MEDIATYPE=text/calendar:https://ftp.example.com/calA.ics|, + $card + ); + $self->assert_matches( + qr|FBURL;PROP-ID=FBURL-1;PREF=1:https://www.example.com/busy/janedoe|, + $card); + $self->assert_matches( + qr|FBURL;PROP-ID=FBURL-2;MEDIATYPE=text/calendar:https://example.com/busy/project-a.ifb|, + $card + ); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_cryptokeys b/cassandane/tiny-tests/JMAPContacts/card_set_create_cryptokeys index 1041e6603b..4298d16d9d 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_cryptokeys +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_cryptokeys @@ -2,59 +2,63 @@ use Cassandane::Tiny; sub test_card_set_create_cryptokeys - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $key = 'data:,-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEA+xGZ/wcz9ugFpP07Nspo6U17l0YhFiFpxxU4pTk3Lifz9R3zsIsu\nERwta7+fWIfxOo208ett/jhskiVodSEt3QBGh4XBipyWopKwZ93HHaDVZAALi/2A\n+xTBtWdEo7XGUujKDvC2/aZKukfjpOiUI8AhLAfjmlcD/UZ1QPh0mHsglRNCmpCw\nmwSXA9VNmhz+PiB+Dml4WWnKW/VHo2ujTXxq7+efMU4H2fny3Se3KYOsFPFGZ1TN\nQSYlFuShWrHPtiLmUdPoP6CV2mML1tk+l7DIIqXrQhLUKDACeM5roMx0kLhUWB8P\n+0uj1CNlNN4JRZlC7xFfqiMbFRU9Z4N6YwIDAQAB\n-----END RSA PUBLIC KEY-----'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - cryptoKeys => { - mykey1 => { - '@type' => 'CryptoResource', - uri => 'https://www.example.com/keys/jdoe.cer' - }, - mykey2 => { - '@type' => 'CryptoResource', - uri => $key - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $key + = 'data:,-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEA+xGZ/wcz9ugFpP07Nspo6U17l0YhFiFpxxU4pTk3Lifz9R3zsIsu\nERwta7+fWIfxOo208ett/jhskiVodSEt3QBGh4XBipyWopKwZ93HHaDVZAALi/2A\n+xTBtWdEo7XGUujKDvC2/aZKukfjpOiUI8AhLAfjmlcD/UZ1QPh0mHsglRNCmpCw\nmwSXA9VNmhz+PiB+Dml4WWnKW/VHo2ujTXxq7+efMU4H2fny3Se3KYOsFPFGZ1TN\nQSYlFuShWrHPtiLmUdPoP6CV2mML1tk+l7DIIqXrQhLUKDACeM5roMx0kLhUWB8P\n+0uj1CNlNN4JRZlC7xFfqiMbFRU9Z4N6YwIDAQAB\n-----END RSA PUBLIC KEY-----'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + cryptoKeys => { + mykey1 => { + '@type' => 'CryptoResource', + uri => 'https://www.example.com/keys/jdoe.cer' + }, + mykey2 => { + '@type' => 'CryptoResource', + uri => $key } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|KEY(;VALUE=URI)?;PROP-ID=mykey1:https://www.example.com/keys/jdoe.cer|, $card); - $self->assert_matches(qr|KEY;PROP-ID=mykey2:data:,|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr|KEY(;VALUE=URI)?;PROP-ID=mykey1:https://www.example.com/keys/jdoe.cer|, + $card); + $self->assert_matches(qr|KEY;PROP-ID=mykey2:data:,|, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_directories b/cassandane/tiny-tests/JMAPContacts/card_set_create_directories index 5a0a85c91f..1dfb003b42 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_directories +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_directories @@ -2,68 +2,78 @@ use Cassandane::Tiny; sub test_card_set_create_directories - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - directories => { - 'DIRECTORY-1' => { - '@type' => 'DirectoryResource', - kind => 'directory', - uri => 'https://directory.mycompany.example.com', - listAs => 1 - }, - 'DIRECTORY-2' => { - '@type' => 'DirectoryResource', - kind => 'directory', - uri => 'ldap://ldap.tech.example/o=Tech,ou=Engineering', - pref => 1 - }, - 'ENTRY-1' => { - '@type' => 'DirectoryResource', - kind => 'entry', - uri => 'https://dir.example.com/addrbook/jdoe/Jean%20Dupont.vcf' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + directories => { + 'DIRECTORY-1' => { + '@type' => 'DirectoryResource', + kind => 'directory', + uri => 'https://directory.mycompany.example.com', + listAs => 1 + }, + 'DIRECTORY-2' => { + '@type' => 'DirectoryResource', + kind => 'directory', + uri => 'ldap://ldap.tech.example/o=Tech,ou=Engineering', + pref => 1 + }, + 'ENTRY-1' => { + '@type' => 'DirectoryResource', + kind => 'entry', + uri => 'https://dir.example.com/addrbook/jdoe/Jean%20Dupont.vcf' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|ORG-DIRECTORY;PROP-ID=DIRECTORY-1;INDEX=1:https://directory.mycompany.example.com|, $card); - $self->assert_matches(qr|ORG-DIRECTORY;PROP-ID=DIRECTORY-2;PREF=1:ldap://ldap.tech.example/o=Tech,ou=Engineering|, $card); - $self->assert_matches(qr|SOURCE;PROP-ID=ENTRY-1:https://dir.example.com/addrbook/jdoe/Jean%20Dupont.vcf|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr|ORG-DIRECTORY;PROP-ID=DIRECTORY-1;INDEX=1:https://directory.mycompany.example.com|, + $card + ); + $self->assert_matches( + qr|ORG-DIRECTORY;PROP-ID=DIRECTORY-2;PREF=1:ldap://ldap.tech.example/o=Tech,ou=Engineering|, + $card + ); + $self->assert_matches( + qr|SOURCE;PROP-ID=ENTRY-1:https://dir.example.com/addrbook/jdoe/Jean%20Dupont.vcf|, + $card + ); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_emails b/cassandane/tiny-tests/JMAPContacts/card_set_create_emails index bce1653c97..52e79afff2 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_emails +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_emails @@ -2,61 +2,64 @@ use Cassandane::Tiny; sub test_card_set_create_emails - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - emails => { - e1 => { - contexts => { - work => JSON::true - }, - address => 'jqpublic@xyz.example.com' - }, - e2 => { - '@type' => 'EmailAddress', - address => 'jane_doe@example.com', - pref => 1 - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + emails => { + e1 => { + contexts => { + work => JSON::true + }, + address => 'jqpublic@xyz.example.com' + }, + e2 => { + '@type' => 'EmailAddress', + address => 'jane_doe@example.com', + pref => 1 } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/EMAIL;PROP-ID=e1;TYPE=WORK:jqpublic\@xyz.example.com/, $card); - $self->assert_matches(qr/EMAIL;PROP-ID=e2;PREF=1:jane_doe\@example.com/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr/EMAIL;PROP-ID=e1;TYPE=WORK:jqpublic\@xyz.example.com/, $card); + $self->assert_matches(qr/EMAIL;PROP-ID=e2;PREF=1:jane_doe\@example.com/, + $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_extra_rejected b/cassandane/tiny-tests/JMAPContacts/card_set_create_extra_rejected index 8026f302b7..d8a98b8dca 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_extra_rejected +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_extra_rejected @@ -2,41 +2,41 @@ use Cassandane::Tiny; sub test_card_set_create_extra_rejected - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - card1 => { - '@type' => 'Card', - name => { - full => 'John', - extra => 'reserved', - }, - extra => 'reserved', - localizations => { - de => { - 'name/extra' => 'reserved2', - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + create => { + card1 => { + '@type' => 'Card', + name => { + full => 'John', + extra => 'reserved', }, - }, 'R1'], - ]); + extra => 'reserved', + localizations => { + de => { + 'name/extra' => 'reserved2', + }, + }, + }, + }, + }, + 'R1' + ], + ]); - $self->assert_null($res->[0][1]{created}{card1}); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notCreated}{card1}{type}); + $self->assert_null($res->[0][1]{created}{card1}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{card1}{type}); - my @wantInvalidProps = ( - "extra", - "localizations/de/name~1extra", - "name/extra", - ); - my @haveInvalidProps = sort @{$res->[0][1]{notCreated}{card1}{properties}}; - $self->assert_deep_equals(\@wantInvalidProps, \@haveInvalidProps); + my @wantInvalidProps + = ("extra", "localizations/de/name~1extra", "name/extra",); + my @haveInvalidProps = sort @{ $res->[0][1]{notCreated}{card1}{properties} }; + $self->assert_deep_equals(\@wantInvalidProps, \@haveInvalidProps); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_keywords b/cassandane/tiny-tests/JMAPContacts/card_set_create_keywords index 07a910a849..81ba314b8c 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_keywords +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_keywords @@ -2,48 +2,49 @@ use Cassandane::Tiny; sub test_card_set_create_keywords - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - keywords => { 'foo' => JSON::true } - } - } - }, 'R1'] - ]); - - $self->assert_not_null($res->[0][1]{created}{1}); - - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/CATEGORIES:foo/, $card); - $self->assert_does_not_match(qr/JSPROP/, $card); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + keywords => { 'foo' => JSON::true } + } + } + }, + 'R1' + ] ]); + + $self->assert_not_null($res->[0][1]{created}{1}); + + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/CATEGORIES:foo/, $card); + $self->assert_does_not_match(qr/JSPROP/, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_links b/cassandane/tiny-tests/JMAPContacts/card_set_create_links index 98465a2771..263a5ddaab 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_links +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_links @@ -2,60 +2,66 @@ use Cassandane::Tiny; sub test_card_set_create_links - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - links => { - 'CONTACT-1' => { - '@type' => 'LinkResource', - kind => 'contact', - uri => 'mailto:contact@example.com', - pref => 1 - }, - 'LINK-1' => { - '@type' => 'LinkResource', - uri => 'https://example.org/restaurant.french/~chezchic.html' - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + links => { + 'CONTACT-1' => { + '@type' => 'LinkResource', + kind => 'contact', + uri => 'mailto:contact@example.com', + pref => 1 + }, + 'LINK-1' => { + '@type' => 'LinkResource', + uri => 'https://example.org/restaurant.french/~chezchic.html' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|URL;PROP-ID=LINK-1:https://example.org/restaurant.french/~chezchic.html|, $card); - $self->assert_matches(qr|CONTACT-URI;PROP-ID=CONTACT-1;PREF=1:mailto:contact\@example.com|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr|URL;PROP-ID=LINK-1:https://example.org/restaurant.french/~chezchic.html|, + $card + ); + $self->assert_matches( + qr|CONTACT-URI;PROP-ID=CONTACT-1;PREF=1:mailto:contact\@example.com|, + $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_localizations b/cassandane/tiny-tests/JMAPContacts/card_set_create_localizations index c9eb3ddc45..34f9cd8d0b 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_localizations +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_localizations @@ -3,152 +3,156 @@ use Cassandane::Tiny; use utf8; sub test_card_set_create_localizations - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - language => 'es', - name => { - '@type' => 'Name', - full => 'Gabriel García Márquez', - isOrdered => JSON::false, - components => [ - { - '@type' => 'Name', - kind => 'given', - value => 'Gabriel' - }, - { - '@type' => 'Name', - kind => 'given2', - value => 'García' - }, - { - '@type' => 'Name', - kind => 'surname', - value => 'Márquez' - } - ] - }, - addresses => { - addr1 => { - '@type' => 'Address', - isOrdered => JSON::false, - components => [ - { kind => 'locality', value => 'Tokio' } - ] - } - }, - speakToAs => { - '@type' => 'SpeakToAs', - grammaticalGender => 'neuter', - pronouns => { - k19 => { - '@type' => 'Pronouns', - pronouns => 'él', - } - } - }, - localizations => { - en => { - titles => { - t1 => { - '@type' => 'Title', - name => 'Novelist' - } - }, - 'addresses/addr1/components/0/value' => 'Tokyo', - 'speakToAs/grammaticalGender' => 'masculine' - }, - de => { - 'speakToAs/pronouns/k19/pronouns' => 'er' - }, - it => { - 'speakToAs/pronouns/k19/pronouns' => 'lui' - }, - fr => { - titles => { - t1 => { - '@type' => 'Title', - name => 'Écrivain' - } - }, - speakToAs => { - '@type' => 'SpeakToAs', - pronouns => { - k19 => { - '@type' => 'Pronouns', - pronouns => 'il', - } - } - } - }, - es => { - titles => { - t1 => { - '@type' => 'Title', - name => 'Novelista' - } - } - }, - jp => { - 'name/full' => 'ガブリエル・ガルシア・マルケス', - 'name/components/0/value' => 'ガブリエル', - 'name/components/1/value' => 'ガルシア', - 'name/components/2/value' => 'マルケス', - 'addresses/addr1/components/0/value' => '東京' - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + language => 'es', + name => { + '@type' => 'Name', + full => 'Gabriel García Márquez', + isOrdered => JSON::false, + components => [ + { + '@type' => 'Name', + kind => 'given', + value => 'Gabriel' + }, + { + '@type' => 'Name', + kind => 'given2', + value => 'García' + }, + { + '@type' => 'Name', + kind => 'surname', + value => 'Márquez' + } + ] + }, + addresses => { + addr1 => { + '@type' => 'Address', + isOrdered => JSON::false, + components => [ { kind => 'locality', value => 'Tokio' } ] + } + }, + speakToAs => { + '@type' => 'SpeakToAs', + grammaticalGender => 'neuter', + pronouns => { + k19 => { + '@type' => 'Pronouns', + pronouns => 'él', + } + } + }, + localizations => { + en => { + titles => { + t1 => { + '@type' => 'Title', + name => 'Novelist' + } + }, + 'addresses/addr1/components/0/value' => 'Tokyo', + 'speakToAs/grammaticalGender' => 'masculine' + }, + de => { + 'speakToAs/pronouns/k19/pronouns' => 'er' + }, + it => { + 'speakToAs/pronouns/k19/pronouns' => 'lui' + }, + fr => { + titles => { + t1 => { + '@type' => 'Title', + name => 'Écrivain' + } + }, + speakToAs => { + '@type' => 'SpeakToAs', + pronouns => { + k19 => { + '@type' => 'Pronouns', + pronouns => 'il', + } + } + } + }, + es => { + titles => { + t1 => { + '@type' => 'Title', + name => 'Novelista' } + } + }, + jp => { + 'name/full' => 'ガブリエル・ガルシア・マルケス', + 'name/components/0/value' => 'ガブリエル', + 'name/components/1/value' => 'ガルシア', + 'name/components/2/value' => 'マルケス', + 'addresses/addr1/components/0/value' => '東京' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/FN:Gabriel/, $card); - $self->assert_matches(qr/FN;LANGUAGE=jp:/, $card); - $self->assert_matches(qr/N;ALTID=n1:M/, $card); - $self->assert_matches(qr/N;ALTID=n1;LANGUAGE=jp:/, $card); - $self->assert_matches(qr/ADR;PROP-ID=addr1;ALTID=addr1:;;;Tokio;;;/, $card); - $self->assert_matches(qr/ADR;PROP-ID=addr1;ALTID=addr1;LANGUAGE=jp:/, $card); - $self->assert_matches(qr/ADR;PROP-ID=addr1;ALTID=addr1;LANGUAGE=en:;;;Tokyo;;;/, $card); - $self->assert_matches(qr/TITLE;PROP-ID=t1;ALTID=t1:Novelista/, $card); - $self->assert_matches(qr/TITLE;PROP-ID=t1;ALTID=t1;LANGUAGE=en:Novelist/, $card); - $self->assert_matches(qr/GRAMGENDER:NEUTER/, $card); - $self->assert_matches(qr/GRAMGENDER;LANGUAGE=en:MASCULINE/, $card); - $self->assert_matches(qr/PRONOUNS;PROP-ID=k19;ALTID=k19:/, $card); - $self->assert_matches(qr/PRONOUNS;PROP-ID=k19;ALTID=k19;LANGUAGE=fr:il/, $card); - $self->assert_matches(qr/PRONOUNS;PROP-ID=k19;ALTID=k19;LANGUAGE=de:er/, $card); - $self->assert_matches(qr/PRONOUNS;PROP-ID=k19;ALTID=k19;LANGUAGE=it:lui/, $card); - $self->assert_does_not_match(qr/JSPROP/, $card); + $self->assert_matches(qr/FN:Gabriel/, $card); + $self->assert_matches(qr/FN;LANGUAGE=jp:/, $card); + $self->assert_matches(qr/N;ALTID=n1:M/, $card); + $self->assert_matches(qr/N;ALTID=n1;LANGUAGE=jp:/, $card); + $self->assert_matches(qr/ADR;PROP-ID=addr1;ALTID=addr1:;;;Tokio;;;/, $card); + $self->assert_matches(qr/ADR;PROP-ID=addr1;ALTID=addr1;LANGUAGE=jp:/, $card); + $self->assert_matches( + qr/ADR;PROP-ID=addr1;ALTID=addr1;LANGUAGE=en:;;;Tokyo;;;/, $card); + $self->assert_matches(qr/TITLE;PROP-ID=t1;ALTID=t1:Novelista/, $card); + $self->assert_matches(qr/TITLE;PROP-ID=t1;ALTID=t1;LANGUAGE=en:Novelist/, + $card); + $self->assert_matches(qr/GRAMGENDER:NEUTER/, $card); + $self->assert_matches(qr/GRAMGENDER;LANGUAGE=en:MASCULINE/, $card); + $self->assert_matches(qr/PRONOUNS;PROP-ID=k19;ALTID=k19:/, $card); + $self->assert_matches(qr/PRONOUNS;PROP-ID=k19;ALTID=k19;LANGUAGE=fr:il/, + $card); + $self->assert_matches(qr/PRONOUNS;PROP-ID=k19;ALTID=k19;LANGUAGE=de:er/, + $card); + $self->assert_matches(qr/PRONOUNS;PROP-ID=k19;ALTID=k19;LANGUAGE=it:lui/, + $card); + $self->assert_does_not_match(qr/JSPROP/, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_localizations_bad b/cassandane/tiny-tests/JMAPContacts/card_set_create_localizations_bad index 6246a0403a..e556293f4a 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_localizations_bad +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_localizations_bad @@ -3,81 +3,83 @@ use Cassandane::Tiny; use utf8; sub test_card_set_create_localizations_bad - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - language => 'es', - name => { full => 'Gabriel García Márquez' }, - speakToAs => { - '@type' => 'SpeakToAs', - grammaticalGender => 'masculino', - pronouns => { - k19 => { - '@type' => 'Pronouns', - pronouns => 'él', - } - } - }, - localizations => { - en => { - 'foo/bar' => JSON::false - }, - de => { - 'titles/foo' => JSON::false - }, - es => { - titles => { - t1 => { - '@type' => 'Title', - title => 'Novelista' - } - } - }, - jp => { - 'name/foo/bar' => JSON::false, - 'titles/t2/title/foo' => JSON::false, - 'speakToAs/foo/bar' => JSON::false - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + language => 'es', + name => { full => 'Gabriel García Márquez' }, + speakToAs => { + '@type' => 'SpeakToAs', + grammaticalGender => 'masculino', + pronouns => { + k19 => { + '@type' => 'Pronouns', + pronouns => 'él', + } + } + }, + localizations => { + en => { + 'foo/bar' => JSON::false + }, + de => { + 'titles/foo' => JSON::false + }, + es => { + titles => { + t1 => { + '@type' => 'Title', + title => 'Novelista' } + } + }, + jp => { + 'name/foo/bar' => JSON::false, + 'titles/t2/title/foo' => JSON::false, + 'speakToAs/foo/bar' => JSON::false } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notCreated}{1}{type}); - $self->assert_num_equals(6, - scalar @{$res->[0][1]{notCreated}{1}{properties}}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{1}{type}); + $self->assert_num_equals(6, + scalar @{ $res->[0][1]{notCreated}{1}{properties} }); - my @bad_props = sort @{$res->[0][1]{notCreated}{1}{properties}}; + my @bad_props = sort @{ $res->[0][1]{notCreated}{1}{properties} }; - $self->assert_str_equals('localizations/de/titles/foo', $bad_props[0]); - $self->assert_str_equals('localizations/en/foo/bar', $bad_props[1]); - $self->assert_str_equals('localizations/es/titles/t1/name', $bad_props[2]); - $self->assert_str_equals('localizations/jp/name/foo/bar', $bad_props[3]); - $self->assert_str_equals('localizations/jp/speakToAs/foo/bar', $bad_props[4]); - $self->assert_str_equals('localizations/jp/titles/t2/title/foo', $bad_props[5]); + $self->assert_str_equals('localizations/de/titles/foo', $bad_props[0]); + $self->assert_str_equals('localizations/en/foo/bar', $bad_props[1]); + $self->assert_str_equals('localizations/es/titles/t1/name', $bad_props[2]); + $self->assert_str_equals('localizations/jp/name/foo/bar', $bad_props[3]); + $self->assert_str_equals('localizations/jp/speakToAs/foo/bar', $bad_props[4]); + $self->assert_str_equals('localizations/jp/titles/t2/title/foo', + $bad_props[5]); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_media b/cassandane/tiny-tests/JMAPContacts/card_set_create_media index a5dbb32619..e7caeafe8a 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_media +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_media @@ -2,74 +2,82 @@ use Cassandane::Tiny; sub test_card_set_create_media - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - media => { - res45 => { - '@type' => 'MediaResource', - kind => 'sound', - uri => 'CID:JOHNQ.part8.19960229T080000.xyzMail@example.com' - }, - res47 => { - '@type' => 'MediaResource', - kind => 'logo', - mediaType => 'image/jpeg', - uri => 'https://www.example.com/pub/logos/abccorp.jpg' - }, - res1 => { - '@type' => 'MediaResource', - kind => 'photo', - uri => 'data:image/jpeg;base64,c29tZSBwaG90bw==' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + media => { + res45 => { + '@type' => 'MediaResource', + kind => 'sound', + uri => 'CID:JOHNQ.part8.19960229T080000.xyzMail@example.com' + }, + res47 => { + '@type' => 'MediaResource', + kind => 'logo', + mediaType => 'image/jpeg', + uri => 'https://www.example.com/pub/logos/abccorp.jpg' + }, + res1 => { + '@type' => 'MediaResource', + kind => 'photo', + uri => 'data:image/jpeg;base64,c29tZSBwaG90bw==' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); - my $blobId = $res->[0][1]{created}{1}{media}{res1}{blobId}; - $self->assert_not_null($blobId); + $self->assert_not_null($res->[0][1]{created}{1}); + my $blobId = $res->[0][1]{created}{1}{media}{res1}{blobId}; + $self->assert_not_null($blobId); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|LOGO;PROP-ID=res47;MEDIATYPE=image/jpeg:https://www.example.com/pub/logos/abccorp.jpg|, $card); - $self->assert_matches(qr|SOUND(;VALUE=URI)?;PROP-ID=res45:CID:JOHNQ.part8.19960229T080000.xyzMail\@example.com|, $card); - $self->assert_matches(qr|PHOTO;PROP-ID=res1:data:image/jpeg;base64,c29tZSBwaG90bw==|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr|LOGO;PROP-ID=res47;MEDIATYPE=image/jpeg:https://www.example.com/pub/logos/abccorp.jpg|, + $card + ); + $self->assert_matches( + qr|SOUND(;VALUE=URI)?;PROP-ID=res45:CID:JOHNQ.part8.19960229T080000.xyzMail\@example.com|, + $card + ); + $self->assert_matches( + qr|PHOTO;PROP-ID=res1:data:image/jpeg;base64,c29tZSBwaG90bw==|, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); - $res = $jmap->Download('cassandane', $blobId); + $res = $jmap->Download('cassandane', $blobId); - $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); - $self->assert_str_equals('some photo', $res->{content}); + $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); + $self->assert_str_equals('some photo', $res->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_name b/cassandane/tiny-tests/JMAPContacts/card_set_create_name index a4a4b335c4..30df183038 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_name +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_name @@ -2,73 +2,77 @@ use Cassandane::Tiny; sub test_card_set_create_name - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { - isOrdered => JSON::true, - components => [ - { - kind => 'given', - value => 'Robert' - }, - { - kind => 'given2', - value => 'Pau' - }, - { - kind => 'surname', - value => 'Shou' - }, - { - '@type' => 'Name', - kind => 'surname2', - value => 'Chang' - } - ], - sortAs => { - surname => 'Pau Shou Chang', - given => 'Robert' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { + isOrdered => JSON::true, + components => [ + { + kind => 'given', + value => 'Robert' + }, + { + kind => 'given2', + value => 'Pau' + }, + { + kind => 'surname', + value => 'Shou' + }, + { + '@type' => 'Name', + kind => 'surname2', + value => 'Chang' + } + ], + sortAs => { + surname => 'Pau Shou Chang', + given => 'Robert' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/N;JSCOMPS=";1;2;0;5";SORT-AS=Pau Shou Chang,Robert:Shou,Chang;Robert;Pau;;;Chang;/, $card); - $self->assert_matches(qr/FN;DERIVED=TRUE:Robert Pau Shou Chang/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr/N;JSCOMPS=";1;2;0;5";SORT-AS=Pau Shou Chang,Robert:Shou,Chang;Robert;Pau;;;Chang;/, + $card + ); + $self->assert_matches(qr/FN;DERIVED=TRUE:Robert Pau Shou Chang/, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_name_bad_type b/cassandane/tiny-tests/JMAPContacts/card_set_create_name_bad_type index 2c3b53e6d0..df0d8d470c 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_name_bad_type +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_name_bad_type @@ -2,64 +2,65 @@ use Cassandane::Tiny; sub test_card_set_create_name_bad_type - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { - isOrdered => JSON::true, - components => [ - { - kind => 'given', - value => 'Robert' - }, - { - kind => 'given2', - value => 'Pau' - }, - { - kind => 'surname', - value => 'Shou' - }, - { - '@type' => 'Foo', - kind => 'surname2', - value => 'Chang' - } - ], - sortAs => { - surname => 'Pau Shou Chang', - given => 'Robert' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { + isOrdered => JSON::true, + components => [ + { + kind => 'given', + value => 'Robert' + }, + { + kind => 'given2', + value => 'Pau' + }, + { + kind => 'surname', + value => 'Shou' + }, + { + '@type' => 'Foo', + kind => 'surname2', + value => 'Chang' + } + ], + sortAs => { + surname => 'Pau Shou Chang', + given => 'Robert' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{notCreated}{1}); - $self->assert_equals("name/components[3]/\@type", - $res->[0][1]{notCreated}{1}{properties}[0]); + $self->assert_not_null($res->[0][1]{notCreated}{1}); + $self->assert_equals("name/components[3]/\@type", + $res->[0][1]{notCreated}{1}{properties}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_name_unknown_kind b/cassandane/tiny-tests/JMAPContacts/card_set_create_name_unknown_kind index 384f2d645b..4d41817815 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_name_unknown_kind +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_name_unknown_kind @@ -2,73 +2,74 @@ use Cassandane::Tiny; sub test_card_set_create_name_unknown_kind - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { - isOrdered => JSON::true, - components => [ - { - kind => 'given', - value => 'Robert' - }, - { - kind => 'middle', - value => 'Pau' - }, - { - kind => 'surname', - value => 'Shou' - }, - { - '@type' => 'Name', - kind => 'surname2', - value => 'Chang' - } - ], - sortAs => { - surname => 'Pau Shou Chang', - given => 'Robert' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { + isOrdered => JSON::true, + components => [ + { + kind => 'given', + value => 'Robert' + }, + { + kind => 'middle', + value => 'Pau' + }, + { + kind => 'surname', + value => 'Shou' + }, + { + '@type' => 'Name', + kind => 'surname2', + value => 'Chang' + } + ], + sortAs => { + surname => 'Pau Shou Chang', + given => 'Robert' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|JSPROP;JSPTR=name/components:|, $card); - $self->assert_matches(qr/FN;DERIVED=TRUE:No Name/, $card); - $self->assert_does_not_match(qr/\r\nN;/, $card); + $self->assert_matches(qr|JSPROP;JSPTR=name/components:|, $card); + $self->assert_matches(qr/FN;DERIVED=TRUE:No Name/, $card); + $self->assert_does_not_match(qr/\r\nN;/, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_name_unknown_prop b/cassandane/tiny-tests/JMAPContacts/card_set_create_name_unknown_prop index e602ce07e9..427c8f8284 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_name_unknown_prop +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_name_unknown_prop @@ -2,74 +2,75 @@ use Cassandane::Tiny; sub test_card_set_create_name_unknown_prop - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { - isOrdered => JSON::true, - components => [ - { - kind => 'given', - value => 'Robert', - foo => bar - }, - { - kind => 'given2', - value => 'Pau' - }, - { - kind => 'surname', - value => 'Shou' - }, - { - '@type' => 'Name', - kind => 'surname2', - value => 'Chang' - } - ], - sortAs => { - surname => 'Pau Shou Chang', - given => 'Robert' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { + isOrdered => JSON::true, + components => [ + { + kind => 'given', + value => 'Robert', + foo => bar + }, + { + kind => 'given2', + value => 'Pau' + }, + { + kind => 'surname', + value => 'Shou' + }, + { + '@type' => 'Name', + kind => 'surname2', + value => 'Chang' + } + ], + sortAs => { + surname => 'Pau Shou Chang', + given => 'Robert' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|JSPROP;JSPTR=name/components:|, $card); - $self->assert_matches(qr/FN;DERIVED=TRUE:No Name/, $card); - $self->assert_does_not_match(qr/\r\nN;/, $card); + $self->assert_matches(qr|JSPROP;JSPTR=name/components:|, $card); + $self->assert_matches(qr/FN;DERIVED=TRUE:No Name/, $card); + $self->assert_does_not_match(qr/\r\nN;/, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_nicknames b/cassandane/tiny-tests/JMAPContacts/card_set_create_nicknames index 827c7d7172..5e4a899659 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_nicknames +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_nicknames @@ -2,53 +2,54 @@ use Cassandane::Tiny; sub test_card_set_create_nicknames - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'John Doe' }, - nicknames => { - k391 => { - '@type' => 'Nickname', - name => 'Johnny' - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'John Doe' }, + nicknames => { + k391 => { + '@type' => 'Nickname', + name => 'Johnny' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/NICKNAME;PROP-ID=k391:Johnny/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches(qr/NICKNAME;PROP-ID=k391:Johnny/, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_notes b/cassandane/tiny-tests/JMAPContacts/card_set_create_notes index cbdbb1ac29..364815415d 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_notes +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_notes @@ -2,58 +2,62 @@ use Cassandane::Tiny; sub test_card_set_create_notes - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - notes => { - 'NOTE-1' => { - '@type' => 'Note', - note => 'Office hours are from 0800 to 1715 EST, Mon-Fri.', - created => '2022-11-23T15:01:32Z', - author => { - '@type' => 'Author', - name => 'John' - } - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + notes => { + 'NOTE-1' => { + '@type' => 'Note', + note => 'Office hours are from 0800 to 1715 EST, Mon-Fri.', + created => '2022-11-23T15:01:32Z', + author => { + '@type' => 'Author', + name => 'John' + } } - }, 'R1'] - ]); - - $self->assert_not_null($res->[0][1]{created}{1}); - - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr|NOTE;AUTHOR-NAME=John;PROP-ID=NOTE-1;CREATED=20221123T150132Z:Office hours are from 0800 to 1715 EST\\, Mon-Fri.|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + } + } + } + }, + 'R1' + ] ]); + + $self->assert_not_null($res->[0][1]{created}{1}); + + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches( + qr|NOTE;AUTHOR-NAME=John;PROP-ID=NOTE-1;CREATED=20221123T150132Z:Office hours are from 0800 to 1715 EST\\, Mon-Fri.|, + $card + ); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_online b/cassandane/tiny-tests/JMAPContacts/card_set_create_online index ae3fca7cb8..6eda004f88 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_online +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_online @@ -2,62 +2,67 @@ use Cassandane::Tiny; sub test_card_set_create_online - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - onlineServices => { - x1 => { - '@type' => 'OnlineService', - uri => 'xmpp:alice@example.com', - vCardName => 'impp', - pref => 1 - }, - x2 => { - '@type' => 'OnlineService', - service => 'Mastodon', - user => '@foo@example.com', - uri => 'https://example.com/@foo' - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + onlineServices => { + x1 => { + '@type' => 'OnlineService', + uri => 'xmpp:alice@example.com', + vCardName => 'impp', + pref => 1 + }, + x2 => { + '@type' => 'OnlineService', + service => 'Mastodon', + user => '@foo@example.com', + uri => 'https://example.com/@foo' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|IMPP;PROP-ID=x1;PREF=1:xmpp:alice\@example.com|, $card); - $self->assert_matches(qr|SOCIALPROFILE;SERVICE-TYPE=Mastodon;USERNAME=\@foo\@example.com;PROP-ID=x2:https://example.com/\@foo|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches(qr|IMPP;PROP-ID=x1;PREF=1:xmpp:alice\@example.com|, + $card); + $self->assert_matches( + qr|SOCIALPROFILE;SERVICE-TYPE=Mastodon;USERNAME=\@foo\@example.com;PROP-ID=x2:https://example.com/\@foo|, + $card + ); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_organizations b/cassandane/tiny-tests/JMAPContacts/card_set_create_organizations index 2d3dab1417..58905496dc 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_organizations +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_organizations @@ -2,64 +2,68 @@ use Cassandane::Tiny; sub test_card_set_create_organizations - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'John Doe' }, - organizations => { - o1 => { - '@type' => 'Organization', - name => 'ABC, Inc.', - units => [ - { - '@type' => 'OrgUnit', - name => 'North American Division' - }, - { - '@type' => 'OrgUnit', - name => 'Marketing' - } - ], - sortAs => 'ABC' - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'John Doe' }, + organizations => { + o1 => { + '@type' => 'Organization', + name => 'ABC, Inc.', + units => [ + { + '@type' => 'OrgUnit', + name => 'North American Division' + }, + { + '@type' => 'OrgUnit', + name => 'Marketing' } + ], + sortAs => 'ABC' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/ORG;SORT-AS=ABC;PROP-ID=o1:ABC\\, Inc.;North American Division;Marketing/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr/ORG;SORT-AS=ABC;PROP-ID=o1:ABC\\, Inc.;North American Division;Marketing/, + $card + ); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_personal b/cassandane/tiny-tests/JMAPContacts/card_set_create_personal index 4f41e55bd1..3951830278 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_personal +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_personal @@ -2,96 +2,105 @@ use Cassandane::Tiny; sub test_card_set_create_personal - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - personalInfo => { - 'PERSINFO-1' => { - '@type' => 'PersonalInfo', - kind => 'expertise', - value => 'chinese literature', - level => 'low', - listAs => 2 - }, - 'PERSINFO-2' => { - '@type' => 'PersonalInfo', - kind => 'expertise', - value => 'chemistry', - level => 'high', - listAs => 1 - }, - 'PERSINFO-3' => { - '@type' => 'PersonalInfo', - kind => 'hobby', - value => 'reading', - level => 'low', - listAs => 1 - }, - 'PERSINFO-4' => { - '@type' => 'PersonalInfo', - kind => 'hobby', - value => 'sewing', - level => 'high', - listAs => 2 - }, - 'PERSINFO-5' => { - '@type' => 'PersonalInfo', - kind => 'interest', - value => 'r&b music', - level => 'medium', - listAs => 1 - }, - 'PERSINFO-6' => { - '@type' => 'PersonalInfo', - kind => 'interest', - value => 'rock&roll music', - level => 'high', - listAs => 2 - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + personalInfo => { + 'PERSINFO-1' => { + '@type' => 'PersonalInfo', + kind => 'expertise', + value => 'chinese literature', + level => 'low', + listAs => 2 + }, + 'PERSINFO-2' => { + '@type' => 'PersonalInfo', + kind => 'expertise', + value => 'chemistry', + level => 'high', + listAs => 1 + }, + 'PERSINFO-3' => { + '@type' => 'PersonalInfo', + kind => 'hobby', + value => 'reading', + level => 'low', + listAs => 1 + }, + 'PERSINFO-4' => { + '@type' => 'PersonalInfo', + kind => 'hobby', + value => 'sewing', + level => 'high', + listAs => 2 + }, + 'PERSINFO-5' => { + '@type' => 'PersonalInfo', + kind => 'interest', + value => 'r&b music', + level => 'medium', + listAs => 1 + }, + 'PERSINFO-6' => { + '@type' => 'PersonalInfo', + kind => 'interest', + value => 'rock&roll music', + level => 'high', + listAs => 2 } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|EXPERTISE;PROP-ID=PERSINFO-1;LEVEL=BEGINNER;INDEX=2:chinese literature|, $card); - $self->assert_matches(qr|EXPERTISE;PROP-ID=PERSINFO-2;LEVEL=EXPERT;INDEX=1:chemistry|, $card); - $self->assert_matches(qr|HOBBY;PROP-ID=PERSINFO-3;LEVEL=LOW;INDEX=1:reading|, $card); - $self->assert_matches(qr|HOBBY;PROP-ID=PERSINFO-4;LEVEL=HIGH;INDEX=2:sewing|, $card); - $self->assert_matches(qr|INTEREST;PROP-ID=PERSINFO-5;LEVEL=MEDIUM;INDEX=1:r&b music|, $card); - $self->assert_matches(qr|INTEREST;PROP-ID=PERSINFO-6;LEVEL=HIGH;INDEX=2:rock&roll music|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr|EXPERTISE;PROP-ID=PERSINFO-1;LEVEL=BEGINNER;INDEX=2:chinese literature|, + $card + ); + $self->assert_matches( + qr|EXPERTISE;PROP-ID=PERSINFO-2;LEVEL=EXPERT;INDEX=1:chemistry|, $card); + $self->assert_matches(qr|HOBBY;PROP-ID=PERSINFO-3;LEVEL=LOW;INDEX=1:reading|, + $card); + $self->assert_matches(qr|HOBBY;PROP-ID=PERSINFO-4;LEVEL=HIGH;INDEX=2:sewing|, + $card); + $self->assert_matches( + qr|INTEREST;PROP-ID=PERSINFO-5;LEVEL=MEDIUM;INDEX=1:r&b music|, $card); + $self->assert_matches( + qr|INTEREST;PROP-ID=PERSINFO-6;LEVEL=HIGH;INDEX=2:rock&roll music|, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_phones b/cassandane/tiny-tests/JMAPContacts/card_set_create_phones index 4aa545892f..37471c8ffd 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_phones +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_phones @@ -2,70 +2,75 @@ use Cassandane::Tiny; sub test_card_set_create_phones - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - phones => { - tel0 => { - '@type' => 'Phone', - contexts => { - private => JSON::true - }, - features => { - voice => JSON::true - }, - number => 'tel:+1-555-555-5555;ext=5555', - pref => 1 - }, - tel3 => { - '@type' => 'Phone', - contexts => { - work => JSON::true - }, - number => 'tel:+1-201-555-0123', - label => 'foo' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + phones => { + tel0 => { + '@type' => 'Phone', + contexts => { + private => JSON::true + }, + features => { + voice => JSON::true + }, + number => 'tel:+1-555-555-5555;ext=5555', + pref => 1 + }, + tel3 => { + '@type' => 'Phone', + contexts => { + work => JSON::true + }, + number => 'tel:+1-201-555-0123', + label => 'foo' } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/TEL;VALUE=URI;PROP-ID=tel0;TYPE=VOICE,HOME;PREF=1:tel:\+1-555-555-5555;ext=55/, $card); - $self->assert_matches(qr/tel0.TEL;VALUE=URI;PROP-ID=tel3;TYPE=WORK:tel:\+1-201-555-0123/, $card); - $self->assert_matches(qr/tel0.X-ABLabel:foo/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr/TEL;VALUE=URI;PROP-ID=tel0;TYPE=VOICE,HOME;PREF=1:tel:\+1-555-555-5555;ext=55/, + $card + ); + $self->assert_matches( + qr/tel0.TEL;VALUE=URI;PROP-ID=tel3;TYPE=WORK:tel:\+1-201-555-0123/, $card); + $self->assert_matches(qr/tel0.X-ABLabel:foo/, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_preferred_languages b/cassandane/tiny-tests/JMAPContacts/card_set_create_preferred_languages index 6fe6534717..468fbff288 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_preferred_languages +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_preferred_languages @@ -2,67 +2,68 @@ use Cassandane::Tiny; sub test_card_set_create_preferred_languages - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - preferredLanguages => { - l1 => { - '@type' => 'LanguagePref', - language => 'en', - contexts => { work => JSON::true }, - pref => 1 - }, - l2 => { - '@type' => 'LanguagePref', - language => 'fr', - contexts => { work => JSON::true }, - pref => 2 - }, - l3 => { - language => 'fr', - contexts => { private => JSON::true } - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + preferredLanguages => { + l1 => { + '@type' => 'LanguagePref', + language => 'en', + contexts => { work => JSON::true }, + pref => 1 + }, + l2 => { + '@type' => 'LanguagePref', + language => 'fr', + contexts => { work => JSON::true }, + pref => 2 + }, + l3 => { + language => 'fr', + contexts => { private => JSON::true } } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/LANG;PROP-ID=l1;PREF=1;TYPE=WORK:en/, $card); - $self->assert_matches(qr/LANG;PROP-ID=l2;PREF=2;TYPE=WORK:fr/, $card); - $self->assert_matches(qr/LANG;PROP-ID=l3;TYPE=HOME:fr/, $card); - $self->assert_does_not_match(qr/JSPROP/, $card); + $self->assert_matches(qr/LANG;PROP-ID=l1;PREF=1;TYPE=WORK:en/, $card); + $self->assert_matches(qr/LANG;PROP-ID=l2;PREF=2;TYPE=WORK:fr/, $card); + $self->assert_matches(qr/LANG;PROP-ID=l3;TYPE=HOME:fr/, $card); + $self->assert_does_not_match(qr/JSPROP/, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_relatedto b/cassandane/tiny-tests/JMAPContacts/card_set_create_relatedto index 09fc73d117..d46bdfcaaa 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_relatedto +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_relatedto @@ -2,67 +2,73 @@ use Cassandane::Tiny; sub test_card_set_create_relatedto - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'John Doe' }, - relatedTo => { - 'urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6' => { - '@type' => 'Relation', - relation => { - friend => JSON::true - } - }, - 'https://example.com/directory/john.vcf' => { - '@type' => 'Relation', - relation => { - contact => JSON::true - } - }, - 'Please contact my deputy John for any inquiries.' => { - '@type' => 'Relation', - relation => { } - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'John Doe' }, + relatedTo => { + 'urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6' => { + '@type' => 'Relation', + relation => { + friend => JSON::true + } + }, + 'https://example.com/directory/john.vcf' => { + '@type' => 'Relation', + relation => { + contact => JSON::true + } + }, + 'Please contact my deputy John for any inquiries.' => { + '@type' => 'Relation', + relation => {} } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|RELATED;TYPE=FRIEND:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6|, $card); - $self->assert_matches(qr|RELATED;TYPE=CONTACT:https://example.com/directory/john.vcf|, $card); - $self->assert_matches(qr|RELATED;VALUE=TEXT:Please contact my deputy John for any inquiries.|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr|RELATED;TYPE=FRIEND:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6|, + $card); + $self->assert_matches( + qr|RELATED;TYPE=CONTACT:https://example.com/directory/john.vcf|, $card); + $self->assert_matches( + qr|RELATED;VALUE=TEXT:Please contact my deputy John for any inquiries.|, + $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_sched_addrs b/cassandane/tiny-tests/JMAPContacts/card_set_create_sched_addrs index a00587dd6d..3f555f5158 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_sched_addrs +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_sched_addrs @@ -2,56 +2,58 @@ use Cassandane::Tiny; sub test_card_set_create_schedaddrs - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - schedulingAddresses => { - sched1 => { - '@type' => 'SchedulingAddress', - uri => 'mailto:janedoe@example.com', - contexts => { - private => JSON::true - } - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + schedulingAddresses => { + sched1 => { + '@type' => 'SchedulingAddress', + uri => 'mailto:janedoe@example.com', + contexts => { + private => JSON::true + } } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/CALADRURI;PROP-ID=sched1;TYPE=HOME:mailto:janedoe\@example.com/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches( + qr/CALADRURI;PROP-ID=sched1;TYPE=HOME:mailto:janedoe\@example.com/, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_speaktoas b/cassandane/tiny-tests/JMAPContacts/card_set_create_speaktoas index e1725e23b2..a615371811 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_speaktoas +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_speaktoas @@ -2,64 +2,65 @@ use Cassandane::Tiny; sub test_card_set_create_speaktoas - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'John Doe' }, - speakToAs => { - '@type' => 'SpeakToAs', - grammaticalGender => 'neuter', - pronouns => { - k19 => { - pronouns => 'they/them', - pref => 2 - }, - k32 => { - '@type' => 'Pronouns', - pronouns => 'xe/xir', - pref => 1 - } - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'John Doe' }, + speakToAs => { + '@type' => 'SpeakToAs', + grammaticalGender => 'neuter', + pronouns => { + k19 => { + pronouns => 'they/them', + pref => 2 + }, + k32 => { + '@type' => 'Pronouns', + pronouns => 'xe/xir', + pref => 1 + } } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr|GRAMGENDER:NEUTER|, $card); - $self->assert_matches(qr|PRONOUNS;PROP-ID=k19;PREF=2:they/them|, $card); - $self->assert_matches(qr|PRONOUNS;PROP-ID=k32;PREF=1:xe/xir|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches(qr|GRAMGENDER:NEUTER|, $card); + $self->assert_matches(qr|PRONOUNS;PROP-ID=k19;PREF=2:they/them|, $card); + $self->assert_matches(qr|PRONOUNS;PROP-ID=k32;PREF=1:xe/xir|, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_titles b/cassandane/tiny-tests/JMAPContacts/card_set_create_titles index 917938979c..bdd8e5c2ab 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_titles +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_titles @@ -2,60 +2,61 @@ use Cassandane::Tiny; sub test_card_set_create_titles - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - titles => { - 'TITLE-1' => { - '@type' => 'Title', - kind => 'title', - name => 'Project Leader' - }, - 'TITLE-2' => { - '@type' => 'Title', - kind => 'role', - name => 'Research Scientist', - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + titles => { + 'TITLE-1' => { + '@type' => 'Title', + kind => 'title', + name => 'Project Leader' + }, + 'TITLE-2' => { + '@type' => 'Title', + kind => 'role', + name => 'Research Scientist', } - }, 'R1'] - ]); + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/ROLE;PROP-ID=TITLE-2:Research Scientist/, $card); - $self->assert_matches(qr/TITLE;PROP-ID=TITLE-1:Project Leader/, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches(qr/ROLE;PROP-ID=TITLE-2:Research Scientist/, $card); + $self->assert_matches(qr/TITLE;PROP-ID=TITLE-1:Project Leader/, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_create_titles_org b/cassandane/tiny-tests/JMAPContacts/card_set_create_titles_org index a16066b3da..2af2ac4313 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_create_titles_org +++ b/cassandane/tiny-tests/JMAPContacts/card_set_create_titles_org @@ -2,68 +2,70 @@ use Cassandane::Tiny; sub test_card_set_create_titles_org - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - titles => { - 'TITLE-1' => { - '@type' => 'Title', - kind => 'title', - name => 'Project Leader' - }, - 'TITLE-2' => { - '@type' => 'Title', - kind => 'role', - name => 'Research Scientist', - organizationId => 'ORG-1' - } - }, - organizations => { - 'ORG-1' => { - '@type' => 'Organization', - name => 'ABC, Inc.' - } - } - } + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + titles => { + 'TITLE-1' => { + '@type' => 'Title', + kind => 'title', + name => 'Project Leader' + }, + 'TITLE-2' => { + '@type' => 'Title', + kind => 'role', + name => 'Research Scientist', + organizationId => 'ORG-1' } - }, 'R1'] - ]); + }, + organizations => { + 'ORG-1' => { + '@type' => 'Organization', + name => 'ABC, Inc.' + } + } + } + } + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/TITLE;PROP-ID=TITLE-1:Project Leader/, $card); - $self->assert_matches(qr/ORG-1.ROLE;PROP-ID=TITLE-2:Research Scientist/, $card); - $self->assert_matches(qr/ORG-1.ORG;PROP-ID=ORG-1:ABC\\, Inc./, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); + $self->assert_matches(qr/TITLE;PROP-ID=TITLE-1:Project Leader/, $card); + $self->assert_matches(qr/ORG-1.ROLE;PROP-ID=TITLE-2:Research Scientist/, + $card); + $self->assert_matches(qr/ORG-1.ORG;PROP-ID=ORG-1:ABC\\, Inc./, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_destroy b/cassandane/tiny-tests/JMAPContacts/card_set_destroy index 81b6e8f4e9..3247383fd4 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_destroy +++ b/cassandane/tiny-tests/JMAPContacts/card_set_destroy @@ -2,26 +2,25 @@ use Cassandane::Tiny; sub test_card_set_destroy - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/test.vcf"; - my $card = <{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $href = "Default/test.vcf"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { }, 'R1'] - ]); - my $cardId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($cardId); + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); + my $cardId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($cardId); - $res = $jmap->CallMethods([ - ['ContactCard/set', { - destroy => [$cardId] - }, 'R1'] - ]); + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + destroy => [$cardId] + }, + 'R1' + ] ]); - $self->assert_str_equals($cardId, $res->[0][1]{destroyed}[0]); + $self->assert_str_equals($cardId, $res->[0][1]{destroyed}[0]); - $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_importance_float b/cassandane/tiny-tests/JMAPContacts/card_set_importance_float index 5326913724..3f0657f7d4 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_importance_float +++ b/cassandane/tiny-tests/JMAPContacts/card_set_importance_float @@ -2,27 +2,34 @@ use Cassandane::Tiny; sub test_card_set_importance_float - :min_version_3_5 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - c1 => { - name => { full => 'John Doe' }, - 'cyrusimap.org:importance' => -122.129545321514, - }, - }, - }, 'R1'], - ['ContactCard/get', { - ids => ['#c1'], - properties => ['cyrusimap.org:importance'], - }, 'R2'], - ]); - my $contactId = $res->[0][1]{created}{c1}{id}; - $self->assert_not_null($contactId); - $self->assert_equals(-122.129545321514, - $res->[1][1]{list}[0]{'cyrusimap.org:importance'}); + my $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + create => { + c1 => { + name => { full => 'John Doe' }, + 'cyrusimap.org:importance' => -122.129545321514, + }, + }, + }, + 'R1' + ], + [ + 'ContactCard/get', + { + ids => ['#c1'], + properties => ['cyrusimap.org:importance'], + }, + 'R2' + ], + ]); + my $contactId = $res->[0][1]{created}{c1}{id}; + $self->assert_not_null($contactId); + $self->assert_equals(-122.129545321514, + $res->[1][1]{list}[0]{'cyrusimap.org:importance'}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_importance_later b/cassandane/tiny-tests/JMAPContacts/card_set_importance_later index 1a324ca91a..f1ec06e40d 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_importance_later +++ b/cassandane/tiny-tests/JMAPContacts/card_set_importance_later @@ -2,39 +2,45 @@ use Cassandane::Tiny; sub test_card_set_importance_later - :min_version_3_1 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with no importance"; - my $res = $jmap->CallMethods([['ContactCard/set', - {create => {"1" => {name => { full => "John Doe" }}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with no importance"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { create => { "1" => { name => { full => "John Doe" } } } }, "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(0.0, - $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + my $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(0.0, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); - $res = $jmap->CallMethods([['ContactCard/set', - {update => {$id => {"cyrusimap.org:importance" => -0.1}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { update => { $id => { "cyrusimap.org:importance" => -0.1 } } }, "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(-0.1, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(-0.1, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_importance_multiedit b/cassandane/tiny-tests/JMAPContacts/card_set_importance_multiedit index e7bad9825f..12842d477d 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_importance_multiedit +++ b/cassandane/tiny-tests/JMAPContacts/card_set_importance_multiedit @@ -2,40 +2,61 @@ use Cassandane::Tiny; sub test_card_set_importance_multiedit - :min_version_3_1 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with no importance"; - my $res = $jmap->CallMethods([['ContactCard/set', - {create => {"1" => {name => { full => "John Doe" }, - "cyrusimap.org:importance" => -5.2}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with no importance"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + name => { full => "John Doe" }, + "cyrusimap.org:importance" => -5.2 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + my $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(-5.2, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); - $res = $jmap->CallMethods([['ContactCard/set', - {update => {$id => {"name" => { full => "Jane Doe" }, - "cyrusimap.org:importance" => -0.2}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + "name" => { full => "Jane Doe" }, + "cyrusimap.org:importance" => -0.2 + } + } + }, + "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('Jane Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(-0.2, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('Jane Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(-0.2, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_importance_peruser b/cassandane/tiny-tests/JMAPContacts/card_set_importance_peruser index 28b28fa2d5..f4a537cad2 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_importance_peruser +++ b/cassandane/tiny-tests/JMAPContacts/card_set_importance_peruser @@ -2,78 +2,101 @@ use Cassandane::Tiny; sub test_card_set_importance_peruser - :min_version_3_5 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_5 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:contacts', - 'https://cyrusimap.org/ns/jmap/contacts', - ]); - $admin->setacl("user.cassandane.#addressbooks.Default", - "manifold" => 'lrswipkxtecdn') or die; + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:contacts', + 'https://cyrusimap.org/ns/jmap/contacts', + ]); + $admin->setacl("user.cassandane.#addressbooks.Default", + "manifold" => 'lrswipkxtecdn') + or die; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - c1 => { - name => { full => 'John Doe' }, - 'cyrusimap.org:importance' => 1.0, - }, - }, - }, 'R1'], - ['ContactCard/get', { - ids => ['#c1'], - properties => ['cyrusimap.org:importance'], - }, 'R2'], - ]); - my $contactId = $res->[0][1]{created}{c1}{id}; - $self->assert_not_null($contactId); - $self->assert_equals(1.0, $res->[1][1]{list}[0]{'cyrusimap.org:importance'}); + my $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + create => { + c1 => { + name => { full => 'John Doe' }, + 'cyrusimap.org:importance' => 1.0, + }, + }, + }, + 'R1' + ], + [ + 'ContactCard/get', + { + ids => ['#c1'], + properties => ['cyrusimap.org:importance'], + }, + 'R2' + ], + ]); + my $contactId = $res->[0][1]{created}{c1}{id}; + $self->assert_not_null($contactId); + $self->assert_equals(1.0, $res->[1][1]{list}[0]{'cyrusimap.org:importance'}); - $res = $manjmap->CallMethods([ - ['ContactCard/get', { - accountId => 'cassandane', - ids => [$contactId], - properties => ['cyrusimap.org:importance'], - }, 'R1'], - ['ContactCard/set', { - accountId => 'cassandane', - update => { - $contactId => { - 'cyrusimap.org:importance' => 2.0, - }, - }, - }, 'R2'], - ['ContactCard/get', { - accountId => 'cassandane', - ids => [$contactId], - properties => ['cyrusimap.org:importance'], - }, 'R3'], - ]); + $res = $manjmap->CallMethods([ + [ + 'ContactCard/get', + { + accountId => 'cassandane', + ids => [$contactId], + properties => ['cyrusimap.org:importance'], + }, + 'R1' + ], + [ + 'ContactCard/set', + { + accountId => 'cassandane', + update => { + $contactId => { + 'cyrusimap.org:importance' => 2.0, + }, + }, + }, + 'R2' + ], + [ + 'ContactCard/get', + { + accountId => 'cassandane', + ids => [$contactId], + properties => ['cyrusimap.org:importance'], + }, + 'R3' + ], + ]); - $self->assert_equals(1.0, $res->[0][1]{list}[0]{'cyrusimap.org:importance'}); - $self->assert(exists $res->[1][1]{updated}{$contactId}); - $self->assert_equals(2.0, $res->[2][1]{list}[0]{'cyrusimap.org:importance'}); + $self->assert_equals(1.0, $res->[0][1]{list}[0]{'cyrusimap.org:importance'}); + $self->assert(exists $res->[1][1]{updated}{$contactId}); + $self->assert_equals(2.0, $res->[2][1]{list}[0]{'cyrusimap.org:importance'}); - $res = $jmap->CallMethods([ - ['ContactCard/get', { - ids => ['#c1'], - properties => ['cyrusimap.org:importance'], - }, 'R1'], - ]); - $self->assert_equals(1.0, $res->[0][1]{list}[0]{'cyrusimap.org:importance'}); + $res = $jmap->CallMethods([ + [ + 'ContactCard/get', + { + ids => ['#c1'], + properties => ['cyrusimap.org:importance'], + }, + 'R1' + ], + ]); + $self->assert_equals(1.0, $res->[0][1]{list}[0]{'cyrusimap.org:importance'}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_importance_shared b/cassandane/tiny-tests/JMAPContacts/card_set_importance_shared index 5fddf426ac..0e9b4f583d 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_importance_shared +++ b/cassandane/tiny-tests/JMAPContacts/card_set_importance_shared @@ -2,54 +2,63 @@ use Cassandane::Tiny; sub test_card_set_importance_shared - :min_version_3_1 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "create contact"; - my $res = $jmap->CallMethods([['ContactCard/set', { - accountId => 'manifold', - create => {"1" => {name => { full => "John Doe" }}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; - - $admintalk->setacl("user.manifold.#addressbooks.Default", - "cassandane" => 'lrsn') or die; - - xlog $self, "update importance"; - $res = $jmap->CallMethods([['ContactCard/set', { - accountId => 'manifold', - update => {$id => {"cyrusimap.org:importance" => -0.1}} - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + : min_version_3_1 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "create contact"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + accountId => 'manifold', + create => { "1" => { name => { full => "John Doe" } } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; + + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrsn') + or die; + + xlog $self, "update importance"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + accountId => 'manifold', + update => { $id => { "cyrusimap.org:importance" => -0.1 } } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_importance_upfront b/cassandane/tiny-tests/JMAPContacts/card_set_importance_upfront index 195645aa65..e612520c76 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_importance_upfront +++ b/cassandane/tiny-tests/JMAPContacts/card_set_importance_upfront @@ -2,39 +2,53 @@ use Cassandane::Tiny; sub test_card_set_importance_upfront - :min_version_3_1 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with importance in initial create"; - my $res = $jmap->CallMethods([['ContactCard/set', - {create => {"1" => {name => { full => "John Doe" }, - "cyrusimap.org:importance" => -5.2}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with importance in initial create"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + name => { full => "John Doe" }, + "cyrusimap.org:importance" => -5.2 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + my $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(-5.2, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); - $res = $jmap->CallMethods([['ContactCard/set', - {update => {$id => {"name" => { full => "Jane Doe" }}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { update => { $id => { "name" => { full => "Jane Doe" } } } }, "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('Jane Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('Jane Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(-5.2, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_importance_zero_byself b/cassandane/tiny-tests/JMAPContacts/card_set_importance_zero_byself index 37e394b7ce..0b11da4f74 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_importance_zero_byself +++ b/cassandane/tiny-tests/JMAPContacts/card_set_importance_zero_byself @@ -2,39 +2,53 @@ use Cassandane::Tiny; sub test_card_set_importance_zero_byself - :min_version_3_1 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with no importance"; - my $res = $jmap->CallMethods([['ContactCard/set', - {create => {"1" => {name => { full => "John Doe" }, - "cyrusimap.org:importance" => -5.2}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with no importance"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + name => { full => "John Doe" }, + "cyrusimap.org:importance" => -5.2 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + my $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(-5.2, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); - $res = $jmap->CallMethods([['ContactCard/set', - {update => {$id => {"cyrusimap.org:importance" => 0}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { update => { $id => { "cyrusimap.org:importance" => 0 } } }, "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(0, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(0, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_importance_zero_multi b/cassandane/tiny-tests/JMAPContacts/card_set_importance_zero_multi index 9d8427571a..00425a0f88 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_importance_zero_multi +++ b/cassandane/tiny-tests/JMAPContacts/card_set_importance_zero_multi @@ -2,40 +2,61 @@ use Cassandane::Tiny; sub test_card_set_importance_zero_multi - :min_version_3_1 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with no importance"; - my $res = $jmap->CallMethods([['ContactCard/set', - {create => {"1" => {name => { full => "John Doe" }, - "cyrusimap.org:importance" => -5.2}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with no importance"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + name => { full => "John Doe" }, + "cyrusimap.org:importance" => -5.2 + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + my $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('John Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(-5.2, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); - $res = $jmap->CallMethods([['ContactCard/set', - {update => {$id => {name => { full => "Jane Doe" }, - "cyrusimap.org:importance" => 0}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + name => { full => "Jane Doe" }, + "cyrusimap.org:importance" => 0 + } + } + }, + "R3" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['ContactCard/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('Jane Doe', $fetch->[0][1]{list}[0]{name}{full}); - $self->assert_num_equals(0, $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); + $fetch + = $jmap->CallMethods([ [ 'ContactCard/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('ContactCard/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('Jane Doe', $fetch->[0][1]{list}[0]{name}{full}); + $self->assert_num_equals(0, + $fetch->[0][1]{list}[0]{"cyrusimap.org:importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_state b/cassandane/tiny-tests/JMAPContacts/card_set_state index 524fb6bf55..bc726778eb 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_state +++ b/cassandane/tiny-tests/JMAPContacts/card_set_state @@ -2,82 +2,95 @@ use Cassandane::Tiny; sub test_card_set_state - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contact"; - my $name = 'Mr. John Q. Public, Esq.'; - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + xlog $self, "create contact"; + my $name = 'Mr. John Q. Public, Esq.'; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => $name } - } - } - }, 'R1'] - ]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; - my $state = $res->[0][1]{newState}; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => $name } + } + } + }, + 'R1' + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; + my $state = $res->[0][1]{newState}; - xlog $self, "get contact $id"; - $res = $jmap->CallMethods([['ContactCard/get', {}, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/get', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - $self->assert_str_equals($name, $res->[0][1]{list}[0]{name}{full}); - $self->assert_str_equals($state, $res->[0][1]{state}); + xlog $self, "get contact $id"; + $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, "R2" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/get', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + $self->assert_str_equals($name, $res->[0][1]{list}[0]{name}{full}); + $self->assert_str_equals($state, $res->[0][1]{state}); - xlog $self, "update $id with state token $state"; - $res = $jmap->CallMethods([['ContactCard/set', { - ifInState => $state, - update => {$id => - {name => { full => $name }} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert(exists $res->[0][1]{updated}{$id}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - my $oldState = $state; - $state = $res->[0][1]{newState}; + xlog $self, "update $id with state token $state"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + ifInState => $state, + update => { $id => { name => { full => $name } } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert(exists $res->[0][1]{updated}{$id}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + my $oldState = $state; + $state = $res->[0][1]{newState}; - xlog $self, "update $id with expired state token $oldState"; - $res = $jmap->CallMethods([['ContactCard/set', { - ifInState => $oldState, - update => {$id => - {name => { full => $name }} - }}, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + xlog $self, "update $id with expired state token $oldState"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + ifInState => $oldState, + update => { $id => { name => { full => $name } } } + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "get contact $id to make sure state didn't change"; - $res = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]{state}); + xlog $self, "get contact $id to make sure state didn't change"; + $res = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]{state}); - xlog $self, "destroy $id with expired state token $oldState"; - $res = $jmap->CallMethods([['ContactCard/set', { - ifInState => $oldState, - destroy => [$id] - }, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + xlog $self, "destroy $id with expired state token $oldState"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + ifInState => $oldState, + destroy => [$id] + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "destroy contact $id with current state"; - $res = $jmap->CallMethods([ - ['ContactCard/set', { - ifInState => $state, - destroy => [$id] - }, "R1"] - ]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy contact $id with current state"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + ifInState => $state, + destroy => [$id] + }, + "R1" + ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_uid_text b/cassandane/tiny-tests/JMAPContacts/card_set_uid_text index 803abb9ec2..de50cbf02c 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_uid_text +++ b/cassandane/tiny-tests/JMAPContacts/card_set_uid_text @@ -2,47 +2,50 @@ use Cassandane::Tiny; sub test_card_set_uid_text - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - - my $res = $jmap->CallMethods([ - ['AddressBook/set', { - create => { "1" => { name => "foo" }} - }, "R1"] - ]); - my $abookid = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($abookid); - - my $id = 'e2640cc234ad93b9@example.com'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - addressBookId => $abookid, - uid => $id, - kind => 'individual', - name => { full => 'foo' } - } - } - }, 'R1'] - ]); - - $self->assert_not_null($res->[0][1]{created}{1}); - $self->assert_not_null($res->[0][1]{created}{1}{id}); - - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/VERSION:4.0/, $card); - $self->assert_matches(qr/UID;VALUE=TEXT:$id/, $card); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + + my $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + create => { "1" => { name => "foo" } } + }, + "R1" + ] ]); + my $abookid = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($abookid); + + my $id = 'e2640cc234ad93b9@example.com'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + addressBookId => $abookid, + uid => $id, + kind => 'individual', + name => { full => 'foo' } + } + } + }, + 'R1' + ] ]); + + $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_not_null($res->[0][1]{created}{1}{id}); + + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/VERSION:4.0/, $card); + $self->assert_matches(qr/UID;VALUE=TEXT:$id/, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_update b/cassandane/tiny-tests/JMAPContacts/card_set_update index ca5bb64f20..50e9fb44fd 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_update +++ b/cassandane/tiny-tests/JMAPContacts/card_set_update @@ -2,177 +2,196 @@ use Cassandane::Tiny; sub test_card_set_update - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'John Doe' }, - nicknames => { - k391 => { - '@type' => 'Nickname', - name => 'Johnny' - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'John Doe' }, + nicknames => { + k391 => { + '@type' => 'Nickname', + name => 'Johnny' } - }, 'R1'] - ]); - - $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - 'nicknames/k391/name' => 'Johnny Boy', - 'nicknames/foo' => { - '@type' => 'Nickname', - name => 'Doey' - } - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{updated}{$id}); - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/NICKNAME;PROP-ID=foo:Doey/, $card); - $self->assert_matches(qr/NICKNAME;PROP-ID=k391:Johnny Boy/, $card); - $self->assert_does_not_match(qr/JSPROP/, $card); - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - 'nicknames/k391' => JSON::null - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{updated}{$id}); - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/NICKNAME;PROP-ID=foo:Doey/, $card); - $self->assert_does_not_match(qr/NICKNAME;PROP-ID=k391:Johnny Boy/, $card); - $self->assert_does_not_match(qr/JSPROP/, $card); - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - 'cyrusimap.org:importance' => -0.1 - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{updated}{$id}); - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - 'cyrusimap.org:importance' => 0.0, - keywords => { foo => JSON::true } - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{updated}{$id}); - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/CATEGORIES:foo/, $card); - $self->assert_does_not_match(qr/JSPROP/, $card); - - xlog $self, "create alternate addressbook"; - $res = $jmap->CallMethods([ - ['AddressBook/set', { create => { "1" => { - name => "foo" - }}}, "R1"] - ]); - - my $abookid = $res->[0][1]{created}{"1"}{id}; - my $href = "$abookid/$id.vcf"; - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - addressBookId => $abookid - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{updated}{$id}); - $self->assert_not_null($res->[0][1]{updated}{$id}{updated}); - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/UID:$id/, $card); - $self->assert_matches(qr/NICKNAME;PROP-ID=foo:Doey/, $card); - $self->assert_matches(qr/CATEGORIES:foo/, $card); - $self->assert_matches(qr/REV:/, $card); - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - kind => 'group' - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{notUpdated}{$id}); - $self->assert_str_equals("invalidProperties", - $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("kind", - $res->[0][1]{notUpdated}{$id}{properties}[0]); + } + } + } + }, + 'R1' + ] ]); + + $self->assert_not_null($res->[0][1]{created}{1}); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + 'nicknames/k391/name' => 'Johnny Boy', + 'nicknames/foo' => { + '@type' => 'Nickname', + name => 'Doey' + } + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{updated}{$id}); + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/NICKNAME;PROP-ID=foo:Doey/, $card); + $self->assert_matches(qr/NICKNAME;PROP-ID=k391:Johnny Boy/, $card); + $self->assert_does_not_match(qr/JSPROP/, $card); + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + 'nicknames/k391' => JSON::null + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{updated}{$id}); + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/NICKNAME;PROP-ID=foo:Doey/, $card); + $self->assert_does_not_match(qr/NICKNAME;PROP-ID=k391:Johnny Boy/, $card); + $self->assert_does_not_match(qr/JSPROP/, $card); + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + 'cyrusimap.org:importance' => -0.1 + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{updated}{$id}); + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + 'cyrusimap.org:importance' => 0.0, + keywords => { foo => JSON::true } + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{updated}{$id}); + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/CATEGORIES:foo/, $card); + $self->assert_does_not_match(qr/JSPROP/, $card); + + xlog $self, "create alternate addressbook"; + $res = $jmap->CallMethods([ [ + 'AddressBook/set', + { + create => { + "1" => { + name => "foo" + } + } + }, + "R1" + ] ]); + + my $abookid = $res->[0][1]{created}{"1"}{id}; + my $href = "$abookid/$id.vcf"; + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + addressBookId => $abookid + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{updated}{$id}); + $self->assert_not_null($res->[0][1]{updated}{$id}{updated}); + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/UID:$id/, $card); + $self->assert_matches(qr/NICKNAME;PROP-ID=foo:Doey/, $card); + $self->assert_matches(qr/CATEGORIES:foo/, $card); + $self->assert_matches(qr/REV:/, $card); + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + kind => 'group' + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{notUpdated}{$id}); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("kind", + $res->[0][1]{notUpdated}{$id}{properties}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_update_extra_rejected b/cassandane/tiny-tests/JMAPContacts/card_set_update_extra_rejected index 0200b27324..c447ed8c3c 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_update_extra_rejected +++ b/cassandane/tiny-tests/JMAPContacts/card_set_update_extra_rejected @@ -2,52 +2,57 @@ use Cassandane::Tiny; sub test_card_set_update_extra_rejected - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - card1 => { - '@type' => 'Card', - name => { - full => 'John', - }, - }, + my $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + create => { + card1 => { + '@type' => 'Card', + name => { + full => 'John', }, - }, 'R1'], - ]); - my $cardId = $res->[0][1]{created}{card1}{id}; - $self->assert_not_null($cardId); + }, + }, + }, + 'R1' + ], + ]); + my $cardId = $res->[0][1]{created}{card1}{id}; + $self->assert_not_null($cardId); - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $cardId => { - extra => 'reserved', - 'name/extra' => 'reserved', - localizations => { - de => { - 'name/extra' => 'reserved2', - }, - }, - }, + $res = $jmap->CallMethods([ + [ + 'ContactCard/set', + { + update => { + $cardId => { + extra => 'reserved', + 'name/extra' => 'reserved', + localizations => { + de => { + 'name/extra' => 'reserved2', + }, }, - }, 'R1'], - ]); + }, + }, + }, + 'R1' + ], + ]); - $self->assert_null($res->[0][1]{created}{card1}); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{$cardId}{type}); + $self->assert_null($res->[0][1]{created}{card1}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$cardId}{type}); - my @wantInvalidProps = ( - "extra", - "localizations/de/name~1extra", - "name/extra", - ); - my @haveInvalidProps = sort @{$res->[0][1]{notUpdated}{$cardId}{properties}}; - $self->assert_deep_equals(\@wantInvalidProps, \@haveInvalidProps); + my @wantInvalidProps + = ("extra", "localizations/de/name~1extra", "name/extra",); + my @haveInvalidProps + = sort @{ $res->[0][1]{notUpdated}{$cardId}{properties} }; + $self->assert_deep_equals(\@wantInvalidProps, \@haveInvalidProps); } diff --git a/cassandane/tiny-tests/JMAPContacts/card_set_update_media_blob b/cassandane/tiny-tests/JMAPContacts/card_set_update_media_blob index 50ef77a0ea..e49c5ecab0 100644 --- a/cassandane/tiny-tests/JMAPContacts/card_set_update_media_blob +++ b/cassandane/tiny-tests/JMAPContacts/card_set_update_media_blob @@ -2,86 +2,94 @@ use Cassandane::Tiny; sub test_card_set_update_media_blob - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - name => { full => 'Jane Doe' }, - media => { - res1 => { - '@type' => 'MediaResource', - kind => 'photo', - uri => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wgALCAABAAEBAREA/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxA=' - } - } - } + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + name => { full => 'Jane Doe' }, + media => { + res1 => { + '@type' => 'MediaResource', + kind => 'photo', + uri => + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wgALCAABAAEBAREA/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxA=' } - }, 'R1'] - ]); - - $self->assert_not_null($res->[0][1]{created}{1}); - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr|PHOTO;PROP-ID=res1:data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wgALCAABAAEBAREA/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxA=|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); - - xlog $self, "upload photo"; - my $res = $jmap->Upload("some photo", "image/jpeg"); - my $blobId = $res->{blobId}; - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - 'media/res1/blobId' => $blobId - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{updated}{$id}); - $blobId = $res->[0][1]{updated}{$id}{media}{res1}{blobId}; - $self->assert_not_null($blobId); - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr|PHOTO;PROP-ID=res1:data:image/jpeg;base64,c29tZSBwaG90bw==|, $card); - $self->assert_does_not_match(qr|JSPROP|, $card); - - $res = $jmap->Download('cassandane', $blobId); - - $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); - $self->assert_str_equals('some photo', $res->{content}); + } + } + } + }, + 'R1' + ] ]); + + $self->assert_not_null($res->[0][1]{created}{1}); + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches( + qr|PHOTO;PROP-ID=res1:data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wgALCAABAAEBAREA/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxA=|, + $card + ); + $self->assert_does_not_match(qr|JSPROP|, $card); + + xlog $self, "upload photo"; + my $res = $jmap->Upload("some photo", "image/jpeg"); + my $blobId = $res->{blobId}; + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + 'media/res1/blobId' => $blobId + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{updated}{$id}); + $blobId = $res->[0][1]{updated}{$id}{media}{res1}{blobId}; + $self->assert_not_null($blobId); + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches( + qr|PHOTO;PROP-ID=res1:data:image/jpeg;base64,c29tZSBwaG90bw==|, $card); + $self->assert_does_not_match(qr|JSPROP|, $card); + + $res = $jmap->Download('cassandane', $blobId); + + $self->assert_str_equals('image/jpeg', $res->{headers}{'content-type'}); + $self->assert_str_equals('some photo', $res->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_changes b/cassandane/tiny-tests/JMAPContacts/cardgroup_changes index ef8b51a8fb..8d4d0c4b94 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_changes +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_changes @@ -2,141 +2,188 @@ use Cassandane::Tiny; sub test_cardgroup_changes - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - -# Update to Card[Group]/[get|set] once implemented - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', {create => { - "a" => {firstName => "a", lastName => "a"}, - "b" => {firstName => "b", lastName => "b"}, - "c" => {firstName => "c", lastName => "c"}, - "d" => {firstName => "d", lastName => "d"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $contactA = $res->[0][1]{created}{"a"}{id}; - my $contactB = $res->[0][1]{created}{"b"}{id}; - my $contactC = $res->[0][1]{created}{"c"}{id}; - my $contactD = $res->[0][1]{created}{"d"}{id}; - - xlog $self, "get contact groups state"; - $res = $jmap->CallMethods([['ContactGroup/get', {}, "R2"]]); - my $state = $res->[0][1]{state}; - - xlog $self, "create contact group 1"; - $res = $jmap->CallMethods([['ContactGroup/set', {create => { - "1" => {name => "first", contactIds => [$contactA, $contactB]}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - - - xlog $self, "get contact group updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - - my $oldState = $state; - $state = $res->[0][1]{newState}; - - xlog $self, "create contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', {create => { - "2" => {name => "second", contactIds => [$contactC, $contactD]}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id2 = $res->[0][1]{created}{"2"}{id}; - - xlog $self, "get contact group updates (since last change)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "get contact group updates (in bulk)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $oldState - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get contact group updates from initial state (maxChanges=1)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $oldState, - maxChanges => 1 - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $interimState = $res->[0][1]{newState}; - - xlog $self, "get contact group updates from interim state (maxChanges=10)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $interimState, - maxChanges => 10 - }, "R2"]]); - $self->assert_str_equals($interimState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "destroy contact group 1, update contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', { - destroy => [$id1], - update => {$id2 => {name => "second (updated)"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - xlog $self, "get contact group updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - - xlog $self, "destroy contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', {destroy => [$id2]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + # Update to Card[Group]/[get|set] once implemented + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "a" => { firstName => "a", lastName => "a" }, + "b" => { firstName => "b", lastName => "b" }, + "c" => { firstName => "c", lastName => "c" }, + "d" => { firstName => "d", lastName => "d" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $contactA = $res->[0][1]{created}{"a"}{id}; + my $contactB = $res->[0][1]{created}{"b"}{id}; + my $contactC = $res->[0][1]{created}{"c"}{id}; + my $contactD = $res->[0][1]{created}{"d"}{id}; + + xlog $self, "get contact groups state"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', {}, "R2" ] ]); + my $state = $res->[0][1]{state}; + + xlog $self, "create contact group 1"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + create => { + "1" => { name => "first", contactIds => [ $contactA, $contactB ] } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get contact group updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + + my $oldState = $state; + $state = $res->[0][1]{newState}; + + xlog $self, "create contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + create => { + "2" => { name => "second", contactIds => [ $contactC, $contactD ] } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id2 = $res->[0][1]{created}{"2"}{id}; + + xlog $self, "get contact group updates (since last change)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "get contact group updates (in bulk)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $oldState + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get contact group updates from initial state (maxChanges=1)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $oldState, + maxChanges => 1 + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + my $interimState = $res->[0][1]{newState}; + + xlog $self, "get contact group updates from interim state (maxChanges=10)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $interimState, + maxChanges => 10 + }, + "R2" + ] ]); + $self->assert_str_equals($interimState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "destroy contact group 1, update contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + destroy => [$id1], + update => { $id2 => { name => "second (updated)" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + xlog $self, "get contact group updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + + xlog $self, "destroy contact group 2"; + $res = $jmap->CallMethods( + [ [ 'ContactGroup/set', { destroy => [$id2] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_changes_shared b/cassandane/tiny-tests/JMAPContacts/cardgroup_changes_shared index 36d33ae173..338e9786d6 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_changes_shared +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_changes_shared @@ -2,178 +2,228 @@ use Cassandane::Tiny; sub test_cardgroup_changes_shared - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrswipkxtecdn') or die; - -# Update to Card[Group]/[get|set] once implemented - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => { - "a" => {firstName => "a", lastName => "a"}, - "b" => {firstName => "b", lastName => "b"}, - "c" => {firstName => "c", lastName => "c"}, - "d" => {firstName => "d", lastName => "d"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $contactA = $res->[0][1]{created}{"a"}{id}; - my $contactB = $res->[0][1]{created}{"b"}{id}; - my $contactC = $res->[0][1]{created}{"c"}{id}; - my $contactD = $res->[0][1]{created}{"d"}{id}; - - xlog $self, "get contact groups state"; - $res = $jmap->CallMethods([['ContactGroup/get', { accountId => 'manifold', }, "R2"]]); - my $state = $res->[0][1]{state}; - - xlog $self, "create contact group 1"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - create => { - "1" => {name => "first", contactIds => [$contactA, $contactB]}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - - - xlog $self, "get contact group updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - - my $oldState = $state; - $state = $res->[0][1]{newState}; - - xlog $self, "create contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - create => { - "2" => {name => "second", contactIds => [$contactC, $contactD]}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id2 = $res->[0][1]{created}{"2"}{id}; - - xlog $self, "get contact group updates (since last change)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "get contact group updates (in bulk)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $oldState - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get contact group updates from initial state (maxChanges=1)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $oldState, - maxChanges => 1 - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $interimState = $res->[0][1]{newState}; - - xlog $self, "get contact group updates from interim state (maxChanges=10)"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $interimState, - maxChanges => 10 - }, "R2"]]); - $self->assert_str_equals($interimState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "destroy contact group 1, update contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - destroy => [$id1], - update => {$id2 => {name => "second (updated)"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - xlog $self, "get contact group updates"; - $res = $jmap->CallMethods([['ContactCard/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - - xlog $self, "destroy contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - destroy => [$id2] - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + # Update to Card[Group]/[get|set] once implemented + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { + "a" => { firstName => "a", lastName => "a" }, + "b" => { firstName => "b", lastName => "b" }, + "c" => { firstName => "c", lastName => "c" }, + "d" => { firstName => "d", lastName => "d" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $contactA = $res->[0][1]{created}{"a"}{id}; + my $contactB = $res->[0][1]{created}{"b"}{id}; + my $contactC = $res->[0][1]{created}{"c"}{id}; + my $contactD = $res->[0][1]{created}{"d"}{id}; + + xlog $self, "get contact groups state"; + $res = $jmap->CallMethods( + [ [ 'ContactGroup/get', { accountId => 'manifold', }, "R2" ] ]); + my $state = $res->[0][1]{state}; + + xlog $self, "create contact group 1"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + create => { + "1" => { name => "first", contactIds => [ $contactA, $contactB ] } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get contact group updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + + my $oldState = $state; + $state = $res->[0][1]{newState}; + + xlog $self, "create contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + create => { + "2" => { name => "second", contactIds => [ $contactC, $contactD ] } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id2 = $res->[0][1]{created}{"2"}{id}; + + xlog $self, "get contact group updates (since last change)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "get contact group updates (in bulk)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $oldState + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get contact group updates from initial state (maxChanges=1)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $oldState, + maxChanges => 1 + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + my $interimState = $res->[0][1]{newState}; + + xlog $self, "get contact group updates from interim state (maxChanges=10)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $interimState, + maxChanges => 10 + }, + "R2" + ] ]); + $self->assert_str_equals($interimState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "destroy contact group 1, update contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + destroy => [$id1], + update => { $id2 => { name => "second (updated)" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + xlog $self, "get contact group updates"; + $res = $jmap->CallMethods([ [ + 'ContactCard/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + + xlog $self, "destroy contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + destroy => [$id2] + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v3 b/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v3 index 2ca3c19afe..0748be0187 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v3 +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v3 @@ -2,28 +2,27 @@ use Cassandane::Tiny; sub test_cardgroup_get_v3 - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $member1 = 'urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af'; - my $member2 = 'urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); - my $want_jscard = { - '@type' => 'Card', - version => '1.0', - addressBookId => 'Default', - 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, - kind => 'group', - vCardProps => [ - [ 'version', {}, 'text', '3.0' ] - ], - name => { - full => 'The Doe Family' - }, - members => { - $member1 => JSON::true, - $member2 => JSON::true - } - }; + my $want_jscard = { + '@type' => 'Card', + version => '1.0', + addressBookId => 'Default', + 'cyrusimap.org:href' => $carddav->fullpath() . $href, + id => $id, + uid => $id, + kind => 'group', + vCardProps => [ [ 'version', {}, 'text', '3.0' ] ], + name => { + full => 'The Doe Family' + }, + members => { + $member1 => JSON::true, + $member2 => JSON::true + } + }; - - my $have_jscard = $res->[0][1]{list}[0]; + my $have_jscard = $res->[0][1]{list}[0]; - # Delete generated fields - delete $have_jscard->{blobId}; - delete $have_jscard->{'cyrusimap.org:blobId'}; - delete $have_jscard->{'cyrusimap.org:size'}; + # Delete generated fields + delete $have_jscard->{blobId}; + delete $have_jscard->{'cyrusimap.org:blobId'}; + delete $have_jscard->{'cyrusimap.org:size'}; - # Normalize and compare cards - normalize_jscard($want_jscard); - normalize_jscard($have_jscard); - $self->assert_deep_equals($want_jscard, $have_jscard); + # Normalize and compare cards + normalize_jscard($want_jscard); + normalize_jscard($have_jscard); + $self->assert_deep_equals($want_jscard, $have_jscard); } diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v4 b/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v4 index 0faff36064..31d7e28922 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v4 +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_get_v4 @@ -2,28 +2,27 @@ use Cassandane::Tiny; sub test_cardgroup_get_v4 - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $member1 = 'urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af'; - my $member2 = 'urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); - my $want_jscard = { - '@type' => 'Card', - version => '1.0', - addressBookId => 'Default', - 'cyrusimap.org:href' => $carddav->fullpath() . $href, - id => $id, - uid => $id, - kind => 'group', - vCardProps => [ - [ 'version', {}, 'text', '4.0' ] - ], - name => { - full => 'The Doe Family' - }, - members => { - $member1 => JSON::true, - $member2 => JSON::true - } - }; + my $want_jscard = { + '@type' => 'Card', + version => '1.0', + addressBookId => 'Default', + 'cyrusimap.org:href' => $carddav->fullpath() . $href, + id => $id, + uid => $id, + kind => 'group', + vCardProps => [ [ 'version', {}, 'text', '4.0' ] ], + name => { + full => 'The Doe Family' + }, + members => { + $member1 => JSON::true, + $member2 => JSON::true + } + }; - - my $have_jscard = $res->[0][1]{list}[0]; + my $have_jscard = $res->[0][1]{list}[0]; - # Delete generated fields - delete $have_jscard->{blobId}; - delete $have_jscard->{'cyrusimap.org:blobId'}; - delete $have_jscard->{'cyrusimap.org:size'}; + # Delete generated fields + delete $have_jscard->{blobId}; + delete $have_jscard->{'cyrusimap.org:blobId'}; + delete $have_jscard->{'cyrusimap.org:size'}; - # Normalize and compare cards - normalize_jscard($want_jscard); - normalize_jscard($have_jscard); - $self->assert_deep_equals($want_jscard, $have_jscard); + # Normalize and compare cards + normalize_jscard($want_jscard); + normalize_jscard($have_jscard); + $self->assert_deep_equals($want_jscard, $have_jscard); } diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_query b/cassandane/tiny-tests/JMAPContacts/cardgroup_query index a36a2b8b58..92754cdd04 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_query +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_query @@ -2,243 +2,271 @@ use Cassandane::Tiny; sub test_cardgroup_query - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create cards"; - my $res = $jmap->CallMethods([['ContactCard/set', { - create => { - "1" => { - name => { - components => [ - { - kind => "given", - value => "foo" - }, - { - kind => "surname", - value => "last" - }, - ], - sortAs => { - surname => 'aaa' - } - }, - nicknames => { - 'n1' => { - name => "foo" - } + xlog $self, "create cards"; + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + name => { + components => [ + { + kind => "given", + value => "foo" + }, + { + kind => "surname", + value => "last" + }, + ], + sortAs => { + surname => 'aaa' + } + }, + nicknames => { + 'n1' => { + name => "foo" + } + }, + emails => { + 'e1' => { + contexts => { + private => JSON::true + }, + address => "foo\@example.com" + } + }, + personalInfo => { + 'p1' => { + kind => 'hobby', + value => 'reading' + } + } + }, + "2" => { + name => { + components => [ + { + kind => "given", + value => "bar" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + emails => { + 'e1' => { + contexts => { + work => JSON::true + }, + address => "bar\@bar.org" + }, + 'e2' => { + contexts => { + other => JSON::true + }, + address => "me\@example.com" + } + }, + addresses => { + 'a1' => { + contexts => { + private => JSON::true + }, + components => [ + { + kind => "name", + value => "Some Lane" }, - emails => { - 'e1' => { - contexts => { - private => JSON::true - }, - address => "foo\@example.com" - } + { + kind => "number", + value => "24" }, - personalInfo => { - 'p1' => { - kind => 'hobby', - value => 'reading' - } - } - }, - "2" => { - name => { - components => [ - { - kind => "given", - value => "bar" - }, - { - kind => "surname", - value => "last" - }, - ] + { + kind => 'locality', + value => "SomeWhere City" }, - emails => { - 'e1' => { - contexts => { - work => JSON::true - }, - address => "bar\@bar.org" - }, - 'e2' => { - contexts => { - other => JSON::true - }, - address => "me\@example.com" - } + { + kind => 'region', + value => "" }, - addresses => { - 'a1' => { - contexts => { - private => JSON::true - }, - components => [ - { - kind => "name", - value => "Some Lane" - }, - { - kind => "number", - value => "24" - }, - { - kind => 'locality', - value => "SomeWhere City" - }, - { - kind => 'region', - value => "" - }, - { - kind => 'postcode', - value => "1234" - } - ], - } + { + kind => 'postcode', + value => "1234" } - }, - "3" => { - name => { - components => [ - { - kind => "given", - value => "baz" - }, - { - kind => "surname", - value => "last" - }, - ] + ], + } + } + }, + "3" => { + name => { + components => [ + { + kind => "given", + value => "baz" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + addresses => { + 'a1' => { + contexts => { + private => JSON::true + }, + components => [ + { + kind => "name", + value => "Some Lane" }, - addresses => { - 'a1' => { - contexts => { - private => JSON::true - }, - components => [ - { - kind => "name", - value => "Some Lane" - }, - { - kind => "number", - value => "24" - }, - { - kind => 'locality', - value => "SomeWhere City" - }, - { - kind => 'postcode', - value => "1234" - }, - { - kind => 'country', - value => "Someinistan" - } - ], - } + { + kind => "number", + value => "24" }, - personalInfo => { - 'p1' => { - kind => 'interest', - value => 'r&b music' - } - } - }, - "4" => { - name => { - components => [ - { - kind => "given", - value => "bam" - }, - { - kind => "surname", - value => "last" - }, - ] + { + kind => 'locality', + value => "SomeWhere City" }, - nicknames => { - 'n1' => { - name => "bam" - } + { + kind => 'postcode', + value => "1234" }, - notes => { - 'n1' => { - note => "hello" - } + { + kind => 'country', + value => "Someinistan" } + ], } + }, + personalInfo => { + 'p1' => { + kind => 'interest', + value => 'r&b music' + } + } + }, + "4" => { + name => { + components => [ + { + kind => "given", + value => "bam" + }, + { + kind => "surname", + value => "last" + }, + ] + }, + nicknames => { + 'n1' => { + name => "bam" + } + }, + notes => { + 'n1' => { + note => "hello" + } + } } - }, "R1"]]); + } + }, + "R1" + ] ]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; - my $id3 = $res->[0][1]{created}{"3"}{id}; - my $id4 = $res->[0][1]{created}{"4"}{id}; + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; + my $id3 = $res->[0][1]{created}{"3"}{id}; + my $id4 = $res->[0][1]{created}{"4"}{id}; - xlog $self, "create card groups"; - $res = $jmap->CallMethods([['ContactCard/set', {create => { - "1" => { kind => 'group', - name => { full => "group1" }, - members => { $id1 => JSON::true, $id2 => JSON::true } - }, - "2" => { kind => 'group', - name => { full => "group2" }, - members => { $id3 => JSON::true } - }, - "3" => { kind => 'group', - name => { full => "group3" }, - members => { $id4 => JSON::true } - } - }}, "R1"]]); + xlog $self, "create card groups"; + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + kind => 'group', + name => { full => "group1" }, + members => { $id1 => JSON::true, $id2 => JSON::true } + }, + "2" => { + kind => 'group', + name => { full => "group2" }, + members => { $id3 => JSON::true } + }, + "3" => { + kind => 'group', + name => { full => "group3" }, + members => { $id4 => JSON::true } + } + } + }, + "R1" + ] ]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactCard/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $group1 = $res->[0][1]{created}{"1"}{id}; - my $group2 = $res->[0][1]{created}{"2"}{id}; - my $group3 = $res->[0][1]{created}{"3"}{id}; + $self->assert_not_null($res); + $self->assert_str_equals('ContactCard/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $group1 = $res->[0][1]{created}{"1"}{id}; + my $group2 = $res->[0][1]{created}{"2"}{id}; + my $group3 = $res->[0][1]{created}{"3"}{id}; - xlog $self, "filter by kind"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => "group" } - }, "R1"] ]); + xlog $self, "filter by kind"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { kind => "group" } + }, + "R1" + ] ]); - $self->assert_num_equals(3, $res->[0][1]{total}); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); + $self->assert_num_equals(3, $res->[0][1]{total}); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by group name (fullName)"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => "group", name => "group1" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($group1, $res->[0][1]{ids}[0]); + xlog $self, "filter by group name (fullName)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { kind => "group", name => "group1" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($group1, $res->[0][1]{ids}[0]); - xlog $self, "filter by group name (fullName)"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => "group", name => "group" } - }, "R1"] ]); - $self->assert_num_equals(0, $res->[0][1]{total}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{ids}}); + xlog $self, "filter by group name (fullName)"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { kind => "group", name => "group" } + }, + "R1" + ] ]); + $self->assert_num_equals(0, $res->[0][1]{total}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{ids} }); - xlog $self, "filter by member"; - $res = $jmap->CallMethods([ ['ContactCard/query', { - filter => { kind => "group", hasMember => $id3 } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($group2, $res->[0][1]{ids}[0]); + xlog $self, "filter by member"; + $res = $jmap->CallMethods([ [ + 'ContactCard/query', + { + filter => { kind => "group", hasMember => $id3 } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($group2, $res->[0][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_create b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_create index f66fc518ad..82c7f6241c 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_create +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_create @@ -2,60 +2,61 @@ use Cassandane::Tiny; sub test_cardgroup_set_create - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $name = 'The Doe Family'; - my $member1 = "urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af"; - my $member2 = "urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519"; - my $id = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - kind => 'group', - name => { full => $name }, - members => { - $member1 => JSON::true, - $member2 => JSON::true - } - } - } - }, 'R1'] - ]); - - $self->assert_not_null($res->[0][1]{created}{1}); - - my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/VERSION:4.0/, $card); - $self->assert_matches(qr/KIND:GROUP/, $card); - $self->assert_matches(qr/UID:$id/, $card); - $self->assert_matches(qr/FN:$name/, $card); - $self->assert_matches(qr/MEMBER:$member1/, $card); - $self->assert_matches(qr/MEMBER:$member2/, $card); - $self->assert_does_not_match(qr/N:;/, $card); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $name = 'The Doe Family'; + my $member1 = "urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af"; + my $member2 = "urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519"; + my $id = 'urn:uuid:ae2640cc-234a-4dd9-95cc-3106258445b9'; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + kind => 'group', + name => { full => $name }, + members => { + $member1 => JSON::true, + $member2 => JSON::true + } + } + } + }, + 'R1' + ] ]); + + $self->assert_not_null($res->[0][1]{created}{1}); + + my $href = $res->[0][1]{created}{1}{'cyrusimap.org:href'}; + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/VERSION:4.0/, $card); + $self->assert_matches(qr/KIND:GROUP/, $card); + $self->assert_matches(qr/UID:$id/, $card); + $self->assert_matches(qr/FN:$name/, $card); + $self->assert_matches(qr/MEMBER:$member1/, $card); + $self->assert_matches(qr/MEMBER:$member2/, $card); + $self->assert_does_not_match(qr/N:;/, $card); } diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_destroy b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_destroy index 49005015f8..7d91c838c2 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_destroy +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_destroy @@ -2,28 +2,27 @@ use Cassandane::Tiny; sub test_cardgroup_set_destroy - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $member1 = '03a0e51f-d1aa-4385-8a53-e29025acd8af'; - my $member2 = 'b8767877-b4a1-4c70-9acc-505d3819e519'; - my $href = "Default/test.vcf"; - my $card = <{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $member1 = '03a0e51f-d1aa-4385-8a53-e29025acd8af'; + my $member2 = 'b8767877-b4a1-4c70-9acc-505d3819e519'; + my $href = "Default/test.vcf"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactCard/get', { } , 'R1'] - ]); - my $cardId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($cardId); + my $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); + my $cardId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($cardId); - $res = $jmap->CallMethods([ - ['ContactCard/set', { - destroy => [$cardId] - }, 'R1'] - ]); + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + destroy => [$cardId] + }, + 'R1' + ] ]); - $self->assert_str_equals($cardId, $res->[0][1]{destroyed}[0]); + $self->assert_str_equals($cardId, $res->[0][1]{destroyed}[0]); - $res = $jmap->CallMethods([ - ['ContactCard/get', { - }, 'R1'] - ]); + $res = $jmap->CallMethods([ [ 'ContactCard/get', {}, 'R1' ] ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_update b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_update index a40c674bdd..7f012cc611 100644 --- a/cassandane/tiny-tests/JMAPContacts/cardgroup_set_update +++ b/cassandane/tiny-tests/JMAPContacts/cardgroup_set_update @@ -2,111 +2,118 @@ use Cassandane::Tiny; sub test_cardgroup_set_update - :min_version_3_9 :needs_component_jmap :needs_dependency_icalvcard -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $name = 'The Doe Family'; - my $member1 = "urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af"; - my $member2 = "urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519"; - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; - - my $res = $jmap->CallMethods([ - ['ContactCard/set', { - create => { - "1" => { - '@type' => 'Card', - version => '1.0', - uid => $id, - kind => 'group', - name => { full => $name }, - members => { - $member1 => JSON::true - } - } - } - }, 'R1'] - ]); - - $self->assert_not_null($res->[0][1]{created}{1}); - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/MEMBER:$member1/, $card); - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - "members/$member2" => JSON::true - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{updated}{$id}); - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - my $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/MEMBER:$member2/, $card); - $self->assert_matches(qr/MEMBER:$member1/, $card); - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - "members/$member1" => JSON::null - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{updated}{$id}); - - $res = $carddav->Request('GET', $href, '', - 'Accept' => 'text/vcard; version=4.0'); - - $card = $res->{content}; - $card =~ s/\r?\n[ \t]+//gs; # unfold long properties - - $self->assert_matches(qr/MEMBER:$member2/, $card); - $self->assert_does_not_match(qr/MEMBER:$member1/, $card); - - $res = $jmap->CallMethods([ - ['ContactCard/set', { - update => { - $id => { - kind => 'individual' - } - } - }, "R2"] - ]); - - $self->assert_not_null($res->[0][1]{notUpdated}{$id}); - $self->assert_str_equals("invalidProperties", - $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("kind", - $res->[0][1]{notUpdated}{$id}{properties}[0]); + : min_version_3_9 : needs_component_jmap : needs_dependency_icalvcard { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $name = 'The Doe Family'; + my $member1 = "urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af"; + my $member2 = "urn:uuid:b8767877-b4a1-4c70-9acc-505d3819e519"; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $href = "Default/$id.vcf"; + + my $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + create => { + "1" => { + '@type' => 'Card', + version => '1.0', + uid => $id, + kind => 'group', + name => { full => $name }, + members => { + $member1 => JSON::true + } + } + } + }, + 'R1' + ] ]); + + $self->assert_not_null($res->[0][1]{created}{1}); + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/MEMBER:$member1/, $card); + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + "members/$member2" => JSON::true + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{updated}{$id}); + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + my $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/MEMBER:$member2/, $card); + $self->assert_matches(qr/MEMBER:$member1/, $card); + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + "members/$member1" => JSON::null + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{updated}{$id}); + + $res = $carddav->Request('GET', $href, '', + 'Accept' => 'text/vcard; version=4.0'); + + $card = $res->{content}; + $card =~ s/\r?\n[ \t]+//gs; # unfold long properties + + $self->assert_matches(qr/MEMBER:$member2/, $card); + $self->assert_does_not_match(qr/MEMBER:$member1/, $card); + + $res = $jmap->CallMethods([ [ + 'ContactCard/set', + { + update => { + $id => { + kind => 'individual' + } + } + }, + "R2" + ] ]); + + $self->assert_not_null($res->[0][1]{notUpdated}{$id}); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("kind", + $res->[0][1]{notUpdated}{$id}{properties}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_apple_label_handling b/cassandane/tiny-tests/JMAPContacts/contact_apple_label_handling index 997b3d29e2..584fd12a17 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_apple_label_handling +++ b/cassandane/tiny-tests/JMAPContacts/contact_apple_label_handling @@ -2,27 +2,27 @@ use Cassandane::Tiny; sub test_contact_apple_label_handling - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "create a contact with 3 labels: unassociated, shared, & unshared"; - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['addresses', 'emails', 'phones'], - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => [ 'addresses', 'emails', 'phones' ], + }, + 'R1' + ] ]); - $id = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($id); - $self->assert_equals("foo", $res->[0][1]{list}[0]{addresses}[0]{label}); - $self->assert_equals("foo", $res->[0][1]{list}[0]{emails}[0]{label}); - $self->assert_equals("bar", $res->[0][1]{list}[0]{phones}[0]{label}); + $id = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($id); + $self->assert_equals("foo", $res->[0][1]{list}[0]{addresses}[0]{label}); + $self->assert_equals("foo", $res->[0][1]{list}[0]{emails}[0]{label}); + $self->assert_equals("bar", $res->[0][1]{list}[0]{phones}[0]{label}); - xlog $self, "update contact"; - $res = $jmap->CallMethods([['Contact/set', { - update => { - $id => { - emails => [{ - type => "work", - label => undef, - value => "bubba\@local" - }, - { - type => "work", - label => "aaa", - value => "shrimp\@local" - }, - { - type => "personal", - label => "bbb", - value => "gump\@local" - }], - phones => [{ - type => "home", - label => undef, - value => "tel:+1-555-555-5555" - }] + xlog $self, "update contact"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + emails => [ + { + type => "work", + label => undef, + value => "bubba\@local" + }, + { + type => "work", + label => "aaa", + value => "shrimp\@local" + }, + { + type => "personal", + label => "bbb", + value => "gump\@local" } + ], + phones => [ { + type => "home", + label => undef, + value => "tel:+1-555-555-5555" + } ] } - }, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['addresses', 'emails', 'phones', 'blobId'], - }, 'R1'] - ]); + $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => [ 'addresses', 'emails', 'phones', 'blobId' ], + }, + 'R1' + ] ]); - $self->assert_equals("foo", $res->[0][1]{list}[0]{addresses}[0]{label}); - $self->assert_null($res->[0][1]{list}[0]{emails}[0]{label}); - $self->assert_null($res->[0][1]{list}[0]{phones}[0]{label}); + $self->assert_equals("foo", $res->[0][1]{list}[0]{addresses}[0]{label}); + $self->assert_null($res->[0][1]{list}[0]{emails}[0]{label}); + $self->assert_null($res->[0][1]{list}[0]{phones}[0]{label}); - xlog $self, "download and check content"; - my $blob = $jmap->Download({ accept => 'text/vcard' }, - 'cassandane', $res->[0][1]{list}[0]{blobId}); + xlog $self, "download and check content"; + my $blob = $jmap->Download({ accept => 'text/vcard' }, + 'cassandane', $res->[0][1]{list}[0]{blobId}); - $self->assert_matches(qr/X-ABLabel:this-should-not-crash-cyrus/, - $blob->{content}); + $self->assert_matches(qr/X-ABLabel:this-should-not-crash-cyrus/, + $blob->{content}); - $self->assert_matches(qr/foo\.X-ABLabel/, $blob->{content}); - $self->assert_matches(qr/foo\.ADR/, $blob->{content}); + $self->assert_matches(qr/foo\.X-ABLabel/, $blob->{content}); + $self->assert_matches(qr/foo\.ADR/, $blob->{content}); - $self->assert_null(grep { m/foo\.EMAIL/ } $blob->{content}); - $self->assert_null(grep { m/email0\./ } $blob->{content}); - $self->assert_matches(qr/email1\.X-ABLabel/, $blob->{content}); - $self->assert_matches(qr/email1\.EMAIL/, $blob->{content}); - $self->assert_matches(qr/email2\.X-ABLabel/, $blob->{content}); - $self->assert_matches(qr/email2\.EMAIL/, $blob->{content}); + $self->assert_null(grep { m/foo\.EMAIL/ } $blob->{content}); + $self->assert_null(grep { m/email0\./ } $blob->{content}); + $self->assert_matches(qr/email1\.X-ABLabel/, $blob->{content}); + $self->assert_matches(qr/email1\.EMAIL/, $blob->{content}); + $self->assert_matches(qr/email2\.X-ABLabel/, $blob->{content}); + $self->assert_matches(qr/email2\.EMAIL/, $blob->{content}); - $self->assert_null(grep { m/bar\.X-ABLabel/ } $blob->{content}); - $self->assert_null(grep { m/bar\.TEL/ } $blob->{content}); + $self->assert_null(grep { m/bar\.X-ABLabel/ } $blob->{content}); + $self->assert_null(grep { m/bar\.TEL/ } $blob->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_blobid b/cassandane/tiny-tests/JMAPContacts/contact_blobid index 8cf6458569..7caaa5ff3e 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_blobid +++ b/cassandane/tiny-tests/JMAPContacts/contact_blobid @@ -2,40 +2,49 @@ use Cassandane::Tiny; sub test_contact_blobid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contact"; - my $res = $jmap->CallMethods([['Contact/set', {create => { + xlog $self, "create contact"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { "1" => { firstName => "foo", lastName => "last1" }, - }}, "R1"]]); - my $contactId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($contactId); + } + }, + "R1" + ] ]); + my $contactId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($contactId); - xlog $self, "get contact blobId"; - $res = $jmap->CallMethods([ - ['Contact/get', { - ids => [$contactId], - properties => ['blobId'], - }, 'R2'] - ]); + xlog $self, "get contact blobId"; + $res = $jmap->CallMethods([ [ + 'Contact/get', + { + ids => [$contactId], + properties => ['blobId'], + }, + 'R2' + ] ]); - # fetch a second time to make sure this works with a cached response - $res = $jmap->CallMethods([ - ['Contact/get', { - ids => [$contactId], - properties => ['blobId'], - }, 'R2'] - ]); - my $blobId = $res->[0][1]{list}[0]{blobId}; - $self->assert_not_null($blobId); + # fetch a second time to make sure this works with a cached response + $res = $jmap->CallMethods([ [ + 'Contact/get', + { + ids => [$contactId], + properties => ['blobId'], + }, + 'R2' + ] ]); + my $blobId = $res->[0][1]{list}[0]{blobId}; + $self->assert_not_null($blobId); - xlog $self, "download blob"; + xlog $self, "download blob"; - $res = $jmap->Download('cassandane', $blobId); - $self->assert_str_equals("BEGIN:VCARD", substr($res->{content}, 0, 11)); - $self->assert_num_not_equals(-1, index($res->{content}, 'FN:foo last1')); + $res = $jmap->Download('cassandane', $blobId); + $self->assert_str_equals("BEGIN:VCARD", substr($res->{content}, 0, 11)); + $self->assert_num_not_equals(-1, index($res->{content}, 'FN:foo last1')); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_changes b/cassandane/tiny-tests/JMAPContacts/contact_changes index c17541b40c..cc872c67f4 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_changes +++ b/cassandane/tiny-tests/JMAPContacts/contact_changes @@ -2,131 +2,174 @@ use Cassandane::Tiny; sub test_contact_changes - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get contacts"; - my $res = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - my $state = $res->[0][1]{state}; + xlog $self, "get contacts"; + my $res = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + my $state = $res->[0][1]{state}; - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['Contact/changes', { - sinceState => $state, - addressbookId => "Default", - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + sinceState => $state, + addressbookId => "Default", + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - xlog $self, "create contact 1"; - $res = $jmap->CallMethods([['Contact/set', {create => {"1" => {firstName => "first", lastName => "last"}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create contact 1"; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { create => { "1" => { firstName => "first", lastName => "last" } } }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['Contact/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $oldState = $state; - $state = $res->[0][1]{newState}; + my $oldState = $state; + $state = $res->[0][1]{newState}; - xlog $self, "create contact 2"; - $res = $jmap->CallMethods([['Contact/set', {create => {"2" => {firstName => "second", lastName => "prev"}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id2 = $res->[0][1]{created}{"2"}{id}; + xlog $self, "create contact 2"; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { create => { "2" => { firstName => "second", lastName => "prev" } } }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id2 = $res->[0][1]{created}{"2"}{id}; - xlog $self, "get contact updates (since last change)"; - $res = $jmap->CallMethods([['Contact/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; + xlog $self, "get contact updates (since last change)"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; - xlog $self, "get contact updates (in bulk)"; - $res = $jmap->CallMethods([['Contact/changes', { - sinceState => $oldState - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); + xlog $self, "get contact updates (in bulk)"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + sinceState => $oldState + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); - xlog $self, "get contact updates from initial state (maxChanges=1)"; - $res = $jmap->CallMethods([['Contact/changes', { - sinceState => $oldState, - maxChanges => 1 - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $interimState = $res->[0][1]{newState}; + xlog $self, "get contact updates from initial state (maxChanges=1)"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + sinceState => $oldState, + maxChanges => 1 + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + my $interimState = $res->[0][1]{newState}; - xlog $self, "get contact updates from interim state (maxChanges=10)"; - $res = $jmap->CallMethods([['Contact/changes', { - sinceState => $interimState, - maxChanges => 10 - }, "R2"]]); - $self->assert_str_equals($interimState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; + xlog $self, "get contact updates from interim state (maxChanges=10)"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + sinceState => $interimState, + maxChanges => 10 + }, + "R2" + ] ]); + $self->assert_str_equals($interimState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; - xlog $self, "destroy contact 1, update contact 2"; - $res = $jmap->CallMethods([['Contact/set', { - destroy => [$id1], - update => {$id2 => {firstName => "foo"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "destroy contact 1, update contact 2"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + destroy => [$id1], + update => { $id2 => { firstName => "foo" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['Contact/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - xlog $self, "destroy contact 2"; - $res = $jmap->CallMethods([['Contact/set', {destroy => [$id2]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "destroy contact 2"; + $res = $jmap->CallMethods([ [ 'Contact/set', { destroy => [$id2] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_changes_shared b/cassandane/tiny-tests/JMAPContacts/contact_changes_shared index f04463f06c..c1758e0f75 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_changes_shared +++ b/cassandane/tiny-tests/JMAPContacts/contact_changes_shared @@ -2,168 +2,214 @@ use Cassandane::Tiny; sub test_contact_changes_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "get contacts"; - my $res = $jmap->CallMethods([['Contact/get', { accountId => 'manifold' }, "R2"]]); - my $state = $res->[0][1]{state}; - - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['Contact/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - - xlog $self, "create contact 1"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => {"1" => {firstName => "first", lastName => "last"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['Contact/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - - my $oldState = $state; - $state = $res->[0][1]{newState}; - - xlog $self, "create contact 2"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => {"2" => {firstName => "second", lastName => "prev"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id2 = $res->[0][1]{created}{"2"}{id}; - - xlog $self, "get contact updates (since last change)"; - $res = $jmap->CallMethods([['Contact/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "get contact updates (in bulk)"; - $res = $jmap->CallMethods([['Contact/changes', { - accountId => 'manifold', - sinceState => $oldState - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get contact updates from initial state (maxChanges=1)"; - $res = $jmap->CallMethods([['Contact/changes', { - accountId => 'manifold', - sinceState => $oldState, - maxChanges => 1 - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $interimState = $res->[0][1]{newState}; - - xlog $self, "get contact updates from interim state (maxChanges=10)"; - $res = $jmap->CallMethods([['Contact/changes', { - accountId => 'manifold', - sinceState => $interimState, - maxChanges => 10 - }, "R2"]]); - $self->assert_str_equals($interimState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "destroy contact 1, update contact 2"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - destroy => [$id1], - update => {$id2 => {firstName => "foo"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - xlog $self, "get contact updates"; - $res = $jmap->CallMethods([['Contact/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - - xlog $self, "destroy contact 2"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - destroy => [$id2] - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "get contacts"; + my $res = $jmap->CallMethods( + [ [ 'Contact/get', { accountId => 'manifold' }, "R2" ] ]); + my $state = $res->[0][1]{state}; + + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + + xlog $self, "create contact 1"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { "1" => { firstName => "first", lastName => "last" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + + my $oldState = $state; + $state = $res->[0][1]{newState}; + + xlog $self, "create contact 2"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { "2" => { firstName => "second", lastName => "prev" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id2 = $res->[0][1]{created}{"2"}{id}; + + xlog $self, "get contact updates (since last change)"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "get contact updates (in bulk)"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + accountId => 'manifold', + sinceState => $oldState + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get contact updates from initial state (maxChanges=1)"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + accountId => 'manifold', + sinceState => $oldState, + maxChanges => 1 + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + my $interimState = $res->[0][1]{newState}; + + xlog $self, "get contact updates from interim state (maxChanges=10)"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + accountId => 'manifold', + sinceState => $interimState, + maxChanges => 10 + }, + "R2" + ] ]); + $self->assert_str_equals($interimState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "destroy contact 1, update contact 2"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + destroy => [$id1], + update => { $id2 => { firstName => "foo" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + xlog $self, "get contact updates"; + $res = $jmap->CallMethods([ [ + 'Contact/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + + xlog $self, "destroy contact 2"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + destroy => [$id2] + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_copy b/cassandane/tiny-tests/JMAPContacts/contact_copy index c73f83837d..61cf1ffb18 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_copy +++ b/cassandane/tiny-tests/JMAPContacts/contact_copy @@ -2,216 +2,251 @@ use Cassandane::Tiny; sub test_contact_copy - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared accounts"; - $admintalk->create("user.other"); - $admintalk->create("user.other2"); - $admintalk->create("user.other3"); - - my $othercarddav = Net::CardDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $other2carddav = Net::CardDAVTalk->new( - user => "other2", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - my $other3carddav = Net::CardDAVTalk->new( - user => "other3", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - xlog $self, "share addressbooks"; - $admintalk->setacl("user.other.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; - $admintalk->setacl("user.other2.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; - $admintalk->setacl("user.other3.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; - - # avatar - xlog $self, "upload avatar"; - my $data = "some photo"; - my $res = $jmap->Upload($data, "image/jpeg"); - my $blobid = $res->{blobId}; - - my $card = { - "addressbookId" => "Default", - "firstName"=> "foo", - "lastName"=> "bar", - "avatar" => { - "blobId" => $blobid, - "size" => 10, - "type" => "image/jpeg", - "name" => JSON::null - } - }; - - xlog $self, "create card"; - $res = $jmap->CallMethods([['Contact/set',{ - create => {"1" => $card}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - my $cardId = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "copy card $cardId w/o changes"; - $res = $jmap->CallMethods([['Contact/copy', { - fromAccountId => 'cassandane', + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared accounts"; + $admintalk->create("user.other"); + $admintalk->create("user.other2"); + $admintalk->create("user.other3"); + + my $othercarddav = Net::CardDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $other2carddav = Net::CardDAVTalk->new( + user => "other2", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + my $other3carddav = Net::CardDAVTalk->new( + user => "other3", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "share addressbooks"; + $admintalk->setacl("user.other.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + $admintalk->setacl("user.other2.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + $admintalk->setacl("user.other3.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + # avatar + xlog $self, "upload avatar"; + my $data = "some photo"; + my $res = $jmap->Upload($data, "image/jpeg"); + my $blobid = $res->{blobId}; + + my $card = { + "addressbookId" => "Default", + "firstName" => "foo", + "lastName" => "bar", + "avatar" => { + "blobId" => $blobid, + "size" => 10, + "type" => "image/jpeg", + "name" => JSON::null + } + }; + + xlog $self, "create card"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { "1" => $card } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $cardId = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "copy card $cardId w/o changes"; + $res = $jmap->CallMethods([ [ + 'Contact/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $cardId, + addressbookId => "Default", + }, + }, + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $copiedCardId = $res->[0][1]{created}{"1"}{id}; + + $res = $jmap->CallMethods([ + [ + 'Contact/get', + { accountId => 'other', - create => { - 1 => { - id => $cardId, - addressbookId => "Default", - }, + ids => [$copiedCardId], + }, + 'R1' + ], + [ + 'Contact/get', + { + accountId => undef, + ids => [$cardId], + }, + 'R2' + ], + ]); + $self->assert_str_equals('foo', $res->[0][1]{list}[0]{firstName}); + my $blob = $jmap->Download({ accept => 'image/jpeg' }, + 'other', $res->[0][1]{list}[0]{avatar}{blobId}); + $self->assert_str_equals('image/jpeg', $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_equals($data, $blob->{content}); + + $self->assert_str_equals('foo', $res->[1][1]{list}[0]{firstName}); + $blob = $jmap->Download({ accept => 'image/jpeg' }, + 'cassandane', $res->[1][1]{list}[0]{avatar}{blobId}); + $self->assert_str_equals('image/jpeg', $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_equals($data, $blob->{content}); + + xlog $self, "move card $cardId with changes"; + $res = $jmap->CallMethods([ [ + 'Contact/copy', + { + fromAccountId => 'cassandane', + accountId => 'other2', + create => { + 1 => { + id => $cardId, + addressbookId => "Default", + avatar => JSON::null, + nickname => "xxxxx" }, + } }, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - my $copiedCardId = $res->[0][1]{created}{"1"}{id}; - - $res = $jmap->CallMethods([ - ['Contact/get', { - accountId => 'other', - ids => [$copiedCardId], - }, 'R1'], - ['Contact/get', { - accountId => undef, - ids => [$cardId], - }, 'R2'], - ]); - $self->assert_str_equals('foo', $res->[0][1]{list}[0]{firstName}); - my $blob = $jmap->Download({ accept => 'image/jpeg' }, - 'other', $res->[0][1]{list}[0]{avatar}{blobId}); - $self->assert_str_equals('image/jpeg', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_equals($data, $blob->{content}); - - $self->assert_str_equals('foo', $res->[1][1]{list}[0]{firstName}); - $blob = $jmap->Download({ accept => 'image/jpeg' }, - 'cassandane', $res->[1][1]{list}[0]{avatar}{blobId}); - $self->assert_str_equals('image/jpeg', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_equals($data, $blob->{content}); - - xlog $self, "move card $cardId with changes"; - $res = $jmap->CallMethods([['Contact/copy', { - fromAccountId => 'cassandane', + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + $copiedCardId = $res->[0][1]{created}{"1"}{id}; + + $res = $jmap->CallMethods([ + [ + 'Contact/get', + { accountId => 'other2', - create => { - 1 => { - id => $cardId, - addressbookId => "Default", - avatar => JSON::null, - nickname => "xxxxx" - }, - } - }, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - $copiedCardId = $res->[0][1]{created}{"1"}{id}; - - $res = $jmap->CallMethods([ - ['Contact/get', { - accountId => 'other2', - ids => [$copiedCardId], - }, 'R1'], - ['Contact/get', { - accountId => undef, - ids => [$cardId], - }, 'R2'], - ]); - $self->assert_str_equals('foo', $res->[0][1]{list}[0]{firstName}); - $self->assert_str_equals('xxxxx', $res->[0][1]{list}[0]{nickname}); - $self->assert_null($res->[0][1]{list}[0]{avatar}); - $self->assert_str_equals('foo', $res->[1][1]{list}[0]{firstName}); - - my $other3Jmap = Mail::JMAPTalk->new( - user => 'other3', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $other3Jmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); - - # avatar - xlog $self, "upload avatar for other3"; - $data = "some other photo"; - $res = $other3Jmap->Upload($data, "image/jpeg"); - $blobid = $res->{blobId}; - - $admintalk->setacl("user.other3.#jmap", - "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "move card $cardId with different avatar"; - $res = $jmap->CallMethods([['Contact/copy', { - fromAccountId => 'cassandane', - accountId => 'other3', - create => { - 1 => { - id => $cardId, - addressbookId => "Default", - avatar => { - blobId => "$blobid", - size => 16, - type => "image/jpeg", - name => JSON::null - } - }, + ids => [$copiedCardId], + }, + 'R1' + ], + [ + 'Contact/get', + { + accountId => undef, + ids => [$cardId], + }, + 'R2' + ], + ]); + $self->assert_str_equals('foo', $res->[0][1]{list}[0]{firstName}); + $self->assert_str_equals('xxxxx', $res->[0][1]{list}[0]{nickname}); + $self->assert_null($res->[0][1]{list}[0]{avatar}); + $self->assert_str_equals('foo', $res->[1][1]{list}[0]{firstName}); + + my $other3Jmap = Mail::JMAPTalk->new( + user => 'other3', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $other3Jmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'https://cyrusimap.org/ns/jmap/calendars', + ]); + + # avatar + xlog $self, "upload avatar for other3"; + $data = "some other photo"; + $res = $other3Jmap->Upload($data, "image/jpeg"); + $blobid = $res->{blobId}; + + $admintalk->setacl("user.other3.#jmap", "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "move card $cardId with different avatar"; + $res = $jmap->CallMethods([ [ + 'Contact/copy', + { + fromAccountId => 'cassandane', + accountId => 'other3', + create => { + 1 => { + id => $cardId, + addressbookId => "Default", + avatar => { + blobId => "$blobid", + size => 16, + type => "image/jpeg", + name => JSON::null + } }, - onSuccessDestroyOriginal => JSON::true, + }, + onSuccessDestroyOriginal => JSON::true, }, - "R1"]]); - $self->assert_not_null($res->[0][1]{created}); - $copiedCardId = $res->[0][1]{created}{"1"}{id}; - - $res = $jmap->CallMethods([ - ['Contact/get', { - accountId => 'other3', - ids => [$copiedCardId], - }, 'R1'], - ['Contact/get', { - accountId => undef, - ids => [$cardId], - }, 'R2'], - ]); - $self->assert_str_equals('foo', $res->[0][1]{list}[0]{firstName}); - $blob = $jmap->Download({ accept => 'image/jpeg' }, - 'other3', $res->[0][1]{list}[0]{avatar}{blobId}); - $self->assert_str_equals('image/jpeg', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_equals($data, $blob->{content}); - - $self->assert_str_equals($cardId, $res->[1][1]{notFound}[0]); + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + $copiedCardId = $res->[0][1]{created}{"1"}{id}; + + $res = $jmap->CallMethods([ + [ + 'Contact/get', + { + accountId => 'other3', + ids => [$copiedCardId], + }, + 'R1' + ], + [ + 'Contact/get', + { + accountId => undef, + ids => [$cardId], + }, + 'R2' + ], + ]); + $self->assert_str_equals('foo', $res->[0][1]{list}[0]{firstName}); + $blob = $jmap->Download({ accept => 'image/jpeg' }, + 'other3', $res->[0][1]{list}[0]{avatar}{blobId}); + $self->assert_str_equals('image/jpeg', $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_equals($data, $blob->{content}); + + $self->assert_str_equals($cardId, $res->[1][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_copy_overquota b/cassandane/tiny-tests/JMAPContacts/contact_copy_overquota index 7d02e1a5b5..7820494f31 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_copy_overquota +++ b/cassandane/tiny-tests/JMAPContacts/contact_copy_overquota @@ -2,57 +2,63 @@ use Cassandane::Tiny; sub test_contact_copy_overquota - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared accounts"; - $admintalk->create("user.other"); - - my $othercarddav = Net::CardDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl('user.other.#addressbooks.Default', - 'cassandane' => 'lrswipkxtecdn') or die; - - $self->_set_quotaroot('user.other.#addressbooks'); - $self->_set_quotalimits(storage => 1); - - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - 1 => { - lastName => 'name', - notes => ('x' x 1024), - }, - }, - }, 'R1'], - ]); - my $contactId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($contactId); - - $res = $jmap->CallMethods([ - ['Contact/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 2 => { - id => $contactId, - }, - }, - }, 'R1'] - ]); - $self->assert_str_equals('overQuota', $res->[0][1]{notCreated}{2}{type}); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared accounts"; + $admintalk->create("user.other"); + + my $othercarddav = Net::CardDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl('user.other.#addressbooks.Default', + 'cassandane' => 'lrswipkxtecdn') + or die; + + $self->_set_quotaroot('user.other.#addressbooks'); + $self->_set_quotalimits(storage => 1); + + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + 1 => { + lastName => 'name', + notes => ('x' x 1024), + }, + }, + }, + 'R1' + ], + ]); + my $contactId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($contactId); + + $res = $jmap->CallMethods([ [ + 'Contact/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 2 => { + id => $contactId, + }, + }, + }, + 'R1' + ] ]); + $self->assert_str_equals('overQuota', $res->[0][1]{notCreated}{2}{type}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_copy_state b/cassandane/tiny-tests/JMAPContacts/contact_copy_state index 736cce7bf0..a33a7d046f 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_copy_state +++ b/cassandane/tiny-tests/JMAPContacts/contact_copy_state @@ -2,91 +2,106 @@ use Cassandane::Tiny; sub test_contact_copy_state - :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.other"); + xlog $self, "create shared account"; + $admintalk->create("user.other"); - my $othercarddav = Net::CardDAVTalk->new( - user => "other", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $othercarddav = Net::CardDAVTalk->new( + user => "other", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "share addressbook"; - $admintalk->setacl("user.other.#addressbooks.Default", - "cassandane" => 'lrswipkxtecdn') or die; + xlog $self, "share addressbook"; + $admintalk->setacl("user.other.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; - my $card = { - "addressbookId" => "Default", - "firstName"=> "foo", - "lastName"=> "bar", - }; + my $card = { + "addressbookId" => "Default", + "firstName" => "foo", + "lastName" => "bar", + }; - xlog $self, "create card"; - $res = $jmap->CallMethods([ - ['Contact/set', { - create => {"1" => $card} - }, "R1"], - ['Contact/get', { - accountId => 'other', - ids => ['foo'], # Just fetching current state for 'other' - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}); - my $cardId = $res->[0][1]{created}{"1"}{id}; - my $fromState = $res->[0][1]->{newState}; - $self->assert_not_null($fromState); - my $state = $res->[1][1]->{state}; - $self->assert_not_null($state); + xlog $self, "create card"; + $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { "1" => $card } + }, + "R1" + ], + [ + 'Contact/get', + { + accountId => 'other', + ids => ['foo'], # Just fetching current state for 'other' + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}); + my $cardId = $res->[0][1]{created}{"1"}{id}; + my $fromState = $res->[0][1]->{newState}; + $self->assert_not_null($fromState); + my $state = $res->[1][1]->{state}; + $self->assert_not_null($state); - xlog $self, "move card"; - $res = $jmap->CallMethods([ - ['Contact/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - ifFromInState => $fromState, - ifInState => $state, - create => { - 1 => { - id => $cardId, - addressbookId => "Default", - }, - }, - onSuccessDestroyOriginal => JSON::true, - destroyFromIfInState => $fromState, - }, "R1"], - ['Contact/get', { - accountId => 'other', - ids => ['#1'], - properties => ['firstName'], - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}); - my $oldState = $res->[0][1]->{oldState}; - $self->assert_str_equals($oldState, $state); - my $newState = $res->[0][1]->{newState}; - $self->assert_not_null($newState); - $self->assert_str_equals('Contact/set', $res->[1][0]); - $self->assert_str_equals($cardId, $res->[1][1]{destroyed}[0]); - $self->assert_str_equals('foo', $res->[2][1]{list}[0]{firstName}); + xlog $self, "move card"; + $res = $jmap->CallMethods([ + [ + 'Contact/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + ifFromInState => $fromState, + ifInState => $state, + create => { + 1 => { + id => $cardId, + addressbookId => "Default", + }, + }, + onSuccessDestroyOriginal => JSON::true, + destroyFromIfInState => $fromState, + }, + "R1" + ], + [ + 'Contact/get', + { + accountId => 'other', + ids => ['#1'], + properties => ['firstName'], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}); + my $oldState = $res->[0][1]->{oldState}; + $self->assert_str_equals($oldState, $state); + my $newState = $res->[0][1]->{newState}; + $self->assert_not_null($newState); + $self->assert_str_equals('Contact/set', $res->[1][0]); + $self->assert_str_equals($cardId, $res->[1][1]{destroyed}[0]); + $self->assert_str_equals('foo', $res->[2][1]{list}[0]{firstName}); - # Is the blobId downloadable? - my $blob = $jmap->Download({ accept => 'text/vcard' }, - 'other', - $res->[0][1]{created}{"1"}{blobId}); - $self->assert_str_equals('text/vcard; version=3.0', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_matches(qr/\r\nFN:foo bar\r\n/, $blob->{content}); + # Is the blobId downloadable? + my $blob = $jmap->Download({ accept => 'text/vcard' }, + 'other', $res->[0][1]{created}{"1"}{blobId}); + $self->assert_str_equals('text/vcard; version=3.0', + $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_matches(qr/\r\nFN:foo bar\r\n/, $blob->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_get_apple_countrycode b/cassandane/tiny-tests/JMAPContacts/contact_get_apple_countrycode index 8b8640e609..c868c0a4d2 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_get_apple_countrycode +++ b/cassandane/tiny-tests/JMAPContacts/contact_get_apple_countrycode @@ -2,26 +2,25 @@ use Cassandane::Tiny; sub test_contact_get_apple_countrycode - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['addresses'] - }, 'R1'] - ]); - $self->assert_str_equals('us', $res->[0][1]{list}[0]{addresses}[0]{countryCode}); - $self->assert_str_equals('xyz', $res->[0][1]{list}[0]{addresses}[0]{label}); - $self->assert_str_equals('de', $res->[0][1]{list}[0]{addresses}[1]{countryCode}); + my $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => ['addresses'] + }, + 'R1' + ] ]); + $self->assert_str_equals('us', + $res->[0][1]{list}[0]{addresses}[0]{countryCode}); + $self->assert_str_equals('xyz', $res->[0][1]{list}[0]{addresses}[0]{label}); + $self->assert_str_equals('de', + $res->[0][1]{list}[0]{addresses}[1]{countryCode}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_get_avatar_v4 b/cassandane/tiny-tests/JMAPContacts/contact_get_avatar_v4 index b45a42d512..04e6e02346 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_get_avatar_v4 +++ b/cassandane/tiny-tests/JMAPContacts/contact_get_avatar_v4 @@ -2,27 +2,26 @@ use Cassandane::Tiny; sub test_contact_get_avatar_v4 - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - xlog $self, "create a v4 contact with a photo"; - my $id = '816ad14a-f9ef-43a8-9039-b57bf321de1f'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['avatar', 'x-hasPhoto'], - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => [ 'avatar', 'x-hasPhoto' ], + }, + 'R1' + ] ]); - $self->assert_not_null($res->[0][1]{list}[0]{id}); - $self->assert_not_null($res->[0][1]{list}[0]{avatar}); - $self->assert_equals("image/png", $res->[0][1]{list}[0]{avatar}{type}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{'x-hasPhoto'}); + $self->assert_not_null($res->[0][1]{list}[0]{id}); + $self->assert_not_null($res->[0][1]{list}[0]{avatar}); + $self->assert_equals("image/png", $res->[0][1]{list}[0]{avatar}{type}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{'x-hasPhoto'}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_get_invalid_utf8 b/cassandane/tiny-tests/JMAPContacts/contact_get_invalid_utf8 index 1f93950538..2213369eb3 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_get_invalid_utf8 +++ b/cassandane/tiny-tests/JMAPContacts/contact_get_invalid_utf8 @@ -2,30 +2,37 @@ use Cassandane::Tiny; sub test_contact_get_invalid_utf8 - :min_version_3_3 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['emails'], - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => ['emails'], + }, + 'R1' + ] ]); - my $datadir = $self->{instance}->folder_to_directory("user.cassandane.#addressbooks.Default"); - copy('data/vcard/invalid-utf8.eml', "$datadir/1.") or die; - $self->{instance}->run_command({ cyrus => 1 }, - 'reconstruct', 'user.cassandane.#addressbooks.Default'); + my $datadir = $self->{instance} + ->folder_to_directory("user.cassandane.#addressbooks.Default"); + copy('data/vcard/invalid-utf8.eml', "$datadir/1.") or die; + $self->{instance}->run_command({ cyrus => 1 }, + 'reconstruct', 'user.cassandane.#addressbooks.Default'); - $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['emails'], - }, 'R1'] - ]); - $self->assert_deep_equals([{ - type => 'work', - value => "beno\N{REPLACEMENT CHARACTER}t\@local", - isDefault => JSON::true, - }], $res->[0][1]{list}[0]{emails}); + $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => ['emails'], + }, + 'R1' + ] ]); + $self->assert_deep_equals( + [ { + type => 'work', + value => "beno\N{REPLACEMENT CHARACTER}t\@local", + isDefault => JSON::true, + } ], + $res->[0][1]{list}[0]{emails} + ); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_get_issue2292 b/cassandane/tiny-tests/JMAPContacts/contact_get_issue2292 index ea7911ccf5..354b4767cb 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_get_issue2292 +++ b/cassandane/tiny-tests/JMAPContacts/contact_get_issue2292 @@ -2,27 +2,32 @@ use Cassandane::Tiny; sub test_contact_get_issue2292 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contact"; - my $res = $jmap->CallMethods([['Contact/set', {create => { + xlog $self, "create contact"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { "1" => { firstName => "foo", lastName => "last1" }, - }}, "R1"]]); - $self->assert_not_null($res->[0][1]{created}{"1"}); + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}{"1"}); - xlog $self, "get contact with no ids"; - $res = $jmap->CallMethods([['Contact/get', { }, "R3"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); + xlog $self, "get contact with no ids"; + $res = $jmap->CallMethods([ [ 'Contact/get', {}, "R3" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); - xlog $self, "get contact with empty ids"; - $res = $jmap->CallMethods([['Contact/get', { ids => [] }, "R3"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + xlog $self, "get contact with empty ids"; + $res = $jmap->CallMethods([ [ 'Contact/get', { ids => [] }, "R3" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); - xlog $self, "get contact with null ids"; - $res = $jmap->CallMethods([['Contact/get', { ids => undef }, "R3"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); + xlog $self, "get contact with null ids"; + $res = $jmap->CallMethods([ [ 'Contact/get', { ids => undef }, "R3" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_get_with_addressbookid b/cassandane/tiny-tests/JMAPContacts/contact_get_with_addressbookid index b10cc035ab..98efb59147 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_get_with_addressbookid +++ b/cassandane/tiny-tests/JMAPContacts/contact_get_with_addressbookid @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_contact_get_with_addressbookid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get contact with addressbookid"; - my $res = $jmap->CallMethods([['Contact/get', - { addressbookId => "Default" }, "R3"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + xlog $self, "get contact with addressbookid"; + my $res = $jmap->CallMethods([ [ + 'Contact/get', { addressbookId => "Default" }, "R3" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_query b/cassandane/tiny-tests/JMAPContacts/contact_query index fa231fe3ec..d744001005 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_query +++ b/cassandane/tiny-tests/JMAPContacts/contact_query @@ -2,173 +2,246 @@ use Cassandane::Tiny; sub test_contact_query - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', {create => { - "1" => - { - firstName => "foo", lastName => "last", - emails => [{ - type => "personal", - value => "foo\@example.com" - }] - }, - "2" => - { - firstName => "bar", lastName => "last", - emails => [{ - type => "work", - value => "bar\@bar.org" - }, { - type => "other", - value => "me\@example.com" - }], - addresses => [{ - type => "home", - label => undef, - street => "Some Lane 24", - locality => "SomeWhere City", - region => "", - postcode => "1234", - country => "Someinistan", - isDefault => JSON::false - }], - isFlagged => JSON::true - }, - "3" => - { - firstName => "baz", lastName => "last", - addresses => [{ - type => "home", - label => undef, - street => "Some Lane 12", - locality => "SomeWhere City", - region => "", - postcode => "1234", - country => "Someinistan", - isDefault => JSON::false - }] - }, - "4" => {firstName => "bam", lastName => "last", - isFlagged => JSON::false } - }}, "R1"]]); - - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; - my $id3 = $res->[0][1]{created}{"3"}{id}; - my $id4 = $res->[0][1]{created}{"4"}{id}; - - xlog $self, "create contact groups"; - $res = $jmap->CallMethods([['ContactGroup/set', {create => { - "1" => {name => "group1", contactIds => [$id1, $id2]}, - "2" => {name => "group2", contactIds => [$id3]}, - "3" => {name => "group3", contactIds => [$id4]} - }}, "R1"]]); - - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $group1 = $res->[0][1]{created}{"1"}{id}; - my $group2 = $res->[0][1]{created}{"2"}{id}; - my $group3 = $res->[0][1]{created}{"3"}{id}; - - xlog $self, "get unfiltered contact list"; - $res = $jmap->CallMethods([ ['Contact/query', { }, "R1"] ]); - - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by firstName"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { firstName => "foo" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - - xlog $self, "filter by lastName"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { lastName => "last" } - }, "R1"] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by firstName and lastName (one filter)"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { firstName => "bam", lastName => "last" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); - - xlog $self, "filter by firstName and lastName (AND filter)"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { operator => "AND", conditions => [{ - lastName => "last" - }, { - firstName => "baz" - }]} - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); - - xlog $self, "filter by firstName (OR filter)"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { operator => "OR", conditions => [{ - firstName => "bar" - }, { - firstName => "baz" - }]} - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by text"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { text => "some" } - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by email"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { email => "example.com" } - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by isFlagged (true)"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { isFlagged => JSON::true } - }, "R1"] ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id2, $res->[0][1]{ids}[0]); - - xlog $self, "filter by isFlagged (false)"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { isFlagged => JSON::false } - }, "R1"] ]); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by inContactGroup"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { inContactGroup => [$group1, $group3] } - }, "R1"] ]); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by inContactGroup and firstName"; - $res = $jmap->CallMethods([ ['Contact/query', { - filter => { inContactGroup => [$group1, $group3], firstName => "foo" } - }, "R1"] ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "1" => { + firstName => "foo", + lastName => "last", + emails => [ { + type => "personal", + value => "foo\@example.com" + } ] + }, + "2" => { + firstName => "bar", + lastName => "last", + emails => [ + { + type => "work", + value => "bar\@bar.org" + }, + { + type => "other", + value => "me\@example.com" + } + ], + addresses => [ { + type => "home", + label => undef, + street => "Some Lane 24", + locality => "SomeWhere City", + region => "", + postcode => "1234", + country => "Someinistan", + isDefault => JSON::false + } ], + isFlagged => JSON::true + }, + "3" => { + firstName => "baz", + lastName => "last", + addresses => [ { + type => "home", + label => undef, + street => "Some Lane 12", + locality => "SomeWhere City", + region => "", + postcode => "1234", + country => "Someinistan", + isDefault => JSON::false + } ] + }, + "4" => { + firstName => "bam", + lastName => "last", + isFlagged => JSON::false + } + } + }, + "R1" + ] ]); + + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; + my $id3 = $res->[0][1]{created}{"3"}{id}; + my $id4 = $res->[0][1]{created}{"4"}{id}; + + xlog $self, "create contact groups"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + create => { + "1" => { name => "group1", contactIds => [ $id1, $id2 ] }, + "2" => { name => "group2", contactIds => [$id3] }, + "3" => { name => "group3", contactIds => [$id4] } + } + }, + "R1" + ] ]); + + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $group1 = $res->[0][1]{created}{"1"}{id}; + my $group2 = $res->[0][1]{created}{"2"}{id}; + my $group3 = $res->[0][1]{created}{"3"}{id}; + + xlog $self, "get unfiltered contact list"; + $res = $jmap->CallMethods([ [ 'Contact/query', {}, "R1" ] ]); + + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by firstName"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { firstName => "foo" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + + xlog $self, "filter by lastName"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { lastName => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by firstName and lastName (one filter)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { firstName => "bam", lastName => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); + + xlog $self, "filter by firstName and lastName (AND filter)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { + operator => "AND", + conditions => [ + { + lastName => "last" + }, + { + firstName => "baz" + } + ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); + + xlog $self, "filter by firstName (OR filter)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { + operator => "OR", + conditions => [ + { + firstName => "bar" + }, + { + firstName => "baz" + } + ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by text"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { text => "some" } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by email"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { email => "example.com" } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by isFlagged (true)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { isFlagged => JSON::true } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id2, $res->[0][1]{ids}[0]); + + xlog $self, "filter by isFlagged (false)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { isFlagged => JSON::false } + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by inContactGroup"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { inContactGroup => [ $group1, $group3 ] } + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by inContactGroup and firstName"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + filter => { inContactGroup => [ $group1, $group3 ], firstName => "foo" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_query_shared b/cassandane/tiny-tests/JMAPContacts/contact_query_shared index 69abf6bcee..da7ca940f6 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_query_shared +++ b/cassandane/tiny-tests/JMAPContacts/contact_query_shared @@ -2,211 +2,285 @@ use Cassandane::Tiny; sub test_contact_query_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => { - "1" => - { - firstName => "foo", lastName => "last", - emails => [{ - type => "personal", - value => "foo\@example.com" - }] - }, - "2" => - { - firstName => "bar", lastName => "last", - emails => [{ - type => "work", - value => "bar\@bar.org" - }, { - type => "other", - value => "me\@example.com" - }], - addresses => [{ - type => "home", - label => undef, - street => "Some Lane 24", - locality => "SomeWhere City", - region => "", - postcode => "1234", - country => "Someinistan", - isDefault => JSON::false - }], - isFlagged => JSON::true - }, - "3" => - { - firstName => "baz", lastName => "last", - addresses => [{ - type => "home", - label => undef, - street => "Some Lane 12", - locality => "SomeWhere City", - region => "", - postcode => "1234", - country => "Someinistan", - isDefault => JSON::false - }] - }, - "4" => {firstName => "bam", lastName => "last", - isFlagged => JSON::false } - }}, "R1"]]); - - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; - my $id3 = $res->[0][1]{created}{"3"}{id}; - my $id4 = $res->[0][1]{created}{"4"}{id}; - - xlog $self, "create contact groups"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - create => { - "1" => {name => "group1", contactIds => [$id1, $id2]}, - "2" => {name => "group2", contactIds => [$id3]}, - "3" => {name => "group3", contactIds => [$id4]} - }}, "R1"]]); - - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $group1 = $res->[0][1]{created}{"1"}{id}; - my $group2 = $res->[0][1]{created}{"2"}{id}; - my $group3 = $res->[0][1]{created}{"3"}{id}; - - xlog $self, "get unfiltered contact list"; - $res = $jmap->CallMethods([ ['Contact/query', { accountId => 'manifold' }, "R1"] ]); - - xlog $self, "check total"; - $self->assert_num_equals(4, $res->[0][1]{total}); - xlog $self, "check ids"; - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by firstName"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { firstName => "foo" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); - - xlog $self, "filter by lastName"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { lastName => "last" } - }, "R1"] ]); - $self->assert_num_equals(4, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by firstName and lastName (one filter)"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { firstName => "bam", lastName => "last" } - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); - - xlog $self, "filter by firstName and lastName (AND filter)"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { operator => "AND", conditions => [{ - lastName => "last" - }, { - firstName => "baz" - }]} - }, "R1"] ]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); - - xlog $self, "filter by firstName (OR filter)"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { operator => "OR", conditions => [{ - firstName => "bar" - }, { - firstName => "baz" - }]} - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by text"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { text => "some" } - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by email"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { email => "example.com" } - }, "R1"] ]); - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by isFlagged (true)"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { isFlagged => JSON::true } - }, "R1"] ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id2, $res->[0][1]{ids}[0]); - - xlog $self, "filter by isFlagged (false)"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { isFlagged => JSON::false } - }, "R1"] ]); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by inContactGroup"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { inContactGroup => [$group1, $group3] } - }, "R1"] ]); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - - xlog $self, "filter by inContactGroup and firstName"; - $res = $jmap->CallMethods([ ['Contact/query', { - accountId => 'manifold', - filter => { inContactGroup => [$group1, $group3], firstName => "foo" } - }, "R1"] ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { + "1" => { + firstName => "foo", + lastName => "last", + emails => [ { + type => "personal", + value => "foo\@example.com" + } ] + }, + "2" => { + firstName => "bar", + lastName => "last", + emails => [ + { + type => "work", + value => "bar\@bar.org" + }, + { + type => "other", + value => "me\@example.com" + } + ], + addresses => [ { + type => "home", + label => undef, + street => "Some Lane 24", + locality => "SomeWhere City", + region => "", + postcode => "1234", + country => "Someinistan", + isDefault => JSON::false + } ], + isFlagged => JSON::true + }, + "3" => { + firstName => "baz", + lastName => "last", + addresses => [ { + type => "home", + label => undef, + street => "Some Lane 12", + locality => "SomeWhere City", + region => "", + postcode => "1234", + country => "Someinistan", + isDefault => JSON::false + } ] + }, + "4" => { + firstName => "bam", + lastName => "last", + isFlagged => JSON::false + } + } + }, + "R1" + ] ]); + + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; + my $id3 = $res->[0][1]{created}{"3"}{id}; + my $id4 = $res->[0][1]{created}{"4"}{id}; + + xlog $self, "create contact groups"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + create => { + "1" => { name => "group1", contactIds => [ $id1, $id2 ] }, + "2" => { name => "group2", contactIds => [$id3] }, + "3" => { name => "group3", contactIds => [$id4] } + } + }, + "R1" + ] ]); + + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $group1 = $res->[0][1]{created}{"1"}{id}; + my $group2 = $res->[0][1]{created}{"2"}{id}; + my $group3 = $res->[0][1]{created}{"3"}{id}; + + xlog $self, "get unfiltered contact list"; + $res = $jmap->CallMethods( + [ [ 'Contact/query', { accountId => 'manifold' }, "R1" ] ]); + + xlog $self, "check total"; + $self->assert_num_equals(4, $res->[0][1]{total}); + xlog $self, "check ids"; + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by firstName"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { firstName => "foo" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); + + xlog $self, "filter by lastName"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { lastName => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(4, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by firstName and lastName (one filter)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { firstName => "bam", lastName => "last" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id4, $res->[0][1]{ids}[0]); + + xlog $self, "filter by firstName and lastName (AND filter)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { + operator => "AND", + conditions => [ + { + lastName => "last" + }, + { + firstName => "baz" + } + ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id3, $res->[0][1]{ids}[0]); + + xlog $self, "filter by firstName (OR filter)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { + operator => "OR", + conditions => [ + { + firstName => "bar" + }, + { + firstName => "baz" + } + ] + } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by text"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { text => "some" } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by email"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { email => "example.com" } + }, + "R1" + ] ]); + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by isFlagged (true)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { isFlagged => JSON::true } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id2, $res->[0][1]{ids}[0]); + + xlog $self, "filter by isFlagged (false)"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { isFlagged => JSON::false } + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by inContactGroup"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { inContactGroup => [ $group1, $group3 ] } + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "filter by inContactGroup and firstName"; + $res = $jmap->CallMethods([ [ + 'Contact/query', + { + accountId => 'manifold', + filter => { inContactGroup => [ $group1, $group3 ], firstName => "foo" } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($id1, $res->[0][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_query_sort b/cassandane/tiny-tests/JMAPContacts/contact_query_sort index 8b521bc788..b2592705a1 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_query_sort +++ b/cassandane/tiny-tests/JMAPContacts/contact_query_sort @@ -2,67 +2,73 @@ use Cassandane::Tiny; sub test_contact_query_sort - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - uid => 'XXX-UID-1', - company => 'companyB', - isFlagged => JSON::true, - }, - contact2 => { - uid => 'XXX-UID-2', - company => 'companyA', - isFlagged => JSON::true, - }, - contact3 => { - uid => 'XXX-UID-3', - company => 'companyB', - isFlagged => JSON::false, - }, - contact4 => { - uid => 'XXX-UID-4', - company => 'companyC', - isFlagged => JSON::true, - }, - }, - }, 'R1'], - ]); - my $contactId1 = $res->[0][1]{created}{contact1}{id}; - $self->assert_not_null($contactId1); + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + contact1 => { + uid => 'XXX-UID-1', + company => 'companyB', + isFlagged => JSON::true, + }, + contact2 => { + uid => 'XXX-UID-2', + company => 'companyA', + isFlagged => JSON::true, + }, + contact3 => { + uid => 'XXX-UID-3', + company => 'companyB', + isFlagged => JSON::false, + }, + contact4 => { + uid => 'XXX-UID-4', + company => 'companyC', + isFlagged => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + my $contactId1 = $res->[0][1]{created}{contact1}{id}; + $self->assert_not_null($contactId1); - my $contactId2 = $res->[0][1]{created}{contact2}{id}; - $self->assert_not_null($contactId2); + my $contactId2 = $res->[0][1]{created}{contact2}{id}; + $self->assert_not_null($contactId2); - my $contactId3 = $res->[0][1]{created}{contact3}{id}; - $self->assert_not_null($contactId3); + my $contactId3 = $res->[0][1]{created}{contact3}{id}; + $self->assert_not_null($contactId3); - my $contactId4 = $res->[0][1]{created}{contact4}{id}; - $self->assert_not_null($contactId4); + my $contactId4 = $res->[0][1]{created}{contact4}{id}; + $self->assert_not_null($contactId4); - xlog $self, "sort by multi-dimensional comparator"; - $res = $jmap->CallMethods([ - ['Contact/query', { - sort => [{ - property => 'company', - }, { - property => 'uid', - isAscending => JSON::false, - }], - }, 'R2'], - ]); - $self->assert_deep_equals([ - $contactId2, - $contactId3, - $contactId1, - $contactId4, - ], $res->[0][1]{ids} - ); + xlog $self, "sort by multi-dimensional comparator"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + sort => [ + { + property => 'company', + }, + { + property => 'uid', + isAscending => JSON::false, + } + ], + }, + 'R2' + ], + ]); + $self->assert_deep_equals( + [ $contactId2, $contactId3, $contactId1, $contactId4, ], + $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_query_text b/cassandane/tiny-tests/JMAPContacts/contact_query_text index db2b0e6391..4f8c5102a8 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_query_text +++ b/cassandane/tiny-tests/JMAPContacts/contact_query_text @@ -2,133 +2,182 @@ use Cassandane::Tiny; sub test_contact_query_text - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - notes => 'cats and dogs', - }, - contact2 => { - notes => 'hats and bats', - }, - }, - }, 'R1'], - ]); - my $contactId1 = $res->[0][1]{created}{contact1}{id}; - $self->assert_not_null($contactId1); - my $contactId2 = $res->[0][1]{created}{contact2}{id}; - $self->assert_not_null($contactId2); + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + contact1 => { + notes => 'cats and dogs', + }, + contact2 => { + notes => 'hats and bats', + }, + }, + }, + 'R1' + ], + ]); + my $contactId1 = $res->[0][1]{created}{contact1}{id}; + $self->assert_not_null($contactId1); + my $contactId2 = $res->[0][1]{created}{contact2}{id}; + $self->assert_not_null($contactId2); - xlog "Query with loose terms"; - $res = $jmap->CallMethods([ - ['Contact/query', { - filter => { - notes => "cats dogs", - }, - }, 'R1'], - ['Contact/query', { - filter => { - operator => 'NOT', - conditions => [{ - notes => 'cats dogs', - }], - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); + xlog "Query with loose terms"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + filter => { + notes => "cats dogs", + }, + }, + 'R1' + ], + [ + 'Contact/query', + { + filter => { + operator => 'NOT', + conditions => [ { + notes => 'cats dogs', + } ], + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); - xlog "Query with phrase"; - $res = $jmap->CallMethods([ - ['Contact/query', { - filter => { - notes => "'cats and dogs'", - }, - }, 'R1'], - ['Contact/query', { - filter => { - operator => 'NOT', - conditions => [{ - notes => "'cats and dogs'", - }], - }, - }, 'R1'], - ]); - $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); + xlog "Query with phrase"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + filter => { + notes => "'cats and dogs'", + }, + }, + 'R1' + ], + [ + 'Contact/query', + { + filter => { + operator => 'NOT', + conditions => [ { + notes => "'cats and dogs'", + } ], + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); - xlog "Query with both phrase and loose terms"; - $res = $jmap->CallMethods([ - ['Contact/query', { - filter => { - notes => "cats 'cats and dogs' dogs", - }, - }, 'R1'], - ['Contact/query', { - filter => { - operator => 'NOT', - conditions => [{ - notes => "cats 'cats and dogs' dogs", - }], - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); + xlog "Query with both phrase and loose terms"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + filter => { + notes => "cats 'cats and dogs' dogs", + }, + }, + 'R1' + ], + [ + 'Contact/query', + { + filter => { + operator => 'NOT', + conditions => [ { + notes => "cats 'cats and dogs' dogs", + } ], + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); - xlog "Query text"; - $res = $jmap->CallMethods([ - ['Contact/query', { - filter => { - text => "cats dogs", - }, - }, 'R1'], - ['Contact/query', { - filter => { - operator => 'NOT', - conditions => [{ - text => "cats dogs", - }], - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); + xlog "Query text"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + filter => { + text => "cats dogs", + }, + }, + 'R1' + ], + [ + 'Contact/query', + { + filter => { + operator => 'NOT', + conditions => [ { + text => "cats dogs", + } ], + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); - xlog "Query text and notes"; - $res = $jmap->CallMethods([ - ['Contact/query', { - filter => { - operator => 'AND', - conditions => [{ - text => "cats", - }, { - notes => "dogs", - }], + xlog "Query text and notes"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + filter => { + operator => 'AND', + conditions => [ + { + text => "cats", }, - }, 'R1'], - ['Contact/query', { + { + notes => "dogs", + } + ], + }, + }, + 'R1' + ], + [ + 'Contact/query', + { - filter => { - operator => 'NOT', - conditions => [{ - operator => 'AND', - conditions => [{ - text => "cats", - }, { - notes => "dogs", - }], - }], - }, - }, 'R2'], - ]); - $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); - $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); + filter => { + operator => 'NOT', + conditions => [ { + operator => 'AND', + conditions => [ + { + text => "cats", + }, + { + notes => "dogs", + } + ], + } ], + }, + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$contactId1], $res->[0][1]{ids}); + $self->assert_deep_equals([$contactId2], $res->[1][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_query_uid b/cassandane/tiny-tests/JMAPContacts/contact_query_uid index a3231e456d..272bdc3b18 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_query_uid +++ b/cassandane/tiny-tests/JMAPContacts/contact_query_uid @@ -2,79 +2,98 @@ use Cassandane::Tiny; sub test_contact_query_uid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - firstName => 'contact1', - }, - contact2 => { - firstName => 'contact2', - }, - contact3 => { - firstName => 'contact3', - }, - }, - }, 'R1'], - ]); - my $contactId1 = $res->[0][1]{created}{contact1}{id}; - $self->assert_not_null($contactId1); - my $contactUid1 = $res->[0][1]{created}{contact1}{uid}; - $self->assert_not_null($contactUid1); + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + contact1 => { + firstName => 'contact1', + }, + contact2 => { + firstName => 'contact2', + }, + contact3 => { + firstName => 'contact3', + }, + }, + }, + 'R1' + ], + ]); + my $contactId1 = $res->[0][1]{created}{contact1}{id}; + $self->assert_not_null($contactId1); + my $contactUid1 = $res->[0][1]{created}{contact1}{uid}; + $self->assert_not_null($contactUid1); - my $contactId2 = $res->[0][1]{created}{contact2}{id}; - $self->assert_not_null($contactId2); - my $contactUid2 = $res->[0][1]{created}{contact2}{uid}; - $self->assert_not_null($contactUid2); + my $contactId2 = $res->[0][1]{created}{contact2}{id}; + $self->assert_not_null($contactId2); + my $contactUid2 = $res->[0][1]{created}{contact2}{uid}; + $self->assert_not_null($contactUid2); - my $contactId3 = $res->[0][1]{created}{contact3}{id}; - $self->assert_not_null($contactId3); - my $contactUid3 = $res->[0][1]{created}{contact3}{uid}; - $self->assert_not_null($contactUid3); + my $contactId3 = $res->[0][1]{created}{contact3}{id}; + $self->assert_not_null($contactId3); + my $contactUid3 = $res->[0][1]{created}{contact3}{uid}; + $self->assert_not_null($contactUid3); - xlog $self, "query by single uid"; - $res = $jmap->CallMethods([ - ['Contact/query', { - filter => { - uid => $contactUid2, - }, - }, 'R2'], - ]); - $self->assert_str_equals("Contact/query", $res->[0][0]); - $self->assert_deep_equals([$contactId2], $res->[0][1]{ids}); + xlog $self, "query by single uid"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + filter => { + uid => $contactUid2, + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("Contact/query", $res->[0][0]); + $self->assert_deep_equals([$contactId2], $res->[0][1]{ids}); - xlog $self, "query by invalid uid"; - $res = $jmap->CallMethods([ - ['Contact/query', { - filter => { - uid => "notarealuid", - }, - }, 'R2'], - ]); - $self->assert_str_equals("Contact/query", $res->[0][0]); - $self->assert_deep_equals([], $res->[0][1]{ids}); + xlog $self, "query by invalid uid"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + filter => { + uid => "notarealuid", + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("Contact/query", $res->[0][0]); + $self->assert_deep_equals([], $res->[0][1]{ids}); - xlog $self, "query by multiple uids"; - $res = $jmap->CallMethods([ - ['Contact/query', { - filter => { - operator => 'OR', - conditions => [{ - uid => $contactUid1, - }, { - uid => $contactUid3, - }], + xlog $self, "query by multiple uids"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + filter => { + operator => 'OR', + conditions => [ + { + uid => $contactUid1, }, - }, 'R2'], - ]); - $self->assert_str_equals("Contact/query", $res->[0][0]); - my %gotIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_deep_equals({ $contactUid1 => 1, $contactUid3 => 1, }, \%gotIds); + { + uid => $contactUid3, + } + ], + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("Contact/query", $res->[0][0]); + my %gotIds = map { $_ => 1 } @{ $res->[0][1]{ids} }; + $self->assert_deep_equals({ $contactUid1 => 1, $contactUid3 => 1, }, + \%gotIds); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_query_windowing b/cassandane/tiny-tests/JMAPContacts/contact_query_windowing index 08c21b9769..f5088ad977 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_query_windowing +++ b/cassandane/tiny-tests/JMAPContacts/contact_query_windowing @@ -2,112 +2,115 @@ use Cassandane::Tiny; sub test_contact_query_windowing - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - uid => 'XXX-UID-1', - company => 'companyB', - isFlagged => JSON::true, - }, - contact2 => { - uid => 'XXX-UID-2', - company => 'companyA', - isFlagged => JSON::true, - }, - contact3 => { - uid => 'XXX-UID-3', - company => 'companyB', - isFlagged => JSON::false, - }, - contact4 => { - uid => 'XXX-UID-4', - company => 'companyC', - isFlagged => JSON::true, - }, - }, - }, 'R1'], - ]); - my $contactId1 = $res->[0][1]{created}{contact1}{id}; - $self->assert_not_null($contactId1); + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + contact1 => { + uid => 'XXX-UID-1', + company => 'companyB', + isFlagged => JSON::true, + }, + contact2 => { + uid => 'XXX-UID-2', + company => 'companyA', + isFlagged => JSON::true, + }, + contact3 => { + uid => 'XXX-UID-3', + company => 'companyB', + isFlagged => JSON::false, + }, + contact4 => { + uid => 'XXX-UID-4', + company => 'companyC', + isFlagged => JSON::true, + }, + }, + }, + 'R1' + ], + ]); + my $contactId1 = $res->[0][1]{created}{contact1}{id}; + $self->assert_not_null($contactId1); - my $contactId2 = $res->[0][1]{created}{contact2}{id}; - $self->assert_not_null($contactId2); + my $contactId2 = $res->[0][1]{created}{contact2}{id}; + $self->assert_not_null($contactId2); - my $contactId3 = $res->[0][1]{created}{contact3}{id}; - $self->assert_not_null($contactId3); + my $contactId3 = $res->[0][1]{created}{contact3}{id}; + $self->assert_not_null($contactId3); - my $contactId4 = $res->[0][1]{created}{contact4}{id}; - $self->assert_not_null($contactId4); + my $contactId4 = $res->[0][1]{created}{contact4}{id}; + $self->assert_not_null($contactId4); - xlog $self, "run query with windowing"; - $res = $jmap->CallMethods([ - ['Contact/query', { - sort => [{ - property => 'uid', - }], - limit => 2, - }, 'R1'], - ['Contact/query', { - sort => [{ - property => 'uid', - }], - limit => 2, - position => 2, - }, 'R2'], - ['Contact/query', { - sort => [{ - property => 'uid', - }], - anchor => $contactId3, - anchorOffset => -1, - limit => 2, - }, 'R3'], - ['Contact/query', { - sort => [{ - property => 'uid', - }], - limit => 2, - position => -2, - }, 'R4'], - ]); - # Request 1 - $self->assert_deep_equals([ - $contactId1, - $contactId2, - ], $res->[0][1]{ids} - ); - $self->assert_num_equals(0, $res->[0][1]{position}); - $self->assert_num_equals(4, $res->[0][1]{total}); - # Request 2 - $self->assert_deep_equals([ - $contactId3, - $contactId4, - ], $res->[1][1]{ids} - ); - $self->assert_num_equals(2, $res->[1][1]{position}); - $self->assert_num_equals(4, $res->[1][1]{total}); - # Request 3 - $self->assert_deep_equals([ - $contactId2, - $contactId3, - ], $res->[2][1]{ids} - ); - $self->assert_num_equals(1, $res->[2][1]{position}); - $self->assert_num_equals(4, $res->[2][1]{total}); - # Request 4 - $self->assert_deep_equals([ - $contactId3, - $contactId4, - ], $res->[3][1]{ids} - ); - $self->assert_num_equals(2, $res->[3][1]{position}); - $self->assert_num_equals(4, $res->[3][1]{total}); + xlog $self, "run query with windowing"; + $res = $jmap->CallMethods([ + [ + 'Contact/query', + { + sort => [ { + property => 'uid', + } ], + limit => 2, + }, + 'R1' + ], + [ + 'Contact/query', + { + sort => [ { + property => 'uid', + } ], + limit => 2, + position => 2, + }, + 'R2' + ], + [ + 'Contact/query', + { + sort => [ { + property => 'uid', + } ], + anchor => $contactId3, + anchorOffset => -1, + limit => 2, + }, + 'R3' + ], + [ + 'Contact/query', + { + sort => [ { + property => 'uid', + } ], + limit => 2, + position => -2, + }, + 'R4' + ], + ]); + # Request 1 + $self->assert_deep_equals([ $contactId1, $contactId2, ], $res->[0][1]{ids}); + $self->assert_num_equals(0, $res->[0][1]{position}); + $self->assert_num_equals(4, $res->[0][1]{total}); + # Request 2 + $self->assert_deep_equals([ $contactId3, $contactId4, ], $res->[1][1]{ids}); + $self->assert_num_equals(2, $res->[1][1]{position}); + $self->assert_num_equals(4, $res->[1][1]{total}); + # Request 3 + $self->assert_deep_equals([ $contactId2, $contactId3, ], $res->[2][1]{ids}); + $self->assert_num_equals(1, $res->[2][1]{position}); + $self->assert_num_equals(4, $res->[2][1]{total}); + # Request 4 + $self->assert_deep_equals([ $contactId3, $contactId4, ], $res->[3][1]{ids}); + $self->assert_num_equals(2, $res->[3][1]{position}); + $self->assert_num_equals(4, $res->[3][1]{total}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set b/cassandane/tiny-tests/JMAPContacts/contact_set index 5894d20efb..dbb265d830 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set +++ b/cassandane/tiny-tests/JMAPContacts/contact_set @@ -2,382 +2,539 @@ use Cassandane::Tiny; sub test_contact_set - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - my $contact = { - firstName => "first", - lastName => "last", - avatar => JSON::null - }; - - my $res = $jmap->CallMethods([['Contact/set', {create => {"1" => $contact }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; - - # get expands default values, so do the same manually - $contact->{id} = $id; - $contact->{uid} = $id; - $contact->{isFlagged} = JSON::false; - $contact->{prefix} = ''; - $contact->{suffix} = ''; - $contact->{nickname} = ''; - $contact->{birthday} = '0000-00-00'; - $contact->{anniversary} = '0000-00-00'; - $contact->{company} = ''; - $contact->{department} = ''; - $contact->{jobTitle} = ''; - $contact->{online} = []; - $contact->{phones} = []; - $contact->{addresses} = []; - $contact->{emails} = []; - $contact->{notes} = ''; - $contact->{avatar} = undef; - - # Non-JMAP properties. - $contact->{"importance"} = 0; - $contact->{"x-hasPhoto"} = JSON::false; - $contact->{"addressbookId"} = 'Default'; - - if ($res->[0][1]{created}{"1"}{blobId}) { - $contact->{blobId} = $res->[0][1]{created}{"1"}{blobId}; - $contact->{size} = $res->[0][1]{created}{"1"}{size}; - } - - xlog $self, "get contact $id"; - my $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $contact->{"x-href"} = $fetch->[0][1]{list}[0]{"x-href"}; - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update isFlagged"; - $contact->{isFlagged} = JSON::true; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {isFlagged => JSON::true} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update prefix"; - $contact->{prefix} = 'foo'; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {prefix => 'foo'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update suffix"; - $contact->{suffix} = 'bar'; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {suffix => 'bar'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update nickname"; - $contact->{nickname} = 'nick'; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {nickname => 'nick'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update birthday (with JMAP datetime error)"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {birthday => '1979-04-01T00:00:00Z'} }}, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("birthday", $res->[0][1]{notUpdated}{$id}{properties}[0]); - - xlog $self, "update birthday"; - $contact->{birthday} = '1979-04-01'; # Happy birthday, El Barto! - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {birthday => '1979-04-01'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update anniversary (with JMAP datetime error)"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {anniversary => '1989-12-17T00:00:00Z'} }}, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("anniversary", $res->[0][1]{notUpdated}{$id}{properties}[0]); - - xlog $self, "update anniversary"; - $contact->{anniversary} = '1989-12-17'; # Happy anniversary, Simpsons! - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {anniversary => '1989-12-17'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update company"; - $contact->{company} = 'acme'; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {company => 'acme'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update department"; - $contact->{department} = 'looney tunes'; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {department => 'looney tunes'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update jobTitle"; - $contact->{jobTitle} = 'director of everything'; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {jobTitle => 'director of everything'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - # emails - xlog $self, "update emails (with missing type error)"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - emails => [{ value => "acme\@example.com" }] - } }}, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("emails[0].type", $res->[0][1]{notUpdated}{$id}{properties}[0]); - - xlog $self, "update emails (with missing value error)"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - emails => [{ type => "other" }] - } }}, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("emails[0].value", $res->[0][1]{notUpdated}{$id}{properties}[0]); - - xlog $self, "update emails"; - $contact->{emails} = [{ type => "work", value => "acme\@example.com", isDefault => JSON::true }]; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - emails => [{ type => "work", value => "acme\@example.com" }] - } }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - # phones - xlog $self, "update phones (with missing type error)"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - phones => [{ value => "12345678" }] - } }}, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("phones[0].type", $res->[0][1]{notUpdated}{$id}{properties}[0]); - - xlog $self, "update phones (with missing value error)"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - phones => [{ type => "home" }] - } }}, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("phones[0].value", $res->[0][1]{notUpdated}{$id}{properties}[0]); - - xlog $self, "update phones"; - $contact->{phones} = [{ type => "home", value => "12345678" }]; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - phones => [{ type => "home", value => "12345678" }] - } }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - # online - xlog $self, "update online (with missing type error)"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - online => [{ value => "http://example.com/me" }] - } }}, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("online[0].type", $res->[0][1]{notUpdated}{$id}{properties}[0]); - - xlog $self, "update online (with missing value error)"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - online => [{ type => "uri" }] - } }}, "R1"]]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals("online[0].value", $res->[0][1]{notUpdated}{$id}{properties}[0]); - - xlog $self, "update online"; - $contact->{online} = [{ type => "uri", value => "http://example.com/me" }]; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - online => [{ type => "uri", value => "http://example.com/me" }] - } }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - # addresses - xlog $self, "update addresses"; - $contact->{addresses} = [{ - type => "home", - street => "acme lane 1", + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + my $contact = { + firstName => "first", + lastName => "last", + avatar => JSON::null + }; + + my $res = $jmap->CallMethods( + [ [ 'Contact/set', { create => { "1" => $contact } }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; + + # get expands default values, so do the same manually + $contact->{id} = $id; + $contact->{uid} = $id; + $contact->{isFlagged} = JSON::false; + $contact->{prefix} = ''; + $contact->{suffix} = ''; + $contact->{nickname} = ''; + $contact->{birthday} = '0000-00-00'; + $contact->{anniversary} = '0000-00-00'; + $contact->{company} = ''; + $contact->{department} = ''; + $contact->{jobTitle} = ''; + $contact->{online} = []; + $contact->{phones} = []; + $contact->{addresses} = []; + $contact->{emails} = []; + $contact->{notes} = ''; + $contact->{avatar} = undef; + + # Non-JMAP properties. + $contact->{"importance"} = 0; + $contact->{"x-hasPhoto"} = JSON::false; + $contact->{"addressbookId"} = 'Default'; + + if ($res->[0][1]{created}{"1"}{blobId}) { + $contact->{blobId} = $res->[0][1]{created}{"1"}{blobId}; + $contact->{size} = $res->[0][1]{created}{"1"}{size}; + } + + xlog $self, "get contact $id"; + my $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $contact->{"x-href"} = $fetch->[0][1]{list}[0]{"x-href"}; + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update isFlagged"; + $contact->{isFlagged} = JSON::true; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', { update => { $id => { isFlagged => JSON::true } } }, + "R1" + ] ] + ); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update prefix"; + $contact->{prefix} = 'foo'; + $res = $jmap->CallMethods( + [ [ 'Contact/set', { update => { $id => { prefix => 'foo' } } }, "R1" ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update suffix"; + $contact->{suffix} = 'bar'; + $res = $jmap->CallMethods( + [ [ 'Contact/set', { update => { $id => { suffix => 'bar' } } }, "R1" ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update nickname"; + $contact->{nickname} = 'nick'; + $res + = $jmap->CallMethods( + [ [ 'Contact/set', { update => { $id => { nickname => 'nick' } } }, "R1" ] ] + ); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update birthday (with JMAP datetime error)"; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { update => { $id => { birthday => '1979-04-01T00:00:00Z' } } }, "R1" + ] ] + ); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("birthday", + $res->[0][1]{notUpdated}{$id}{properties}[0]); + + xlog $self, "update birthday"; + $contact->{birthday} = '1979-04-01'; # Happy birthday, El Barto! + $res = $jmap->CallMethods( + [ [ + 'Contact/set', { update => { $id => { birthday => '1979-04-01' } } }, + "R1" + ] ] + ); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update anniversary (with JMAP datetime error)"; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { update => { $id => { anniversary => '1989-12-17T00:00:00Z' } } }, "R1" + ] ] + ); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("anniversary", + $res->[0][1]{notUpdated}{$id}{properties}[0]); + + xlog $self, "update anniversary"; + $contact->{anniversary} = '1989-12-17'; # Happy anniversary, Simpsons! + $res = $jmap->CallMethods( + [ [ + 'Contact/set', { update => { $id => { anniversary => '1989-12-17' } } }, + "R1" + ] ] + ); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update company"; + $contact->{company} = 'acme'; + $res + = $jmap->CallMethods( + [ [ 'Contact/set', { update => { $id => { company => 'acme' } } }, "R1" ] ] + ); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update department"; + $contact->{department} = 'looney tunes'; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', { update => { $id => { department => 'looney tunes' } } }, + "R1" + ] ] + ); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update jobTitle"; + $contact->{jobTitle} = 'director of everything'; + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { update => { $id => { jobTitle => 'director of everything' } } }, "R1" + ] ] + ); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + # emails + xlog $self, "update emails (with missing type error)"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + emails => [ { value => "acme\@example.com" } ] + } + } + }, + "R1" + ] ]); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("emails[0].type", + $res->[0][1]{notUpdated}{$id}{properties}[0]); + + xlog $self, "update emails (with missing value error)"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + emails => [ { type => "other" } ] + } + } + }, + "R1" + ] ]); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("emails[0].value", + $res->[0][1]{notUpdated}{$id}{properties}[0]); + + xlog $self, "update emails"; + $contact->{emails} = [ { + type => "work", value => "acme\@example.com", isDefault => JSON::true } ]; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + emails => [ { type => "work", value => "acme\@example.com" } ] + } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + # phones + xlog $self, "update phones (with missing type error)"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + phones => [ { value => "12345678" } ] + } + } + }, + "R1" + ] ]); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("phones[0].type", + $res->[0][1]{notUpdated}{$id}{properties}[0]); + + xlog $self, "update phones (with missing value error)"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + phones => [ { type => "home" } ] + } + } + }, + "R1" + ] ]); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("phones[0].value", + $res->[0][1]{notUpdated}{$id}{properties}[0]); + + xlog $self, "update phones"; + $contact->{phones} = [ { type => "home", value => "12345678" } ]; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + phones => [ { type => "home", value => "12345678" } ] + } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + # online + xlog $self, "update online (with missing type error)"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + online => [ { value => "http://example.com/me" } ] + } + } + }, + "R1" + ] ]); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("online[0].type", + $res->[0][1]{notUpdated}{$id}{properties}[0]); + + xlog $self, "update online (with missing value error)"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + online => [ { type => "uri" } ] + } + } + }, + "R1" + ] ]); + $self->assert_str_equals("invalidProperties", + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals("online[0].value", + $res->[0][1]{notUpdated}{$id}{properties}[0]); + + xlog $self, "update online"; + $contact->{online} = [ { type => "uri", value => "http://example.com/me" } ]; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + online => [ { type => "uri", value => "http://example.com/me" } ] + } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + # addresses + xlog $self, "update addresses"; + $contact->{addresses} = [ { + type => "home", + street => "acme lane 1", + locality => "acme city", + region => "", + postcode => "1234", + country => "acme land", + label => undef, + } ]; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + addresses => [ { + type => "home", + street => "acme lane 1", locality => "acme city", - region => "", + region => "", postcode => "1234", - country => "acme land", - label => undef, - }]; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => { - addresses => [{ - type => "home", - street => "acme lane 1", - locality => "acme city", - region => "", - postcode => "1234", - country => "acme land", - label => undef, - }] - } }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - xlog $self, "update notes"; - $contact->{notes} = 'baz'; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {notes => 'baz'} }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - xlog $self, "get contact $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); - - # avatar - xlog $self, "upload avatar"; - $res = $jmap->Upload("some photo", "image/jpeg"); - my $blobId = $res->{blobId}; - $contact->{"x-hasPhoto"} = JSON::true; - $contact->{avatar} = { - blobId => $blobId, - size => 10, - type => "image/jpeg", - name => JSON::null - }; - - xlog $self, "attempt to update avatar with invalid type"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => - {avatar => { - blobId => $blobId, - size => 10, - type => "JPEG", - name => JSON::null - } - } }}, "R1"]]); - $self->assert_null($res->[0][1]{updated}); - $self->assert_not_null($res->[0][1]{notUpdated}{$id}); - - xlog $self, "update avatar"; - $res = $jmap->CallMethods([['Contact/set', {update => {$id => - {avatar => { - blobId => $blobId, - size => 10, - type => "image/jpeg", - name => JSON::null - } - } }}, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - if ($res->[0][1]{updated}{$id}{blobId}) { - $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; - $contact->{size} = $res->[0][1]{updated}{$id}{size}; - } - - if ($res->[0][1]{updated}{$id}{avatar}{blobId}) { - $contact->{avatar}{blobId} = $res->[0][1]{updated}{$id}{avatar}{blobId}; - } - - xlog $self, "get avatar $id"; - $fetch = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + country => "acme land", + label => undef, + } ] + } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + xlog $self, "update notes"; + $contact->{notes} = 'baz'; + $res = $jmap->CallMethods( + [ [ 'Contact/set', { update => { $id => { notes => 'baz' } } }, "R1" ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + xlog $self, "get contact $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); + + # avatar + xlog $self, "upload avatar"; + $res = $jmap->Upload("some photo", "image/jpeg"); + my $blobId = $res->{blobId}; + $contact->{"x-hasPhoto"} = JSON::true; + $contact->{avatar} = { + blobId => $blobId, + size => 10, + type => "image/jpeg", + name => JSON::null + }; + + xlog $self, "attempt to update avatar with invalid type"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + avatar => { + blobId => $blobId, + size => 10, + type => "JPEG", + name => JSON::null + } + } + } + }, + "R1" + ] ]); + $self->assert_null($res->[0][1]{updated}); + $self->assert_not_null($res->[0][1]{notUpdated}{$id}); + + xlog $self, "update avatar"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + avatar => { + blobId => $blobId, + size => 10, + type => "image/jpeg", + name => JSON::null + } + } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + if ($res->[0][1]{updated}{$id}{blobId}) { + $contact->{blobId} = $res->[0][1]{updated}{$id}{blobId}; + $contact->{size} = $res->[0][1]{updated}{$id}{size}; + } + + if ($res->[0][1]{updated}{$id}{avatar}{blobId}) { + $contact->{avatar}{blobId} = $res->[0][1]{updated}{$id}{avatar}{blobId}; + } + + xlog $self, "get avatar $id"; + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_deep_equals($contact, $fetch->[0][1]{list}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_apple_countrycode b/cassandane/tiny-tests/JMAPContacts/contact_set_apple_countrycode index 3b2219a543..3d79427130 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_apple_countrycode +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_apple_countrycode @@ -2,43 +2,55 @@ use Cassandane::Tiny; sub test_contact_set_apple_countrycode - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - lastName => "Smith", - addresses => [{ - type => "work", - label => "xyz", - street => "2 Example Avenue", - locality => "Anytown", - region => "NY", - postcode => "01111", - country => "USA", - countryCode => "us" - }, { - type => "work", - street => "Beispielstrasse 2", - locality => 'IrgendwoStadt', - region => 'IrgendwoLand', - postcode => '00000', - country => "Germany", - countryCode => 'DE', - }], - }, - }, - }, 'R1'], - ['Contact/get', { - ids => ['#contact1'], - properties => ['addresses'], - }, 'R2'], - ]); - $self->assert_str_equals('us', $res->[1][1]{list}[0]{addresses}[0]{countryCode}); - $self->assert_str_equals('xyz', $res->[1][1]{list}[0]{addresses}[0]{label}); - $self->assert_str_equals('de', $res->[1][1]{list}[0]{addresses}[1]{countryCode}); + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + contact1 => { + lastName => "Smith", + addresses => [ + { + type => "work", + label => "xyz", + street => "2 Example Avenue", + locality => "Anytown", + region => "NY", + postcode => "01111", + country => "USA", + countryCode => "us" + }, + { + type => "work", + street => "Beispielstrasse 2", + locality => 'IrgendwoStadt', + region => 'IrgendwoLand', + postcode => '00000', + country => "Germany", + countryCode => 'DE', + } + ], + }, + }, + }, + 'R1' + ], + [ + 'Contact/get', + { + ids => ['#contact1'], + properties => ['addresses'], + }, + 'R2' + ], + ]); + $self->assert_str_equals('us', + $res->[1][1]{list}[0]{addresses}[0]{countryCode}); + $self->assert_str_equals('xyz', $res->[1][1]{list}[0]{addresses}[0]{label}); + $self->assert_str_equals('de', + $res->[1][1]{list}[0]{addresses}[1]{countryCode}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_from_deleted_contact b/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_from_deleted_contact index 6e995b9f09..d878d20864 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_from_deleted_contact +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_from_deleted_contact @@ -2,99 +2,111 @@ use Cassandane::Tiny; sub test_contact_set_avatar_from_deleted_contact - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - my $contact = { - firstName => "first", - lastName => "last", - avatar => { - blobId => "#img", - size => 10, - type => "image/jpeg", - name => JSON::null - } - }; - - my $using = [ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/contacts', - 'https://cyrusimap.org/ns/jmap/blob', - ]; - - xlog $self, "create initial card"; - my $res = $jmap->CallMethods([ - ['Blob/upload', { create => { - "img" => { data => [{'data:asText' => 'some photo'}], - type => 'image/jpeg' } } }, 'R0'], - ['Contact/set', {create => {"1" => $contact }}, "R1"], - ['Contact/get', {}, "R2"]], - $using); - $self->assert_not_null($res); - $self->assert_str_equals('Blob/upload', $res->[0][0]); - $self->assert_str_equals('R0', $res->[0][2]); - - $contact->{avatar}{blobId} = $res->[0][1]{created}{"img"}{blobId}; - - $self->assert_str_equals('Contact/set', $res->[1][0]); - $self->assert_str_equals('R1', $res->[1][2]); - my $id = $res->[1][1]{created}{"1"}{id}; - - $contact->{avatar}{blobId} = $res->[1][1]{created}{"1"}{avatar}{blobId}; - - $self->assert_str_equals('Contact/get', $res->[2][0]); - $self->assert_str_equals('R2', $res->[2][2]); - $self->assert_str_equals($id, $res->[2][1]{list}[0]{id}); - $self->assert_str_equals('first', $res->[2][1]{list}[0]{firstName}); - $self->assert_deep_equals($contact->{avatar}, $res->[2][1]{list}[0]{avatar}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{"x-hasPhoto"}); - - my $newcontact = { - firstName => "first2", - lastName => "last2", - avatar => { - blobId => "$contact->{avatar}{blobId}", - size => 10, - type => "image/jpeg", - name => JSON::null - } - }; - - xlog $self, "delete initial card"; - $res = $jmap->CallMethods([ - ['Contact/set', { destroy => [ "$id"] }, 'R0']], - $using); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R0', $res->[0][2]); - - xlog $self, "create new card using avatar from deleted card"; - $res = $jmap->CallMethods([ - ['Contact/set', {create => {"1" => $newcontact }}, "R1"], - ['Contact/get', {}, "R2"]], - $using); - - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $id = $res->[0][1]{created}{"1"}{id}; - - $contact->{avatar}{blobId} = $res->[0][1]{created}{"1"}{avatar}{blobId}; - - $self->assert_str_equals('Contact/get', $res->[1][0]); - $self->assert_str_equals('R2', $res->[1][2]); - $self->assert_str_equals($id, $res->[1][1]{list}[0]{id}); - $self->assert_str_equals('first2', $res->[1][1]{list}[0]{firstName}); - $self->assert_deep_equals($contact->{avatar}, $res->[1][1]{list}[0]{avatar}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{"x-hasPhoto"}); - - xlog $self, "download and check avatar content"; - my $blob = $jmap->Download({ accept => 'image/jpeg' }, - 'cassandane', $res->[1][1]{list}[0]{avatar}{blobId}); - $self->assert_str_equals('image/jpeg', - $blob->{headers}->{'content-type'}); - $self->assert_num_equals(10, $blob->{headers}->{'content-length'}); - $self->assert_equals('some photo', $blob->{content}); + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + my $contact = { + firstName => "first", + lastName => "last", + avatar => { + blobId => "#img", + size => 10, + type => "image/jpeg", + name => JSON::null + } + }; + + my $using = [ + 'urn:ietf:params:jmap:core', + 'https://cyrusimap.org/ns/jmap/contacts', + 'https://cyrusimap.org/ns/jmap/blob', + ]; + + xlog $self, "create initial card"; + my $res = $jmap->CallMethods( + [ + [ + 'Blob/upload', + { + create => { + "img" => { + data => [ { 'data:asText' => 'some photo' } ], + type => 'image/jpeg' + } + } + }, + 'R0' + ], + [ 'Contact/set', { create => { "1" => $contact } }, "R1" ], + [ 'Contact/get', {}, "R2" ] + ], + $using + ); + $self->assert_not_null($res); + $self->assert_str_equals('Blob/upload', $res->[0][0]); + $self->assert_str_equals('R0', $res->[0][2]); + + $contact->{avatar}{blobId} = $res->[0][1]{created}{"img"}{blobId}; + + $self->assert_str_equals('Contact/set', $res->[1][0]); + $self->assert_str_equals('R1', $res->[1][2]); + my $id = $res->[1][1]{created}{"1"}{id}; + + $contact->{avatar}{blobId} = $res->[1][1]{created}{"1"}{avatar}{blobId}; + + $self->assert_str_equals('Contact/get', $res->[2][0]); + $self->assert_str_equals('R2', $res->[2][2]); + $self->assert_str_equals($id, $res->[2][1]{list}[0]{id}); + $self->assert_str_equals('first', $res->[2][1]{list}[0]{firstName}); + $self->assert_deep_equals($contact->{avatar}, $res->[2][1]{list}[0]{avatar}); + $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{"x-hasPhoto"}); + + my $newcontact = { + firstName => "first2", + lastName => "last2", + avatar => { + blobId => "$contact->{avatar}{blobId}", + size => 10, + type => "image/jpeg", + name => JSON::null + } + }; + + xlog $self, "delete initial card"; + $res = $jmap->CallMethods([ [ 'Contact/set', { destroy => ["$id"] }, 'R0' ] ], + $using); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R0', $res->[0][2]); + + xlog $self, "create new card using avatar from deleted card"; + $res = $jmap->CallMethods( + [ + [ 'Contact/set', { create => { "1" => $newcontact } }, "R1" ], + [ 'Contact/get', {}, "R2" ] + ], + $using + ); + + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $id = $res->[0][1]{created}{"1"}{id}; + + $contact->{avatar}{blobId} = $res->[0][1]{created}{"1"}{avatar}{blobId}; + + $self->assert_str_equals('Contact/get', $res->[1][0]); + $self->assert_str_equals('R2', $res->[1][2]); + $self->assert_str_equals($id, $res->[1][1]{list}[0]{id}); + $self->assert_str_equals('first2', $res->[1][1]{list}[0]{firstName}); + $self->assert_deep_equals($contact->{avatar}, $res->[1][1]{list}[0]{avatar}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{"x-hasPhoto"}); + + xlog $self, "download and check avatar content"; + my $blob = $jmap->Download({ accept => 'image/jpeg' }, + 'cassandane', $res->[1][1]{list}[0]{avatar}{blobId}); + $self->assert_str_equals('image/jpeg', $blob->{headers}->{'content-type'}); + $self->assert_num_equals(10, $blob->{headers}->{'content-length'}); + $self->assert_equals('some photo', $blob->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_shared b/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_shared index 0805349153..a5da4a0c28 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_shared +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_shared @@ -2,82 +2,96 @@ use Cassandane::Tiny; sub test_contact_set_avatar_shared - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); - xlog $self, "create #jmap folder"; - $admintalk->create("user.manifold.#jmap", ['TYPE', 'COLLECTION']); + xlog $self, "create #jmap folder"; + $admintalk->create("user.manifold.#jmap", [ 'TYPE', 'COLLECTION' ]); - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold.#jmap", admin => 'lrswipkxtecdn'); + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold.#jmap", admin => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrswipkxtecdn') or die; + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; - # avatar - xlog $self, "upload avatar - setacl on shared #jmap folder"; - my $res = $jmap->Upload("some photo", "image/jpeg", "manifold"); - my $blobId = $res->{blobId}; + # avatar + xlog $self, "upload avatar - setacl on shared #jmap folder"; + my $res = $jmap->Upload("some photo", "image/jpeg", "manifold"); + my $blobId = $res->{blobId}; - xlog $self, "create contact"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => {"1" => {firstName => "first", lastName => "last", - avatar => { - blobId => $blobId, - size => 10, - type => "image/jpeg", - name => JSON::null - } - }} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create contact"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { + "1" => { + firstName => "first", + lastName => "last", + avatar => { + blobId => $blobId, + size => 10, + type => "image/jpeg", + name => JSON::null + } + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "delete #jmap folder"; - $admintalk->delete("user.manifold.#jmap") || die; + xlog $self, "delete #jmap folder"; + $admintalk->delete("user.manifold.#jmap") || die; - # avatar - xlog $self, "upload new avatar - create new shared #jmap folder"; - $res = $jmap->Upload("some other photo", "image/jpeg", "manifold"); - $blobId = $res->{blobId}; + # avatar + xlog $self, "upload new avatar - create new shared #jmap folder"; + $res = $jmap->Upload("some other photo", "image/jpeg", "manifold"); + $blobId = $res->{blobId}; - xlog $self, "update avatar"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - update => {$id => - {avatar => { - blobId => $blobId, - size => 10, - type => "image/jpeg", - name => JSON::null - } - } + xlog $self, "update avatar"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + update => { + $id => { + avatar => { + blobId => $blobId, + size => 10, + type => "image/jpeg", + name => JSON::null + } } - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_singlecommand b/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_singlecommand index 78cc733a56..3e185cfcce 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_singlecommand +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_singlecommand @@ -2,68 +2,80 @@ use Cassandane::Tiny; sub test_contact_set_avatar_singlecommand - :min_version_3_3 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $contact = { - firstName => "first", - lastName => "last", - avatar => { - blobId => "#img", - size => 10, - type => "image/jpeg", - name => JSON::null - } - }; + my $contact = { + firstName => "first", + lastName => "last", + avatar => { + blobId => "#img", + size => 10, + type => "image/jpeg", + name => JSON::null + } + }; - my $using = [ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/contacts', - 'https://cyrusimap.org/ns/jmap/blob', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'https://cyrusimap.org/ns/jmap/contacts', + 'https://cyrusimap.org/ns/jmap/blob', + ]; - my $res = $jmap->CallMethods([ - ['Blob/upload', { create => { - "img" => { data => [{'data:asText' => 'some photo'}], - type => 'image/jpeg' } } }, 'R0'], - ['Contact/set', {create => {"1" => $contact }}, "R1"], - ['Contact/get', {}, "R2"]], - $using); - $self->assert_not_null($res); - $self->assert_str_equals('Blob/upload', $res->[0][0]); - $self->assert_str_equals('R0', $res->[0][2]); + my $res = $jmap->CallMethods( + [ + [ + 'Blob/upload', + { + create => { + "img" => { + data => [ { 'data:asText' => 'some photo' } ], + type => 'image/jpeg' + } + } + }, + 'R0' + ], + [ 'Contact/set', { create => { "1" => $contact } }, "R1" ], + [ 'Contact/get', {}, "R2" ] + ], + $using + ); + $self->assert_not_null($res); + $self->assert_str_equals('Blob/upload', $res->[0][0]); + $self->assert_str_equals('R0', $res->[0][2]); - $contact->{avatar}{blobId} = $res->[0][1]{created}{"img"}{blobId}; + $contact->{avatar}{blobId} = $res->[0][1]{created}{"img"}{blobId}; - $self->assert_str_equals('Contact/set', $res->[1][0]); - $self->assert_str_equals('R1', $res->[1][2]); - my $id = $res->[1][1]{created}{"1"}{id}; + $self->assert_str_equals('Contact/set', $res->[1][0]); + $self->assert_str_equals('R1', $res->[1][2]); + my $id = $res->[1][1]{created}{"1"}{id}; - if ($res->[1][1]{created}{"1"}{avatar}{blobId}) { - $contact->{avatar}{blobId} = $res->[1][1]{created}{"1"}{avatar}{blobId}; - } + if ($res->[1][1]{created}{"1"}{avatar}{blobId}) { + $contact->{avatar}{blobId} = $res->[1][1]{created}{"1"}{avatar}{blobId}; + } - $self->assert_str_equals('Contact/get', $res->[2][0]); - $self->assert_str_equals('R2', $res->[2][2]); - $self->assert_str_equals($id, $res->[2][1]{list}[0]{id}); - $self->assert_deep_equals($contact->{avatar}, $res->[2][1]{list}[0]{avatar}); - $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{"x-hasPhoto"}); + $self->assert_str_equals('Contact/get', $res->[2][0]); + $self->assert_str_equals('R2', $res->[2][2]); + $self->assert_str_equals($id, $res->[2][1]{list}[0]{id}); + $self->assert_deep_equals($contact->{avatar}, $res->[2][1]{list}[0]{avatar}); + $self->assert_equals(JSON::true, $res->[2][1]{list}[0]{"x-hasPhoto"}); - xlog $self, "remove avatar"; - $res = $jmap->CallMethods([ - ['Contact/set', {update => {$id => {avatar => JSON::null} }}, "R1"], - ['Contact/get', {}, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + xlog $self, "remove avatar"; + $res = $jmap->CallMethods([ + [ 'Contact/set', { update => { $id => { avatar => JSON::null } } }, "R1" ], + [ 'Contact/get', {}, "R2" ] + ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $self->assert_str_equals('Contact/get', $res->[1][0]); - $self->assert_str_equals('R2', $res->[1][2]); - $self->assert_str_equals($id, $res->[1][1]{list}[0]{id}); - $self->assert_null($res->[1][1]{list}[0]{avatar}); - $self->assert_equals(JSON::false, $res->[1][1]{list}[0]{"x-hasPhoto"}); + $self->assert_str_equals('Contact/get', $res->[1][0]); + $self->assert_str_equals('R2', $res->[1][2]); + $self->assert_str_equals($id, $res->[1][1]{list}[0]{id}); + $self->assert_null($res->[1][1]{list}[0]{avatar}); + $self->assert_equals(JSON::false, $res->[1][1]{list}[0]{"x-hasPhoto"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_v4 b/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_v4 index 0bbea68683..f5c2c00574 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_v4 +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_avatar_v4 @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_contact_set_avatar_v4 - :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - xlog $self, "Create a v4 vCard over CardDAV"; - my $id = '816ad14a-f9ef-43a8-9039-b57bf321de1f'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - xlog $self, "Get JMAP Contact"; - my $res = $jmap->CallMethods([ - ['Contact/get', { - properties => ['avatar', 'x-hasPhoto'], - }, 'R1'] - ]); - my $contactId = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($contactId); - $self->assert_null($res->[0][1]{list}[0]{avatar}); + xlog $self, "Get JMAP Contact"; + my $res = $jmap->CallMethods([ [ + 'Contact/get', + { + properties => [ 'avatar', 'x-hasPhoto' ], + }, + 'R1' + ] ]); + my $contactId = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($contactId); + $self->assert_null($res->[0][1]{list}[0]{avatar}); - xlog $self, "Set avatar on contact"; - my $binary = slurp_file(abs_path('data/logo.gif')); - my $data = $jmap->Upload($binary, "image/gif"); - $res = $jmap->CallMethods([ - ['Contact/set', { - update => { - $contactId => { - avatar => { - blobId => $data->{blobId}, - type => "image/gif", - } - } - } - }, 'R1'] - ]); - my $avatarBlobId = $res->[0][1]{updated}{$contactId}{avatar}{blobId}; - $self->assert_not_null($avatarBlobId); + xlog $self, "Set avatar on contact"; + my $binary = slurp_file(abs_path('data/logo.gif')); + my $data = $jmap->Upload($binary, "image/gif"); + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $contactId => { + avatar => { + blobId => $data->{blobId}, + type => "image/gif", + } + } + } + }, + 'R1' + ] ]); + my $avatarBlobId = $res->[0][1]{updated}{$contactId}{avatar}{blobId}; + $self->assert_not_null($avatarBlobId); - xlog $self, "Get vCard over CardDAV as version 4.0"; - $res = $carddav->Request('GET', $href, undef, - "Accept" => "text/vcard; version=4.0"); - my $vcard = Net::CardDAVTalk::VCard->new_fromstring($res->{content}); - my $photo = $vcard->{properties}->{photo}->[0] // undef; - $self->assert(not $photo->{binary}); - $self->assert_equals("data:image/gif;base64,", substr($photo->{value}, 0, 22)); + xlog $self, "Get vCard over CardDAV as version 4.0"; + $res = $carddav->Request('GET', $href, undef, + "Accept" => "text/vcard; version=4.0"); + my $vcard = Net::CardDAVTalk::VCard->new_fromstring($res->{content}); + my $photo = $vcard->{properties}->{photo}->[0] // undef; + $self->assert(not $photo->{binary}); + $self->assert_equals("data:image/gif;base64,", + substr($photo->{value}, 0, 22)); - xlog $self, "Assert avatar blob contents"; - $data = $jmap->Download('cassandane', $avatarBlobId); - $self->assert($binary eq $data->{content}); + xlog $self, "Assert avatar blob contents"; + $data = $jmap->Download('cassandane', $avatarBlobId); + $self->assert($binary eq $data->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_emaillabel b/cassandane/tiny-tests/JMAPContacts/contact_set_emaillabel index 4a4de9e870..a33c6d3408 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_emaillabel +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_emaillabel @@ -2,51 +2,55 @@ use Cassandane::Tiny; sub test_contact_set_emaillabel - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - # See https://github.com/cyrusimap/cyrus-imapd/issues/2273 - - my $contact = { - firstName => "first", - lastName => "last", - emails => [{ - type => "other", - label => "foo", - value => "foo\@local", + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + # See https://github.com/cyrusimap/cyrus-imapd/issues/2273 + + my $contact = { + firstName => "first", + lastName => "last", + emails => [ { + type => "other", + label => "foo", + value => "foo\@local", + isDefault => JSON::true + } ] + }; + + xlog $self, "create contact"; + my $res = $jmap->CallMethods( + [ [ 'Contact/set', { create => { "1" => $contact } }, "R1" ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($id); + + xlog $self, "get contact $id"; + $res = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_str_equals('foo', $res->[0][1]{list}[0]{emails}[0]{label}); + + xlog $self, "update contact"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $id => { + emails => [ { + type => "personal", + label => undef, + value => "bar\@local", isDefault => JSON::true - }] - }; - - xlog $self, "create contact"; - my $res = $jmap->CallMethods([['Contact/set', {create => {"1" => $contact }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($id); - - xlog $self, "get contact $id"; - $res = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_str_equals('foo', $res->[0][1]{list}[0]{emails}[0]{label}); - - xlog $self, "update contact"; - $res = $jmap->CallMethods([['Contact/set', { - update => { - $id => { - emails => [{ - type => "personal", - label => undef, - value => "bar\@local", - isDefault => JSON::true - }] - } + } ] } - }, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - xlog $self, "get contact $id"; - $res = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_str_equals('personal', $res->[0][1]{list}[0]{emails}[0]{type}); - $self->assert_null($res->[0][1]{list}[0]{emails}[0]{label}); + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + xlog $self, "get contact $id"; + $res = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_str_equals('personal', $res->[0][1]{list}[0]{emails}[0]{type}); + $self->assert_null($res->[0][1]{list}[0]{emails}[0]{label}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_float b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_float index ea5d0bbf5f..853abdc662 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_float +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_float @@ -2,26 +2,33 @@ use Cassandane::Tiny; sub test_contact_set_importance_float - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - c1 => { - lastName => 'test', - importance => -122.129545321514, - }, - }, - }, 'R1'], - ['Contact/get', { - ids => ['#c1'], - properties => ['importance'], - }, 'R2'], - ]); - my $contactId = $res->[0][1]{created}{c1}{id}; - $self->assert_not_null($contactId); - $self->assert_equals(-122.129545321514, $res->[1][1]{list}[0]{importance}); + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + c1 => { + lastName => 'test', + importance => -122.129545321514, + }, + }, + }, + 'R1' + ], + [ + 'Contact/get', + { + ids => ['#c1'], + properties => ['importance'], + }, + 'R2' + ], + ]); + my $contactId = $res->[0][1]{created}{c1}{id}; + $self->assert_not_null($contactId); + $self->assert_equals(-122.129545321514, $res->[1][1]{list}[0]{importance}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_later b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_later index e0bbe1021c..bbda7fb00f 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_later +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_later @@ -2,36 +2,45 @@ use Cassandane::Tiny; sub test_contact_set_importance_later - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with no importance"; - my $res = $jmap->CallMethods([['Contact/set', {create => {"1" => {firstName => "first", lastName => "last"}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with no importance"; + my $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { create => { "1" => { firstName => "first", lastName => "last" } } }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(0.0, $fetch->[0][1]{list}[0]{"importance"}); + my $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(0.0, $fetch->[0][1]{list}[0]{"importance"}); - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {"importance" => -0.1}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods( + [ [ + 'Contact/set', { update => { $id => { "importance" => -0.1 } } }, "R3" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(-0.1, $fetch->[0][1]{list}[0]{"importance"}); + $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(-0.1, $fetch->[0][1]{list}[0]{"importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_multiedit b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_multiedit index ff446c055a..b5696385f7 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_multiedit +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_multiedit @@ -2,36 +2,54 @@ use Cassandane::Tiny; sub test_contact_set_importance_multiedit - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with no importance"; - my $res = $jmap->CallMethods([['Contact/set', {create => {"1" => {firstName => "first", lastName => "last", "importance" => -5.2}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with no importance"; + my $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { + create => { + "1" => + { firstName => "first", lastName => "last", "importance" => -5.2 } + } + }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); + my $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {"firstName" => "second", "importance" => -0.2}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { + update => { $id => { "firstName" => "second", "importance" => -0.2 } } + }, + "R3" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('second', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(-0.2, $fetch->[0][1]{list}[0]{"importance"}); + $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('second', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(-0.2, $fetch->[0][1]{list}[0]{"importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_peruser b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_peruser index 82270a2fe0..4d068eb0c5 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_peruser +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_peruser @@ -2,77 +2,99 @@ use Cassandane::Tiny; sub test_contact_set_importance_peruser - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); - $admin->create("user.manifold"); - my $http = $self->{instance}->get_service("http"); - my $manjmap = Mail::JMAPTalk->new( - user => 'manifold', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $manjmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'https://cyrusimap.org/ns/jmap/contacts', - ]); - $admin->setacl("user.cassandane.#addressbooks.Default", - "manifold" => 'lrswipkxtecdn') or die; + $admin->create("user.manifold"); + my $http = $self->{instance}->get_service("http"); + my $manjmap = Mail::JMAPTalk->new( + user => 'manifold', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $manjmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'https://cyrusimap.org/ns/jmap/contacts', ]); + $admin->setacl("user.cassandane.#addressbooks.Default", + "manifold" => 'lrswipkxtecdn') + or die; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - c1 => { - lastName => 'test', - importance => 1.0, - }, - }, - }, 'R1'], - ['Contact/get', { - ids => ['#c1'], - properties => ['importance'], - }, 'R2'], - ]); - my $contactId = $res->[0][1]{created}{c1}{id}; - $self->assert_not_null($contactId); - $self->assert_equals(1.0, $res->[1][1]{list}[0]{importance}); + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + c1 => { + lastName => 'test', + importance => 1.0, + }, + }, + }, + 'R1' + ], + [ + 'Contact/get', + { + ids => ['#c1'], + properties => ['importance'], + }, + 'R2' + ], + ]); + my $contactId = $res->[0][1]{created}{c1}{id}; + $self->assert_not_null($contactId); + $self->assert_equals(1.0, $res->[1][1]{list}[0]{importance}); - $res = $manjmap->CallMethods([ - ['Contact/get', { - accountId => 'cassandane', - ids => [$contactId], - properties => ['importance'], - }, 'R1'], - ['Contact/set', { - accountId => 'cassandane', - update => { - $contactId => { - importance => 2.0, - }, - }, - }, 'R2'], - ['Contact/get', { - accountId => 'cassandane', - ids => [$contactId], - properties => ['importance'], - }, 'R3'], - ]); + $res = $manjmap->CallMethods([ + [ + 'Contact/get', + { + accountId => 'cassandane', + ids => [$contactId], + properties => ['importance'], + }, + 'R1' + ], + [ + 'Contact/set', + { + accountId => 'cassandane', + update => { + $contactId => { + importance => 2.0, + }, + }, + }, + 'R2' + ], + [ + 'Contact/get', + { + accountId => 'cassandane', + ids => [$contactId], + properties => ['importance'], + }, + 'R3' + ], + ]); - $self->assert_equals(1.0, $res->[0][1]{list}[0]{importance}); - $self->assert(exists $res->[1][1]{updated}{$contactId}); - $self->assert_equals(2.0, $res->[2][1]{list}[0]{importance}); + $self->assert_equals(1.0, $res->[0][1]{list}[0]{importance}); + $self->assert(exists $res->[1][1]{updated}{$contactId}); + $self->assert_equals(2.0, $res->[2][1]{list}[0]{importance}); - $res = $jmap->CallMethods([ - ['Contact/get', { - ids => ['#c1'], - properties => ['importance'], - }, 'R1'], - ]); - $self->assert_equals(1.0, $res->[0][1]{list}[0]{importance}); + $res = $jmap->CallMethods([ + [ + 'Contact/get', + { + ids => ['#c1'], + properties => ['importance'], + }, + 'R1' + ], + ]); + $self->assert_equals(1.0, $res->[0][1]{list}[0]{importance}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_shared b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_shared index 9dd55e4f39..bfbfc05f4b 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_shared +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_shared @@ -2,52 +2,63 @@ use Cassandane::Tiny; sub test_contact_set_importance_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "create contact"; - my $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => {"1" => {firstName => "first", lastName => "last"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; - - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrsn') or die; - - xlog $self, "update importance"; - $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - update => {$id => {"importance" => -0.1}} - }, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "create contact"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { "1" => { firstName => "first", lastName => "last" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; + + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrsn') + or die; + + xlog $self, "update importance"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + update => { $id => { "importance" => -0.1 } } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_upfront b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_upfront index fd12147528..54fbab67c3 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_upfront +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_upfront @@ -2,36 +2,51 @@ use Cassandane::Tiny; sub test_contact_set_importance_upfront - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with importance in initial create"; - my $res = $jmap->CallMethods([['Contact/set', {create => {"1" => {firstName => "first", lastName => "last", "importance" => -5.2}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with importance in initial create"; + my $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { + create => { + "1" => + { firstName => "first", lastName => "last", "importance" => -5.2 } + } + }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); + my $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {"firstName" => "second"}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods( + [ [ + 'Contact/set', { update => { $id => { "firstName" => "second" } } }, + "R3" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('second', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); + $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('second', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_zero_byself b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_zero_byself index d5cbf87bff..889ee0971f 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_zero_byself +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_zero_byself @@ -2,36 +2,49 @@ use Cassandane::Tiny; sub test_contact_set_importance_zero_byself - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with no importance"; - my $res = $jmap->CallMethods([['Contact/set', {create => {"1" => {firstName => "first", lastName => "last", "importance" => -5.2}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with no importance"; + my $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { + create => { + "1" => + { firstName => "first", lastName => "last", "importance" => -5.2 } + } + }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); + my $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {"importance" => 0}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res + = $jmap->CallMethods( + [ [ 'Contact/set', { update => { $id => { "importance" => 0 } } }, "R3" ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(0, $fetch->[0][1]{list}[0]{"importance"}); + $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(0, $fetch->[0][1]{list}[0]{"importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_zero_multi b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_zero_multi index a7c6d6d2bd..c93d284a63 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_importance_zero_multi +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_importance_zero_multi @@ -2,36 +2,52 @@ use Cassandane::Tiny; sub test_contact_set_importance_zero_multi - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create with no importance"; - my $res = $jmap->CallMethods([['Contact/set', {create => {"1" => {firstName => "first", lastName => "last", "importance" => -5.2}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create with no importance"; + my $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { + create => { + "1" => + { firstName => "first", lastName => "last", "importance" => -5.2 } + } + }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - my $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); + my $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(-5.2, $fetch->[0][1]{list}[0]{"importance"}); - $res = $jmap->CallMethods([['Contact/set', {update => {$id => {"firstName" => "second", "importance" => 0}}}, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { update => { $id => { "firstName" => "second", "importance" => 0 } } }, + "R3" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_str_equals('second', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_num_equals(0, $fetch->[0][1]{list}[0]{"importance"}); + $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_str_equals('second', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_num_equals(0, $fetch->[0][1]{list}[0]{"importance"}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_invalid b/cassandane/tiny-tests/JMAPContacts/contact_set_invalid index 43e5cae1da..0107d7625e 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_invalid +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_invalid @@ -2,70 +2,87 @@ use Cassandane::Tiny; sub test_contact_set_invalid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contact with invalid properties"; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - "1" => { - id => "xyz", - firstName => "foo", - lastName => "last1", - foo => "", - "x-hasPhoto" => JSON::true - }, - }}, "R1"]]); - $self->assert_not_null($res); - my $notCreated = $res->[0][1]{notCreated}{"1"}; - $self->assert_not_null($notCreated); - $self->assert_num_equals(3, scalar @{$notCreated->{properties}}); + xlog $self, "create contact with invalid properties"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "1" => { + id => "xyz", + firstName => "foo", + lastName => "last1", + foo => "", + "x-hasPhoto" => JSON::true + }, + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + my $notCreated = $res->[0][1]{notCreated}{"1"}; + $self->assert_not_null($notCreated); + $self->assert_num_equals(3, scalar @{ $notCreated->{properties} }); - xlog $self, "create contacts"; - $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - "1" => { - firstName => "foo", - lastName => "last1" - }, - }}, "R2"]]); - $self->assert_not_null($res); - my $contact = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($contact); + xlog $self, "create contacts"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "1" => { + firstName => "foo", + lastName => "last1" + }, + } + }, + "R2" + ] ]); + $self->assert_not_null($res); + my $contact = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($contact); - xlog $self, "get contact x-href"; - $res = $jmap->CallMethods([['Contact/get', {}, "R3"]]); - my $href = $res->[0][1]{list}[0]{"x-href"}; + xlog $self, "get contact x-href"; + $res = $jmap->CallMethods([ [ 'Contact/get', {}, "R3" ] ]); + my $href = $res->[0][1]{list}[0]{"x-href"}; - xlog $self, "update contact with invalid properties"; - $res = $jmap->CallMethods([['Contact/set', { - update => { - $contact => { - id => "xyz", - foo => "", - "x-hasPhoto" => "yes", - "x-ref" => "abc" - }, - }}, "R4"]]); - $self->assert_not_null($res); - my $notUpdated = $res->[0][1]{notUpdated}{$contact}; - $self->assert_not_null($notUpdated); - $self->assert_num_equals(3, scalar @{$notUpdated->{properties}}); + xlog $self, "update contact with invalid properties"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $contact => { + id => "xyz", + foo => "", + "x-hasPhoto" => "yes", + "x-ref" => "abc" + }, + } + }, + "R4" + ] ]); + $self->assert_not_null($res); + my $notUpdated = $res->[0][1]{notUpdated}{$contact}; + $self->assert_not_null($notUpdated); + $self->assert_num_equals(3, scalar @{ $notUpdated->{properties} }); - xlog $self, "update contact with server-set properties"; - $res = $jmap->CallMethods([['Contact/set', { - update => { - $contact => { - id => $contact, - "x-hasPhoto" => JSON::false, - "x-href" => $href - }, - }}, "R5"]]); - $self->assert_not_null($res); - $self->assert_not_null($res->[0][1]{updated}); + xlog $self, "update contact with server-set properties"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $contact => { + id => $contact, + "x-hasPhoto" => JSON::false, + "x-href" => $href + }, + } + }, + "R5" + ] ]); + $self->assert_not_null($res); + $self->assert_not_null($res->[0][1]{updated}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_issue2953 b/cassandane/tiny-tests/JMAPContacts/contact_set_issue2953 index 4d03992f8d..2d30a76d67 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_issue2953 +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_issue2953 @@ -2,29 +2,37 @@ use Cassandane::Tiny; sub test_contact_set_issue2953 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - 1 => { - online => [{ - type => 'username', - value => 'foo,bar', - label => 'Github', - }], - }, - }, - }, 'R1'], - ['Contact/get', { - ids => ['#1'], properties => ['online'], - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}{1}); - $self->assert_str_equals('foo,bar', $res->[1][1]{list}[0]{online}[0]{value}); + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + 1 => { + online => [ { + type => 'username', + value => 'foo,bar', + label => 'Github', + } ], + }, + }, + }, + 'R1' + ], + [ + 'Contact/get', + { + ids => ['#1'], + properties => ['online'], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_str_equals('foo,bar', $res->[1][1]{list}[0]{online}[0]{value}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_multicontact b/cassandane/tiny-tests/JMAPContacts/contact_set_multicontact index dd490e4afc..8725b78db5 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_multicontact +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_multicontact @@ -2,49 +2,55 @@ use Cassandane::Tiny; sub test_contact_set_multicontact - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([['Contact/set', { - create => { - "1" => {firstName => "first", lastName => "last"}, - "2" => {firstName => "second", lastName => "last"}, - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "1" => { firstName => "first", lastName => "last" }, + "2" => { firstName => "second", lastName => "last" }, + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; - my $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id1, 'notacontact']}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_not_null($fetch->[0][1]{notFound}); - $self->assert_str_equals('notacontact', $fetch->[0][1]{notFound}[0]); + my $fetch = $jmap->CallMethods( + [ [ 'Contact/get', { ids => [ $id1, 'notacontact' ] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('first', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_not_null($fetch->[0][1]{notFound}); + $self->assert_str_equals('notacontact', $fetch->[0][1]{notFound}[0]); - $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id2]}, "R3"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R3', $fetch->[0][2]); - $self->assert_str_equals('second', $fetch->[0][1]{list}[0]{firstName}); - $self->assert_deep_equals([], $fetch->[0][1]{notFound}); + $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id2] }, "R3" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R3', $fetch->[0][2]); + $self->assert_str_equals('second', $fetch->[0][1]{list}[0]{firstName}); + $self->assert_deep_equals([], $fetch->[0][1]{notFound}); - $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id1, $id2]}, "R4"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R4', $fetch->[0][2]); - $self->assert_num_equals(2, scalar @{$fetch->[0][1]{list}}); - $self->assert_deep_equals([], $fetch->[0][1]{notFound}); + $fetch = $jmap->CallMethods( + [ [ 'Contact/get', { ids => [ $id1, $id2 ] }, "R4" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R4', $fetch->[0][2]); + $self->assert_num_equals(2, scalar @{ $fetch->[0][1]{list} }); + $self->assert_deep_equals([], $fetch->[0][1]{notFound}); - $fetch = $jmap->CallMethods([['Contact/get', {}, "R5"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R5', $fetch->[0][2]); - $self->assert_num_equals(2, scalar @{$fetch->[0][1]{list}}); - $self->assert_deep_equals([], $fetch->[0][1]{notFound}); + $fetch = $jmap->CallMethods([ [ 'Contact/get', {}, "R5" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R5', $fetch->[0][2]); + $self->assert_num_equals(2, scalar @{ $fetch->[0][1]{list} }); + $self->assert_deep_equals([], $fetch->[0][1]{notFound}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_nickname b/cassandane/tiny-tests/JMAPContacts/contact_set_nickname index 520ef53af6..6de58490b3 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_nickname +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_nickname @@ -2,28 +2,44 @@ use Cassandane::Tiny; sub test_contact_set_nickname - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', {create => { - "1" => { firstName => "foo", lastName => "last1", nickname => "" }, - "2" => { firstName => "bar", lastName => "last2", nickname => "string" }, - "3" => { firstName => "bar", lastName => "last3", nickname => "string,list" }, - }}, "R1"]]); - $self->assert_not_null($res); - my $contact1 = $res->[0][1]{created}{"1"}{id}; - my $contact2 = $res->[0][1]{created}{"2"}{id}; - my $contact3 = $res->[0][1]{created}{"3"}{id}; - $self->assert_not_null($contact1); - $self->assert_not_null($contact2); - $self->assert_not_null($contact3); + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "1" => { firstName => "foo", lastName => "last1", nickname => "" }, + "2" => + { firstName => "bar", lastName => "last2", nickname => "string" }, + "3" => { + firstName => "bar", + lastName => "last3", + nickname => "string,list" + }, + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + my $contact1 = $res->[0][1]{created}{"1"}{id}; + my $contact2 = $res->[0][1]{created}{"2"}{id}; + my $contact3 = $res->[0][1]{created}{"3"}{id}; + $self->assert_not_null($contact1); + $self->assert_not_null($contact2); + $self->assert_not_null($contact3); - $res = $jmap->CallMethods([['Contact/set', {update => { - $contact2 => { nickname => "" }, - }}, "R2"]]); - $self->assert_not_null($res); + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { + $contact2 => { nickname => "" }, + } + }, + "R2" + ] ]); + $self->assert_not_null($res); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_reject_duplicate_uid b/cassandane/tiny-tests/JMAPContacts/contact_set_reject_duplicate_uid index 317f8ed38c..9bdabca3fe 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_reject_duplicate_uid +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_reject_duplicate_uid @@ -2,40 +2,47 @@ use Cassandane::Tiny; sub test_contact_set_reject_duplicate_uid - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; - $carddav->NewAddressBook('addrbookB') or die; + $carddav->NewAddressBook('addrbookB') or die; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contactA => { - uid => '123456789', - lastName => 'contactA', - }, - } - }, 'R1'], - ]); - my $contactA = $res->[0][1]{created}{contactA}{id}; - $self->assert_not_null($contactA); + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + contactA => { + uid => '123456789', + lastName => 'contactA', + }, + } + }, + 'R1' + ], + ]); + my $contactA = $res->[0][1]{created}{contactA}{id}; + $self->assert_not_null($contactA); - $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contactB => { - addressbookId => 'addrbookB', - uid => '123456789', - lastName => 'contactB', - }, - } - }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notCreated}{contactB}{type}); - $self->assert_deep_equals(['uid'], - $res->[0][1]{notCreated}{contactB}{properties}); + $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + contactB => { + addressbookId => 'addrbookB', + uid => '123456789', + lastName => 'contactB', + }, + } + }, + 'R1' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{contactB}{type}); + $self->assert_deep_equals(['uid'], + $res->[0][1]{notCreated}{contactB}{properties}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_state b/cassandane/tiny-tests/JMAPContacts/contact_set_state index 683cb0339c..642eeae626 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_state +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_state @@ -2,68 +2,85 @@ use Cassandane::Tiny; sub test_contact_set_state - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contact"; - my $res = $jmap->CallMethods([['Contact/set', {create => {"1" => {firstName => "first", lastName => "last"}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; - my $state = $res->[0][1]{newState}; + xlog $self, "create contact"; + my $res = $jmap->CallMethods( + [ [ + 'Contact/set', + { create => { "1" => { firstName => "first", lastName => "last" } } }, + "R1" + ] ] + ); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; + my $state = $res->[0][1]{newState}; - xlog $self, "get contact $id"; - $res = $jmap->CallMethods([['Contact/get', {}, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/get', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - $self->assert_str_equals('first', $res->[0][1]{list}[0]{firstName}); - $self->assert_str_equals($state, $res->[0][1]{state}); + xlog $self, "get contact $id"; + $res = $jmap->CallMethods([ [ 'Contact/get', {}, "R2" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/get', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + $self->assert_str_equals('first', $res->[0][1]{list}[0]{firstName}); + $self->assert_str_equals($state, $res->[0][1]{state}); - xlog $self, "update $id with state token $state"; - $res = $jmap->CallMethods([['Contact/set', { - ifInState => $state, - update => {$id => - {firstName => "first", lastName => "last"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert(exists $res->[0][1]{updated}{$id}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - my $oldState = $state; - $state = $res->[0][1]{newState}; + xlog $self, "update $id with state token $state"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + ifInState => $state, + update => { $id => { firstName => "first", lastName => "last" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert(exists $res->[0][1]{updated}{$id}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + my $oldState = $state; + $state = $res->[0][1]{newState}; - xlog $self, "update $id with expired state token $oldState"; - $res = $jmap->CallMethods([['Contact/set', { - ifInState => $oldState, - update => {$id => - {firstName => "first", lastName => "last"} - }}, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + xlog $self, "update $id with expired state token $oldState"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + ifInState => $oldState, + update => { $id => { firstName => "first", lastName => "last" } } + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "get contact $id to make sure state didn't change"; - $res = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]{state}); + xlog $self, "get contact $id to make sure state didn't change"; + $res = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]{state}); - xlog $self, "destroy $id with expired state token $oldState"; - $res = $jmap->CallMethods([['Contact/set', { - ifInState => $oldState, - destroy => [$id] - }, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + xlog $self, "destroy $id with expired state token $oldState"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + ifInState => $oldState, + destroy => [$id] + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "destroy contact $id with current state"; - $res = $jmap->CallMethods([ - ['Contact/set', { - ifInState => $state, - destroy => [$id] - }, "R1"] - ]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy contact $id with current state"; + $res = $jmap->CallMethods([ [ + 'Contact/set', + { + ifInState => $state, + destroy => [$id] + }, + "R1" + ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_toolarge b/cassandane/tiny-tests/JMAPContacts/contact_set_toolarge index d25a1d1869..43ec81482e 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_toolarge +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_toolarge @@ -2,39 +2,46 @@ use Cassandane::Tiny; sub test_contact_set_toolarge - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - 1 => { - lastName => 'name', - notes => ('x' x 100000), - }, - 2 => { - lastName => 'othername', - notes => ('x' x 10000), - }, - }, - }, 'R1'], - ]); - $self->assert_str_equals('tooLarge', $res->[0][1]{notCreated}{1}{type}); - $self->assert_not_null($res->[0][1]{created}{2}); - my $id = $res->[0][1]{created}{2}{id}; + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + 1 => { + lastName => 'name', + notes => ('x' x 100000), + }, + 2 => { + lastName => 'othername', + notes => ('x' x 10000), + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('tooLarge', $res->[0][1]{notCreated}{1}{type}); + $self->assert_not_null($res->[0][1]{created}{2}); + my $id = $res->[0][1]{created}{2}{id}; - $res = $jmap->CallMethods([ - ['Contact/set', { - update => { - $id => { - notes => ('x' x 100000), - }, - }, - }, 'R1'], - ]); - $self->assert_str_equals('tooLarge', $res->[0][1]{notUpdated}{$id}{type}); + $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + update => { + $id => { + notes => ('x' x 100000), + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('tooLarge', $res->[0][1]{notUpdated}{$id}{type}); -# Is there a way to shutdown httpd, change vcard_ax_size and restart httpd? + # Is there a way to shutdown httpd, change vcard_ax_size and restart httpd? } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_set_uid b/cassandane/tiny-tests/JMAPContacts/contact_set_uid index c55286fede..d2755c689b 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_set_uid +++ b/cassandane/tiny-tests/JMAPContacts/contact_set_uid @@ -2,80 +2,100 @@ use Cassandane::Tiny; sub test_contact_set_uid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # An empty UID generates a random uid. - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - "1" => { - firstName => "first1", - lastName => "last1", - } - } - }, "R1"], - ['Contact/get', { ids => ['#1'] }, 'R2'], - ]); - $self->assert_not_null($res->[1][1]{list}[0]{uid}); - $jmap->{CreatedIds} = {}; + # An empty UID generates a random uid. + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + "1" => { + firstName => "first1", + lastName => "last1", + } + } + }, + "R1" + ], + [ 'Contact/get', { ids => ['#1'] }, 'R2' ], + ]); + $self->assert_not_null($res->[1][1]{list}[0]{uid}); + $jmap->{CreatedIds} = {}; - # A sane UID maps to both the JMAP id and the DAV resource. - $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - "2" => { - firstName => "first2", - lastName => "last2", - uid => '1234-56789-01234-56789', - } - } - }, "R1"], - ['Contact/get', { ids => ['#2'] }, 'R2'], - ]); - $self->assert_not_null($res->[1][1]{list}[0]{uid}); - my($filename, $dirs, $suffix) = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); - $self->assert_not_null($res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($res->[1][1]{list}[0]->{uid}, $res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($filename, $res->[1][1]{list}[0]->{id}); - $jmap->{CreatedIds} = {}; + # A sane UID maps to both the JMAP id and the DAV resource. + $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + "2" => { + firstName => "first2", + lastName => "last2", + uid => '1234-56789-01234-56789', + } + } + }, + "R1" + ], + [ 'Contact/get', { ids => ['#2'] }, 'R2' ], + ]); + $self->assert_not_null($res->[1][1]{list}[0]{uid}); + my ($filename, $dirs, $suffix) + = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); + $self->assert_not_null($res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($res->[1][1]{list}[0]->{uid}, + $res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($filename, $res->[1][1]{list}[0]->{id}); + $jmap->{CreatedIds} = {}; - # A non-pathsafe UID maps to uid but not the DAV resource. - $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - "3" => { - firstName => "first3", - lastName => "last3", - uid => 'a/bogus/path#uid', - } - } - }, "R1"], - ['Contact/get', { ids => ['#3'] }, 'R2'], - ]); - $self->assert_not_null($res->[1][1]{list}[0]{uid}); - ($filename, $dirs, $suffix) = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); - $self->assert_not_null($res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($res->[1][1]{list}[0]->{id}, $res->[1][1]{list}[0]->{uid}); - $self->assert_str_not_equals('path#uid', $filename); - $jmap->{CreatedIds} = {}; + # A non-pathsafe UID maps to uid but not the DAV resource. + $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + create => { + "3" => { + firstName => "first3", + lastName => "last3", + uid => 'a/bogus/path#uid', + } + } + }, + "R1" + ], + [ 'Contact/get', { ids => ['#3'] }, 'R2' ], + ]); + $self->assert_not_null($res->[1][1]{list}[0]{uid}); + ($filename, $dirs, $suffix) + = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); + $self->assert_not_null($res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($res->[1][1]{list}[0]->{id}, + $res->[1][1]{list}[0]->{uid}); + $self->assert_str_not_equals('path#uid', $filename); + $jmap->{CreatedIds} = {}; - # Can't change an UID - my $contactId = $res->[0][1]{created}{3}{id}; - $self->assert_not_null($contactId); - $res = $jmap->CallMethods([ - ['Contact/set', { - update => { - $contactId => { - uid => '0000-1234-56789-01234-56789-000' - } - } - }, "R1"], - ]); - $self->assert_str_equals('uid', $res->[0][1]{notUpdated}{$contactId}{properties}[0]); - $jmap->{CreatedIds} = {}; + # Can't change an UID + my $contactId = $res->[0][1]{created}{3}{id}; + $self->assert_not_null($contactId); + $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + update => { + $contactId => { + uid => '0000-1234-56789-01234-56789-000' + } + } + }, + "R1" + ], + ]); + $self->assert_str_equals('uid', + $res->[0][1]{notUpdated}{$contactId}{properties}[0]); + $jmap->{CreatedIds} = {}; } diff --git a/cassandane/tiny-tests/JMAPContacts/contact_update_grouped_property b/cassandane/tiny-tests/JMAPContacts/contact_update_grouped_property index 2227cfa0fb..c979b5c12f 100644 --- a/cassandane/tiny-tests/JMAPContacts/contact_update_grouped_property +++ b/cassandane/tiny-tests/JMAPContacts/contact_update_grouped_property @@ -2,26 +2,25 @@ use Cassandane::Tiny; sub test_contact_update_grouped_property - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['Contact/get', { - }, 'R1'] - ]); + my $res = $jmap->CallMethods([ [ 'Contact/get', {}, 'R1' ] ]); - $self->assert_equals("Bubba Gump Shrimp Co.", - $res->[0][1]{list}[0]{company}); + $self->assert_equals("Bubba Gump Shrimp Co.", $res->[0][1]{list}[0]{company}); - $res = $jmap->CallMethods([ - ['Contact/set', { - update => {$id => { company => "BGSCO" }} - }, "R1"], - ['Contact/get', { - }, 'R2'] - ]); + $res = $jmap->CallMethods([ + [ + 'Contact/set', + { + update => { $id => { company => "BGSCO" } } + }, + "R1" + ], + [ 'Contact/get', {}, 'R2' ] + ]); - $self->assert_equals("BGSCO", $res->[1][1]{list}[0]{company}); + $self->assert_equals("BGSCO", $res->[1][1]{list}[0]{company}); - $res = $carddav->Request('GET', $href); + $res = $carddav->Request('GET', $href); - my $newcard = $res->{content}; - $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties + my $newcard = $res->{content}; + $newcard =~ s/\r?\n[ \t]+//gs; # unfold long properties - $self->assert_matches(qr/\nITEM1.ORG:BGSCO/, $newcard); - $self->assert_does_not_match(qr/\nORG:/, $newcard); + $self->assert_matches(qr/\nITEM1.ORG:BGSCO/, $newcard); + $self->assert_does_not_match(qr/\nORG:/, $newcard); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_changes b/cassandane/tiny-tests/JMAPContacts/contactgroup_changes index ed8532c5bd..d165515615 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_changes +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_changes @@ -2,140 +2,187 @@ use Cassandane::Tiny; sub test_contactgroup_changes - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', {create => { - "a" => {firstName => "a", lastName => "a"}, - "b" => {firstName => "b", lastName => "b"}, - "c" => {firstName => "c", lastName => "c"}, - "d" => {firstName => "d", lastName => "d"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $contactA = $res->[0][1]{created}{"a"}{id}; - my $contactB = $res->[0][1]{created}{"b"}{id}; - my $contactC = $res->[0][1]{created}{"c"}{id}; - my $contactD = $res->[0][1]{created}{"d"}{id}; - - xlog $self, "get contact groups state"; - $res = $jmap->CallMethods([['ContactGroup/get', {}, "R2"]]); - my $state = $res->[0][1]{state}; - - xlog $self, "create contact group 1"; - $res = $jmap->CallMethods([['ContactGroup/set', {create => { - "1" => {name => "first", contactIds => [$contactA, $contactB]}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - - - xlog $self, "get contact group updates"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - - my $oldState = $state; - $state = $res->[0][1]{newState}; - - xlog $self, "create contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', {create => { - "2" => {name => "second", contactIds => [$contactC, $contactD]}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id2 = $res->[0][1]{created}{"2"}{id}; - - xlog $self, "get contact group updates (since last change)"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "get contact group updates (in bulk)"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - sinceState => $oldState - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get contact group updates from initial state (maxChanges=1)"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - sinceState => $oldState, - maxChanges => 1 - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $interimState = $res->[0][1]{newState}; - - xlog $self, "get contact group updates from interim state (maxChanges=10)"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - sinceState => $interimState, - maxChanges => 10 - }, "R2"]]); - $self->assert_str_equals($interimState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "destroy contact group 1, update contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', { - destroy => [$id1], - update => {$id2 => {name => "second (updated)"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - xlog $self, "get contact group updates"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - - xlog $self, "destroy contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', {destroy => [$id2]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "a" => { firstName => "a", lastName => "a" }, + "b" => { firstName => "b", lastName => "b" }, + "c" => { firstName => "c", lastName => "c" }, + "d" => { firstName => "d", lastName => "d" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $contactA = $res->[0][1]{created}{"a"}{id}; + my $contactB = $res->[0][1]{created}{"b"}{id}; + my $contactC = $res->[0][1]{created}{"c"}{id}; + my $contactD = $res->[0][1]{created}{"d"}{id}; + + xlog $self, "get contact groups state"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', {}, "R2" ] ]); + my $state = $res->[0][1]{state}; + + xlog $self, "create contact group 1"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + create => { + "1" => { name => "first", contactIds => [ $contactA, $contactB ] } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get contact group updates"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + + my $oldState = $state; + $state = $res->[0][1]{newState}; + + xlog $self, "create contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + create => { + "2" => { name => "second", contactIds => [ $contactC, $contactD ] } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id2 = $res->[0][1]{created}{"2"}{id}; + + xlog $self, "get contact group updates (since last change)"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "get contact group updates (in bulk)"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + sinceState => $oldState + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get contact group updates from initial state (maxChanges=1)"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + sinceState => $oldState, + maxChanges => 1 + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + my $interimState = $res->[0][1]{newState}; + + xlog $self, "get contact group updates from interim state (maxChanges=10)"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + sinceState => $interimState, + maxChanges => 10 + }, + "R2" + ] ]); + $self->assert_str_equals($interimState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "destroy contact group 1, update contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + destroy => [$id1], + update => { $id2 => { name => "second (updated)" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + xlog $self, "get contact group updates"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + + xlog $self, "destroy contact group 2"; + $res = $jmap->CallMethods( + [ [ 'ContactGroup/set', { destroy => [$id2] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_changes_shared b/cassandane/tiny-tests/JMAPContacts/contactgroup_changes_shared index 528ade65be..784bbacf13 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_changes_shared +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_changes_shared @@ -2,177 +2,227 @@ use Cassandane::Tiny; sub test_contactgroup_changes_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - my $admintalk = $self->{adminstore}->get_client(); - my $service = $self->{instance}->get_service("http"); - - xlog $self, "create shared account"; - $admintalk->create("user.manifold"); - - my $mantalk = Net::CardDAVTalk->new( - user => "manifold", - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); - $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); - xlog $self, "share to user"; - $admintalk->setacl("user.manifold.#addressbooks.Default", "cassandane" => 'lrswipkxtecdn') or die; - - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', { - accountId => 'manifold', - create => { - "a" => {firstName => "a", lastName => "a"}, - "b" => {firstName => "b", lastName => "b"}, - "c" => {firstName => "c", lastName => "c"}, - "d" => {firstName => "d", lastName => "d"} - }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $contactA = $res->[0][1]{created}{"a"}{id}; - my $contactB = $res->[0][1]{created}{"b"}{id}; - my $contactC = $res->[0][1]{created}{"c"}{id}; - my $contactD = $res->[0][1]{created}{"d"}{id}; - - xlog $self, "get contact groups state"; - $res = $jmap->CallMethods([['ContactGroup/get', { accountId => 'manifold', }, "R2"]]); - my $state = $res->[0][1]{state}; - - xlog $self, "create contact group 1"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - create => { - "1" => {name => "first", contactIds => [$contactA, $contactB]}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - - - xlog $self, "get contact group updates"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - - my $oldState = $state; - $state = $res->[0][1]{newState}; - - xlog $self, "create contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - create => { - "2" => {name => "second", contactIds => [$contactC, $contactD]}}}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $id2 = $res->[0][1]{created}{"2"}{id}; - - xlog $self, "get contact group updates (since last change)"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "get contact group updates (in bulk)"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - accountId => 'manifold', - sinceState => $oldState - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get contact group updates from initial state (maxChanges=1)"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - accountId => 'manifold', - sinceState => $oldState, - maxChanges => 1 - }, "R2"]]); - $self->assert_str_equals($oldState, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{created}[0]); - my $interimState = $res->[0][1]{newState}; - - xlog $self, "get contact group updates from interim state (maxChanges=10)"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - accountId => 'manifold', - sinceState => $interimState, - maxChanges => 10 - }, "R2"]]); - $self->assert_str_equals($interimState, $res->[0][1]{oldState}); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id2, $res->[0][1]{created}[0]); - $state = $res->[0][1]{newState}; - - xlog $self, "destroy contact group 1, update contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - destroy => [$id1], - update => {$id2 => {name => "second (updated)"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - xlog $self, "get contact group updates"; - $res = $jmap->CallMethods([['ContactGroup/changes', { - accountId => 'manifold', - sinceState => $state - }, "R2"]]); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - - xlog $self, "destroy contact group 2"; - $res = $jmap->CallMethods([['ContactGroup/set', { - accountId => 'manifold', - destroy => [$id2] - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $carddav = $self->{carddav}; + my $admintalk = $self->{adminstore}->get_client(); + my $service = $self->{instance}->get_service("http"); + + xlog $self, "create shared account"; + $admintalk->create("user.manifold"); + + my $mantalk = Net::CardDAVTalk->new( + user => "manifold", + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + $admintalk->setacl("user.manifold", admin => 'lrswipkxtecdan'); + $admintalk->setacl("user.manifold", manifold => 'lrswipkxtecdn'); + xlog $self, "share to user"; + $admintalk->setacl("user.manifold.#addressbooks.Default", + "cassandane" => 'lrswipkxtecdn') + or die; + + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + accountId => 'manifold', + create => { + "a" => { firstName => "a", lastName => "a" }, + "b" => { firstName => "b", lastName => "b" }, + "c" => { firstName => "c", lastName => "c" }, + "d" => { firstName => "d", lastName => "d" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $contactA = $res->[0][1]{created}{"a"}{id}; + my $contactB = $res->[0][1]{created}{"b"}{id}; + my $contactC = $res->[0][1]{created}{"c"}{id}; + my $contactD = $res->[0][1]{created}{"d"}{id}; + + xlog $self, "get contact groups state"; + $res = $jmap->CallMethods( + [ [ 'ContactGroup/get', { accountId => 'manifold', }, "R2" ] ]); + my $state = $res->[0][1]{state}; + + xlog $self, "create contact group 1"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + create => { + "1" => { name => "first", contactIds => [ $contactA, $contactB ] } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get contact group updates"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + + my $oldState = $state; + $state = $res->[0][1]{newState}; + + xlog $self, "create contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + create => { + "2" => { name => "second", contactIds => [ $contactC, $contactD ] } + } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $id2 = $res->[0][1]{created}{"2"}{id}; + + xlog $self, "get contact group updates (since last change)"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "get contact group updates (in bulk)"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + accountId => 'manifold', + sinceState => $oldState + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get contact group updates from initial state (maxChanges=1)"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + accountId => 'manifold', + sinceState => $oldState, + maxChanges => 1 + }, + "R2" + ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::true, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{created}[0]); + my $interimState = $res->[0][1]{newState}; + + xlog $self, "get contact group updates from interim state (maxChanges=10)"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + accountId => 'manifold', + sinceState => $interimState, + maxChanges => 10 + }, + "R2" + ] ]); + $self->assert_str_equals($interimState, $res->[0][1]{oldState}); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id2, $res->[0][1]{created}[0]); + $state = $res->[0][1]{newState}; + + xlog $self, "destroy contact group 1, update contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + destroy => [$id1], + update => { $id2 => { name => "second (updated)" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + xlog $self, "get contact group updates"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/changes', + { + accountId => 'manifold', + sinceState => $state + }, + "R2" + ] ]); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_equals(JSON::false, $res->[0][1]{hasMoreChanges}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($id2, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + + xlog $self, "destroy contact group 2"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + accountId => 'manifold', + destroy => [$id2] + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_deduplicate_contactids b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_deduplicate_contactids index a068432515..547d51c634 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_deduplicate_contactids +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_deduplicate_contactids @@ -2,33 +2,32 @@ use Cassandane::Tiny; sub test_contactgroup_get_deduplicate_contactids - :min_version_3_7 :needs_component_jmap -{ - - my ($self) = @_; - my $jmap = $self->{jmap}; - my $carddav = $self->{carddav}; - - # define duplicate ids - my @ids = qw ( - b48259ca-1524-4df0-af54-e65f60bf27b5 - b48259ca-1524-4df0-af54-e65f60bf27b5 - 2391c8ef-1cfc-40da-8730-cb5664005973 - b48259ca-1524-4df0-af54-e65f60bf27b5 - f1fc45d4-809a-4d10-8abd-2bfc84dcab8c - 2391c8ef-1cfc-40da-8730-cb5664005973 - ); - - # deduplicate ids - my %idhash = map { $_, 1 } @ids; - my @wantids = sort keys %idhash; - - my @wantOtherAccountIds = qw (5b3b9ce1-0b5e-4cbd-8add-018321cad51b); - - xlog $self, "create a v3 contact group with duplicate members"; - my $id = '816ad14a-f9ef-43a8-9039-b57bf321de1f'; - my $href = "Default/$id.vcf"; - my $vgroup = <{jmap}; + my $carddav = $self->{carddav}; + + # define duplicate ids + my @ids = qw ( + b48259ca-1524-4df0-af54-e65f60bf27b5 + b48259ca-1524-4df0-af54-e65f60bf27b5 + 2391c8ef-1cfc-40da-8730-cb5664005973 + b48259ca-1524-4df0-af54-e65f60bf27b5 + f1fc45d4-809a-4d10-8abd-2bfc84dcab8c + 2391c8ef-1cfc-40da-8730-cb5664005973 + ); + + # deduplicate ids + my %idhash = map { $_, 1 } @ids; + my @wantids = sort keys %idhash; + + my @wantOtherAccountIds = qw (5b3b9ce1-0b5e-4cbd-8add-018321cad51b); + + xlog $self, "create a v3 contact group with duplicate members"; + my $id = '816ad14a-f9ef-43a8-9039-b57bf321de1f'; + my $href = "Default/$id.vcf"; + my $vgroup = <Request('PUT', $href, $vgroup, 'Content-Type' => 'text/vcard'); - - my $res = $jmap->CallMethods([ - ['ContactGroup/get', { - properties => ['contactIds', 'otherAccountContactIds' ], - }, 'R1'] - ]); - my @gotids = sort @{$res->[0][1]{list}[0]{contactIds}}; - - xlog "Assert contactIds in group got deduplicated"; - $self->assert_deep_equals(\@wantids, \@gotids); - - xlog "Assert otherAccountContactIds got deduplicated"; - $self->assert_deep_equals({ foo => \@wantOtherAccountIds }, - $res->[0][1]{list}[0]{otherAccountContactIds}); + $vgroup .= join("", + map { "X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:" . $_ . "\n" } @ids); + $vgroup .= "END:VCARD"; + $vgroup =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $vgroup, 'Content-Type' => 'text/vcard'); + + my $res = $jmap->CallMethods([ [ + 'ContactGroup/get', + { + properties => [ 'contactIds', 'otherAccountContactIds' ], + }, + 'R1' + ] ]); + my @gotids = sort @{ $res->[0][1]{list}[0]{contactIds} }; + + xlog "Assert contactIds in group got deduplicated"; + $self->assert_deep_equals(\@wantids, \@gotids); + + xlog "Assert otherAccountContactIds got deduplicated"; + $self->assert_deep_equals({ foo => \@wantOtherAccountIds }, + $res->[0][1]{list}[0]{otherAccountContactIds}); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_issue2292 b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_issue2292 index f30e8744ae..fee6ce7229 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_issue2292 +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_issue2292 @@ -2,27 +2,32 @@ use Cassandane::Tiny; sub test_contactgroup_get_issue2292 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contact group"; - my $res = $jmap->CallMethods([['ContactGroup/set', {create => { - "1" => {name => "group1"} - }}, "R2"]]); - $self->assert_not_null($res->[0][1]{created}{"1"}); + xlog $self, "create contact group"; + my $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + create => { + "1" => { name => "group1" } + } + }, + "R2" + ] ]); + $self->assert_not_null($res->[0][1]{created}{"1"}); - xlog $self, "get contact group with no ids"; - $res = $jmap->CallMethods([['ContactGroup/get', { }, "R3"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); + xlog $self, "get contact group with no ids"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', {}, "R3" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); - xlog $self, "get contact group with empty ids"; - $res = $jmap->CallMethods([['ContactGroup/get', { ids => [] }, "R3"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + xlog $self, "get contact group with empty ids"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', { ids => [] }, "R3" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); - xlog $self, "get contact group with null ids"; - $res = $jmap->CallMethods([['ContactGroup/get', { ids => undef }, "R3"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); + xlog $self, "get contact group with null ids"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', { ids => undef }, "R3" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_v4 b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_v4 index 2edb6efabd..53bcda29fb 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_get_v4 +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_get_v4 @@ -2,26 +2,25 @@ use Cassandane::Tiny; sub test_contactgroup_get_v4 - :min_version_3_5 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactGroup/get', { - }, 'R1'] - ]); - $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals('Test', $res->[0][1]{list}[0]{name}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}[0]{contactIds}}); + my $res = $jmap->CallMethods([ [ 'ContactGroup/get', {}, 'R1' ] ]); + $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals('Test', $res->[0][1]{list}[0]{name}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list}[0]{contactIds} }); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_query b/cassandane/tiny-tests/JMAPContacts/contactgroup_query index e9c8926dbb..a217b10684 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_query +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_query @@ -2,79 +2,98 @@ use Cassandane::Tiny; sub test_contactgroup_query - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contact groups"; - my $res = $jmap->CallMethods([ - ['ContactGroup/set', { - create => { - contactGroup1 => { - name => 'dogs and cats', - }, - contactGroup2 => { - name => 'cats and bats', - }, - contactGroup3 => { - name => 'bats and hats', - }, - }, - }, 'R1'], - ]); - my $contactGroupId1 = $res->[0][1]{created}{contactGroup1}{id}; - $self->assert_not_null($contactGroupId1); - my $contactGroupUid1 = $res->[0][1]{created}{contactGroup1}{uid}; - $self->assert_not_null($contactGroupUid1); + xlog $self, "create contact groups"; + my $res = $jmap->CallMethods([ + [ + 'ContactGroup/set', + { + create => { + contactGroup1 => { + name => 'dogs and cats', + }, + contactGroup2 => { + name => 'cats and bats', + }, + contactGroup3 => { + name => 'bats and hats', + }, + }, + }, + 'R1' + ], + ]); + my $contactGroupId1 = $res->[0][1]{created}{contactGroup1}{id}; + $self->assert_not_null($contactGroupId1); + my $contactGroupUid1 = $res->[0][1]{created}{contactGroup1}{uid}; + $self->assert_not_null($contactGroupUid1); - my $contactGroupId2 = $res->[0][1]{created}{contactGroup2}{id}; - $self->assert_not_null($contactGroupId2); - my $contactGroupUid2 = $res->[0][1]{created}{contactGroup2}{uid}; - $self->assert_not_null($contactGroupUid2); + my $contactGroupId2 = $res->[0][1]{created}{contactGroup2}{id}; + $self->assert_not_null($contactGroupId2); + my $contactGroupUid2 = $res->[0][1]{created}{contactGroup2}{uid}; + $self->assert_not_null($contactGroupUid2); - my $contactGroupId3 = $res->[0][1]{created}{contactGroup3}{id}; - $self->assert_not_null($contactGroupId3); - my $contactGroupUid3 = $res->[0][1]{created}{contactGroup3}{uid}; - $self->assert_not_null($contactGroupUid3); + my $contactGroupId3 = $res->[0][1]{created}{contactGroup3}{id}; + $self->assert_not_null($contactGroupId3); + my $contactGroupUid3 = $res->[0][1]{created}{contactGroup3}{uid}; + $self->assert_not_null($contactGroupUid3); - xlog $self, "query by exact name"; - $res = $jmap->CallMethods([ - ['ContactGroup/query', { - filter => { - name => 'dogs and cats', - }, - }, 'R2'], - ]); - $self->assert_str_equals("ContactGroup/query", $res->[0][0]); - $self->assert_deep_equals([$contactGroupId1], $res->[0][1]{ids}); + xlog $self, "query by exact name"; + $res = $jmap->CallMethods([ + [ + 'ContactGroup/query', + { + filter => { + name => 'dogs and cats', + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("ContactGroup/query", $res->[0][0]); + $self->assert_deep_equals([$contactGroupId1], $res->[0][1]{ids}); - xlog $self, "query by unknown name"; - $res = $jmap->CallMethods([ - ['ContactGroup/query', { - filter => { - name => 'nope', - }, - }, 'R2'], - ]); - $self->assert_str_equals("ContactGroup/query", $res->[0][0]); - $self->assert_deep_equals([], $res->[0][1]{ids}); + xlog $self, "query by unknown name"; + $res = $jmap->CallMethods([ + [ + 'ContactGroup/query', + { + filter => { + name => 'nope', + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("ContactGroup/query", $res->[0][0]); + $self->assert_deep_equals([], $res->[0][1]{ids}); - xlog $self, "query substring of name"; - $res = $jmap->CallMethods([ - ['ContactGroup/query', { - filter => { - operator => 'OR', - conditions => [{ - name => 'bats', - }, { - text => 'hats', - }], + xlog $self, "query substring of name"; + $res = $jmap->CallMethods([ + [ + 'ContactGroup/query', + { + filter => { + operator => 'OR', + conditions => [ + { + name => 'bats', }, - }, 'R2'], - ]); - $self->assert_str_equals("ContactGroup/query", $res->[0][0]); - my %gotIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_deep_equals({ $contactGroupUid2 => 1, $contactGroupUid3 => 1, }, \%gotIds); + { + text => 'hats', + } + ], + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("ContactGroup/query", $res->[0][0]); + my %gotIds = map { $_ => 1 } @{ $res->[0][1]{ids} }; + $self->assert_deep_equals({ $contactGroupUid2 => 1, $contactGroupUid3 => 1, }, + \%gotIds); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_query_uid b/cassandane/tiny-tests/JMAPContacts/contactgroup_query_uid index 3e4eb151d0..1f6a0b24c4 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_query_uid +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_query_uid @@ -2,79 +2,98 @@ use Cassandane::Tiny; sub test_contactgroup_query_uid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contact groups"; - my $res = $jmap->CallMethods([ - ['ContactGroup/set', { - create => { - contactGroup1 => { - name => 'contactGroup1', - }, - contactGroup2 => { - name => 'contactGroup2', - }, - contactGroup3 => { - name => 'contactGroup3', - }, - }, - }, 'R1'], - ]); - my $contactGroupId1 = $res->[0][1]{created}{contactGroup1}{id}; - $self->assert_not_null($contactGroupId1); - my $contactGroupUid1 = $res->[0][1]{created}{contactGroup1}{uid}; - $self->assert_not_null($contactGroupUid1); + xlog $self, "create contact groups"; + my $res = $jmap->CallMethods([ + [ + 'ContactGroup/set', + { + create => { + contactGroup1 => { + name => 'contactGroup1', + }, + contactGroup2 => { + name => 'contactGroup2', + }, + contactGroup3 => { + name => 'contactGroup3', + }, + }, + }, + 'R1' + ], + ]); + my $contactGroupId1 = $res->[0][1]{created}{contactGroup1}{id}; + $self->assert_not_null($contactGroupId1); + my $contactGroupUid1 = $res->[0][1]{created}{contactGroup1}{uid}; + $self->assert_not_null($contactGroupUid1); - my $contactGroupId2 = $res->[0][1]{created}{contactGroup2}{id}; - $self->assert_not_null($contactGroupId2); - my $contactGroupUid2 = $res->[0][1]{created}{contactGroup2}{uid}; - $self->assert_not_null($contactGroupUid2); + my $contactGroupId2 = $res->[0][1]{created}{contactGroup2}{id}; + $self->assert_not_null($contactGroupId2); + my $contactGroupUid2 = $res->[0][1]{created}{contactGroup2}{uid}; + $self->assert_not_null($contactGroupUid2); - my $contactGroupId3 = $res->[0][1]{created}{contactGroup3}{id}; - $self->assert_not_null($contactGroupId3); - my $contactGroupUid3 = $res->[0][1]{created}{contactGroup3}{uid}; - $self->assert_not_null($contactGroupUid3); + my $contactGroupId3 = $res->[0][1]{created}{contactGroup3}{id}; + $self->assert_not_null($contactGroupId3); + my $contactGroupUid3 = $res->[0][1]{created}{contactGroup3}{uid}; + $self->assert_not_null($contactGroupUid3); - xlog $self, "query by single uid"; - $res = $jmap->CallMethods([ - ['ContactGroup/query', { - filter => { - uid => $contactGroupUid2, - }, - }, 'R2'], - ]); - $self->assert_str_equals("ContactGroup/query", $res->[0][0]); - $self->assert_deep_equals([$contactGroupId2], $res->[0][1]{ids}); + xlog $self, "query by single uid"; + $res = $jmap->CallMethods([ + [ + 'ContactGroup/query', + { + filter => { + uid => $contactGroupUid2, + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("ContactGroup/query", $res->[0][0]); + $self->assert_deep_equals([$contactGroupId2], $res->[0][1]{ids}); - xlog $self, "query by invalid uid"; - $res = $jmap->CallMethods([ - ['ContactGroup/query', { - filter => { - uid => "notarealuid", - }, - }, 'R2'], - ]); - $self->assert_str_equals("ContactGroup/query", $res->[0][0]); - $self->assert_deep_equals([], $res->[0][1]{ids}); + xlog $self, "query by invalid uid"; + $res = $jmap->CallMethods([ + [ + 'ContactGroup/query', + { + filter => { + uid => "notarealuid", + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("ContactGroup/query", $res->[0][0]); + $self->assert_deep_equals([], $res->[0][1]{ids}); - xlog $self, "query by multiple uids"; - $res = $jmap->CallMethods([ - ['ContactGroup/query', { - filter => { - operator => 'OR', - conditions => [{ - uid => $contactGroupUid1, - }, { - uid => $contactGroupUid3, - }], + xlog $self, "query by multiple uids"; + $res = $jmap->CallMethods([ + [ + 'ContactGroup/query', + { + filter => { + operator => 'OR', + conditions => [ + { + uid => $contactGroupUid1, }, - }, 'R2'], - ]); - $self->assert_str_equals("ContactGroup/query", $res->[0][0]); - my %gotIds = map { $_ => 1 } @{$res->[0][1]{ids}}; - $self->assert_deep_equals({ $contactGroupUid1 => 1, $contactGroupUid3 => 1, }, \%gotIds); + { + uid => $contactGroupUid3, + } + ], + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals("ContactGroup/query", $res->[0][0]); + my %gotIds = map { $_ => 1 } @{ $res->[0][1]{ids} }; + $self->assert_deep_equals({ $contactGroupUid1 => 1, $contactGroupUid3 => 1, }, + \%gotIds); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_set b/cassandane/tiny-tests/JMAPContacts/contactgroup_set index 704027d0ee..a4ad4d40d0 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_set +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_set @@ -2,67 +2,91 @@ use Cassandane::Tiny; sub test_contactgroup_set - :min_version_3_1 :needs_component_jmap -{ + : min_version_3_1 : needs_component_jmap { - my ($self) = @_; + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create contacts"; - my $res = $jmap->CallMethods([['Contact/set', {create => { - "1" => { firstName => "foo", lastName => "last1" }, - "2" => { firstName => "bar", lastName => "last2" } - }}, "R1"]]); - my $contact1 = $res->[0][1]{created}{"1"}{id}; - my $contact2 = $res->[0][1]{created}{"2"}{id}; + xlog $self, "create contacts"; + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + create => { + "1" => { firstName => "foo", lastName => "last1" }, + "2" => { firstName => "bar", lastName => "last2" } + } + }, + "R1" + ] ]); + my $contact1 = $res->[0][1]{created}{"1"}{id}; + my $contact2 = $res->[0][1]{created}{"2"}{id}; - xlog $self, "create contact group with no contact ids"; - $res = $jmap->CallMethods([['ContactGroup/set', {create => { - "1" => {name => "group1"} - }}, "R2"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert_str_equals('R2', $res->[0][2]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create contact group with no contact ids"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + create => { + "1" => { name => "group1" } + } + }, + "R2" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert_str_equals('R2', $res->[0][2]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "get contact group $id"; - $res = $jmap->CallMethods([['ContactGroup/get', { ids => [$id] }, "R3"]]); - $self->assert_not_null($res); - $self->assert_str_equals('ContactGroup/get', $res->[0][0]); - $self->assert_str_equals('R3', $res->[0][2]); - $self->assert_str_equals('group1', $res->[0][1]{list}[0]{name}); - $self->assert(exists $res->[0][1]{list}[0]{contactIds}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}[0]{contactIds}}); + xlog $self, "get contact group $id"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', { ids => [$id] }, "R3" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('ContactGroup/get', $res->[0][0]); + $self->assert_str_equals('R3', $res->[0][2]); + $self->assert_str_equals('group1', $res->[0][1]{list}[0]{name}); + $self->assert(exists $res->[0][1]{list}[0]{contactIds}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list}[0]{contactIds} }); - xlog $self, "update contact group with invalid contact ids"; - $res = $jmap->CallMethods([['ContactGroup/set', {update => { - $id => {name => "group1", contactIds => [$contact1, $contact2, 255]} - }}, "R4"]]); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert(exists $res->[0][1]{notUpdated}{$id}); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notUpdated}{$id}{type}); - $self->assert_str_equals('contactIds[2]', $res->[0][1]{notUpdated}{$id}{properties}[0]); - $self->assert_str_equals('R4', $res->[0][2]); + xlog $self, "update contact group with invalid contact ids"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + update => { + $id => { name => "group1", contactIds => [ $contact1, $contact2, 255 ] } + } + }, + "R4" + ] ]); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert(exists $res->[0][1]{notUpdated}{$id}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$id}{type}); + $self->assert_str_equals('contactIds[2]', + $res->[0][1]{notUpdated}{$id}{properties}[0]); + $self->assert_str_equals('R4', $res->[0][2]); - xlog $self, "get contact group $id"; - $res = $jmap->CallMethods([['ContactGroup/get', { ids => [$id] }, "R3"]]); - $self->assert(exists $res->[0][1]{list}[0]{contactIds}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}[0]{contactIds}}); + xlog $self, "get contact group $id"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', { ids => [$id] }, "R3" ] ]); + $self->assert(exists $res->[0][1]{list}[0]{contactIds}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list}[0]{contactIds} }); + xlog $self, "update contact group with valid contact ids"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + update => { + $id => { name => "group1", contactIds => [ $contact1, $contact2 ] } + } + }, + "R4" + ] ]); - xlog $self, "update contact group with valid contact ids"; - $res = $jmap->CallMethods([['ContactGroup/set', {update => { - $id => {name => "group1", contactIds => [$contact1, $contact2]} - }}, "R4"]]); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert(exists $res->[0][1]{updated}{$id}); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - xlog $self, "get contact group $id"; - $res = $jmap->CallMethods([['ContactGroup/get', { ids => [$id] }, "R3"]]); - $self->assert(exists $res->[0][1]{list}[0]{contactIds}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}[0]{contactIds}}); - $self->assert_str_equals($contact1, $res->[0][1]{list}[0]{contactIds}[0]); - $self->assert_str_equals($contact2, $res->[0][1]{list}[0]{contactIds}[1]); + xlog $self, "get contact group $id"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', { ids => [$id] }, "R3" ] ]); + $self->assert(exists $res->[0][1]{list}[0]{contactIds}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list}[0]{contactIds} }); + $self->assert_str_equals($contact1, $res->[0][1]{list}[0]{contactIds}[0]); + $self->assert_str_equals($contact2, $res->[0][1]{list}[0]{contactIds}[1]); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_patch b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_patch index d8a9c2a757..10f35d4c1b 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_patch +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_patch @@ -2,46 +2,59 @@ use Cassandane::Tiny; sub test_contactgroup_set_patch - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['ContactGroup/set', { - create => { - 1 => { - name => 'name1', - otherAccountContactIds => { - other1 => ['contact1'], - other2 => ['contact2'] - } - } + my $res = $jmap->CallMethods([ + [ + 'ContactGroup/set', + { + create => { + 1 => { + name => 'name1', + otherAccountContactIds => { + other1 => ['contact1'], + other2 => ['contact2'] } - }, "R1"], - ['ContactGroup/get', { ids => ['#1'] }, 'R2'], - ]); - $self->assert_str_equals('name1', $res->[1][1]{list}[0]{name}); - $self->assert_deep_equals({ - other1 => ['contact1'], - other2 => ['contact2'] - }, $res->[1][1]{list}[0]{otherAccountContactIds}); - my $groupId1 = $res->[1][1]{list}[0]{id}; + } + } + }, + "R1" + ], + [ 'ContactGroup/get', { ids => ['#1'] }, 'R2' ], + ]); + $self->assert_str_equals('name1', $res->[1][1]{list}[0]{name}); + $self->assert_deep_equals( + { + other1 => ['contact1'], + other2 => ['contact2'] + }, + $res->[1][1]{list}[0]{otherAccountContactIds} + ); + my $groupId1 = $res->[1][1]{list}[0]{id}; - $res = $jmap->CallMethods([ - ['ContactGroup/set', { - update => { - $groupId1 => { - name => 'updatedname1', - 'otherAccountContactIds/other2' => undef, - } - } - }, "R1"], - ['ContactGroup/get', { ids => [$groupId1] }, 'R2'], - ]); - $self->assert_str_equals('updatedname1', $res->[1][1]{list}[0]{name}); - $self->assert_deep_equals({ - other1 => ['contact1'], - }, $res->[1][1]{list}[0]{otherAccountContactIds}); + $res = $jmap->CallMethods([ + [ + 'ContactGroup/set', + { + update => { + $groupId1 => { + name => 'updatedname1', + 'otherAccountContactIds/other2' => undef, + } + } + }, + "R1" + ], + [ 'ContactGroup/get', { ids => [$groupId1] }, 'R2' ], + ]); + $self->assert_str_equals('updatedname1', $res->[1][1]{list}[0]{name}); + $self->assert_deep_equals( + { + other1 => ['contact1'], + }, + $res->[1][1]{list}[0]{otherAccountContactIds} + ); } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_uid b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_uid index bde9c49a30..4f9bcc69a8 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_uid +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_uid @@ -2,77 +2,97 @@ use Cassandane::Tiny; sub test_contactgroup_set_uid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # An empty UID generates a random uid. - my $res = $jmap->CallMethods([ - ['ContactGroup/set', { - create => { - "1" => { - name => "name1", - } - } - }, "R1"], - ['ContactGroup/get', { ids => ['#1'] }, 'R2'], - ]); - $self->assert_not_null($res->[1][1]{list}[0]{uid}); - $jmap->{CreatedIds} = {}; + # An empty UID generates a random uid. + my $res = $jmap->CallMethods([ + [ + 'ContactGroup/set', + { + create => { + "1" => { + name => "name1", + } + } + }, + "R1" + ], + [ 'ContactGroup/get', { ids => ['#1'] }, 'R2' ], + ]); + $self->assert_not_null($res->[1][1]{list}[0]{uid}); + $jmap->{CreatedIds} = {}; - # A sane UID maps to both the JMAP id and the DAV resource. - $res = $jmap->CallMethods([ - ['ContactGroup/set', { - create => { - "2" => { - name => "name2", - uid => '1234-56789-01234-56789', - } - } - }, "R1"], - ['ContactGroup/get', { ids => ['#2'] }, 'R2'], - ]); - $self->assert_not_null($res->[1][1]{list}[0]{uid}); - my($filename, $dirs, $suffix) = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); - $self->assert_not_null($res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($res->[1][1]{list}[0]->{uid}, $res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($filename, $res->[1][1]{list}[0]->{id}); - $jmap->{CreatedIds} = {}; + # A sane UID maps to both the JMAP id and the DAV resource. + $res = $jmap->CallMethods([ + [ + 'ContactGroup/set', + { + create => { + "2" => { + name => "name2", + uid => '1234-56789-01234-56789', + } + } + }, + "R1" + ], + [ 'ContactGroup/get', { ids => ['#2'] }, 'R2' ], + ]); + $self->assert_not_null($res->[1][1]{list}[0]{uid}); + my ($filename, $dirs, $suffix) + = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); + $self->assert_not_null($res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($res->[1][1]{list}[0]->{uid}, + $res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($filename, $res->[1][1]{list}[0]->{id}); + $jmap->{CreatedIds} = {}; - # A non-pathsafe UID maps to uid but not the DAV resource. - $res = $jmap->CallMethods([ - ['ContactGroup/set', { - create => { - "3" => { - name => "name3", - uid => 'a/bogus/path#uid', - } - } - }, "R1"], - ['ContactGroup/get', { ids => ['#3'] }, 'R2'], - ]); - $self->assert_not_null($res->[1][1]{list}[0]{uid}); - ($filename, $dirs, $suffix) = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); - $self->assert_not_null($res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($res->[1][1]{list}[0]->{id}, $res->[1][1]{list}[0]->{uid}); - $self->assert_str_not_equals('path#uid', $filename); - $jmap->{CreatedIds} = {}; + # A non-pathsafe UID maps to uid but not the DAV resource. + $res = $jmap->CallMethods([ + [ + 'ContactGroup/set', + { + create => { + "3" => { + name => "name3", + uid => 'a/bogus/path#uid', + } + } + }, + "R1" + ], + [ 'ContactGroup/get', { ids => ['#3'] }, 'R2' ], + ]); + $self->assert_not_null($res->[1][1]{list}[0]{uid}); + ($filename, $dirs, $suffix) + = fileparse($res->[1][1]{list}[0]{"x-href"}, ".vcf"); + $self->assert_not_null($res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($res->[1][1]{list}[0]->{id}, + $res->[1][1]{list}[0]->{uid}); + $self->assert_str_not_equals('path#uid', $filename); + $jmap->{CreatedIds} = {}; - # Can't change an UID - my $contactId = $res->[0][1]{created}{3}{id}; - $self->assert_not_null($contactId); - $res = $jmap->CallMethods([ - ['ContactGroup/set', { - update => { - $contactId => { - uid => '0000-1234-56789-01234-56789-000' - } - } - }, "R1"], - ]); - $self->assert_str_equals('uid', $res->[0][1]{notUpdated}{$contactId}{properties}[0]); - $jmap->{CreatedIds} = {}; + # Can't change an UID + my $contactId = $res->[0][1]{created}{3}{id}; + $self->assert_not_null($contactId); + $res = $jmap->CallMethods([ + [ + 'ContactGroup/set', + { + update => { + $contactId => { + uid => '0000-1234-56789-01234-56789-000' + } + } + }, + "R1" + ], + ]); + $self->assert_str_equals('uid', + $res->[0][1]{notUpdated}{$contactId}{properties}[0]); + $jmap->{CreatedIds} = {}; } diff --git a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_update_v4 b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_update_v4 index 4f6668616a..6a0ae2fc61 100644 --- a/cassandane/tiny-tests/JMAPContacts/contactgroup_set_update_v4 +++ b/cassandane/tiny-tests/JMAPContacts/contactgroup_set_update_v4 @@ -2,31 +2,30 @@ use Cassandane::Tiny; sub test_contactgroup_set_update_v4 - :min_version_3_9 :needs_component_jmap -{ + : min_version_3_9 : needs_component_jmap { - my ($self) = @_; + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $contact1 = '60f60d95-1f33-480c-bfd6-02b93a07aefc'; - my $contact2 = '3e7cfbaf-3199-41bd-8749-38b8d1c89605'; - my $contact3 = '5b3b9ce1-0b5e-4cbd-8add-018321cad51b'; - my $href = "Default/$id.vcf"; - my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + $card =~ s/\r?\n/\r\n/gs; + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - my $res = $jmap->CallMethods([ - ['ContactGroup/get', { - }, 'R1'] - ]); - $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); - $self->assert_str_equals('Test', $res->[0][1]{list}[0]{name}); - $self->assert_num_equals(3, scalar @{$res->[0][1]{list}[0]{contactIds}}); - $self->assert_str_equals($contact1, $res->[0][1]{list}[0]{contactIds}[0]); - $self->assert_str_equals($contact2, $res->[0][1]{list}[0]{contactIds}[1]); - $self->assert_str_equals($contact3, $res->[0][1]{list}[0]{contactIds}[2]); + my $res = $jmap->CallMethods([ [ 'ContactGroup/get', {}, 'R1' ] ]); + $self->assert_str_equals($id, $res->[0][1]{list}[0]{id}); + $self->assert_str_equals('Test', $res->[0][1]{list}[0]{name}); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{list}[0]{contactIds} }); + $self->assert_str_equals($contact1, $res->[0][1]{list}[0]{contactIds}[0]); + $self->assert_str_equals($contact2, $res->[0][1]{list}[0]{contactIds}[1]); + $self->assert_str_equals($contact3, $res->[0][1]{list}[0]{contactIds}[2]); - xlog $self, "update contact group by removing a member and reordering"; - $res = $jmap->CallMethods([['ContactGroup/set', {update => { - $id => {name => "group1", contactIds => [$contact3, $contact1]} - }}, "R4"]]); + xlog $self, "update contact group by removing a member and reordering"; + $res = $jmap->CallMethods([ [ + 'ContactGroup/set', + { + update => { + $id => { name => "group1", contactIds => [ $contact3, $contact1 ] } + } + }, + "R4" + ] ]); - $self->assert_str_equals('ContactGroup/set', $res->[0][0]); - $self->assert(exists $res->[0][1]{updated}{$id}); + $self->assert_str_equals('ContactGroup/set', $res->[0][0]); + $self->assert(exists $res->[0][1]{updated}{$id}); - xlog $self, "get contact group $id"; - $res = $jmap->CallMethods([['ContactGroup/get', { ids => [$id] }, "R3"]]); - $self->assert(exists $res->[0][1]{list}[0]{contactIds}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}[0]{contactIds}}); - $self->assert_str_equals($contact3, $res->[0][1]{list}[0]{contactIds}[0]); - $self->assert_str_equals($contact1, $res->[0][1]{list}[0]{contactIds}[1]); + xlog $self, "get contact group $id"; + $res = $jmap->CallMethods([ [ 'ContactGroup/get', { ids => [$id] }, "R3" ] ]); + $self->assert(exists $res->[0][1]{list}[0]{contactIds}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list}[0]{contactIds} }); + $self->assert_str_equals($contact3, $res->[0][1]{list}[0]{contactIds}[0]); + $self->assert_str_equals($contact1, $res->[0][1]{list}[0]{contactIds}[1]); } diff --git a/cassandane/tiny-tests/JMAPContacts/misc_categories b/cassandane/tiny-tests/JMAPContacts/misc_categories index 8512e08a8a..79ee49302d 100644 --- a/cassandane/tiny-tests/JMAPContacts/misc_categories +++ b/cassandane/tiny-tests/JMAPContacts/misc_categories @@ -2,29 +2,27 @@ use Cassandane::Tiny; sub test_misc_categories - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - my $service = $self->{instance}->get_service("http"); - $ENV{DEBUGDAV} = 1; - my $carddav = Net::CardDAVTalk->new( - user => 'cassandane', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - - - xlog $self, "create a contact with two categories"; - my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; - my $href = "Default/$id.vcf"; - my $card = <{jmap}; + + my $service = $self->{instance}->get_service("http"); + $ENV{DEBUGDAV} = 1; + my $carddav = Net::CardDAVTalk->new( + user => 'cassandane', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + + xlog $self, "create a contact with two categories"; + my $id = 'ae2640cc-234a-4dd9-95cc-3106258445b9'; + my $href = "Default/$id.vcf"; + my $card = <Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); - - my $data = $carddav->Request('GET', $href); - $self->assert_matches(qr/cat1,cat2/, $data->{content}); - - my $fetch = $jmap->CallMethods([['Contact/get', {ids => [$id]}, "R2"]]); - $self->assert_not_null($fetch); - $self->assert_str_equals('Contact/get', $fetch->[0][0]); - $self->assert_str_equals('R2', $fetch->[0][2]); - $self->assert_str_equals('Forrest', $fetch->[0][1]{list}[0]{firstName}); - - my $res = $jmap->CallMethods([['Contact/set', { - update => {$id => {firstName => "foo"}} - }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - $data = $carddav->Request('GET', $href); - $self->assert_matches(qr/cat1,cat2/, $data->{content}); + $carddav->Request('PUT', $href, $card, 'Content-Type' => 'text/vcard'); + + my $data = $carddav->Request('GET', $href); + $self->assert_matches(qr/cat1,cat2/, $data->{content}); + + my $fetch = $jmap->CallMethods([ [ 'Contact/get', { ids => [$id] }, "R2" ] ]); + $self->assert_not_null($fetch); + $self->assert_str_equals('Contact/get', $fetch->[0][0]); + $self->assert_str_equals('R2', $fetch->[0][2]); + $self->assert_str_equals('Forrest', $fetch->[0][1]{list}[0]{firstName}); + + my $res = $jmap->CallMethods([ [ + 'Contact/set', + { + update => { $id => { firstName => "foo" } } + }, + "R1" + ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + $data = $carddav->Request('GET', $href); + $self->assert_matches(qr/cat1,cat2/, $data->{content}); } diff --git a/cassandane/tiny-tests/JMAPContacts/misc_creationids b/cassandane/tiny-tests/JMAPContacts/misc_creationids index 88735e52fb..dea89c41f5 100644 --- a/cassandane/tiny-tests/JMAPContacts/misc_creationids +++ b/cassandane/tiny-tests/JMAPContacts/misc_creationids @@ -2,24 +2,31 @@ use Cassandane::Tiny; sub test_misc_creationids - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create and get contact group and contact"; - my $res = $jmap->CallMethods([ - ['Contact/set', {create => { "c1" => { firstName => "foo", lastName => "last1" }, }}, "R2"], - ['ContactGroup/set', {create => { "g1" => {name => "group1", contactIds => ["#c1"]} }}, "R2"], - ['Contact/get', {ids => ["#c1"]}, "R3"], - ['ContactGroup/get', {ids => ["#g1"]}, "R4"], - ]); - my $contact = $res->[2][1]{list}[0]; - $self->assert_str_equals("foo", $contact->{firstName}); + xlog $self, "create and get contact group and contact"; + my $res = $jmap->CallMethods([ + [ + 'Contact/set', + { create => { "c1" => { firstName => "foo", lastName => "last1" }, } }, + "R2" + ], + [ + 'ContactGroup/set', + { create => { "g1" => { name => "group1", contactIds => ["#c1"] } } }, + "R2" + ], + [ 'Contact/get', { ids => ["#c1"] }, "R3" ], + [ 'ContactGroup/get', { ids => ["#g1"] }, "R4" ], + ]); + my $contact = $res->[2][1]{list}[0]; + $self->assert_str_equals("foo", $contact->{firstName}); - my $group = $res->[3][1]{list}[0]; - $self->assert_str_equals("group1", $group->{name}); + my $group = $res->[3][1]{list}[0]; + $self->assert_str_equals("group1", $group->{name}); - $self->assert_str_equals($contact->{id}, $group->{contactIds}[0]); + $self->assert_str_equals($contact->{id}, $group->{contactIds}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/attach_base64_email b/cassandane/tiny-tests/JMAPEmail/attach_base64_email index 660947b9d5..b4c6d05b04 100644 --- a/cassandane/tiny-tests/JMAPEmail/attach_base64_email +++ b/cassandane/tiny-tests/JMAPEmail/attach_base64_email @@ -2,98 +2,120 @@ use Cassandane::Tiny; sub test_attach_base64_email - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - open(my $F, 'data/mime/base64-body.eml') || die $!; - $imap->append('INBOX', $F) || die $@; - close($F); + open(my $F, 'data/mime/base64-body.eml') || die $!; + $imap->append('INBOX', $F) || die $@; + close($F); - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - }, "R2"], - ['Mailbox/get', {}, 'R3'], - ]); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + }, + "R2" + ], + [ 'Mailbox/get', {}, 'R3' ], + ]); - my $blobId = $res->[1][1]{list}[0]{blobId}; - my $size = $res->[1][1]{list}[0]{size}; - my $name = $res->[1][1]{list}[0]{subject} . ".eml"; + my $blobId = $res->[1][1]{list}[0]{blobId}; + my $size = $res->[1][1]{list}[0]{size}; + my $name = $res->[1][1]{list}[0]{subject} . ".eml"; - my $mailboxId = $res->[2][1]{list}[0]{id}; + my $mailboxId = $res->[2][1]{list}[0]{id}; - xlog $self, "Now we create an email which includes this"; + xlog $self, "Now we create an email which includes this"; - $res = $jmap->CallMethods([ - ['Email/set', { create => { 1 => { - bcc => undef, + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + bcc => undef, bodyStructure => { - subParts => [{ - partId => "text", - type => "text/plain" - },{ - blobId => $blobId, - cid => undef, - disposition => "attachment", - name => $name, - size => $size, - type => "message/rfc822" - }], - type => "multipart/mixed", + subParts => [ + { + partId => "text", + type => "text/plain" + }, + { + blobId => $blobId, + cid => undef, + disposition => "attachment", + name => $name, + size => $size, + type => "message/rfc822" + } + ], + type => "multipart/mixed", }, bodyValues => { - text => { - isTruncated => $JSON::false, - value => "Hello World", - }, + text => { + isTruncated => $JSON::false, + value => "Hello World", + }, }, - cc => undef, - from => [{ - email => "foo\@example.com", - name => "Captain Foo", - }], + cc => undef, + from => [ { + email => "foo\@example.com", + name => "Captain Foo", + } ], keywords => { - '$draft' => $JSON::true, - '$seen' => $JSON::true, + '$draft' => $JSON::true, + '$seen' => $JSON::true, }, mailboxIds => { - $mailboxId => $JSON::true, + $mailboxId => $JSON::true, }, - messageId => ["9048d4db-bd84-4ea4-9be3-ae4a136c532d\@example.com"], + messageId => ["9048d4db-bd84-4ea4-9be3-ae4a136c532d\@example.com"], receivedAt => "2019-05-09T12:48:08Z", references => undef, - replyTo => undef, - sentAt => "2019-05-09T14:48:08+02:00", - subject => "Hello again", - to => [{ - email => "bar\@example.com", - name => "Private Bar", - }], - }}}, "S1"], - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - }, "R2"], - ]); + replyTo => undef, + sentAt => "2019-05-09T14:48:08+02:00", + subject => "Hello again", + to => [ { + email => "bar\@example.com", + name => "Private Bar", + } ], + } + } + }, + "S1" + ], + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + }, + "R2" + ], + ]); - $imap->select("INBOX"); - my $ires = $imap->fetch('1:*', '(BODYSTRUCTURE)'); + $imap->select("INBOX"); + my $ires = $imap->fetch('1:*', '(BODYSTRUCTURE)'); - $self->assert_str_equals('RE: Hello.eml', $ires->{2}{bodystructure}{'MIME-Subparts'}[1]{'Content-Disposition'}{filename}); - $self->assert_str_not_equals('BINARY', $ires->{2}{bodystructure}{'MIME-Subparts'}[1]{'Content-Transfer-Encoding'}); + $self->assert_str_equals('RE: Hello.eml', + $ires->{2}{bodystructure}{'MIME-Subparts'}[1]{'Content-Disposition'} + {filename}); + $self->assert_str_not_equals('BINARY', + $ires->{2}{bodystructure}{'MIME-Subparts'}[1]{'Content-Transfer-Encoding'}); - my ($replyEmail) = grep { $_->{subject} eq 'Hello again' } @{$res->[2][1]{list}}; - $self->assert_str_equals($blobId, $replyEmail->{attachments}[0]{blobId}); + my ($replyEmail) + = grep { $_->{subject} eq 'Hello again' } @{ $res->[2][1]{list} }; + $self->assert_str_equals($blobId, $replyEmail->{attachments}[0]{blobId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/base64_forward b/cassandane/tiny-tests/JMAPEmail/base64_forward index a6aaaeb297..6a6ac75674 100644 --- a/cassandane/tiny-tests/JMAPEmail/base64_forward +++ b/cassandane/tiny-tests/JMAPEmail/base64_forward @@ -2,111 +2,126 @@ use Cassandane::Tiny; sub test_base64_forward - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; - # Generate an email to have some blob ids - xlog $self, "Generate an email in $inbox via IMAP"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: image/jpeg\r\n" - . "Content-Transfer-Encoding: base64\r\n" . "\r\n" - . "beefc0de" - . "\r\n--sub--\r\n", - ); + # Generate an email to have some blob ids + xlog $self, "Generate an email in $inbox via IMAP"; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: image/jpeg\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "beefc0de" + . "\r\n--sub--\r\n", + ); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { - ids => $ids, - properties => ['bodyStructure', 'mailboxIds'], - }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => $ids, + properties => [ 'bodyStructure', 'mailboxIds' ], + }, + "R1" + ] ]); + my $msg = $res->[0][1]{list}[0]; - my $blobid = $msg->{bodyStructure}{subParts}[1]{blobId}; - $self->assert_not_null($blobid); - my $size = $msg->{bodyStructure}{subParts}[1]{size}; - $self->assert_num_equals(6, $size); + my $blobid = $msg->{bodyStructure}{subParts}[1]{blobId}; + $self->assert_not_null($blobid); + my $size = $msg->{bodyStructure}{subParts}[1]{size}; + $self->assert_num_equals(6, $size); - $res = $jmap->Download('cassandane', $blobid); - $self->assert_str_equals("beefc0de", encode_base64($res->{content}, '')); + $res = $jmap->Download('cassandane', $blobid); + $self->assert_str_equals("beefc0de", encode_base64($res->{content}, '')); - # now create a new message referencing this blobId: + # now create a new message referencing this blobId: - $res = $jmap->CallMethods([['Email/set', { - create => { - k1 => { - bcc => undef, - bodyStructure => { - subParts => [{ - partId => 'text', - type => 'text/plain', - },{ - blobId => $blobid, - cid => undef, - disposition => 'attachment', - height => undef, - name => 'foobar.jpg', - size => $size, - type => 'image/jpeg', - width => undef, - }], - type => 'multipart/mixed', - }, - bodyValues => { - text => { - isTruncated => $JSON::false, - value => "Hello world", - }, - }, - cc => undef, - inReplyTo => undef, - mailboxIds => $msg->{mailboxIds}, - from => [ {email => 'foo@example.com', name => 'foo' } ], - keywords => { '$draft' => $JSON::true, '$seen' => $JSON::true }, - receivedAt => '2018-06-26T03:10:07Z', - references => undef, - replyTo => undef, - sentAt => '2018-06-26T03:10:07Z', - subject => 'test email', - to => [ {email => 'foo@example.com', name => 'foo' } ], + $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { + k1 => { + bcc => undef, + bodyStructure => { + subParts => [ + { + partId => 'text', + type => 'text/plain', + }, + { + blobId => $blobid, + cid => undef, + disposition => 'attachment', + height => undef, + name => 'foobar.jpg', + size => $size, + type => 'image/jpeg', + width => undef, + } + ], + type => 'multipart/mixed', + }, + bodyValues => { + text => { + isTruncated => $JSON::false, + value => "Hello world", }, + }, + cc => undef, + inReplyTo => undef, + mailboxIds => $msg->{mailboxIds}, + from => [ { email => 'foo@example.com', name => 'foo' } ], + keywords => { '$draft' => $JSON::true, '$seen' => $JSON::true }, + receivedAt => '2018-06-26T03:10:07Z', + references => undef, + replyTo => undef, + sentAt => '2018-06-26T03:10:07Z', + subject => 'test email', + to => [ { email => 'foo@example.com', name => 'foo' } ], }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - my $id = $res->[0][1]{created}{k1}{id}; - $self->assert_not_null($id); + my $id = $res->[0][1]{created}{k1}{id}; + $self->assert_not_null($id); - $res = $jmap->CallMethods([['Email/get', { - ids => [$id], - properties => ['bodyStructure'], - }, "R1"]]); - $msg = $res->[0][1]{list}[0]; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$id], + properties => ['bodyStructure'], + }, + "R1" + ] ]); + $msg = $res->[0][1]{list}[0]; - my $newpart = $msg->{bodyStructure}{subParts}[1]; - $self->assert_str_equals("foobar.jpg", $newpart->{name}); - $self->assert_str_equals("image/jpeg", $newpart->{type}); - $self->assert_num_equals(6, $newpart->{size}); + my $newpart = $msg->{bodyStructure}{subParts}[1]; + $self->assert_str_equals("foobar.jpg", $newpart->{name}); + $self->assert_str_equals("image/jpeg", $newpart->{type}); + $self->assert_num_equals(6, $newpart->{size}); - # XXX - in theory, this IS allowed to change - if ($newpart->{blobId} ne $blobid) { - $res = $jmap->Download('cassandane', $blobid); - # but this isn't! - $self->assert_str_equals("beefc0de", encode_base64($res->{content}, '')); - } + # XXX - in theory, this IS allowed to change + if ($newpart->{blobId} ne $blobid) { + $res = $jmap->Download('cassandane', $blobid); + # but this isn't! + $self->assert_str_equals("beefc0de", encode_base64($res->{content}, '')); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/blob_copy b/cassandane/tiny-tests/JMAPEmail/blob_copy index 8b882f56a9..11de5e2f88 100644 --- a/cassandane/tiny-tests/JMAPEmail/blob_copy +++ b/cassandane/tiny-tests/JMAPEmail/blob_copy @@ -2,63 +2,74 @@ use Cassandane::Tiny; sub test_blob_copy - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # FIXME how to share just #jmap folder? - xlog $self, "create user foo and share inbox"; - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lrkintex") or die; + # FIXME how to share just #jmap folder? + xlog $self, "create user foo and share inbox"; + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lrkintex") or die; - xlog $self, "upload blob in main account"; - my $data = $jmap->Upload('somedata', "text/plain"); - $self->assert_not_null($data); + xlog $self, "upload blob in main account"; + my $data = $jmap->Upload('somedata', "text/plain"); + $self->assert_not_null($data); - xlog $self, "attempt to download from shared account (should fail)"; - my $res = $self->download('foo', $data->{blobId}); - $self->assert_str_equals('404', $res->{status}); + xlog $self, "attempt to download from shared account (should fail)"; + my $res = $self->download('foo', $data->{blobId}); + $self->assert_str_equals('404', $res->{status}); - xlog $self, "copy blob to shared account"; - $res = $jmap->CallMethods([['Blob/copy', { - fromAccountId => 'cassandane', - accountId => 'foo', - blobIds => [ $data->{blobId} ], - }, 'R1']]); + xlog $self, "copy blob to shared account"; + $res = $jmap->CallMethods([ [ + 'Blob/copy', + { + fromAccountId => 'cassandane', + accountId => 'foo', + blobIds => [ $data->{blobId} ], + }, + 'R1' + ] ]); - xlog $self, "download from shared account"; - $res = $self->download('foo', $data->{blobId}); - $self->assert_str_equals('200', $res->{status}); + xlog $self, "download from shared account"; + $res = $self->download('foo', $data->{blobId}); + $self->assert_str_equals('200', $res->{status}); - xlog $self, "generate an email in INBOX via IMAP"; - $self->make_message("Email A") || die; + xlog $self, "generate an email in INBOX via IMAP"; + $self->make_message("Email A") || die; - xlog $self, "get email blob id"; - $res = $jmap->CallMethods([ - ['Email/query', {}, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'blobId' ], - }, 'R2'] - ]); - my $msgblobId = $res->[1][1]->{list}[0]{blobId}; + xlog $self, "get email blob id"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['blobId'], + }, + 'R2' + ] + ]); + my $msgblobId = $res->[1][1]->{list}[0]{blobId}; - xlog $self, "copy Email blob to shared account"; - $res = $jmap->CallMethods([['Blob/copy', { - fromAccountId => 'cassandane', - accountId => 'foo', - blobIds => [ $msgblobId ], - }, 'R1']]); + xlog $self, "copy Email blob to shared account"; + $res = $jmap->CallMethods([ [ + 'Blob/copy', + { + fromAccountId => 'cassandane', + accountId => 'foo', + blobIds => [$msgblobId], + }, + 'R1' + ] ]); - xlog $self, "download Email blob from shared account"; - $res = $self->download('foo', $msgblobId); - $self->assert_str_equals('200', $res->{status}); + xlog $self, "download Email blob from shared account"; + $res = $self->download('foo', $msgblobId); + $self->assert_str_equals('200', $res->{status}); } diff --git a/cassandane/tiny-tests/JMAPEmail/blob_download b/cassandane/tiny-tests/JMAPEmail/blob_download index b88ab73b84..d4d5510fee 100644 --- a/cassandane/tiny-tests/JMAPEmail/blob_download +++ b/cassandane/tiny-tests/JMAPEmail/blob_download @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_blob_download - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $binary = slurp_file(abs_path('data/logo.gif')); - my $data = $jmap->Upload($binary, "image/gif"); + my $binary = slurp_file(abs_path('data/logo.gif')); + my $data = $jmap->Upload($binary, "image/gif"); - my $blob = $jmap->Download({ accept => 'image/gif' }, 'cassandane', $data->{blobId}); - $self->assert_str_equals('image/gif', $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_equals($binary, $blob->{content}); + my $blob + = $jmap->Download({ accept => 'image/gif' }, 'cassandane', $data->{blobId}); + $self->assert_str_equals('image/gif', $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_equals($binary, $blob->{content}); } diff --git a/cassandane/tiny-tests/JMAPEmail/blob_get b/cassandane/tiny-tests/JMAPEmail/blob_get index f8d7edac74..8f7cbefdad 100644 --- a/cassandane/tiny-tests/JMAPEmail/blob_get +++ b/cassandane/tiny-tests/JMAPEmail/blob_get @@ -2,39 +2,45 @@ use Cassandane::Tiny; sub test_blob_get - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - $self->make_message("foo") || die; - - my $res = $jmap->CallMethods([ - ['Email/query', {}, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2'], - ]); - - my $blobId = $res->[1][1]{list}[0]{blobId}; - $self->assert_not_null($blobId); - - my $wantMailboxIds = [keys %{$res->[1][1]{list}[0]{mailboxIds}}]; - my $wantEmailIds = [$res->[1][1]{list}[0]{id}]; - my $wantThreadIds = [$res->[1][1]{list}[0]{threadId}]; - - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/blob'; - $jmap->DefaultUsing(\@using); - - $res = $jmap->CallMethods([ - ['Blob/lookup', { ids => [$blobId], types => ['Mailbox', 'Thread', 'Email']}, "R1"], - ]); - - my $blob = $res->[0][1]{list}[0]; - $self->assert_deep_equals($wantMailboxIds, $blob->{types}{Mailbox}); - $self->assert_deep_equals($wantEmailIds, $blob->{types}{Email}); - $self->assert_deep_equals($wantThreadIds, $blob->{types}{Thread}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + $self->make_message("foo") || die; + + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + + my $blobId = $res->[1][1]{list}[0]{blobId}; + $self->assert_not_null($blobId); + + my $wantMailboxIds = [ keys %{ $res->[1][1]{list}[0]{mailboxIds} } ]; + my $wantEmailIds = [ $res->[1][1]{list}[0]{id} ]; + my $wantThreadIds = [ $res->[1][1]{list}[0]{threadId} ]; + + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/blob'; + $jmap->DefaultUsing(\@using); + + $res = $jmap->CallMethods([ + [ + 'Blob/lookup', + { ids => [$blobId], types => [ 'Mailbox', 'Thread', 'Email' ] }, "R1" + ], + ]); + + my $blob = $res->[0][1]{list}[0]; + $self->assert_deep_equals($wantMailboxIds, $blob->{types}{Mailbox}); + $self->assert_deep_equals($wantEmailIds, $blob->{types}{Email}); + $self->assert_deep_equals($wantThreadIds, $blob->{types}{Thread}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_attach_contact_by_blobid b/cassandane/tiny-tests/JMAPEmail/email_attach_contact_by_blobid index 75330f3efe..e3d5c2011b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_attach_contact_by_blobid +++ b/cassandane/tiny-tests/JMAPEmail/email_attach_contact_by_blobid @@ -2,81 +2,91 @@ use Cassandane::Tiny; sub test_email_attach_contact_by_blobid - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - push @using, 'https://cyrusimap.org/ns/jmap/contacts'; - $jmap->DefaultUsing(\@using); + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + push @using, 'https://cyrusimap.org/ns/jmap/contacts'; + $jmap->DefaultUsing(\@using); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - my $contact = { - firstName => "first", - lastName => "last" - }; + my $contact = { + firstName => "first", + lastName => "last" + }; - $res = $jmap->CallMethods([['Contact/set', - {create => {"1" => $contact }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); + $res = $jmap->CallMethods([ [ + 'Contact/set', { create => { "1" => $contact } }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); - my $blobid = $res->[0][1]{created}{"1"}{blobId}; - my $size = $res->[0][1]{created}{"1"}{size}; + my $blobid = $res->[0][1]{created}{"1"}{blobId}; + my $size = $res->[0][1]{created}{"1"}{size}; - $res = $jmap->CallMethods([['Email/set', { - create => { - k1 => { - bcc => undef, - bodyStructure => { - subParts => [{ - partId => 'text', - type => 'text/plain', - },{ - blobId => $blobid, - cid => undef, - disposition => 'attachment', - height => undef, - name => 'last.vcf', - size => $size, - type => 'text/vcard', - width => undef, - }], - type => 'multipart/mixed', - }, - bodyValues => { - text => { - isTruncated => $JSON::false, - value => "Hello world", - }, - }, - mailboxIds => { $inboxid => JSON::true }, - subject => 'email with vCard', - from => [ {email => 'foo@example.com', name => 'foo' } ], - to => [ {email => 'foo@example.com', name => 'foo' } ], + $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { + k1 => { + bcc => undef, + bodyStructure => { + subParts => [ + { + partId => 'text', + type => 'text/plain', + }, + { + blobId => $blobid, + cid => undef, + disposition => 'attachment', + height => undef, + name => 'last.vcf', + size => $size, + type => 'text/vcard', + width => undef, + } + ], + type => 'multipart/mixed', + }, + bodyValues => { + text => { + isTruncated => $JSON::false, + value => "Hello world", }, + }, + mailboxIds => { $inboxid => JSON::true }, + subject => 'email with vCard', + from => [ { email => 'foo@example.com', name => 'foo' } ], + to => [ { email => 'foo@example.com', name => 'foo' } ], }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - my $id = $res->[0][1]{created}{k1}{id}; - $self->assert_not_null($id); + my $id = $res->[0][1]{created}{k1}{id}; + $self->assert_not_null($id); - $res = $jmap->CallMethods([['Email/get', { - ids => [$id], - properties => ['bodyStructure'], - }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$id], + properties => ['bodyStructure'], + }, + "R1" + ] ]); + my $msg = $res->[0][1]{list}[0]; - my $newpart = $msg->{bodyStructure}{subParts}[1]; - $self->assert_str_equals("last.vcf", $newpart->{name}); - $self->assert_str_equals("text/vcard", $newpart->{type}); - $self->assert_num_equals($size, $newpart->{size}); + my $newpart = $msg->{bodyStructure}{subParts}[1]; + $self->assert_str_equals("last.vcf", $newpart->{name}); + $self->assert_str_equals("text/vcard", $newpart->{type}); + $self->assert_num_equals($size, $newpart->{size}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_bimi_blob b/cassandane/tiny-tests/JMAPEmail/email_bimi_blob index e7da854d26..3a0f75424b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_bimi_blob +++ b/cassandane/tiny-tests/JMAPEmail/email_bimi_blob @@ -2,46 +2,47 @@ use Cassandane::Tiny; sub test_email_bimi_blob - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # bimiBlobId property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); - - my $binary = slurp_file(abs_path('data/FM_BIMI.svg')); - - $self->make_message("foo", - mime_type => 'text/plain', - extra_headers => [ - ['BIMI-Indicator', encode_base64($binary, '')], - ], - body => 'foo', - ) || die; - - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; - - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { - ids => $ids, - properties => ['bimiBlobId'], - }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; - - my $blobid = $msg->{bimiBlobId}; - $self->assert_not_null($blobid); - - my $blob = $jmap->Download({ accept => 'image/svg+xml' }, - 'cassandane', $blobid); - $self->assert_str_equals('image/svg+xml', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_equals($binary, $blob->{content}); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # bimiBlobId property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); + + my $binary = slurp_file(abs_path('data/FM_BIMI.svg')); + + $self->make_message( + "foo", + mime_type => 'text/plain', + extra_headers => [ [ 'BIMI-Indicator', encode_base64($binary, '') ], ], + body => 'foo', + ) || die; + + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; + + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => $ids, + properties => ['bimiBlobId'], + }, + "R1" + ] ]); + my $msg = $res->[0][1]{list}[0]; + + my $blobid = $msg->{bimiBlobId}; + $self->assert_not_null($blobid); + + my $blob + = $jmap->Download({ accept => 'image/svg+xml' }, 'cassandane', $blobid); + $self->assert_str_equals('image/svg+xml', $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_equals($binary, $blob->{content}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_bimi_blob_as_contact_avatar b/cassandane/tiny-tests/JMAPEmail/email_bimi_blob_as_contact_avatar index b8a6ebf058..9af3764a56 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_bimi_blob_as_contact_avatar +++ b/cassandane/tiny-tests/JMAPEmail/email_bimi_blob_as_contact_avatar @@ -2,66 +2,67 @@ use Cassandane::Tiny; sub test_email_bimi_blob_as_contact_avatar - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # bimiBlobId property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - push @using, 'https://cyrusimap.org/ns/jmap/contacts'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # bimiBlobId property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + push @using, 'https://cyrusimap.org/ns/jmap/contacts'; + $jmap->DefaultUsing(\@using); - my $binary = slurp_file(abs_path('data/FM_BIMI.svg')); + my $binary = slurp_file(abs_path('data/FM_BIMI.svg')); - $self->make_message("foo", - mime_type => 'text/plain', - extra_headers => [ - ['BIMI-Indicator', encode_base64($binary, '')], - ], - body => 'foo', - ) || die; + $self->make_message( + "foo", + mime_type => 'text/plain', + extra_headers => [ [ 'BIMI-Indicator', encode_base64($binary, '') ], ], + body => 'foo', + ) || die; - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { - ids => $ids, - properties => ['bimiBlobId'], - }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => $ids, + properties => ['bimiBlobId'], + }, + "R1" + ] ]); + my $msg = $res->[0][1]{list}[0]; - my $blobid = $msg->{bimiBlobId}; - $self->assert_not_null($blobid); + my $blobid = $msg->{bimiBlobId}; + $self->assert_not_null($blobid); - my $blob = $jmap->Download({ accept => 'image/svg+xml' }, - 'cassandane', $blobid); - $self->assert_str_equals('image/svg+xml', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_equals($binary, $blob->{content}); + my $blob + = $jmap->Download({ accept => 'image/svg+xml' }, 'cassandane', $blobid); + $self->assert_str_equals('image/svg+xml', $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_equals($binary, $blob->{content}); - my $contact = { - firstName => "first", - lastName => "last", - avatar => { - blobId => $blobid, - size => $blob->{headers}->{'content-length'}, - type => 'image/svg+xml', - name => JSON::null - } - }; + my $contact = { + firstName => "first", + lastName => "last", + avatar => { + blobId => $blobid, + size => $blob->{headers}->{'content-length'}, + type => 'image/svg+xml', + name => JSON::null + } + }; - $res = $jmap->CallMethods([['Contact/set', - {create => {"1" => $contact }}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Contact/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - $self->assert_not_null($res->[0][1]{created}{"1"}{avatar}{blobId}); + $res = $jmap->CallMethods([ [ + 'Contact/set', { create => { "1" => $contact } }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Contact/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + $self->assert_not_null($res->[0][1]{created}{"1"}{avatar}{blobId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_blob_set_singlecommand b/cassandane/tiny-tests/JMAPEmail/email_blob_set_singlecommand index d9dda91c5d..cae4870289 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_blob_set_singlecommand +++ b/cassandane/tiny-tests/JMAPEmail/email_blob_set_singlecommand @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_email_blob_set_singlecommand - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -19,74 +18,110 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email =~ s/\r?\n/\r\n/gs; + $email =~ s/\r?\n/\r\n/gs; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/blob', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/blob', + ]; - xlog $self, "do the lot!"; - $res = $jmap->CallMethods([ - ['Blob/upload', { create => { "a" => { data => [{'data:asText' => $email }] } } }, 'R0'], - ['Email/import', { - emails => { - "1" => { - blobId => '#a', - mailboxIds => { $draftsmbox => JSON::true}, - keywords => { - '$draft' => JSON::true, - }, - }, + xlog $self, "do the lot!"; + $res = $jmap->CallMethods( + [ + [ + 'Blob/upload', + { create => { "a" => { data => [ { 'data:asText' => $email } ] } } }, + 'R0' + ], + [ + 'Email/import', + { + emails => { + "1" => { + blobId => '#a', + mailboxIds => { $draftsmbox => JSON::true }, + keywords => { + '$draft' => JSON::true, + }, }, - }, "R1"] - ], $using); + }, + }, + "R1" + ] + ], + $using + ); - my $msg = $res->[1][1]->{created}{"1"}; - $self->assert_not_null($msg); + my $msg = $res->[1][1]->{created}{"1"}; + $self->assert_not_null($msg); - my $binary = slurp_file(abs_path('data/logo.gif')); + my $binary = slurp_file(abs_path('data/logo.gif')); - $res = $jmap->CallMethods([ - ['Blob/upload', { create => { "img" => { data => [{'data:asBase64' => encode_base64($binary, '')}], type => 'image/gif' } } }, 'R0'], - ['Email/set', { - create => { - "2" => { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo", - textBody => [{ partId => '1' }], - bodyValues => { - '1' => { - value => "I'm givin' ya one last chance ta surrenda!" - } - }, - attachments => [{ - blobId => '#img', - name => "logo.gif", - }], - keywords => { '$draft' => JSON::true }, - } } }, 'R1'], - ], $using); + $res = $jmap->CallMethods( + [ + [ + 'Blob/upload', + { + create => { + "img" => { + data => [ { 'data:asBase64' => encode_base64($binary, '') } ], + type => 'image/gif' + } + } + }, + 'R0' + ], + [ + 'Email/set', + { + create => { + "2" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => + [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { + '1' => { + value => "I'm givin' ya one last chance ta surrenda!" + } + }, + attachments => [ { + blobId => '#img', + name => "logo.gif", + } ], + keywords => { '$draft' => JSON::true }, + } + } + }, + 'R1' + ], + ], + $using + ); - $msg = $res->[1][1]->{created}{"2"}; - $self->assert_not_null($msg); + $msg = $res->[1][1]->{created}{"2"}; + $self->assert_not_null($msg); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_body_alternative_without_html b/cassandane/tiny-tests/JMAPEmail/email_body_alternative_without_html index def048d656..a076424c50 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_body_alternative_without_html +++ b/cassandane/tiny-tests/JMAPEmail/email_body_alternative_without_html @@ -2,49 +2,52 @@ use Cassandane::Tiny; sub test_email_body_alternative_without_html - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my %exp_sub; - $store->set_folder("INBOX"); - $store->_select(); - $self->{gen}->set_next_uid(1); + my %exp_sub; + $store->set_folder("INBOX"); + $store->_select(); + $self->{gen}->set_next_uid(1); - my $body = "". - "--sub\r\n". - "Content-Type: text/plain\r\n". - "\r\n" . - "plain text". - "\r\n--sub\r\n". - "Content-Type: some/part\r\n". - "Content-Transfer-Encoding: base64\r\n". - "\r\n" . - "abc=". - "\r\n--sub--\r\n"; + my $body + = "" + . "--sub\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "plain text" + . "\r\n--sub\r\n" + . "Content-Type: some/part\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" . "abc=" + . "\r\n--sub--\r\n"; - $exp_sub{A} = $self->make_message("foo", - mime_type => "multipart/alternative", - mime_boundary => "sub", - body => $body - ); + $exp_sub{A} = $self->make_message( + "foo", + mime_type => "multipart/alternative", + mime_boundary => "sub", + body => $body + ); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { - ids => $ids, - properties => ['textBody', 'htmlBody', 'bodyStructure'], - fetchAllBodyValues => JSON::true - }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; - $self->assert_num_equals(1, scalar @{$msg->{textBody}}); - $self->assert_num_equals(1, scalar @{$msg->{htmlBody}}); - $self->assert_str_equals($msg->{textBody}[0]->{partId}, $msg->{htmlBody}[0]->{partId}); + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => $ids, + properties => [ 'textBody', 'htmlBody', 'bodyStructure' ], + fetchAllBodyValues => JSON::true + }, + "R1" + ] ]); + my $msg = $res->[0][1]{list}[0]; + $self->assert_num_equals(1, scalar @{ $msg->{textBody} }); + $self->assert_num_equals(1, scalar @{ $msg->{htmlBody} }); + $self->assert_str_equals($msg->{textBody}[0]->{partId}, + $msg->{htmlBody}[0]->{partId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_changes b/cassandane/tiny-tests/JMAPEmail/email_changes index 9632769dc3..7e304cf2b8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_changes +++ b/cassandane/tiny-tests/JMAPEmail/email_changes @@ -2,170 +2,204 @@ use Cassandane::Tiny; sub test_email_changes - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $draftsmbox; - - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $draftsmbox = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get email updates (expect error)"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => 0 }, "R1"]]); - $self->assert_str_equals("cannotCalculateChanges", $res->[0][1]->{type}); - - xlog $self, "get email state"; - $res = $jmap->CallMethods([['Email/get', { ids => []}, "R1"]]); - $state = $res->[0][1]->{state}; - $self->assert_not_null($state); - - xlog $self, "get email updates"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("Email A") || die; - - xlog $self, "Get email id"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); - - xlog $self, "get email updates"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals($ida, $res->[0][1]{created}[0]); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - - xlog $self, "get email updates (expect no changes)"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - - xlog $self, "update email $ida"; - $res = $jmap->CallMethods([['Email/set', { - update => { $ida => { keywords => { '$seen' => JSON::true }}} - }, "R1"]]); - $self->assert(exists $res->[0][1]->{updated}{$ida}); - - xlog $self, "get email updates"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($ida, $res->[0][1]{updated}[0]); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - - xlog $self, "delete email $ida"; - $res = $jmap->CallMethods([['Email/set', {destroy => [ $ida ] }, "R1"]]); - $self->assert_str_equals($ida, $res->[0][1]->{destroyed}[0]); - - xlog $self, "get email updates"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($ida, $res->[0][1]{destroyed}[0]); - $state = $res->[0][1]->{newState}; - - xlog $self, "get email updates (expect no changes)"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - - xlog $self, "create email B"; - $res = $jmap->CallMethods( - [[ 'Email/set', { create => { "1" => { - mailboxIds => {$draftsmbox => JSON::true}, - from => [ { name => "", email => "sam\@acme.local" } ], - to => [ { name => "", email => "bugs\@acme.local" } ], - subject => "Email B", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "I'm givin' ya one last chance ta surrenda!" }}, - keywords => { '$draft' => JSON::true }, - }}}, "R1" ]]); - my $idb = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($idb); - - xlog $self, "create email C"; - $res = $jmap->CallMethods( - [[ 'Email/set', { create => { "1" => { - mailboxIds => {$draftsmbox => JSON::true}, - from => [ { name => "", email => "sam\@acme.local" } ], - to => [ { name => "", email => "bugs\@acme.local" } ], - subject => "Email C", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "I *hate* that rabbit!" } }, - keywords => { '$draft' => JSON::true }, - }}}, "R1" ]]); - my $idc = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($idc); - - xlog $self, "get max 1 email updates"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state, maxChanges => 1 }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::true, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals($idb, $res->[0][1]{created}[0]); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - - xlog $self, "get max 1 email updates"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state, maxChanges => 1 }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals($idc, $res->[0][1]{created}[0]); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - - xlog $self, "get email updates (expect no changes)"; - $res = $jmap->CallMethods([['Email/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $draftsmbox; + + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $draftsmbox = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get email updates (expect error)"; + $res = $jmap->CallMethods([ [ 'Email/changes', { sinceState => 0 }, "R1" ] ]); + $self->assert_str_equals("cannotCalculateChanges", $res->[0][1]->{type}); + + xlog $self, "get email state"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [] }, "R1" ] ]); + $state = $res->[0][1]->{state}; + $self->assert_not_null($state); + + xlog $self, "get email updates"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("Email A") || die; + + xlog $self, "Get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); + + xlog $self, "get email updates"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals($ida, $res->[0][1]{created}[0]); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + + xlog $self, "get email updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + + xlog $self, "update email $ida"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { $ida => { keywords => { '$seen' => JSON::true } } } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]->{updated}{$ida}); + + xlog $self, "get email updates"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($ida, $res->[0][1]{updated}[0]); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + + xlog $self, "delete email $ida"; + $res = $jmap->CallMethods([ [ 'Email/set', { destroy => [$ida] }, "R1" ] ]); + $self->assert_str_equals($ida, $res->[0][1]->{destroyed}[0]); + + xlog $self, "get email updates"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($ida, $res->[0][1]{destroyed}[0]); + $state = $res->[0][1]->{newState}; + + xlog $self, "get email updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + + xlog $self, "create email B"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { + "1" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "", email => "sam\@acme.local" } ], + to => [ { name => "", email => "bugs\@acme.local" } ], + subject => "Email B", + textBody => [ { partId => '1' } ], + bodyValues => { + '1' => { value => "I'm givin' ya one last chance ta surrenda!" } + }, + keywords => { '$draft' => JSON::true }, + } + } + }, + "R1" + ] ]); + my $idb = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($idb); + + xlog $self, "create email C"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { + "1" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "", email => "sam\@acme.local" } ], + to => [ { name => "", email => "bugs\@acme.local" } ], + subject => "Email C", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "I *hate* that rabbit!" } }, + keywords => { '$draft' => JSON::true }, + } + } + }, + "R1" + ] ]); + my $idc = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($idc); + + xlog $self, "get max 1 email updates"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state, maxChanges => 1 }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::true, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals($idb, $res->[0][1]{created}[0]); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + + xlog $self, "get max 1 email updates"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state, maxChanges => 1 }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals($idc, $res->[0][1]{created}[0]); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + + xlog $self, "get email updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_changes_shared b/cassandane/tiny-tests/JMAPEmail/email_changes_shared index 41832c3781..11b6e62de1 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_changes_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_changes_shared @@ -2,95 +2,111 @@ use Cassandane::Tiny; sub test_email_changes_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; - my $store = $self->{store}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $store = $self->{store}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "create user and share inbox"; - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lrwkxd") or die; + xlog $self, "create user and share inbox"; + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lrwkxd") or die; - xlog $self, "create non-shared mailbox box1"; - $admintalk->create("user.foo.box1") or die; - $admintalk->setacl("user.foo.box1", "cassandane", "") or die; + xlog $self, "create non-shared mailbox box1"; + $admintalk->create("user.foo.box1") or die; + $admintalk->setacl("user.foo.box1", "cassandane", "") or die; - xlog $self, "get email state"; - $res = $jmap->CallMethods([['Email/get', { accountId => 'foo', ids => []}, "R1"]]); - my $state = $res->[0][1]->{state}; - $self->assert_not_null($state); + xlog $self, "get email state"; + $res = $jmap->CallMethods( + [ [ 'Email/get', { accountId => 'foo', ids => [] }, "R1" ] ]); + my $state = $res->[0][1]->{state}; + $self->assert_not_null($state); - xlog $self, "get email updates (expect empty changes)"; - $res = $jmap->CallMethods([['Email/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - # This could be the same as oldState, or not, as we might leak - # unshared modseqs (but not the according mail!). - $self->assert_not_null($res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + xlog $self, "get email updates (expect empty changes)"; + $res + = $jmap->CallMethods( + [ [ 'Email/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + # This could be the same as oldState, or not, as we might leak + # unshared modseqs (but not the according mail!). + $self->assert_not_null($res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); - xlog $self, "Generate an email in shared account INBOX via IMAP"; - $self->{adminstore}->set_folder('user.foo'); - $self->make_message("Email A", store => $self->{adminstore}) || die; + xlog $self, "Generate an email in shared account INBOX via IMAP"; + $self->{adminstore}->set_folder('user.foo'); + $self->make_message("Email A", store => $self->{adminstore}) || die; - xlog $self, "get email updates"; - $res = $jmap->CallMethods([['Email/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - my $ida = $res->[0][1]{created}[0]; + xlog $self, "get email updates"; + $res + = $jmap->CallMethods( + [ [ 'Email/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + my $ida = $res->[0][1]{created}[0]; - xlog $self, "create email in non-shared mailbox"; - $self->{adminstore}->set_folder('user.foo.box1'); - $self->make_message("Email B", store => $self->{adminstore}) || die; + xlog $self, "create email in non-shared mailbox"; + $self->{adminstore}->set_folder('user.foo.box1'); + $self->make_message("Email B", store => $self->{adminstore}) || die; - xlog $self, "get email updates (expect empty changes)"; - $res = $jmap->CallMethods([['Email/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - # This could be the same as oldState, or not, as we might leak - # unshared modseqs (but not the according mail!). - $self->assert_not_null($res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + xlog $self, "get email updates (expect empty changes)"; + $res + = $jmap->CallMethods( + [ [ 'Email/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + # This could be the same as oldState, or not, as we might leak + # unshared modseqs (but not the according mail!). + $self->assert_not_null($res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); - xlog $self, "share private mailbox box1"; - $admintalk->setacl("user.foo.box1", "cassandane", "lr") or die; + xlog $self, "share private mailbox box1"; + $admintalk->setacl("user.foo.box1", "cassandane", "lr") or die; - xlog $self, "get email updates"; - $res = $jmap->CallMethods([['Email/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; + xlog $self, "get email updates"; + $res + = $jmap->CallMethods( + [ [ 'Email/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; - xlog $self, "delete email $ida"; - $res = $jmap->CallMethods([['Email/set', { accountId => 'foo', destroy => [ $ida ] }, "R1"]]); - $self->assert_str_equals($ida, $res->[0][1]->{destroyed}[0]); + xlog $self, "delete email $ida"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { accountId => 'foo', destroy => [$ida] }, "R1" ] ]); + $self->assert_str_equals($ida, $res->[0][1]->{destroyed}[0]); - xlog $self, "get email updates"; - $res = $jmap->CallMethods([['Email/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($ida, $res->[0][1]{destroyed}[0]); - $state = $res->[0][1]->{newState}; + xlog $self, "get email updates"; + $res + = $jmap->CallMethods( + [ [ 'Email/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($ida, $res->[0][1]{destroyed}[0]); + $state = $res->[0][1]->{newState}; } diff --git a/cassandane/tiny-tests/JMAPEmail/email_copy b/cassandane/tiny-tests/JMAPEmail/email_copy index 73633e458c..ff69335233 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_copy +++ b/cassandane/tiny-tests/JMAPEmail/email_copy @@ -2,140 +2,160 @@ use Cassandane::Tiny; sub test_email_copy - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Create user and share mailbox"; - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; + xlog $self, "Create user and share mailbox"; + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; - my $srcInboxId = $self->getinbox()->{id}; - $self->assert_not_null($srcInboxId); + my $srcInboxId = $self->getinbox()->{id}; + $self->assert_not_null($srcInboxId); - my $dstInboxId = $self->getinbox({accountId => 'other'})->{id}; - $self->assert_not_null($dstInboxId); + my $dstInboxId = $self->getinbox({ accountId => 'other' })->{id}; + $self->assert_not_null($dstInboxId); - xlog $self, "create email"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 1 => { - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'foo' => JSON::true, - }, - subject => 'hello', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'world', - } - }, - }, + xlog $self, "create email"; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + mailboxIds => { + $srcInboxId => JSON::true, }, - }, 'R1'], - ]); - my $emailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($emailId); + keywords => { + 'foo' => JSON::true, + }, + subject => 'hello', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($emailId); - my $email = $res = $jmap->CallMethods([ - ['Email/get', { - ids => [$emailId], - properties => ['receivedAt'], - }, 'R1'] - ]); - my $receivedAt = $res->[0][1]{list}[0]{receivedAt}; - $self->assert_not_null($receivedAt); + my $email = $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => ['receivedAt'], + }, + 'R1' + ] ]); + my $receivedAt = $res->[0][1]{list}[0]{receivedAt}; + $self->assert_not_null($receivedAt); - # Safeguard receivedAt asserts. - sleep 1; + # Safeguard receivedAt asserts. + sleep 1; - xlog $self, "move email"; - $res = $jmap->CallMethods([ - ['Email/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 1 => { - id => $emailId, - mailboxIds => { - $dstInboxId => JSON::true, - }, - keywords => { - 'bar' => JSON::true, - }, - }, + xlog $self, "move email"; + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $emailId, + mailboxIds => { + $dstInboxId => JSON::true, }, - onSuccessDestroyOriginal => JSON::true, - }, 'R1'], - ]); + keywords => { + 'bar' => JSON::true, + }, + }, + }, + onSuccessDestroyOriginal => JSON::true, + }, + 'R1' + ], + ]); - my $copiedEmailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($copiedEmailId); - $self->assert_str_equals('Email/set', $res->[1][0]); - $self->assert_str_equals($emailId, $res->[1][1]{destroyed}[0]); + my $copiedEmailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($copiedEmailId); + $self->assert_str_equals('Email/set', $res->[1][0]); + $self->assert_str_equals($emailId, $res->[1][1]{destroyed}[0]); - xlog $self, "get copied email"; - $res = $jmap->CallMethods([ - ['Email/get', { - accountId => 'other', - ids => [$copiedEmailId], - properties => ['keywords', 'receivedAt'], - }, 'R1'] - ]); - my $wantKeywords = { 'bar' => JSON::true }; - $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); - $self->assert_str_equals($receivedAt, $res->[0][1]{list}[0]{receivedAt}); + xlog $self, "get copied email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + ids => [$copiedEmailId], + properties => [ 'keywords', 'receivedAt' ], + }, + 'R1' + ] ]); + my $wantKeywords = { 'bar' => JSON::true }; + $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); + $self->assert_str_equals($receivedAt, $res->[0][1]{list}[0]{receivedAt}); - xlog $self, "copy email back"; - $res = $jmap->CallMethods([ - ['Email/copy', { - accountId => 'cassandane', - fromAccountId => 'other', - create => { - 1 => { - id => $copiedEmailId, - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'bar' => JSON::true, - }, - }, + xlog $self, "copy email back"; + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + accountId => 'cassandane', + fromAccountId => 'other', + create => { + 1 => { + id => $copiedEmailId, + mailboxIds => { + $srcInboxId => JSON::true, + }, + keywords => { + 'bar' => JSON::true, }, - }, 'R1'], - ]); + }, + }, + }, + 'R1' + ], + ]); - $self->assert_str_equals($copiedEmailId, $res->[0][1]->{created}{1}{id}); + $self->assert_str_equals($copiedEmailId, $res->[0][1]->{created}{1}{id}); - xlog $self, "copy email back (again)"; - $res = $jmap->CallMethods([ - ['Email/copy', { - accountId => 'cassandane', - fromAccountId => 'other', - create => { - 1 => { - id => $copiedEmailId, - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'bar' => JSON::true, - }, - }, + xlog $self, "copy email back (again)"; + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + accountId => 'cassandane', + fromAccountId => 'other', + create => { + 1 => { + id => $copiedEmailId, + mailboxIds => { + $srcInboxId => JSON::true, + }, + keywords => { + 'bar' => JSON::true, }, - }, 'R1'], - ]); + }, + }, + }, + 'R1' + ], + ]); - $self->assert_str_equals('alreadyExists', $res->[0][1]->{notCreated}{1}{type}); - $self->assert_not_null($res->[0][1]->{notCreated}{1}{existingId}); + $self->assert_str_equals('alreadyExists', + $res->[0][1]->{notCreated}{1}{type}); + $self->assert_not_null($res->[0][1]->{notCreated}{1}{existingId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_copy_has_expunged b/cassandane/tiny-tests/JMAPEmail/email_copy_has_expunged index 2f8ccfdd8d..a58be4e85a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_copy_has_expunged +++ b/cassandane/tiny-tests/JMAPEmail/email_copy_has_expunged @@ -2,83 +2,94 @@ use Cassandane::Tiny; sub test_email_copy_has_expunged - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Create user and share mailbox"; - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; + xlog $self, "Create user and share mailbox"; + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; - my $srcInboxId = $self->getinbox()->{id}; - $self->assert_not_null($srcInboxId); + my $srcInboxId = $self->getinbox()->{id}; + $self->assert_not_null($srcInboxId); - my $dstInboxId = $self->getinbox({accountId => 'other'})->{id}; - $self->assert_not_null($dstInboxId); + my $dstInboxId = $self->getinbox({ accountId => 'other' })->{id}; + $self->assert_not_null($dstInboxId); - xlog $self, "create email"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 1 => { - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'foo' => JSON::true, - }, - subject => 'hello', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'world', - } - }, - }, + xlog $self, "create email"; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + mailboxIds => { + $srcInboxId => JSON::true, }, - }, 'R1'], - ]); + keywords => { + 'foo' => JSON::true, + }, + subject => 'hello', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, + }, + }, + 'R1' + ], + ]); - my $emailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($emailId); + my $emailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($emailId); - # move to Trash and back - $imaptalk->create("INBOX.Trash"); - $imaptalk->select("INBOX"); - $imaptalk->move('1:*', "INBOX.Trash"); - $imaptalk->select("INBOX.Trash"); - $imaptalk->move('1:*', "INBOX"); + # move to Trash and back + $imaptalk->create("INBOX.Trash"); + $imaptalk->select("INBOX"); + $imaptalk->move('1:*', "INBOX.Trash"); + $imaptalk->select("INBOX.Trash"); + $imaptalk->move('1:*', "INBOX"); - # move into Temp - $imaptalk->create("INBOX.Temp"); - $imaptalk->select("INBOX"); - $imaptalk->move('1:*', "INBOX.Temp"); + # move into Temp + $imaptalk->create("INBOX.Temp"); + $imaptalk->select("INBOX"); + $imaptalk->move('1:*', "INBOX.Temp"); - # Copy to other account, with mailbox identified by role - $res = $jmap->CallMethods([ - ['Email/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 1 => { - id => $emailId, - mailboxIds => { - '$inbox' => JSON::true, - }, - }, + # Copy to other account, with mailbox identified by role + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $emailId, + mailboxIds => { + '$inbox' => JSON::true, }, - }, 'R1'], - ['Email/get', { - accountId => 'other', - ids => ['#1'], - properties => ['mailboxIds'], - }, 'R2'] - ]); - $self->assert_not_null($res->[1][1]{list}[0]{mailboxIds}{$dstInboxId}); + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + ids => ['#1'], + properties => ['mailboxIds'], + }, + 'R2' + ] + ]); + $self->assert_not_null($res->[1][1]{list}[0]{mailboxIds}{$dstInboxId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_copy_hasattachment b/cassandane/tiny-tests/JMAPEmail/email_copy_hasattachment index cd1af4edb4..81822e9535 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_copy_hasattachment +++ b/cassandane/tiny-tests/JMAPEmail/email_copy_hasattachment @@ -2,128 +2,137 @@ use Cassandane::Tiny; sub test_email_copy_hasattachment - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPNoHasAttachment -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPNoHasAttachment { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Create user and share mailbox"; - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; + xlog $self, "Create user and share mailbox"; + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; - my $srcInboxId = $self->getinbox()->{id}; - $self->assert_not_null($srcInboxId); + my $srcInboxId = $self->getinbox()->{id}; + $self->assert_not_null($srcInboxId); - my $dstInboxId = $self->getinbox({accountId => 'other'})->{id}; - $self->assert_not_null($dstInboxId); + my $dstInboxId = $self->getinbox({ accountId => 'other' })->{id}; + $self->assert_not_null($dstInboxId); - xlog $self, "create emails"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 1 => { - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'foo' => JSON::true, - '$seen' => JSON::true, - }, - subject => 'email1', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'part1', - } - }, - }, - 2 => { - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'foo' => JSON::true, - '$seen' => JSON::true, - }, - subject => 'email2', - bodyStructure => { - type => 'text/plain', - partId => 'part2', - }, - bodyValues => { - part2 => { - value => 'part2', - } - }, - }, + xlog $self, "create emails"; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + mailboxIds => { + $srcInboxId => JSON::true, }, - }, 'R1'], - ]); - my $emailId1 = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($emailId1); - my $emailId2 = $res->[0][1]->{created}{2}{id}; - $self->assert_not_null($emailId2); + keywords => { + 'foo' => JSON::true, + '$seen' => JSON::true, + }, + subject => 'email1', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'part1', + } + }, + }, + 2 => { + mailboxIds => { + $srcInboxId => JSON::true, + }, + keywords => { + 'foo' => JSON::true, + '$seen' => JSON::true, + }, + subject => 'email2', + bodyStructure => { + type => 'text/plain', + partId => 'part2', + }, + bodyValues => { + part2 => { + value => 'part2', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailId1 = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($emailId1); + my $emailId2 = $res->[0][1]->{created}{2}{id}; + $self->assert_not_null($emailId2); - xlog $self, "set hasAttachment"; - my $store = $self->{store}; - $store->set_folder('INBOX'); - $store->_select(); - my $talk = $store->get_client(); - $talk->store('1:2', '+flags', '($HasAttachment)') or die; + xlog $self, "set hasAttachment"; + my $store = $self->{store}; + $store->set_folder('INBOX'); + $store->_select(); + my $talk = $store->get_client(); + $talk->store('1:2', '+flags', '($HasAttachment)') or die; - xlog $self, "copy email"; - $res = $jmap->CallMethods([ - ['Email/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 1 => { - id => $emailId1, - mailboxIds => { - $dstInboxId => JSON::true, - }, - }, - 2 => { - id => $emailId2, - mailboxIds => { - $dstInboxId => JSON::true, - }, - keywords => { - 'baz' => JSON::true, - }, - }, + xlog $self, "copy email"; + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $emailId1, + mailboxIds => { + $dstInboxId => JSON::true, + }, + }, + 2 => { + id => $emailId2, + mailboxIds => { + $dstInboxId => JSON::true, + }, + keywords => { + 'baz' => JSON::true, }, - }, 'R1'], - ]); + }, + }, + }, + 'R1' + ], + ]); - my $copiedEmailId1 = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($copiedEmailId1); - my $copiedEmailId2 = $res->[0][1]->{created}{2}{id}; - $self->assert_not_null($copiedEmailId2); + my $copiedEmailId1 = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($copiedEmailId1); + my $copiedEmailId2 = $res->[0][1]->{created}{2}{id}; + $self->assert_not_null($copiedEmailId2); - xlog $self, "get copied email"; - $res = $jmap->CallMethods([ - ['Email/get', { - accountId => 'other', - ids => [$copiedEmailId1, $copiedEmailId2], - properties => ['keywords'], - }, 'R1'] - ]); - my $wantKeywords1 = { - '$hasattachment' => JSON::true, - foo => JSON::true, - '$seen' => JSON::true, - }; - my $wantKeywords2 = { - '$hasattachment' => JSON::true, - baz => JSON::true, - }; - $self->assert_deep_equals($wantKeywords1, $res->[0][1]{list}[0]{keywords}); - $self->assert_deep_equals($wantKeywords2, $res->[0][1]{list}[1]{keywords}); + xlog $self, "get copied email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + ids => [ $copiedEmailId1, $copiedEmailId2 ], + properties => ['keywords'], + }, + 'R1' + ] ]); + my $wantKeywords1 = { + '$hasattachment' => JSON::true, + foo => JSON::true, + '$seen' => JSON::true, + }; + my $wantKeywords2 = { + '$hasattachment' => JSON::true, + baz => JSON::true, + }; + $self->assert_deep_equals($wantKeywords1, $res->[0][1]{list}[0]{keywords}); + $self->assert_deep_equals($wantKeywords2, $res->[0][1]{list}[1]{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_copy_intermediary b/cassandane/tiny-tests/JMAPEmail/email_copy_intermediary index 30cfe12c43..63afaf32f7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_copy_intermediary +++ b/cassandane/tiny-tests/JMAPEmail/email_copy_intermediary @@ -2,84 +2,96 @@ use Cassandane::Tiny; sub test_email_copy_intermediary - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Create user and share mailbox"; - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; - $admintalk->create("user.other.i1.box") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - accountId => 'other', - properties => ['name'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $dstMboxId = $mboxByName{'i1'}->{id}; - $self->assert_not_null($dstMboxId); + xlog $self, "Create user and share mailbox"; + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; + $admintalk->create("user.other.i1.box") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + accountId => 'other', + properties => ['name'], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $dstMboxId = $mboxByName{'i1'}->{id}; + $self->assert_not_null($dstMboxId); - my $srcInboxId = $self->getinbox()->{id}; - $self->assert_not_null($srcInboxId); + my $srcInboxId = $self->getinbox()->{id}; + $self->assert_not_null($srcInboxId); - xlog $self, "create email"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 1 => { - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'foo' => JSON::true, - }, - subject => 'hello', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'world', - } - }, - }, + xlog $self, "create email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + mailboxIds => { + $srcInboxId => JSON::true, }, - }, 'R1'], - ]); - my $emailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($emailId); + keywords => { + 'foo' => JSON::true, + }, + subject => 'hello', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($emailId); - xlog $self, "move email"; - $res = $jmap->CallMethods([ - ['Email/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 1 => { - id => $emailId, - mailboxIds => { - $dstMboxId => JSON::true, - }, - }, + xlog $self, "move email"; + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $emailId, + mailboxIds => { + $dstMboxId => JSON::true, }, - }, 'R1'], - ]); + }, + }, + }, + 'R1' + ], + ]); - my $copiedEmailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($copiedEmailId); + my $copiedEmailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($copiedEmailId); - xlog $self, "get copied email"; - $res = $jmap->CallMethods([ - ['Email/get', { - accountId => 'other', - ids => [$copiedEmailId], - properties => ['mailboxIds'], - }, 'R1'] - ]); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{mailboxIds}{$dstMboxId}); + xlog $self, "get copied email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + ids => [$copiedEmailId], + properties => ['mailboxIds'], + }, + 'R1' + ] ]); + $self->assert_equals(JSON::true, + $res->[0][1]{list}[0]{mailboxIds}{$dstMboxId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_copy_mailboxid_by_role b/cassandane/tiny-tests/JMAPEmail/email_copy_mailboxid_by_role index 27d6e44026..8bd92b6509 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_copy_mailboxid_by_role +++ b/cassandane/tiny-tests/JMAPEmail/email_copy_mailboxid_by_role @@ -2,70 +2,81 @@ use Cassandane::Tiny; sub test_email_copy_mailboxid_by_role - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Create user and share mailbox"; - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; + xlog $self, "Create user and share mailbox"; + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; - my $srcInboxId = $self->getinbox()->{id}; - $self->assert_not_null($srcInboxId); + my $srcInboxId = $self->getinbox()->{id}; + $self->assert_not_null($srcInboxId); - my $dstInboxId = $self->getinbox({accountId => 'other'})->{id}; - $self->assert_not_null($dstInboxId); + my $dstInboxId = $self->getinbox({ accountId => 'other' })->{id}; + $self->assert_not_null($dstInboxId); - xlog $self, "create email"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 1 => { - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'foo' => JSON::true, - }, - subject => 'hello', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'world', - } - }, - }, + xlog $self, "create email"; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + mailboxIds => { + $srcInboxId => JSON::true, }, - }, 'R1'], - ]); - my $emailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($emailId); + keywords => { + 'foo' => JSON::true, + }, + subject => 'hello', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($emailId); - # Copy to other account, with mailbox identified by role - $res = $jmap->CallMethods([ - ['Email/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 1 => { - id => $emailId, - mailboxIds => { - '$inbox' => JSON::true, - }, - }, + # Copy to other account, with mailbox identified by role + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $emailId, + mailboxIds => { + '$inbox' => JSON::true, }, - }, 'R1'], - ['Email/get', { - accountId => 'other', - ids => ['#1'], - properties => ['mailboxIds'], - }, 'R2'] - ]); - $self->assert_not_null($res->[1][1]{list}[0]{mailboxIds}{$dstInboxId}); + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + ids => ['#1'], + properties => ['mailboxIds'], + }, + 'R2' + ] + ]); + $self->assert_not_null($res->[1][1]{list}[0]{mailboxIds}{$dstInboxId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_copy_snoozed b/cassandane/tiny-tests/JMAPEmail/email_copy_snoozed index 7c56721b47..cfb3baa753 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_copy_snoozed +++ b/cassandane/tiny-tests/JMAPEmail/email_copy_snoozed @@ -2,106 +2,121 @@ use Cassandane::Tiny; sub test_email_copy_snoozed - :min_version_3_9 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_9 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # snoozed property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # snoozed property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $inboxId = $self->getinbox()->{id}; - $self->assert_not_null($inboxId); + my $inboxId = $self->getinbox()->{id}; + $self->assert_not_null($inboxId); - xlog $self, "create snooze mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "snoozed", - parentId => undef, - role => "snoozed" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $snoozedId = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create snooze mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "snoozed", + parentId => undef, + role => "snoozed" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $snoozedId = $res->[0][1]{created}{"1"}{id}; - xlog $self, "Create user and share mailbox"; - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; + xlog $self, "Create user and share mailbox"; + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; - my $dstInboxId = $self->getinbox({accountId => 'other'})->{id}; - $self->assert_not_null($dstInboxId); + my $dstInboxId = $self->getinbox({ accountId => 'other' })->{id}; + $self->assert_not_null($dstInboxId); - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => 30)); - my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => 30)); + my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - xlog $self, "create snoozed email"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 1 => { - mailboxIds => { - $snoozedId => JSON::true, - }, - keywords => { - 'foo' => JSON::true, - }, - subject => 'hello', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'world', - } - }, - }, + xlog $self, "create snoozed email"; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + mailboxIds => { + $snoozedId => JSON::true, }, - }, 'R1'], - ]); - my $emailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($emailId); + keywords => { + 'foo' => JSON::true, + }, + subject => 'hello', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($emailId); - xlog $self, "copy email to shared mailbox - removing snoozed"; - $res = $jmap->CallMethods([ - ['Email/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - create => { - 1 => { - id => $emailId, - mailboxIds => { - $dstInboxId => JSON::true, - }, - keywords => { - 'bar' => JSON::true, - }, - snoozed => JSON::null - }, + xlog $self, "copy email to shared mailbox - removing snoozed"; + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + create => { + 1 => { + id => $emailId, + mailboxIds => { + $dstInboxId => JSON::true, + }, + keywords => { + 'bar' => JSON::true, }, - }, 'R1'], - ]); + snoozed => JSON::null + }, + }, + }, + 'R1' + ], + ]); - my $copiedEmailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($copiedEmailId); + my $copiedEmailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($copiedEmailId); - xlog $self, "get copied email"; - $res = $jmap->CallMethods([ - ['Email/get', { - accountId => 'other', - ids => [$copiedEmailId], - properties => ['keywords', 'snoozed'], - }, 'R1'] - ]); - my $wantKeywords = { 'bar' => JSON::true }; - $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); - $self->assert_null($res->[0][1]{list}[0]{snoozed}); + xlog $self, "get copied email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + ids => [$copiedEmailId], + properties => [ 'keywords', 'snoozed' ], + }, + 'R1' + ] ]); + my $wantKeywords = { 'bar' => JSON::true }; + $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); + $self->assert_null($res->[0][1]{list}[0]{snoozed}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_copy_state b/cassandane/tiny-tests/JMAPEmail/email_copy_state index 225db16529..b8499bbc00 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_copy_state +++ b/cassandane/tiny-tests/JMAPEmail/email_copy_state @@ -2,98 +2,111 @@ use Cassandane::Tiny; sub test_email_copy_state - :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "Create user and share mailbox"; - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; + xlog $self, "Create user and share mailbox"; + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lrsiwntex") or die; - my $srcInboxId = $self->getinbox()->{id}; - $self->assert_not_null($srcInboxId); + my $srcInboxId = $self->getinbox()->{id}; + $self->assert_not_null($srcInboxId); - my $dstInboxId = $self->getinbox({accountId => 'other'})->{id}; - $self->assert_not_null($dstInboxId); + my $dstInboxId = $self->getinbox({ accountId => 'other' })->{id}; + $self->assert_not_null($dstInboxId); - xlog $self, "create email"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 1 => { - mailboxIds => { - $srcInboxId => JSON::true, - }, - keywords => { - 'foo' => JSON::true, - }, - subject => 'hello', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'world', - } - }, - }, + xlog $self, "create email"; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + mailboxIds => { + $srcInboxId => JSON::true, }, - }, 'R1'], - ['Email/get', { - accountId => 'other', - ids => ['foo'], # Just fetching current state for 'other' - }, 'R2'] - ]); - my $emailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($emailId); - my $fromState = $res->[0][1]->{newState}; - $self->assert_not_null($fromState); - my $state = $res->[1][1]->{state}; - $self->assert_not_null($state); + keywords => { + 'foo' => JSON::true, + }, + subject => 'hello', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + ids => ['foo'], # Just fetching current state for 'other' + }, + 'R2' + ] + ]); + my $emailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($emailId); + my $fromState = $res->[0][1]->{newState}; + $self->assert_not_null($fromState); + my $state = $res->[1][1]->{state}; + $self->assert_not_null($state); - xlog $self, "move email"; - $res = $jmap->CallMethods([ - ['Email/copy', { - fromAccountId => 'cassandane', - accountId => 'other', - ifFromInState => $fromState, - ifInState => $state, - create => { - 1 => { - id => $emailId, - mailboxIds => { - $dstInboxId => JSON::true, - }, - }, + xlog $self, "move email"; + $res = $jmap->CallMethods([ + [ + 'Email/copy', + { + fromAccountId => 'cassandane', + accountId => 'other', + ifFromInState => $fromState, + ifInState => $state, + create => { + 1 => { + id => $emailId, + mailboxIds => { + $dstInboxId => JSON::true, }, - onSuccessDestroyOriginal => JSON::true, - destroyFromIfInState => $fromState, - }, 'R1'], - ['Email/get', { - accountId => 'other', - ids => ['#1'], - properties => ['mailboxIds'], - }, 'R2'] - ]); - $self->assert_not_null($res->[0][1]{created}); - my $oldState = $res->[0][1]->{oldState}; - $self->assert_str_equals($oldState, $state); - my $newState = $res->[0][1]->{newState}; - $self->assert_not_null($newState); - $self->assert_str_equals('Email/set', $res->[1][0]); - $self->assert_str_equals($emailId, $res->[1][1]{destroyed}[0]); - $self->assert_not_null($res->[2][1]{list}[0]{mailboxIds}{$dstInboxId}); + }, + }, + onSuccessDestroyOriginal => JSON::true, + destroyFromIfInState => $fromState, + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + ids => ['#1'], + properties => ['mailboxIds'], + }, + 'R2' + ] + ]); + $self->assert_not_null($res->[0][1]{created}); + my $oldState = $res->[0][1]->{oldState}; + $self->assert_str_equals($oldState, $state); + my $newState = $res->[0][1]->{newState}; + $self->assert_not_null($newState); + $self->assert_str_equals('Email/set', $res->[1][0]); + $self->assert_str_equals($emailId, $res->[1][1]{destroyed}[0]); + $self->assert_not_null($res->[2][1]{list}[0]{mailboxIds}{$dstInboxId}); - # Is the blobId downloadable? - my $blob = $jmap->Download({ accept => 'text/plain' }, - 'other', - $res->[0][1]{created}{"1"}{blobId}); - $self->assert_str_equals('text/plain', - $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); - $self->assert_matches(qr/\r\nSubject: hello\r\n/, $blob->{content}); + # Is the blobId downloadable? + my $blob = $jmap->Download({ accept => 'text/plain' }, + 'other', $res->[0][1]{created}{"1"}{blobId}); + $self->assert_str_equals('text/plain', $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + $self->assert_matches(qr/\r\nSubject: hello\r\n/, $blob->{content}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_download b/cassandane/tiny-tests/JMAPEmail/email_download index 58a89850d4..87697fd5e3 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_download +++ b/cassandane/tiny-tests/JMAPEmail/email_download @@ -2,47 +2,53 @@ use Cassandane::Tiny; sub test_email_download - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Generate an email in INBOX via IMAP"; - my $body = "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; - $body .= "\r\n"; - $body .= "some text"; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; - $body .= "\r\n"; - $body .= "

some HTML text

"; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; - $self->make_message("foo", - mime_type => "multipart/alternative", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body - ); + xlog $self, "Generate an email in INBOX via IMAP"; + my $body = "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $body .= "\r\n"; + $body .= "some text"; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; + $body .= "\r\n"; + $body .= "

some HTML text

"; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; + $self->make_message( + "foo", + mime_type => "multipart/alternative", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body + ); - xlog $self, "get email"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'blobId' ], - }, 'R2'], - ]); - my $msg = $res->[1][1]->{list}[0]; + xlog $self, "get email"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['blobId'], + }, + 'R2' + ], + ]); + my $msg = $res->[1][1]->{list}[0]; - my $blob = $jmap->Download({ accept => 'message/rfc822' }, 'cassandane', $msg->{blobId}); - $self->assert_str_equals('message/rfc822', $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + my $blob = $jmap->Download({ accept => 'message/rfc822' }, + 'cassandane', $msg->{blobId}); + $self->assert_str_equals('message/rfc822', + $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_draft_reply_new_subject_new_thrid b/cassandane/tiny-tests/JMAPEmail/email_draft_reply_new_subject_new_thrid index 20301b3c4f..327d8b6fae 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_draft_reply_new_subject_new_thrid +++ b/cassandane/tiny-tests/JMAPEmail/email_draft_reply_new_subject_new_thrid @@ -2,99 +2,118 @@ use Cassandane::Tiny; sub test_email_draft_reply_new_subject_new_thrid - :min_version_3_3 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $draft = { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - sender => [{ name => "Marvin the Martian", email => "marvin\@acme.local" }], - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - { name => "Rainer M\N{LATIN SMALL LETTER U WITH DIAERESIS}ller", email => "rainer\@de.local" }, - ], - cc => [ - { name => "Elmer Fudd", email => "elmer\@acme.local" }, - { name => "Porky Pig", email => "porky\@acme.local" }, - ], - bcc => [ - { name => "Wile E. Coyote", email => "coyote\@acme.local" }, - ], - replyTo => [ { name => undef, email => "the.other.sam\@acme.local" } ], - subject => "Memo", - textBody => [{ partId => '1' }], - htmlBody => [{ partId => '2' }], - bodyValues => { - '1' => { value => "I'm givin' ya one last chance ta surrenda!" }, - '2' => { value => "Oh!!! I hate that Rabbit." }, - }, - keywords => { '$draft' => JSON::true }, - }; + my $draft = { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + sender => + [ { name => "Marvin the Martian", email => "marvin\@acme.local" } ], + to => [ + { name => "Bugs Bunny", email => "bugs\@acme.local" }, + { + name => "Rainer M\N{LATIN SMALL LETTER U WITH DIAERESIS}ller", + email => "rainer\@de.local" + }, + ], + cc => [ + { name => "Elmer Fudd", email => "elmer\@acme.local" }, + { name => "Porky Pig", email => "porky\@acme.local" }, + ], + bcc => [ { name => "Wile E. Coyote", email => "coyote\@acme.local" }, ], + replyTo => [ { name => undef, email => "the.other.sam\@acme.local" } ], + subject => "Memo", + textBody => [ { partId => '1' } ], + htmlBody => [ { partId => '2' } ], + bodyValues => { + '1' => { value => "I'm givin' ya one last chance ta surrenda!" }, + '2' => { value => "Oh!!! I hate that Rabbit." }, + }, + keywords => { '$draft' => JSON::true }, + }; - xlog $self, "Create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "Create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; - $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); - $self->assert_deep_equals($msg->{from}, $draft->{from}); - $self->assert_deep_equals($msg->{sender}, $draft->{sender}); - $self->assert_deep_equals($msg->{to}, $draft->{to}); - $self->assert_deep_equals($msg->{cc}, $draft->{cc}); - $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); - $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); - $self->assert_str_equals($msg->{subject}, $draft->{subject}); - $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); - $self->assert_num_equals(1, scalar keys %{$msg->{keywords}}); + $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); + $self->assert_deep_equals($msg->{from}, $draft->{from}); + $self->assert_deep_equals($msg->{sender}, $draft->{sender}); + $self->assert_deep_equals($msg->{to}, $draft->{to}); + $self->assert_deep_equals($msg->{cc}, $draft->{cc}); + $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); + $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); + $self->assert_str_equals($msg->{subject}, $draft->{subject}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); + $self->assert_num_equals(1, scalar keys %{ $msg->{keywords} }); - xlog $self, "create sent mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "sent", - parentId => undef, - role => "sent" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $sentmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create sent mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "sent", + parentId => undef, + role => "sent" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $sentmbox = $res->[0][1]{created}{"1"}{id}; - # Now change the draft keyword, which is allowed since approx ~Q1/2018. - xlog $self, "Update the email into the sent mailbox and remove draft"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $id => { - 'keywords' => { '$seen' => JSON::true }, - 'mailboxIds' => { $sentmbox => JSON::true }, - } }, - }, "R1"] - ]); - $self->assert(exists $res->[0][1]{updated}{$id}); + # Now change the draft keyword, which is allowed since approx ~Q1/2018. + xlog $self, "Update the email into the sent mailbox and remove draft"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $id => { + 'keywords' => { '$seen' => JSON::true }, + 'mailboxIds' => { $sentmbox => JSON::true }, + } + }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); - xlog $self, "Create a new draft which is in reply"; - $draft->{inReplyTo} = $msg->{messageId}; - $draft->{subject} = "Rubbish different subject"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id2 = $res->[0][1]{created}{"1"}{id}; - my $thread2 = $res->[0][1]{created}{"1"}{threadId}; - $self->assert_str_not_equals($msg->{threadId}, $thread2); + xlog $self, "Create a new draft which is in reply"; + $draft->{inReplyTo} = $msg->{messageId}; + $draft->{subject} = "Rubbish different subject"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id2 = $res->[0][1]{created}{"1"}{id}; + my $thread2 = $res->[0][1]{created}{"1"}{threadId}; + $self->assert_str_not_equals($msg->{threadId}, $thread2); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_draft_subject_keeps_thrid b/cassandane/tiny-tests/JMAPEmail/email_draft_subject_keeps_thrid index 317be8cb0e..6c59f89db2 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_draft_subject_keeps_thrid +++ b/cassandane/tiny-tests/JMAPEmail/email_draft_subject_keeps_thrid @@ -2,96 +2,108 @@ use Cassandane::Tiny; sub test_email_draft_subject_keeps_thrid - :min_version_3_3 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $messageId = "71cdcf3a-6dc5-4d95-b600-14e7f99719f0\@example.com"; + my $messageId = "71cdcf3a-6dc5-4d95-b600-14e7f99719f0\@example.com"; - my $draft = { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - sender => [{ name => "Marvin the Martian", email => "marvin\@acme.local" }], - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - { name => "Rainer M\N{LATIN SMALL LETTER U WITH DIAERESIS}ller", email => "rainer\@de.local" }, - ], - cc => [ - { name => "Elmer Fudd", email => "elmer\@acme.local" }, - { name => "Porky Pig", email => "porky\@acme.local" }, - ], - bcc => [ - { name => "Wile E. Coyote", email => "coyote\@acme.local" }, - ], - replyTo => [ { name => undef, email => "the.other.sam\@acme.local" } ], - subject => "Memo", - textBody => [{ partId => '1' }], - htmlBody => [{ partId => '2' }], - messageId => [$messageId], - bodyValues => { - '1' => { value => "I'm givin' ya one last chance ta surrenda!" }, - '2' => { value => "Oh!!! I hate that Rabbit." }, - }, - keywords => { '$draft' => JSON::true }, - }; + my $draft = { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + sender => + [ { name => "Marvin the Martian", email => "marvin\@acme.local" } ], + to => [ + { name => "Bugs Bunny", email => "bugs\@acme.local" }, + { + name => "Rainer M\N{LATIN SMALL LETTER U WITH DIAERESIS}ller", + email => "rainer\@de.local" + }, + ], + cc => [ + { name => "Elmer Fudd", email => "elmer\@acme.local" }, + { name => "Porky Pig", email => "porky\@acme.local" }, + ], + bcc => [ { name => "Wile E. Coyote", email => "coyote\@acme.local" }, ], + replyTo => [ { name => undef, email => "the.other.sam\@acme.local" } ], + subject => "Memo", + textBody => [ { partId => '1' } ], + htmlBody => [ { partId => '2' } ], + messageId => [$messageId], + bodyValues => { + '1' => { value => "I'm givin' ya one last chance ta surrenda!" }, + '2' => { value => "Oh!!! I hate that Rabbit." }, + }, + keywords => { '$draft' => JSON::true }, + }; - xlog $self, "create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $threadId = $res->[0][1]{created}{"1"}{threadId}; + xlog $self, "create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $threadId = $res->[0][1]{created}{"1"}{threadId}; - xlog $self, "Get draft $id1"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id1] }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id1"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id1] }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; - $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); - $self->assert_deep_equals($msg->{from}, $draft->{from}); - $self->assert_deep_equals($msg->{sender}, $draft->{sender}); - $self->assert_deep_equals($msg->{to}, $draft->{to}); - $self->assert_deep_equals($msg->{cc}, $draft->{cc}); - $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); - $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); - $self->assert_str_equals($msg->{subject}, $draft->{subject}); - $self->assert_str_equals($msg->{threadId}, $threadId); - $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); - $self->assert_num_equals(1, scalar keys %{$msg->{keywords}}); + $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); + $self->assert_deep_equals($msg->{from}, $draft->{from}); + $self->assert_deep_equals($msg->{sender}, $draft->{sender}); + $self->assert_deep_equals($msg->{to}, $draft->{to}); + $self->assert_deep_equals($msg->{cc}, $draft->{cc}); + $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); + $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); + $self->assert_str_equals($msg->{subject}, $draft->{subject}); + $self->assert_str_equals($msg->{threadId}, $threadId); + $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); + $self->assert_num_equals(1, scalar keys %{ $msg->{keywords} }); - # change subject and prep for replace - $draft->{subject} = "Wabbit Season!"; + # change subject and prep for replace + $draft->{subject} = "Wabbit Season!"; - xlog $self, "replace the draft with a new copy with a new subject"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }, destroy => [ $id1 ] }, "R1"]]); - my $id2 = $res->[0][1]{created}{"1"}{id}; - $self->assert_str_not_equals($id1, $id2); - $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); + xlog $self, "replace the draft with a new copy with a new subject"; + $res + = $jmap->CallMethods( + [ [ + 'Email/set', { create => { "1" => $draft }, destroy => [$id1] }, "R1" ] ] + ); + my $id2 = $res->[0][1]{created}{"1"}{id}; + $self->assert_str_not_equals($id1, $id2); + $self->assert_str_equals($id1, $res->[0][1]{destroyed}[0]); - xlog $self, "Get draft $id2"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id2] }, "R1"]]); - $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id2"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id2] }, "R1" ] ]); + $msg = $res->[0][1]->{list}[0]; - $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); - $self->assert_deep_equals($msg->{from}, $draft->{from}); - $self->assert_deep_equals($msg->{sender}, $draft->{sender}); - $self->assert_deep_equals($msg->{to}, $draft->{to}); - $self->assert_deep_equals($msg->{cc}, $draft->{cc}); - $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); - $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); - $self->assert_str_equals($msg->{subject}, $draft->{subject}); - $self->assert_str_equals($msg->{threadId}, $threadId); - $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); - $self->assert_num_equals(1, scalar keys %{$msg->{keywords}}); + $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); + $self->assert_deep_equals($msg->{from}, $draft->{from}); + $self->assert_deep_equals($msg->{sender}, $draft->{sender}); + $self->assert_deep_equals($msg->{to}, $draft->{to}); + $self->assert_deep_equals($msg->{cc}, $draft->{cc}); + $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); + $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); + $self->assert_str_equals($msg->{subject}, $draft->{subject}); + $self->assert_str_equals($msg->{threadId}, $threadId); + $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); + $self->assert_num_equals(1, scalar keys %{ $msg->{keywords} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_embedded_download b/cassandane/tiny-tests/JMAPEmail/email_embedded_download index 1179588c65..5fc1dd2aae 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_embedded_download +++ b/cassandane/tiny-tests/JMAPEmail/email_embedded_download @@ -2,52 +2,56 @@ use Cassandane::Tiny; sub test_email_embedded_download - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - # Generate an embedded email - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "Content-Disposition: inline\r\n" . "\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . "Return-Path: \r\n" - . "Mime-Version: 1.0\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Transfer-Encoding: 7bit\r\n" - . "Subject: bar\r\n" - . "From: Ava T. Nguyen \r\n" - . "Message-ID: \r\n" - . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "An embedded email" - . "\r\n--sub--\r\n", - ) || die; + # Generate an embedded email + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "Return-Path: \r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" + . "Subject: bar\r\n" + . "From: Ava T. Nguyen \r\n" + . "Message-ID: \r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: Test User \r\n" . "\r\n" + . "An embedded email" + . "\r\n--sub--\r\n", + ) || die; - xlog $self, "get blobId"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['attachments'], - }, 'R2' ], - ]); - my $blobId = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; + xlog $self, "get blobId"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['attachments'], + }, + 'R2' + ], + ]); + my $blobId = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; - my $blob = $jmap->Download({ accept => 'message/rfc822' }, 'cassandane', $blobId); - $self->assert_str_equals('message/rfc822', $blob->{headers}->{'content-type'}); - $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); + my $blob + = $jmap->Download({ accept => 'message/rfc822' }, 'cassandane', $blobId); + $self->assert_str_equals('message/rfc822', + $blob->{headers}->{'content-type'}); + $self->assert_num_not_equals(0, $blob->{headers}->{'content-length'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_flagged_shared_twofolder_hidden b/cassandane/tiny-tests/JMAPEmail/email_flagged_shared_twofolder_hidden index d33ee29ca1..656a2cb9f1 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_flagged_shared_twofolder_hidden +++ b/cassandane/tiny-tests/JMAPEmail/email_flagged_shared_twofolder_hidden @@ -2,74 +2,87 @@ use Cassandane::Tiny; sub test_email_flagged_shared_twofolder_hidden - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Share account - $self->{instance}->create_user("other"); + # Share account + $self->{instance}->create_user("other"); - # Create mailbox A - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lrsiwn") or die; - # NOTE: user cassandane does NOT get permission to see this one - $admintalk->create("user.other.A.sub") or die; + # Create mailbox A + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lrsiwn") or die; + # NOTE: user cassandane does NOT get permission to see this one + $admintalk->create("user.other.A.sub") or die; - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - $self->make_message("Email", store => $self->{adminstore}) or die; + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + $self->make_message("Email", store => $self->{adminstore}) or die; - # Set \Flagged on message A as user cassandane - $self->{store}->set_folder('user.other.A'); - $admintalk->select('user.other.A'); - $admintalk->copy('1', 'user.other.A.sub'); - $talk->select('user.other.A'); - $talk->store('1', '+flags', '(\\Flagged)'); + # Set \Flagged on message A as user cassandane + $self->{store}->set_folder('user.other.A'); + $admintalk->select('user.other.A'); + $admintalk->copy('1', 'user.other.A.sub'); + $talk->select('user.other.A'); + $talk->store('1', '+flags', '(\\Flagged)'); - # Get email and assert $seen - my $res = $jmap->CallMethods([ - ['Email/query', { - accountId => 'other', - }, 'R1'], - ['Email/get', { - accountId => 'other', - properties => ['keywords'], - '#ids' => { - resultOf => 'R1', name => 'Email/query', path => '/ids' - } - }, 'R2' ] - ]); - my $emailId = $res->[1][1]{list}[0]{id}; - my $wantKeywords = { '$flagged' => JSON::true }; - $self->assert_deep_equals($wantKeywords, $res->[1][1]{list}[0]{keywords}); + # Get email and assert $seen + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + accountId => 'other', + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + properties => ['keywords'], + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + } + }, + 'R2' + ] + ]); + my $emailId = $res->[1][1]{list}[0]{id}; + my $wantKeywords = { '$flagged' => JSON::true }; + $self->assert_deep_equals($wantKeywords, $res->[1][1]{list}[0]{keywords}); - # Set $seen via JMAP on the shared mailbox - $res = $jmap->CallMethods([ - ['Email/set', { - accountId => 'other', - update => { - $emailId => { - keywords => { }, - }, - }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); + # Set $seen via JMAP on the shared mailbox + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'other', + update => { + $emailId => { + keywords => {}, + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); - # Assert $seen got updated - $res = $jmap->CallMethods([ - ['Email/get', { - accountId => 'other', - properties => ['keywords'], - ids => [$emailId], - }, 'R1' ] - ]); - $wantKeywords = { }; - $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); + # Assert $seen got updated + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + properties => ['keywords'], + ids => [$emailId], + }, + 'R1' + ] ]); + $wantKeywords = {}; + $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get b/cassandane/tiny-tests/JMAPEmail/email_get index 70af839c06..bb0cc4f447 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get +++ b/cassandane/tiny-tests/JMAPEmail/email_get @@ -2,103 +2,116 @@ use Cassandane::Tiny; sub test_email_get - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - my $body = ""; - $body .= "Lorem ipsum dolor sit amet, consectetur adipiscing\r\n"; - $body .= "elit. Nunc in fermentum nibh. Vivamus enim metus."; + my $body = ""; + $body .= "Lorem ipsum dolor sit amet, consectetur adipiscing\r\n"; + $body .= "elit. Nunc in fermentum nibh. Vivamus enim metus."; - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => -10)); + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => -10)); - xlog $self, "Generate an email in INBOX via IMAP"; - my %exp_inbox; - my %params = ( - date => $maildate, - from => Cassandane::Address->new( - name => "Sally Sender", - localpart => "sally", - domain => "local" - ), - to => Cassandane::Address->new( - name => "Tom To", - localpart => 'tom', - domain => 'local' - ), - cc => Cassandane::Address->new( - name => "Cindy CeeCee", - localpart => 'cindy', - domain => 'local' - ), - bcc => Cassandane::Address->new( - name => "Benny CarbonCopy", - localpart => 'benny', - domain => 'local' - ), - messageid => 'fake.123456789@local', - extra_headers => [ - ['x-tra', "foo bar\r\n baz"], - ['sender', "Bla "], - ], - body => $body - ); - $self->make_message("Email A", %params) || die; + xlog $self, "Generate an email in INBOX via IMAP"; + my %exp_inbox; + my %params = ( + date => $maildate, + from => Cassandane::Address->new( + name => "Sally Sender", + localpart => "sally", + domain => "local" + ), + to => Cassandane::Address->new( + name => "Tom To", + localpart => 'tom', + domain => 'local' + ), + cc => Cassandane::Address->new( + name => "Cindy CeeCee", + localpart => 'cindy', + domain => 'local' + ), + bcc => Cassandane::Address->new( + name => "Benny CarbonCopy", + localpart => 'benny', + domain => 'local' + ), + messageid => 'fake.123456789@local', + extra_headers => + [ [ 'x-tra', "foo bar\r\n baz" ], [ 'sender', "Bla " ], ], + body => $body + ); + $self->make_message("Email A", %params) || die; - xlog $self, "get email list"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); + xlog $self, "get email list"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); - my @props = $self->defaultprops_for_email_get(); + my @props = $self->defaultprops_for_email_get(); - push @props, "header:x-tra"; + push @props, "header:x-tra"; - xlog $self, "get emails"; - my $ids = $res->[0][1]->{ids}; - $res = $jmap->CallMethods([['Email/get', { ids => $ids, properties => \@props }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; + xlog $self, "get emails"; + my $ids = $res->[0][1]->{ids}; + $res = $jmap->CallMethods( + [ [ 'Email/get', { ids => $ids, properties => \@props }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; - $self->assert_not_null($msg->{mailboxIds}{$inboxid}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_num_equals(0, scalar keys %{$msg->{keywords}}); + $self->assert_not_null($msg->{mailboxIds}{$inboxid}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_num_equals(0, scalar keys %{ $msg->{keywords} }); - $self->assert_str_equals('fake.123456789@local', $msg->{messageId}[0]); - $self->assert_str_equals(" foo bar\r\n baz", $msg->{'header:x-tra'}); - $self->assert_deep_equals({ - name => "Sally Sender", - email => "sally\@local" - }, $msg->{from}[0]); - $self->assert_deep_equals({ - name => "Tom To", - email => "tom\@local" - }, $msg->{to}[0]); - $self->assert_num_equals(1, scalar @{$msg->{to}}); - $self->assert_deep_equals({ - name => "Cindy CeeCee", - email => "cindy\@local" - }, $msg->{cc}[0]); - $self->assert_num_equals(1, scalar @{$msg->{cc}}); - $self->assert_deep_equals({ - name => "Benny CarbonCopy", - email => "benny\@local" - }, $msg->{bcc}[0]); - $self->assert_num_equals(1, scalar @{$msg->{bcc}}); - $self->assert_null($msg->{replyTo}); - $self->assert_deep_equals([{ - name => "Bla", - email => "blu\@local" - }], $msg->{sender}); - $self->assert_str_equals("Email A", $msg->{subject}); + $self->assert_str_equals('fake.123456789@local', $msg->{messageId}[0]); + $self->assert_str_equals(" foo bar\r\n baz", $msg->{'header:x-tra'}); + $self->assert_deep_equals( + { + name => "Sally Sender", + email => "sally\@local" + }, + $msg->{from}[0] + ); + $self->assert_deep_equals( + { + name => "Tom To", + email => "tom\@local" + }, + $msg->{to}[0] + ); + $self->assert_num_equals(1, scalar @{ $msg->{to} }); + $self->assert_deep_equals( + { + name => "Cindy CeeCee", + email => "cindy\@local" + }, + $msg->{cc}[0] + ); + $self->assert_num_equals(1, scalar @{ $msg->{cc} }); + $self->assert_deep_equals( + { + name => "Benny CarbonCopy", + email => "benny\@local" + }, + $msg->{bcc}[0] + ); + $self->assert_num_equals(1, scalar @{ $msg->{bcc} }); + $self->assert_null($msg->{replyTo}); + $self->assert_deep_equals( + [ { + name => "Bla", + email => "blu\@local" + } ], + $msg->{sender} + ); + $self->assert_str_equals("Email A", $msg->{subject}); - my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - $self->assert_str_equals($datestr, $msg->{receivedAt}); - $self->assert_not_null($msg->{size}); + my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + $self->assert_str_equals($datestr, $msg->{receivedAt}); + $self->assert_not_null($msg->{size}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_8bit_headers b/cassandane/tiny-tests/JMAPEmail/email_get_8bit_headers index 670a62910c..0d3648e29e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_8bit_headers +++ b/cassandane/tiny-tests/JMAPEmail/email_get_8bit_headers @@ -2,62 +2,72 @@ use Cassandane::Tiny; sub test_email_get_8bit_headers - :min_version_3_1 :needs_component_jmap :needs_dependency_chardet - :needs_component_sieve :NoMunge8Bit :RFC2047_UTF8 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_jmap : needs_dependency_chardet + : needs_component_sieve : NoMunge8Bit : RFC2047_UTF8 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - # Москва - столица России. - "Moscow is the capital of Russia." - my $wantSubject = - "\xd0\x9c\xd0\xbe\xd1\x81\xd0\xba\xd0\xb2\xd0\xb0\x20\x2d\x20\xd1". - "\x81\xd1\x82\xd0\xbe\xd0\xbb\xd0\xb8\xd1\x86\xd0\xb0\x20\xd0\xa0". - "\xd0\xbe\xd1\x81\xd1\x81\xd0\xb8\xd0\xb8\x2e"; - utf8::decode($wantSubject) || die $@; + # Москва - столица России. - "Moscow is the capital of Russia." + my $wantSubject + = "\xd0\x9c\xd0\xbe\xd1\x81\xd0\xba\xd0\xb2\xd0\xb0\x20\x2d\x20\xd1" + . "\x81\xd1\x82\xd0\xbe\xd0\xbb\xd0\xb8\xd1\x86\xd0\xb0\x20\xd0\xa0" + . "\xd0\xbe\xd1\x81\xd1\x81\xd0\xb8\xd0\xb8\x2e"; + utf8::decode($wantSubject) || die $@; - # Фёдор Михайлович Достоевский - "Fyódor Mikháylovich Dostoyévskiy" - my $wantName = - "\xd0\xa4\xd1\x91\xd0\xb4\xd0\xbe\xd1\x80\x20\xd0\x9c\xd0\xb8\xd1". - "\x85\xd0\xb0\xd0\xb9\xd0\xbb\xd0\xbe\xd0\xb2\xd0\xb8\xd1\x87\x20". - "\xd0\x94\xd0\xbe\xd1\x81\xd1\x82\xd0\xbe\xd0\xb5\xd0\xb2\xd1\x81". - "\xd0\xba\xd0\xb8\xd0\xb9"; - utf8::decode($wantName) || die $@; + # Фёдор Михайлович Достоевский - "Fyódor Mikháylovich Dostoyévskiy" + my $wantName + = "\xd0\xa4\xd1\x91\xd0\xb4\xd0\xbe\xd1\x80\x20\xd0\x9c\xd0\xb8\xd1" + . "\x85\xd0\xb0\xd0\xb9\xd0\xbb\xd0\xbe\xd0\xb2\xd0\xb8\xd1\x87\x20" + . "\xd0\x94\xd0\xbe\xd1\x81\xd1\x82\xd0\xbe\xd0\xb5\xd0\xb2\xd1\x81" + . "\xd0\xba\xd0\xb8\xd0\xb9"; + utf8::decode($wantName) || die $@; - my $wantEmail = 'fyodor@local'; + my $wantEmail = 'fyodor@local'; - my @testCases = ({ - file => 'data/mime/headers-utf8.bin', - }, { - file => 'data/mime/headers-koi8r.bin', - }); + my @testCases = ( + { + file => 'data/mime/headers-utf8.bin', + }, + { + file => 'data/mime/headers-koi8r.bin', + } + ); - foreach (@testCases) { - open(my $F, $_->{file}) || die $!; - $imap->append('INBOX', $F) || die $@; - close($F); + foreach (@testCases) { + open(my $F, $_->{file}) || die $!; + $imap->append('INBOX', $F) || die $@; + close($F); - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['subject', 'from'], - }, 'R2' ], - ['Email/set', { - '#destroy' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - }, 'R3' ], - ]); - my $email = $res->[1][1]{list}[0]; - $self->assert_str_equals($wantSubject, $email->{subject}); - $self->assert_str_equals($wantName, $email->{from}[0]{name}); - $self->assert_str_equals($wantEmail, $email->{from}[0]{email}); - } + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => [ 'subject', 'from' ], + }, + 'R2' + ], + [ + 'Email/set', + { + '#destroy' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + }, + 'R3' + ], + ]); + my $email = $res->[1][1]{list}[0]; + $self->assert_str_equals($wantSubject, $email->{subject}); + $self->assert_str_equals($wantName, $email->{from}[0]{name}); + $self->assert_str_equals($wantEmail, $email->{from}[0]{email}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_attachedemails b/cassandane/tiny-tests/JMAPEmail/email_get_attachedemails index 62ba756712..306b913e38 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_attachedemails +++ b/cassandane/tiny-tests/JMAPEmail/email_get_attachedemails @@ -2,58 +2,57 @@ use Cassandane::Tiny; sub test_email_get_attachedemails - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); - - my $body = "". - "--sub\r\n". - "Content-Type: text/plain; charset=UTF-8\r\n". - "Content-Disposition: inline\r\n". - "\r\n". - "Short text". # Exactly 10 byte long body - "\r\n--sub\r\n". - "Content-Type: message/rfc822\r\n". - "\r\n" . - "Return-Path: \r\n". - "Mime-Version: 1.0\r\n". - "Content-Type: text/plain\r\n". - "Content-Transfer-Encoding: 7bit\r\n". - "Subject: bar\r\n". - "From: Ava T. Nguyen \r\n". - "Message-ID: \r\n". - "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n". - "To: Test User \r\n". - "\r\n". - "Jeez....an embedded email". - "\r\n--sub--\r\n"; - - $exp_sub{A} = $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => $body - ); - $talk->store('1', '+flags', '($HasAttachment)'); - - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; - - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; - - $self->assert_num_equals(1, scalar @{$msg->{attachments}}); - $self->assert_str_equals("message/rfc822", $msg->{attachments}[0]{type}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); + + my $body + = "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" + . "Short text" + . # Exactly 10 byte long body + "\r\n--sub\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "Return-Path: \r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" + . "Subject: bar\r\n" + . "From: Ava T. Nguyen \r\n" + . "Message-ID: \r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: Test User \r\n" . "\r\n" + . "Jeez....an embedded email" + . "\r\n--sub--\r\n"; + + $exp_sub{A} = $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => $body + ); + $talk->store('1', '+flags', '($HasAttachment)'); + + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; + + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => $ids }, "R1" ] ]); + my $msg = $res->[0][1]{list}[0]; + + $self->assert_num_equals(1, scalar @{ $msg->{attachments} }); + $self->assert_str_equals("message/rfc822", $msg->{attachments}[0]{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_attachment_name b/cassandane/tiny-tests/JMAPEmail/email_get_attachment_name index 392dc23579..d43246d359 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_attachment_name +++ b/cassandane/tiny-tests/JMAPEmail/email_get_attachment_name @@ -2,184 +2,173 @@ use Cassandane::Tiny; sub test_email_get_attachment_name - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); - - my $body = "". - "--sub\r\n". - "Content-Type: image/jpeg\r\n". - "Content-Disposition: attachment; filename\r\n\t=\"image1.jpg\"\r\n". - "Content-Transfer-Encoding: base64\r\n". - "\r\n" . - "beefc0de". - "\r\n--sub\r\n". - "Content-Type: image/tiff\r\n". - "Content-Transfer-Encoding: base64\r\n". - "\r\n" . - "abc=". - "\r\n--sub\r\n". - "Content-Type: application/x-excel\r\n". - "Content-Transfer-Encoding: base64\r\n". - "Content-Disposition: attachment; filename\r\n\t=\"f.xls\"\r\n". - "\r\n" . - "012312312313". - "\r\n--sub\r\n". - "Content-Type: application/test1;name=y.dat\r\n". - "Content-Disposition: attachment; filename=z.dat\r\n". - "\r\n" . - "test1". - "\r\n--sub\r\n". - "Content-Type: application/test2;name*0=looo;name*1=ooong;name*2=.name\r\n". - "\r\n" . - "test2". - "\r\n--sub\r\n". - "Content-Type: application/test3\r\n". - "Content-Disposition: attachment; filename*0=cont;\r\n filename*1=inue\r\n". - "\r\n" . - "test3". - "\r\n--sub\r\n". - "Content-Type: application/test4; name=\"=?utf-8?Q?=F0=9F=98=80=2Etxt?=\"\r\n". - "\r\n" . - "test4". - "\r\n--sub\r\n". - "Content-Type: application/test5\r\n". - "Content-Disposition: attachment; filename*0*=utf-8''%F0%9F%98%80;\r\n filename*1=\".txt\"\r\n". - "\r\n" . - "test5". - "\r\n--sub\r\n". - "Content-Type: application/test6\r\n" . - "Content-Disposition: attachment;\r\n". - " filename*0*=\"Unencoded ' char\";\r\n" . - " filename*1*=\".txt\"\r\n" . - "\r\n" . - "test6". + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); + + my $body + = "" + . "--sub\r\n" + . "Content-Type: image/jpeg\r\n" + . "Content-Disposition: attachment; filename\r\n\t=\"image1.jpg\"\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "beefc0de" + . "\r\n--sub\r\n" + . "Content-Type: image/tiff\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" . "abc=" + . "\r\n--sub\r\n" + . "Content-Type: application/x-excel\r\n" + . "Content-Transfer-Encoding: base64\r\n" + . "Content-Disposition: attachment; filename\r\n\t=\"f.xls\"\r\n" . "\r\n" + . "012312312313" + . "\r\n--sub\r\n" + . "Content-Type: application/test1;name=y.dat\r\n" + . "Content-Disposition: attachment; filename=z.dat\r\n" . "\r\n" . "test1" + . "\r\n--sub\r\n" + . "Content-Type: application/test2;name*0=looo;name*1=ooong;name*2=.name\r\n" + . "\r\n" + . "test2" + . "\r\n--sub\r\n" + . "Content-Type: application/test3\r\n" + . "Content-Disposition: attachment; filename*0=cont;\r\n filename*1=inue\r\n" + . "\r\n" + . "test3" + . "\r\n--sub\r\n" + . "Content-Type: application/test4; name=\"=?utf-8?Q?=F0=9F=98=80=2Etxt?=\"\r\n" + . "\r\n" + . "test4" + . "\r\n--sub\r\n" + . "Content-Type: application/test5\r\n" + . "Content-Disposition: attachment; filename*0*=utf-8''%F0%9F%98%80;\r\n filename*1=\".txt\"\r\n" + . "\r\n" + . "test5" + . "\r\n--sub\r\n" + . "Content-Type: application/test6\r\n" + . "Content-Disposition: attachment;\r\n" + . " filename*0*=\"Unencoded ' char\";\r\n" + . " filename*1*=\".txt\"\r\n" . "\r\n" . "test6" + . # RFC 2045, section 5.1. requires quoted-string for parameter # values with tspecial or whitespace, but some clients ignore # this. The following tests check Cyrus leniently accept this. - "\r\n--sub\r\n". - "Content-Type: application/test7; name==?iso-8859-1?b?Q2Fm6S5kb2M=?=\r\n". - "Content-Disposition: attachment; filename==?iso-8859-1?b?Q2Fm6S5kb2M=?=\r\n". - "\r\n" . - "test7". - "\r\n--sub\r\n". - "Content-Type: application/test8; name= foo \r\n". - "\r\n" . - "test8". - "\r\n--sub\r\n". - "Content-Type: application/test9; name=foo bar\r\n". - "\r\n" . - "test9". - "\r\n--sub\r\n". - "Content-Type: application/test10; name=foo bar\r\n\t baz \r\n". - "\r\n" . - "test10". - "\r\n--sub\r\n". - "Content-Type: application/test11; name=\r\n\t baz \r\n". - "\r\n" . - "test11". - "\r\n--sub\r\n". - "Content-Type: application/test12; name= \r\n\t \r\n". - "\r\n" . - "test12". - - "\r\n--sub\r\n". - "Content-Type: application/test13\r\n". - "Content-Disposition: attachment; filename=\"q\\\".dat\"\r\n". - "\r\n" . - "test13". + "\r\n--sub\r\n" + . "Content-Type: application/test7; name==?iso-8859-1?b?Q2Fm6S5kb2M=?=\r\n" + . "Content-Disposition: attachment; filename==?iso-8859-1?b?Q2Fm6S5kb2M=?=\r\n" + . "\r\n" + . "test7" + . "\r\n--sub\r\n" + . "Content-Type: application/test8; name= foo \r\n" . "\r\n" . "test8" + . "\r\n--sub\r\n" + . "Content-Type: application/test9; name=foo bar\r\n" . "\r\n" . "test9" + . "\r\n--sub\r\n" + . "Content-Type: application/test10; name=foo bar\r\n\t baz \r\n" . "\r\n" + . "test10" + . "\r\n--sub\r\n" + . "Content-Type: application/test11; name=\r\n\t baz \r\n" . "\r\n" + . "test11" + . "\r\n--sub\r\n" + . "Content-Type: application/test12; name= \r\n\t \r\n" . "\r\n" + . "test12" + . + + "\r\n--sub\r\n" + . "Content-Type: application/test13\r\n" + . "Content-Disposition: attachment; filename=\"q\\\".dat\"\r\n" . "\r\n" + . "test13" + . # Some clients send raw UTF-8 characters in MIME parameters. # The following test checks Cyrus leniently accept this. - "\r\n--sub\r\n". - "Content-Type: application/test14; name=😀.txt\r\n". - "\r\n" . - "test14". + "\r\n--sub\r\n" + . "Content-Type: application/test14; name=😀.txt\r\n" . "\r\n" + . "test14" + . "\r\n--sub--\r\n"; - $exp_sub{A} = $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => $body - ); - $talk->store('1', '+flags', '($HasAttachment)'); + $exp_sub{A} = $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => $body + ); + $talk->store('1', '+flags', '($HasAttachment)'); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => $ids }, "R1" ] ]); + my $msg = $res->[0][1]{list}[0]; - $self->assert_equals(JSON::true, $msg->{hasAttachment}); + $self->assert_equals(JSON::true, $msg->{hasAttachment}); - # Assert embedded email support - my %m = map { $_->{type} => $_ } @{$msg->{attachments}}; - my $att; + # Assert embedded email support + my %m = map { $_->{type} => $_ } @{ $msg->{attachments} }; + my $att; - $att = $m{"image/tiff"}; - $self->assert_null($att->{name}); + $att = $m{"image/tiff"}; + $self->assert_null($att->{name}); - $att = $m{"application/x-excel"}; - $self->assert_str_equals("f.xls", $att->{name}); + $att = $m{"application/x-excel"}; + $self->assert_str_equals("f.xls", $att->{name}); - $att = $m{"image/jpeg"}; - $self->assert_str_equals("image1.jpg", $att->{name}); + $att = $m{"image/jpeg"}; + $self->assert_str_equals("image1.jpg", $att->{name}); - $att = $m{"application/test1"}; - $self->assert_str_equals("z.dat", $att->{name}); + $att = $m{"application/test1"}; + $self->assert_str_equals("z.dat", $att->{name}); - $att = $m{"application/test2"}; - $self->assert_str_equals("loooooong.name", $att->{name}); + $att = $m{"application/test2"}; + $self->assert_str_equals("loooooong.name", $att->{name}); - $att = $m{"application/test3"}; - $self->assert_str_equals("continue", $att->{name}); + $att = $m{"application/test3"}; + $self->assert_str_equals("continue", $att->{name}); - $att = $m{"application/test4"}; - $self->assert_str_equals("\N{GRINNING FACE}.txt", $att->{name}); + $att = $m{"application/test4"}; + $self->assert_str_equals("\N{GRINNING FACE}.txt", $att->{name}); - $att = $m{"application/test5"}; - $self->assert_str_equals("\N{GRINNING FACE}.txt", $att->{name}); + $att = $m{"application/test5"}; + $self->assert_str_equals("\N{GRINNING FACE}.txt", $att->{name}); - $att = $m{"application/test6"}; - $self->assert_str_equals("Unencoded ' char.txt", $att->{name}); + $att = $m{"application/test6"}; + $self->assert_str_equals("Unencoded ' char.txt", $att->{name}); - $att = $m{"application/test7"}; - $self->assert_str_equals("Caf\N{LATIN SMALL LETTER E WITH ACUTE}.doc", $att->{name}); + $att = $m{"application/test7"}; + $self->assert_str_equals("Caf\N{LATIN SMALL LETTER E WITH ACUTE}.doc", + $att->{name}); - $att = $m{"application/test8"}; - $self->assert_str_equals("foo", $att->{name}); + $att = $m{"application/test8"}; + $self->assert_str_equals("foo", $att->{name}); - $att = $m{"application/test9"}; - $self->assert_str_equals("foo bar", $att->{name}); + $att = $m{"application/test9"}; + $self->assert_str_equals("foo bar", $att->{name}); - $att = $m{"application/test10"}; - $self->assert_str_equals("foo bar\t baz", $att->{name}); + $att = $m{"application/test10"}; + $self->assert_str_equals("foo bar\t baz", $att->{name}); - $att = $m{"application/test11"}; - $self->assert_str_equals("baz", $att->{name}); + $att = $m{"application/test11"}; + $self->assert_str_equals("baz", $att->{name}); - $att = $m{"application/test12"}; - $self->assert_null($att->{name}); + $att = $m{"application/test12"}; + $self->assert_null($att->{name}); - $att = $m{"application/test13"}; - $self->assert_str_equals('q".dat', $att->{name}); + $att = $m{"application/test13"}; + $self->assert_str_equals('q".dat', $att->{name}); - $att = $m{"application/test14"}; - $self->assert_str_equals("\N{GRINNING FACE}.txt", $att->{name}); + $att = $m{"application/test14"}; + $self->assert_str_equals("\N{GRINNING FACE}.txt", $att->{name}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_body_both b/cassandane/tiny-tests/JMAPEmail/email_get_body_both index f0021b391d..9e743e14cf 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_body_both +++ b/cassandane/tiny-tests/JMAPEmail/email_get_body_both @@ -2,51 +2,54 @@ use Cassandane::Tiny; sub test_email_get_body_both - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); - - my $htmlBody = "

This is the html part.

"; - my $textBody = "This is the plain text part."; - - my $body = "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; - $body .= "\r\n"; - $body .= $textBody; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; - $body .= "\r\n"; - $body .= $htmlBody; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; - $exp_sub{A} = $self->make_message("foo", - mime_type => "multipart/alternative", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); + + my $htmlBody = "

This is the html part.

"; + my $textBody = "This is the plain text part."; + + my $body = "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $body .= "\r\n"; + $body .= $textBody; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; + $body .= "\r\n"; + $body .= $htmlBody; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; + $exp_sub{A} = $self->make_message( + "foo", + mime_type => "multipart/alternative", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body + ); + + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; + + xlog $self, "get email"; + $res + = $jmap->CallMethods( + [ [ 'Email/get', { ids => $ids, fetchAllBodyValues => JSON::true }, "R1" ] ] ); + my $msg = $res->[0][1]{list}[0]; - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; - - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids, fetchAllBodyValues => JSON::true }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; - - my $partId = $msg->{textBody}[0]{partId}; - $self->assert_str_equals($textBody, $msg->{bodyValues}{$partId}{value}); - $partId = $msg->{htmlBody}[0]{partId}; - $self->assert_str_equals($htmlBody, $msg->{bodyValues}{$partId}{value}); + my $partId = $msg->{textBody}[0]{partId}; + $self->assert_str_equals($textBody, $msg->{bodyValues}{$partId}{value}); + $partId = $msg->{htmlBody}[0]{partId}; + $self->assert_str_equals($htmlBody, $msg->{bodyValues}{$partId}{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_body_html b/cassandane/tiny-tests/JMAPEmail/email_get_body_html index 5d7001f2ff..8732238f89 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_body_html +++ b/cassandane/tiny-tests/JMAPEmail/email_get_body_html @@ -2,35 +2,38 @@ use Cassandane::Tiny; sub test_email_get_body_html - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); - my $body = "

A HTML email.

"; - $exp_sub{A} = $self->make_message("foo", - mime_type => "text/html", - body => $body - ); + my $body = "

A HTML email.

"; + $exp_sub{A} = $self->make_message( + "foo", + mime_type => "text/html", + body => $body + ); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids, fetchAllBodyValues => JSON::true }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get email"; + $res + = $jmap->CallMethods( + [ [ 'Email/get', { ids => $ids, fetchAllBodyValues => JSON::true }, "R1" ] ] + ); + my $msg = $res->[0][1]{list}[0]; - my $partId = $msg->{htmlBody}[0]{partId}; - $self->assert_str_equals($body, $msg->{bodyValues}{$partId}{value}); + my $partId = $msg->{htmlBody}[0]{partId}; + $self->assert_str_equals($body, $msg->{bodyValues}{$partId}{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_body_notext b/cassandane/tiny-tests/JMAPEmail/email_get_body_notext index 1131400805..ba6cdde6e0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_body_notext +++ b/cassandane/tiny-tests/JMAPEmail/email_get_body_notext @@ -2,29 +2,33 @@ use Cassandane::Tiny; sub test_email_get_body_notext - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; - # Generate an email to have some blob ids - xlog $self, "Generate an email in $inbox via IMAP"; - $self->make_message("foo", - mime_type => "application/zip", - body => "boguszip", - ); + # Generate an email to have some blob ids + xlog $self, "Generate an email in $inbox via IMAP"; + $self->make_message( + "foo", + mime_type => "application/zip", + body => "boguszip", + ); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2'], - ]); - my $msg = $res->[1][1]->{list}[0]; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + my $msg = $res->[1][1]->{list}[0]; - $self->assert_deep_equals([], $msg->{textBody}); - $self->assert_deep_equals([], $msg->{htmlBody}); + $self->assert_deep_equals([], $msg->{textBody}); + $self->assert_deep_equals([], $msg->{htmlBody}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_body_plain b/cassandane/tiny-tests/JMAPEmail/email_get_body_plain index f1cc048e81..89d89af1dd 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_body_plain +++ b/cassandane/tiny-tests/JMAPEmail/email_get_body_plain @@ -2,35 +2,37 @@ use Cassandane::Tiny; sub test_email_get_body_plain - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); - my $body = "A plain text email."; - $exp_sub{A} = $self->make_message("foo", - body => $body - ); + my $body = "A plain text email."; + $exp_sub{A} = $self->make_message("foo", body => $body); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get emails"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids, fetchAllBodyValues => JSON::true, }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get emails"; + $res + = $jmap->CallMethods( + [ [ + 'Email/get', { ids => $ids, fetchAllBodyValues => JSON::true, }, "R1" ] ] + ); + my $msg = $res->[0][1]{list}[0]; - my $partId = $msg->{textBody}[0]{partId}; - $self->assert_str_equals($body, $msg->{bodyValues}{$partId}{value}); - $self->assert_str_equals($msg->{textBody}[0]{partId}, $msg->{htmlBody}[0]{partId}); + my $partId = $msg->{textBody}[0]{partId}; + $self->assert_str_equals($body, $msg->{bodyValues}{$partId}{value}); + $self->assert_str_equals($msg->{textBody}[0]{partId}, + $msg->{htmlBody}[0]{partId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_bodystructure b/cassandane/tiny-tests/JMAPEmail/email_get_bodystructure index 64dc0568d8..407a8e7737 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_bodystructure +++ b/cassandane/tiny-tests/JMAPEmail/email_get_bodystructure @@ -2,221 +2,196 @@ use Cassandane::Tiny; sub test_email_get_bodystructure - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "boundary_1", - body => "" - # body A - . "\r\n--boundary_1\r\n" - . "X-Body-Id:A\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Disposition: inline\r\n" - . "\r\n" - . "A" - # multipart/mixed - . "\r\n--boundary_1\r\n" - . "Content-Type: multipart/mixed; boundary=\"boundary_1_1\"\r\n" - # multipart/alternative - . "\r\n--boundary_1_1\r\n" - . "Content-Type: multipart/alternative; boundary=\"boundary_1_1_1\"\r\n" - # multipart/mixed - . "\r\n--boundary_1_1_1\r\n" - . "Content-Type: multipart/mixed; boundary=\"boundary_1_1_1_1\"\r\n" - # body B - . "\r\n--boundary_1_1_1_1\r\n" - . "X-Body-Id:B\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Disposition: inline\r\n" - . "\r\n" - . "B" - # body C - . "\r\n--boundary_1_1_1_1\r\n" - . "X-Body-Id:C\r\n" - . "Content-Type: image/jpeg\r\n" - . "Content-Disposition: inline\r\n" - . "\r\n" - . "C" - # body D - . "\r\n--boundary_1_1_1_1\r\n" - . "X-Body-Id:D\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Disposition: inline\r\n" - . "\r\n" - . "D" - # end multipart/mixed - . "\r\n--boundary_1_1_1_1--\r\n" - # multipart/mixed - . "\r\n--boundary_1_1_1\r\n" - . "Content-Type: multipart/related; boundary=\"boundary_1_1_1_2\"\r\n" - # body E - . "\r\n--boundary_1_1_1_2\r\n" - . "X-Body-Id:E\r\n" - . "Content-Type: text/html\r\n" - . "\r\n" - . "E" - # body F - . "\r\n--boundary_1_1_1_2\r\n" - . "X-Body-Id:F\r\n" - . "Content-Type: image/jpeg\r\n" - . "\r\n" - . "F" - # end multipart/mixed - . "\r\n--boundary_1_1_1_2--\r\n" - # end multipart/alternative - . "\r\n--boundary_1_1_1--\r\n" - # body G - . "\r\n--boundary_1_1\r\n" - . "X-Body-Id:G\r\n" - . "Content-Type: image/jpeg\r\n" - . "Content-Disposition: attachment\r\n" - . "\r\n" - . "G" - # body H - . "\r\n--boundary_1_1\r\n" - . "X-Body-Id:H\r\n" - . "Content-Type: application/x-excel\r\n" - . "\r\n" - . "H" - # body J - . "\r\n--boundary_1_1\r\n" - . "Content-Type: message/rfc822\r\n" - . "X-Body-Id:J\r\n" - . "\r\n" - . "From: foo\@local\r\n" - . "Date: Thu, 10 May 2018 15:15:38 +0200\r\n" - . "\r\n" - . "J" - . "\r\n--boundary_1_1--\r\n" - # body K - . "\r\n--boundary_1\r\n" - . "X-Body-Id:K\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Disposition: inline\r\n" - . "\r\n" - . "K" - . "\r\n--boundary_1--\r\n" - ) || die; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "boundary_1", + body => "" + # body A + . "\r\n--boundary_1\r\n" + . "X-Body-Id:A\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" . "A" + # multipart/mixed + . "\r\n--boundary_1\r\n" + . "Content-Type: multipart/mixed; boundary=\"boundary_1_1\"\r\n" + # multipart/alternative + . "\r\n--boundary_1_1\r\n" + . "Content-Type: multipart/alternative; boundary=\"boundary_1_1_1\"\r\n" + # multipart/mixed + . "\r\n--boundary_1_1_1\r\n" + . "Content-Type: multipart/mixed; boundary=\"boundary_1_1_1_1\"\r\n" + # body B + . "\r\n--boundary_1_1_1_1\r\n" + . "X-Body-Id:B\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" . "B" + # body C + . "\r\n--boundary_1_1_1_1\r\n" + . "X-Body-Id:C\r\n" + . "Content-Type: image/jpeg\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" . "C" + # body D + . "\r\n--boundary_1_1_1_1\r\n" + . "X-Body-Id:D\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" . "D" + # end multipart/mixed + . "\r\n--boundary_1_1_1_1--\r\n" + # multipart/mixed + . "\r\n--boundary_1_1_1\r\n" + . "Content-Type: multipart/related; boundary=\"boundary_1_1_1_2\"\r\n" + # body E + . "\r\n--boundary_1_1_1_2\r\n" + . "X-Body-Id:E\r\n" + . "Content-Type: text/html\r\n" . "\r\n" . "E" + # body F + . "\r\n--boundary_1_1_1_2\r\n" + . "X-Body-Id:F\r\n" + . "Content-Type: image/jpeg\r\n" . "\r\n" . "F" + # end multipart/mixed + . "\r\n--boundary_1_1_1_2--\r\n" + # end multipart/alternative + . "\r\n--boundary_1_1_1--\r\n" + # body G + . "\r\n--boundary_1_1\r\n" + . "X-Body-Id:G\r\n" + . "Content-Type: image/jpeg\r\n" + . "Content-Disposition: attachment\r\n" . "\r\n" . "G" + # body H + . "\r\n--boundary_1_1\r\n" + . "X-Body-Id:H\r\n" + . "Content-Type: application/x-excel\r\n" . "\r\n" . "H" + # body J + . "\r\n--boundary_1_1\r\n" + . "Content-Type: message/rfc822\r\n" + . "X-Body-Id:J\r\n" . "\r\n" + . "From: foo\@local\r\n" + . "Date: Thu, 10 May 2018 15:15:38 +0200\r\n" . "\r\n" . "J" + . "\r\n--boundary_1_1--\r\n" + # body K + . "\r\n--boundary_1\r\n" + . "X-Body-Id:K\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" . "K" + . "\r\n--boundary_1--\r\n" + ) || die; - my $bodyA = { - 'header:x-body-id' => 'A', - type => 'text/plain', - disposition => 'inline', - }; - my $bodyB = { - 'header:x-body-id' => 'B', - type => 'text/plain', - disposition => 'inline', - }; - my $bodyC = { - 'header:x-body-id' => 'C', - type => 'image/jpeg', - disposition => 'inline', - }; - my $bodyD = { - 'header:x-body-id' => 'D', - type => 'text/plain', - disposition => 'inline', - }; - my $bodyE = { - 'header:x-body-id' => 'E', - type => 'text/html', - disposition => undef, - }; - my $bodyF = { - 'header:x-body-id' => 'F', - type => 'image/jpeg', - disposition => undef, - }; - my $bodyG = { - 'header:x-body-id' => 'G', - type => 'image/jpeg', - disposition => 'attachment', - }; - my $bodyH = { - 'header:x-body-id' => 'H', - type => 'application/x-excel', - disposition => undef, - }; - my $bodyJ = { - 'header:x-body-id' => 'J', - type => 'message/rfc822', - disposition => undef, - }; - my $bodyK = { - 'header:x-body-id' => 'K', - type => 'text/plain', - disposition => 'inline', - }; + my $bodyA = { + 'header:x-body-id' => 'A', + type => 'text/plain', + disposition => 'inline', + }; + my $bodyB = { + 'header:x-body-id' => 'B', + type => 'text/plain', + disposition => 'inline', + }; + my $bodyC = { + 'header:x-body-id' => 'C', + type => 'image/jpeg', + disposition => 'inline', + }; + my $bodyD = { + 'header:x-body-id' => 'D', + type => 'text/plain', + disposition => 'inline', + }; + my $bodyE = { + 'header:x-body-id' => 'E', + type => 'text/html', + disposition => undef, + }; + my $bodyF = { + 'header:x-body-id' => 'F', + type => 'image/jpeg', + disposition => undef, + }; + my $bodyG = { + 'header:x-body-id' => 'G', + type => 'image/jpeg', + disposition => 'attachment', + }; + my $bodyH = { + 'header:x-body-id' => 'H', + type => 'application/x-excel', + disposition => undef, + }; + my $bodyJ = { + 'header:x-body-id' => 'J', + type => 'message/rfc822', + disposition => undef, + }; + my $bodyK = { + 'header:x-body-id' => 'K', + type => 'text/plain', + disposition => 'inline', + }; - my $wantBodyStructure = { + my $wantBodyStructure = { + 'header:x-body-id' => undef, + type => 'multipart/mixed', + disposition => undef, + subParts => [ + $bodyA, + { 'header:x-body-id' => undef, - type => 'multipart/mixed', - disposition => undef, - subParts => [ - $bodyA, - { + type => 'multipart/mixed', + disposition => undef, + subParts => [ + { + 'header:x-body-id' => undef, + type => 'multipart/alternative', + disposition => undef, + subParts => [ + { 'header:x-body-id' => undef, - type => 'multipart/mixed', - disposition => undef, - subParts => [ - { - 'header:x-body-id' => undef, - type => 'multipart/alternative', - disposition => undef, - subParts => [ - { - 'header:x-body-id' => undef, - type => 'multipart/mixed', - disposition => undef, - subParts => [ - $bodyB, - $bodyC, - $bodyD, - ], - }, - { - 'header:x-body-id' => undef, - type => 'multipart/related', - disposition => undef, - subParts => [ - $bodyE, - $bodyF, - ], - }, - ], - }, - $bodyG, - $bodyH, - $bodyJ, - ], - }, - $bodyK, + type => 'multipart/mixed', + disposition => undef, + subParts => [ $bodyB, $bodyC, $bodyD, ], + }, + { + 'header:x-body-id' => undef, + type => 'multipart/related', + disposition => undef, + subParts => [ $bodyE, $bodyF, ], + }, + ], + }, + $bodyG, $bodyH, $bodyJ, ], - }; + }, + $bodyK, + ], + }; - my $wantTextBody = [ $bodyA, $bodyB, $bodyC, $bodyD, $bodyK ]; - my $wantHtmlBody = [ $bodyA, $bodyE, $bodyK ]; - my $wantAttachments = [ $bodyC, $bodyF, $bodyG, $bodyH, $bodyJ ]; + my $wantTextBody = [ $bodyA, $bodyB, $bodyC, $bodyD, $bodyK ]; + my $wantHtmlBody = [ $bodyA, $bodyE, $bodyK ]; + my $wantAttachments = [ $bodyC, $bodyF, $bodyG, $bodyH, $bodyJ ]; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['bodyStructure', 'textBody', 'htmlBody', 'attachments' ], - bodyProperties => ['type', 'disposition', 'header:x-body-id'], - }, 'R2' ], - ]); - my $msg = $res->[1][1]{list}[0]; - $self->assert_deep_equals($wantBodyStructure, $msg->{bodyStructure}); - $self->assert_deep_equals($wantTextBody, $msg->{textBody}); - $self->assert_deep_equals($wantHtmlBody, $msg->{htmlBody}); - $self->assert_deep_equals($wantAttachments, $msg->{attachments}); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => + [ 'bodyStructure', 'textBody', 'htmlBody', 'attachments' ], + bodyProperties => [ 'type', 'disposition', 'header:x-body-id' ], + }, + 'R2' + ], + ]); + my $msg = $res->[1][1]{list}[0]; + $self->assert_deep_equals($wantBodyStructure, $msg->{bodyStructure}); + $self->assert_deep_equals($wantTextBody, $msg->{textBody}); + $self->assert_deep_equals($wantHtmlBody, $msg->{htmlBody}); + $self->assert_deep_equals($wantAttachments, $msg->{attachments}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_crlf b/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_crlf index 9711b2e3c5..08c015d205 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_crlf +++ b/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_crlf @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_email_get_bodyvalues_crlf - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $mimeMsg = <<'EOF'; + my $mimeMsg = <<'EOF'; From: To: to@local Bcc: bcc@local @@ -22,23 +21,27 @@ one two three EOF - $mimeMsg =~ s/\r?\n/\r\n/gs; - $mimeMsg =~ s/\r\n$//; - $imap->append('INBOX', $mimeMsg) || die $@; + $mimeMsg =~ s/\r?\n/\r\n/gs; + $mimeMsg =~ s/\r\n$//; + $imap->append('INBOX', $mimeMsg) || die $@; - $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['bodyValues'], - fetchAllBodyValues => JSON::true, - }, 'R2'], - ]); + $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['bodyValues'], + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); - $self->assert_str_equals("one\ntwo\nthree", - $res->[1][1]{list}[0]{bodyValues}{1}{value}); + $self->assert_str_equals("one\ntwo\nthree", + $res->[1][1]{list}[0]{bodyValues}{1}{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_markdown b/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_markdown index d123c7bf78..1880e92960 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_markdown +++ b/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_markdown @@ -2,56 +2,60 @@ use Cassandane::Tiny; sub test_email_get_bodyvalues_markdown - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog "Upload email blob"; - my $rawEmail = "" + xlog "Upload email blob"; + my $rawEmail + = "" . "From: foo\@local\r\n" . "To: bar\@local\r\n" . "Subject: test\r\n" . "Date: Tue, 24 Mar 2020 11:21:50 -0500\r\n" . "Content-Type: text/x-markdown\r\n" - . "MIME-Version: 1.0\r\n" - . "\r\n" + . "MIME-Version: 1.0\r\n" . "\r\n" . "This is a test"; - my $data = $jmap->Upload($rawEmail, "application/octet-stream"); - my $blobId = $data->{blobId}; + my $data = $jmap->Upload($rawEmail, "application/octet-stream"); + my $blobId = $data->{blobId}; - xlog "Import and get email"; - my $res = $jmap->CallMethods([ - ['Email/import', { - emails => { - 1 => { - mailboxIds => { - '$inbox' => JSON::true, - }, - blobId => $blobId, - }, + xlog "Import and get email"; + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { + emails => { + 1 => { + mailboxIds => { + '$inbox' => JSON::true, }, - }, 'R1'], - ['Email/get', { - ids => ['#1'], - properties => ['bodyStructure', 'bodyValues'], - bodyProperties => [ - 'partId', - 'type', - ], - fetchAllBodyValues => JSON::true, - }, '$2'], - ]); + blobId => $blobId, + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#1'], + properties => [ 'bodyStructure', 'bodyValues' ], + bodyProperties => [ 'partId', 'type', ], + fetchAllBodyValues => JSON::true, + }, + '$2' + ], + ]); - $self->assert_str_equals('text/x-markdown', - $res->[1][1]{list}[0]{bodyStructure}{type}); - my $partId = $res->[1][1]{list}[0]{bodyStructure}{partId}; - $self->assert_not_null($partId); - $self->assert_str_equals('This is a test', - $res->[1][1]{list}[0]{bodyValues}{$partId}{value}); - $self->assert_equals(JSON::false, - $res->[1][1]{list}[0]{bodyValues}{$partId}{isEncodingProblem}); + $self->assert_str_equals('text/x-markdown', + $res->[1][1]{list}[0]{bodyStructure}{type}); + my $partId = $res->[1][1]{list}[0]{bodyStructure}{partId}; + $self->assert_not_null($partId); + $self->assert_str_equals('This is a test', + $res->[1][1]{list}[0]{bodyValues}{$partId}{value}); + $self->assert_equals(JSON::false, + $res->[1][1]{list}[0]{bodyValues}{$partId}{isEncodingProblem}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_nulbyte b/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_nulbyte index 49e7387850..dce3a05bf9 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_nulbyte +++ b/cassandane/tiny-tests/JMAPEmail/email_get_bodyvalues_nulbyte @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_email_get_bodyvalues_nulbyte - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $mimeMsg = <<'EOF'; + my $mimeMsg = <<'EOF'; From: To: to@local Bcc: bcc@local @@ -20,26 +19,30 @@ Content-Type: text/html;charset=utf-8 hello=00 world EOF - $mimeMsg =~ s/\r?\n/\r\n/gs; - $mimeMsg =~ s/\r\n$//; - $imap->append('INBOX', $mimeMsg) || die $@; + $mimeMsg =~ s/\r?\n/\r\n/gs; + $mimeMsg =~ s/\r\n$//; + $imap->append('INBOX', $mimeMsg) || die $@; - $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['bodyValues'], - fetchAllBodyValues => JSON::true, - }, 'R2'], - ]); + $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['bodyValues'], + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); - my $bodyValue = $res->[1][1]{list}[0]{bodyValues}{1}; - $self->assert_str_equals("hello world", - $bodyValue->{value}); - $self->assert_equals(JSON::true, $bodyValue->{isEncodingProblem}); - $self->assert_equals(JSON::false, $bodyValue->{isTruncated}); + my $bodyValue = $res->[1][1]{list}[0]{bodyValues}{1}; + $self->assert_str_equals("hello world", + $bodyValue->{value}); + $self->assert_equals(JSON::true, $bodyValue->{isEncodingProblem}); + $self->assert_equals(JSON::false, $bodyValue->{isTruncated}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_bogus_encoding b/cassandane/tiny-tests/JMAPEmail/email_get_bogus_encoding index aba540735b..8b10b425dc 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_bogus_encoding +++ b/cassandane/tiny-tests/JMAPEmail/email_get_bogus_encoding @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_get_bogus_encoding - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -18,31 +17,42 @@ Content-Transfer-Encoding: foobar This is a test email. EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; - my $inboxid = $self->getinbox()->{id}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; + my $inboxid = $self->getinbox()->{id}; - xlog $self, "import and get email from blob $blobid"; - my $res = $jmap->CallMethods([['Email/import', { + xlog $self, "import and get email from blob $blobid"; + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$inboxid => JSON::true}, - }, + "1" => { + blobId => $blobid, + mailboxIds => { $inboxid => JSON::true }, + }, }, - }, "R1"], ["Email/get", { - ids => ["#1"], - properties => ['bodyStructure', 'bodyValues'], + }, + "R1" + ], + [ + "Email/get", + { + ids => ["#1"], + properties => [ 'bodyStructure', 'bodyValues' ], fetchAllBodyValues => JSON::true, - }, "R2" ]]); + }, + "R2" + ] + ]); - $self->assert_str_equals("Email/import", $res->[0][0]); - $self->assert_str_equals("Email/get", $res->[1][0]); + $self->assert_str_equals("Email/import", $res->[0][0]); + $self->assert_str_equals("Email/get", $res->[1][0]); - my $msg = $res->[1][1]{list}[0]; - my $partId = $msg->{bodyStructure}{partId}; - my $bodyValue = $msg->{bodyValues}{$partId}; - $self->assert_str_equals("", $bodyValue->{value}); - $self->assert_equals(JSON::true, $bodyValue->{isEncodingProblem}); + my $msg = $res->[1][1]{list}[0]; + my $partId = $msg->{bodyStructure}{partId}; + my $bodyValue = $msg->{bodyValues}{$partId}; + $self->assert_str_equals("", $bodyValue->{value}); + $self->assert_equals(JSON::true, $bodyValue->{isEncodingProblem}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_brokenheader_split_codepoint b/cassandane/tiny-tests/JMAPEmail/email_get_brokenheader_split_codepoint index 2534028233..18856b15f3 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_brokenheader_split_codepoint +++ b/cassandane/tiny-tests/JMAPEmail/email_get_brokenheader_split_codepoint @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_get_brokenheader_split_codepoint - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: =?UTF-8?Q?=F0=9F=98=80=F0=9F=98=83=F0=9F=98=84=F0=9F=98=81=F0=9F=98=86=F0?= @@ -20,26 +19,37 @@ Content-Transfer-Encoding: foobar This is a test email. EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; - my $inboxid = $self->getinbox()->{id}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; + my $inboxid = $self->getinbox()->{id}; - my $wantSubject = '😀😃😄😁😆😅😂🤣☺️😊😇'; - utf8::decode($wantSubject); + my $wantSubject = '😀😃😄😁😆😅😂🤣☺️😊😇'; + utf8::decode($wantSubject); - xlog $self, "import and get email from blob $blobid"; - my $res = $jmap->CallMethods([['Email/import', { + xlog $self, "import and get email from blob $blobid"; + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$inboxid => JSON::true}, - }, + "1" => { + blobId => $blobid, + mailboxIds => { $inboxid => JSON::true }, + }, }, - }, "R1"], ["Email/get", { - ids => ["#1"], + }, + "R1" + ], + [ + "Email/get", + { + ids => ["#1"], properties => ['subject'], - }, "R2" ]]); + }, + "R2" + ] + ]); - $self->assert_str_equals($wantSubject, $res->[1][1]{list}[0]{subject}); + $self->assert_str_equals($wantSubject, $res->[1][1]{list}[0]{subject}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents index c112086126..72026f2cda 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents @@ -2,88 +2,91 @@ use Cassandane::Tiny; sub test_email_get_calendarevents - :min_version_3_7 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # calendarEvents property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # calendarEvents property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - $self->make_message("foo", - mime_type => "multipart/related", - mime_boundary => "boundary_1", - body => "" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "txt body" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/calendar;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" - . "BEGIN:VCALENDAR\r\n" - . "VERSION:2.0\r\n" - . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" - . "CALSCALE:GREGORIAN\r\n" - . "BEGIN:VTIMEZONE\r\n" - . "TZID:Europe/Vienna\r\n" - . "BEGIN:STANDARD\r\n" - . "DTSTART:19700101T000000\r\n" - . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" - . "TZOFFSETFROM:+0200\r\n" - . "TZOFFSETTO:+0100\r\n" - . "END:STANDARD\r\n" - . "BEGIN:DAYLIGHT\r\n" - . "DTSTART:19700101T000000\r\n" - . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\r\n" - . "TZOFFSETFROM:+0100\r\n" - . "TZOFFSETTO:+0200\r\n" - . "END:DAYLIGHT\r\n" - . "END:VTIMEZONE\r\n" - . "BEGIN:VEVENT\r\n" - . "CREATED:20180518T090306Z\r\n" - . "DTEND;TZID=Europe/Vienna:20180518T100000\r\n" - . "DTSTAMP:20180518T090306Z\r\n" - . "DTSTART;TZID=Europe/Vienna:20180518T090000\r\n" - . "LAST-MODIFIED:20180518T090306Z\r\n" - . "SEQUENCE:1\r\n" - . "SUMMARY:K=C3=A4se\r\n" - . "TRANSP:OPAQUE\r\n" - . "UID:d9e7f7d6-ce1a-4a71-94c0-b4edd41e5959\r\n" - . "END:VEVENT\r\n" - . "END:VCALENDAR\r\n" - . "\r\n--boundary_1--\r\n" - ) || die; + $self->make_message( + "foo", + mime_type => "multipart/related", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "txt body" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/calendar;charset=utf-8\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" + . "BEGIN:VCALENDAR\r\n" + . "VERSION:2.0\r\n" + . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" + . "CALSCALE:GREGORIAN\r\n" + . "BEGIN:VTIMEZONE\r\n" + . "TZID:Europe/Vienna\r\n" + . "BEGIN:STANDARD\r\n" + . "DTSTART:19700101T000000\r\n" + . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" + . "TZOFFSETFROM:+0200\r\n" + . "TZOFFSETTO:+0100\r\n" + . "END:STANDARD\r\n" + . "BEGIN:DAYLIGHT\r\n" + . "DTSTART:19700101T000000\r\n" + . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\r\n" + . "TZOFFSETFROM:+0100\r\n" + . "TZOFFSETTO:+0200\r\n" + . "END:DAYLIGHT\r\n" + . "END:VTIMEZONE\r\n" + . "BEGIN:VEVENT\r\n" + . "CREATED:20180518T090306Z\r\n" + . "DTEND;TZID=Europe/Vienna:20180518T100000\r\n" + . "DTSTAMP:20180518T090306Z\r\n" + . "DTSTART;TZID=Europe/Vienna:20180518T090000\r\n" + . "LAST-MODIFIED:20180518T090306Z\r\n" + . "SEQUENCE:1\r\n" + . "SUMMARY:K=C3=A4se\r\n" + . "TRANSP:OPAQUE\r\n" + . "UID:d9e7f7d6-ce1a-4a71-94c0-b4edd41e5959\r\n" + . "END:VEVENT\r\n" + . "END:VCALENDAR\r\n" + . "\r\n--boundary_1--\r\n" + ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['textBody', 'attachments', 'calendarEvents'], - }, 'R2' ], - ]); - my $msg = $res->[1][1]{list}[0]; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ 'textBody', 'attachments', 'calendarEvents' ], + }, + 'R2' + ], + ]); + my $msg = $res->[1][1]{list}[0]; - $self->assert_num_equals(1, scalar @{$msg->{attachments}}); - $self->assert_str_equals('text/calendar', $msg->{attachments}[0]{type}); + $self->assert_num_equals(1, scalar @{ $msg->{attachments} }); + $self->assert_str_equals('text/calendar', $msg->{attachments}[0]{type}); - $self->assert_num_equals(1, scalar keys %{$msg->{calendarEvents}}); - my $partId = $msg->{attachments}[0]{partId}; + $self->assert_num_equals(1, scalar keys %{ $msg->{calendarEvents} }); + my $partId = $msg->{attachments}[0]{partId}; - my @jsevents = @{$msg->{calendarEvents}{$partId}}; - $self->assert_num_equals(1, scalar @jsevents); - my $jsevent = $jsevents[0]; + my @jsevents = @{ $msg->{calendarEvents}{$partId} }; + $self->assert_num_equals(1, scalar @jsevents); + my $jsevent = $jsevents[0]; - $self->assert_str_equals("K\N{LATIN SMALL LETTER A WITH DIAERESIS}se", $jsevent->{title}); - $self->assert_str_equals('2018-05-18T09:00:00', $jsevent->{start}); - $self->assert_str_equals('Europe/Vienna', $jsevent->{timeZone}); - $self->assert_str_equals('PT1H', $jsevent->{duration}); + $self->assert_str_equals("K\N{LATIN SMALL LETTER A WITH DIAERESIS}se", + $jsevent->{title}); + $self->assert_str_equals('2018-05-18T09:00:00', $jsevent->{start}); + $self->assert_str_equals('Europe/Vienna', $jsevent->{timeZone}); + $self->assert_str_equals('PT1H', $jsevent->{duration}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_allow_max_uids_only b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_allow_max_uids_only index dba5382ca9..a84c865f0b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_allow_max_uids_only +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_allow_max_uids_only @@ -3,32 +3,31 @@ use Cassandane::Tiny; use Data::UUID; -sub do -{ - my ($self, $nevents, $exceedsThreshold) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $instance = $self->{instance}; - - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; - - # Generate calendar attachment - - my $vevents; - my $now = DateTime->now(); - $now->set_time_zone('Etc/UTC'); - for (my $i = 1; $i <= $nevents; $i++) { - my $uuid = Data::UUID->new; - my $uid = $uuid->create_str; - my $start = $now->strftime('%Y-%m-%dT%H:%M:%SZ'); - $vevents .= <<"EOF"; +sub do { + my ($self, $nevents, $exceedsThreshold) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $instance = $self->{instance}; + + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; + + # Generate calendar attachment + + my $vevents; + my $now = DateTime->now(); + $now->set_time_zone('Etc/UTC'); + for (my $i = 1; $i <= $nevents; $i++) { + my $uuid = Data::UUID->new; + my $uid = $uuid->create_str; + my $start = $now->strftime('%Y-%m-%dT%H:%M:%SZ'); + $vevents .= <<"EOF"; BEGIN:VEVENT DTSTART:$start DURATION:PT1H @@ -36,14 +35,14 @@ UID:$uid SUMMARY:test$i END:VEVENT EOF - $now->add(DateTime::Duration->new(seconds => 300)); - } - $vevents =~ s/\s+$//; + $now->add(DateTime::Duration->new(seconds => 300)); + } + $vevents =~ s/\s+$//; - # Generate MIME message + # Generate MIME message - my $subject = "nevents$nevents"; - my $mimeMessage = <<"EOF"; + my $subject = "nevents$nevents"; + my $mimeMessage = <<"EOF"; From: from\@local To: to\@local Subject: $subject @@ -70,46 +69,56 @@ END:VCALENDAR --c4683f7a320d4d20902b000486fbdf9b-- EOF - $mimeMessage =~ s/\r?\n/\r\n/gs; - - xlog $self, "Generate MIME message"; - $imap->append('INBOX', $mimeMessage) || die $@; - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "Query email"; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - subject => $subject, - }, - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - properties => ['calendarEvents'], - }, 'R2'], - ], $using); - - if ($exceedsThreshold) { - $self->assert_null($res->[1][1]{list}[0]{calendarEvents}); - } else { - $self->assert_num_equals($nevents, - scalar @{$res->[1][1]{list}[0]{calendarEvents}{2}}); - } + $mimeMessage =~ s/\r?\n/\r\n/gs; + + xlog $self, "Generate MIME message"; + $imap->append('INBOX', $mimeMessage) || die $@; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "Query email"; + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + subject => $subject, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + properties => ['calendarEvents'], + }, + 'R2' + ], + ], + $using + ); + + if ($exceedsThreshold) { + $self->assert_null($res->[1][1]{list}[0]{calendarEvents}); + } else { + $self->assert_num_equals($nevents, + scalar @{ $res->[1][1]{list}[0]{calendarEvents}{2} }); + } } sub test_email_get_calendarevents_allow_max_uids_only - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - xlog $self, "Assert that 8 unique UIDs are allowed"; - $self->do(8, 0); + xlog $self, "Assert that 8 unique UIDs are allowed"; + $self->do(8, 0); - xlog $self, "Assert that 9 unique UIDs are rejected"; - $self->do(9, 1); + xlog $self, "Assert that 9 unique UIDs are rejected"; + $self->do(9, 1); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_deduplicate b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_deduplicate index ea41aac415..bcd4a5f0c7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_deduplicate +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_deduplicate @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_email_get_calendarevents_deduplicate - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $ical = <<'EOF'; + xlog "Create event via CalDAV"; + my $ical = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -25,53 +24,57 @@ SEQUENCE:0 END:VEVENT END:VCALENDAR EOF - $ical =~ s/\r?\n/\r\n/gs; - my $b64Ical = encode_base64($ical); - $b64Ical =~ s/\r?\n/\r\n/gs; + $ical =~ s/\r?\n/\r\n/gs; + my $b64Ical = encode_base64($ical); + $b64Ical =~ s/\r?\n/\r\n/gs; - $self->make_message('test', - mime_type => 'multipart/related', - mime_boundary => 'boundary', - body => "" - . "\r\n--boundary\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "test" - . "\r\n--boundary\r\n" - . "Content-Type: text/calendar;charset=\"utf-8\"; method=REPLY\r\n" - . "Content-Transfer-Encoding: 7bit\r\n" - . "\r\n" - . $ical - . "\r\n--boundary\r\n" - . "Content-Type: application/ics; name=\"test.ics\"\r\n" - . "Content-Disposition: attachment; filename=\"test.ics\"\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . $b64Ical - . "\r\n--boundary--\r\n" - ) || die; + $self->make_message( + 'test', + mime_type => 'multipart/related', + mime_boundary => 'boundary', + body => "" + . "\r\n--boundary\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "test" + . "\r\n--boundary\r\n" + . "Content-Type: text/calendar;charset=\"utf-8\"; method=REPLY\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" . "\r\n" + . $ical + . "\r\n--boundary\r\n" + . "Content-Type: application/ics; name=\"test.ics\"\r\n" + . "Content-Disposition: attachment; filename=\"test.ics\"\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . $b64Ical + . "\r\n--boundary--\r\n" + ) || die; - xlog "Fetch email via JMAP"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['calendarEvents'], - }, 'R2' ], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + xlog "Fetch email via JMAP"; + my $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['calendarEvents'], + }, + 'R2' + ], + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/calendars', + ] + ); - my @eventMimeParts = values %{$res->[1][1]{list}[0]{calendarEvents}}; - $self->assert_num_equals(1, scalar @eventMimeParts); - $self->assert_num_equals(1, scalar @{$eventMimeParts[0]}); + my @eventMimeParts = values %{ $res->[1][1]{list}[0]{calendarEvents} }; + $self->assert_num_equals(1, scalar @eventMimeParts); + $self->assert_num_equals(1, scalar @{ $eventMimeParts[0] }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_extended_filename b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_extended_filename index 26243e5288..b24db84104 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_extended_filename +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_extended_filename @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_get_calendarevents_extended_filename - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $body = <<'EOF'; + my $body = <<'EOF'; --boundary_1 Content-Type: text/plain @@ -32,35 +31,44 @@ END:VEVENT END:VCALENDAR --boundary_1-- EOF - $body =~ s/\r?\n/\r\n/gs; + $body =~ s/\r?\n/\r\n/gs; - $self->make_message('test', mime_type => 'multipart/related', - mime_boundary => 'boundary_1', body => $body) or die; + $self->make_message( + 'test', + mime_type => 'multipart/related', + mime_boundary => 'boundary_1', + body => $body + ) or die; - xlog $self, 'get email'; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ - 'calendarEvents', 'bodyStructure', - ], - }, 'R2'], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + xlog $self, 'get email'; + my $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => [ 'calendarEvents', 'bodyStructure', ], + }, + 'R2' + ], + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/calendars', + ] + ); - $self->assert_num_equals(1, - scalar @{$res->[1][1]{list}[0]{calendarEvents}{2}}); - $self->assert_str_equals('test', - $res->[1][1]{list}[0]{calendarEvents}{2}[0]{title}); + $self->assert_num_equals(1, + scalar @{ $res->[1][1]{list}[0]{calendarEvents}{2} }); + $self->assert_str_equals('test', + $res->[1][1]{list}[0]{calendarEvents}{2}[0]{title}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_icsfile b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_icsfile index f02c7f6b5c..bdffda6438 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_icsfile +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_icsfile @@ -2,81 +2,85 @@ use Cassandane::Tiny; sub test_email_get_calendarevents_icsfile - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # calendarEvents property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # calendarEvents property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $rawEvent = "" - . "BEGIN:VCALENDAR\r\n" - . "VERSION:2.0\r\n" - . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" - . "CALSCALE:GREGORIAN\r\n" - . "BEGIN:VTIMEZONE\r\n" - . "TZID:Europe/Vienna\r\n" - . "BEGIN:STANDARD\r\n" - . "DTSTART:19700101T000000\r\n" - . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" - . "TZOFFSETFROM:+0200\r\n" - . "TZOFFSETTO:+0100\r\n" - . "END:STANDARD\r\n" - . "BEGIN:DAYLIGHT\r\n" - . "DTSTART:19700101T000000\r\n" - . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\r\n" - . "TZOFFSETFROM:+0100\r\n" - . "TZOFFSETTO:+0200\r\n" - . "END:DAYLIGHT\r\n" - . "END:VTIMEZONE\r\n" - . "BEGIN:VEVENT\r\n" - . "CREATED:20180518T090306Z\r\n" - . "DTEND;TZID=Europe/Vienna:20180518T100000\r\n" - . "DTSTAMP:20180518T090306Z\r\n" - . "DTSTART;TZID=Europe/Vienna:20180518T090000\r\n" - . "LAST-MODIFIED:20180518T090306Z\r\n" - . "SEQUENCE:1\r\n" - . "SUMMARY:Hello\r\n" - . "TRANSP:OPAQUE\r\n" - . "UID:d9e7f7d6-ce1a-4a71-94c0-b4edd41e5959\r\n" - . "END:VEVENT\r\n" - . "END:VCALENDAR\r\n"; + my $rawEvent + = "" + . "BEGIN:VCALENDAR\r\n" + . "VERSION:2.0\r\n" + . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" + . "CALSCALE:GREGORIAN\r\n" + . "BEGIN:VTIMEZONE\r\n" + . "TZID:Europe/Vienna\r\n" + . "BEGIN:STANDARD\r\n" + . "DTSTART:19700101T000000\r\n" + . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" + . "TZOFFSETFROM:+0200\r\n" + . "TZOFFSETTO:+0100\r\n" + . "END:STANDARD\r\n" + . "BEGIN:DAYLIGHT\r\n" + . "DTSTART:19700101T000000\r\n" + . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\r\n" + . "TZOFFSETFROM:+0100\r\n" + . "TZOFFSETTO:+0200\r\n" + . "END:DAYLIGHT\r\n" + . "END:VTIMEZONE\r\n" + . "BEGIN:VEVENT\r\n" + . "CREATED:20180518T090306Z\r\n" + . "DTEND;TZID=Europe/Vienna:20180518T100000\r\n" + . "DTSTAMP:20180518T090306Z\r\n" + . "DTSTART;TZID=Europe/Vienna:20180518T090000\r\n" + . "LAST-MODIFIED:20180518T090306Z\r\n" + . "SEQUENCE:1\r\n" + . "SUMMARY:Hello\r\n" + . "TRANSP:OPAQUE\r\n" + . "UID:d9e7f7d6-ce1a-4a71-94c0-b4edd41e5959\r\n" + . "END:VEVENT\r\n" + . "END:VCALENDAR\r\n"; - $self->make_message("foo", - mime_type => "multipart/related", - mime_boundary => "boundary_1", - body => "" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "txt body" - . "\r\n--boundary_1\r\n" - . "Content-Type: application/unknown\r\n" - . "Content-Transfer-Encoding: base64\r\n" - ."Content-Disposition: attachment; filename*0=Add_Appointment_;\r\n filename*1=To_Calendar.ics\r\n" - . "\r\n" - . encode_base64($rawEvent, "\r\n") - . "\r\n--boundary_1--\r\n" - ) || die; + $self->make_message( + "foo", + mime_type => "multipart/related", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "txt body" + . "\r\n--boundary_1\r\n" + . "Content-Type: application/unknown\r\n" + . "Content-Transfer-Encoding: base64\r\n" + . "Content-Disposition: attachment; filename*0=Add_Appointment_;\r\n filename*1=To_Calendar.ics\r\n" + . "\r\n" + . encode_base64($rawEvent, "\r\n") + . "\r\n--boundary_1--\r\n" + ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['textBody', 'attachments', 'calendarEvents'], - }, 'R2' ], - ]); - my $msg = $res->[1][1]{list}[0]; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ 'textBody', 'attachments', 'calendarEvents' ], + }, + 'R2' + ], + ]); + my $msg = $res->[1][1]{list}[0]; - my $partId = $msg->{attachments}[0]{partId}; - my $jsevent = $msg->{calendarEvents}{$partId}[0]; - $self->assert_str_equals("Hello", $jsevent->{title}); + my $partId = $msg->{attachments}[0]{partId}; + my $jsevent = $msg->{calendarEvents}{$partId}[0]; + $self->assert_str_equals("Hello", $jsevent->{title}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_itip_schedprops b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_itip_schedprops index bc8d148581..3490ef2a98 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_itip_schedprops +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_itip_schedprops @@ -2,21 +2,21 @@ use Cassandane::Tiny; sub test_email_get_calendarevents_itip_schedprops - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my ($maj, $min) = Cassandane::Instance->get_version(); + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my ($maj, $min) = Cassandane::Instance->get_version(); - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # calendarEvents property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # calendarEvents property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my @testCases = ({ - ical => <<'EOF', + my @testCases = ( + { + ical => <<'EOF', BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 @@ -41,12 +41,13 @@ TRANSP:OPAQUE END:VEVENT END:VCALENDAR EOF - wantMethod => 'reply', - wantScheduleUpdated => '2021-08-02T03:22:34Z', - wantScheduleSequence => 3, - wantParticipationComment => 'Hello, World!', - }, { - ical => <<'EOF', + wantMethod => 'reply', + wantScheduleUpdated => '2021-08-02T03:22:34Z', + wantScheduleSequence => 3, + wantParticipationComment => 'Hello, World!', + }, + { + ical => <<'EOF', BEGIN:VCALENDAR METHOD:REPLY PRODID:Microsoft Exchange Server 2010 @@ -89,12 +90,13 @@ X-MICROSOFT-DISALLOW-COUNTER:FALSE END:VEVENT END:VCALENDAR EOF - wantMethod => 'reply', - wantScheduleUpdated => '2021-08-02T03:24:46Z', - wantScheduleSequence => 5, - wantParticipationComment => "A comment.\n", - }, { - ical => <<'EOF', + wantMethod => 'reply', + wantScheduleUpdated => '2021-08-02T03:24:46Z', + wantScheduleSequence => 5, + wantParticipationComment => "A comment.\n", + }, + { + ical => <<'EOF', BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 @@ -120,67 +122,78 @@ TRANSP:OPAQUE END:VEVENT END:VCALENDAR EOF - sinceVersion => ['3','7'], - wantMethod => 'counter', - wantScheduleUpdated => '2021-08-02T03:22:34Z', - wantScheduleSequence => 3, - wantParticipationComment => 'A counter comment', - }); + sinceVersion => [ '3', '7' ], + wantMethod => 'counter', + wantScheduleUpdated => '2021-08-02T03:22:34Z', + wantScheduleSequence => 3, + wantParticipationComment => 'A counter comment', + } + ); - foreach my $i (0 .. $#testCases) { - my $tc = $testCases[$i]; + foreach my $i (0 .. $#testCases) { + my $tc = $testCases[$i]; - # skip tests for older Cyrus versions - next if $tc->{sinceVersion} && - ($maj le $tc->{sinceVersion}[0] || - ($maj eq $tc->{sinceVersion}[0] && - $min le $tc->{sinceVersion}[1])); + # skip tests for older Cyrus versions + next + if $tc->{sinceVersion} + && ( + $maj le $tc->{sinceVersion}[0] + || ( $maj eq $tc->{sinceVersion}[0] + && $min le $tc->{sinceVersion}[1]) + ); - $tc->{ical} =~ s/\r?\n/\r\n/gs; - $self->make_message("test$i", - mime_type => 'multipart/related', - mime_boundary => 'boundary', - body => "" - . "\r\n--boundary\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "test" - . "\r\n--boundary\r\n" - . "Content-Type: text/calendar;charset=utf-8\r\n" - . "\r\n" - . $tc->{ical} - . "\r\n--boundary--\r\n" - ) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-i'); - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ - property => 'subject', - isAscending => JSON::false, - }], - limit => 1, - }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['calendarEvents'], - }, 'R2' ], - ]); + $tc->{ical} =~ s/\r?\n/\r\n/gs; + $self->make_message( + "test$i", + mime_type => 'multipart/related', + mime_boundary => 'boundary', + body => "" + . "\r\n--boundary\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "test" + . "\r\n--boundary\r\n" + . "Content-Type: text/calendar;charset=utf-8\r\n" . "\r\n" + . $tc->{ical} + . "\r\n--boundary--\r\n" + ) || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-i'); + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { + property => 'subject', + isAscending => JSON::false, + } ], + limit => 1, + }, + "R1" + ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['calendarEvents'], + }, + 'R2' + ], + ]); - my $event = (values %{$res->[1][1]{list}[0]{calendarEvents}})[0][0]; - $self->assert_not_null($event); - $self->assert_str_equals($tc->{wantMethod}, $event->{method}); + my $event = (values %{ $res->[1][1]{list}[0]{calendarEvents} })[0][0]; + $self->assert_not_null($event); + $self->assert_str_equals($tc->{wantMethod}, $event->{method}); - my @attendees = grep { exists $_->{roles}{attendee} } values %{$event->{participants}}; - $self->assert_num_equals(1, scalar @attendees); - $self->assert_str_equals($tc->{wantScheduleUpdated}, - $attendees[0]->{scheduleUpdated}); - $self->assert_num_equals($tc->{wantScheduleSequence}, - $attendees[0]->{scheduleSequence}); - $self->assert_str_equals($tc->{wantParticipationComment}, - $attendees[0]->{participationComment}); - } + my @attendees = grep { exists $_->{roles}{attendee} } + values %{ $event->{participants} }; + $self->assert_num_equals(1, scalar @attendees); + $self->assert_str_equals($tc->{wantScheduleUpdated}, + $attendees[0]->{scheduleUpdated}); + $self->assert_num_equals($tc->{wantScheduleSequence}, + $attendees[0]->{scheduleSequence}); + $self->assert_str_equals($tc->{wantParticipationComment}, + $attendees[0]->{participationComment}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_links_blobid b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_links_blobid index 2604f4d30d..4261a15167 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_links_blobid +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_links_blobid @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_email_get_calendarevents_links_blobid - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + my $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -28,82 +27,92 @@ SEQUENCE:0 END:VEVENT END:VCALENDAR EOF - $caldav->Request('PUT', 'Default/test.ics', $rawIcal, - 'Content-Type' => 'text/calendar'); - my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; + $caldav->Request('PUT', 'Default/test.ics', $rawIcal, + 'Content-Type' => 'text/calendar'); + my $eventHref = '/dav/calendars/user/cassandane/Default/test.ics'; - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog "Add attachment via CalDAV"; - my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; - my $caldavResponse = $caldav->ua->post($url, { - headers => { - 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment;filename=test', - 'Prefer' => 'return=representation', - 'Authorization' => $caldav->auth_header(), - }, - content => 'someblob', - }); - $self->assert_str_equals('201', $caldavResponse->{status}); + xlog "Add attachment via CalDAV"; + my $url = $caldav->request_url($eventHref) . '?action=attachment-add'; + my $caldavResponse = $caldav->ua->post( + $url, + { + headers => { + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment;filename=test', + 'Prefer' => 'return=representation', + 'Authorization' => $caldav->auth_header(), + }, + content => 'someblob', + } + ); + $self->assert_str_equals('201', $caldavResponse->{status}); - xlog "Get updated VEVENT via CalDAV"; - $caldavResponse = $caldav->Request('GET', $eventHref); - my $veventWithManagedAttachUrl = $caldavResponse->{content}; - $self->assert_not_null($veventWithManagedAttachUrl); + xlog "Get updated VEVENT via CalDAV"; + $caldavResponse = $caldav->Request('GET', $eventHref); + my $veventWithManagedAttachUrl = $caldavResponse->{content}; + $self->assert_not_null($veventWithManagedAttachUrl); - xlog "Get updated VEVENT via iTIP"; - my $notif = $self->{instance}->getnotify(); - my ($imip) = grep { $_->{METHOD} eq 'imip' } @$notif; - my $payload = decode_json($imip->{MESSAGE}); - my $veventWithManagedAttachBinary = $payload->{ical}; - $self->assert_not_null($veventWithManagedAttachBinary); + xlog "Get updated VEVENT via iTIP"; + my $notif = $self->{instance}->getnotify(); + my ($imip) = grep { $_->{METHOD} eq 'imip' } @$notif; + my $payload = decode_json($imip->{MESSAGE}); + my $veventWithManagedAttachBinary = $payload->{ical}; + $self->assert_not_null($veventWithManagedAttachBinary); - xlog "Embed VEVENT in email"; - $self->make_message('test', - mime_type => 'multipart/related', - mime_boundary => 'boundary', - body => "" - . "\r\n--boundary\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "test" - . "\r\n--boundary\r\n" - . "Content-Type: text/calendar;charset=utf-8\r\n" - . "\r\n" - . $veventWithManagedAttachUrl - . "\r\n--boundary\r\n" - . "Content-Type: text/calendar;charset=utf-8\r\n" - . "\r\n" - . $veventWithManagedAttachBinary - . "\r\n--boundary--\r\n" - ) || die; + xlog "Embed VEVENT in email"; + $self->make_message( + 'test', + mime_type => 'multipart/related', + mime_boundary => 'boundary', + body => "" + . "\r\n--boundary\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "test" + . "\r\n--boundary\r\n" + . "Content-Type: text/calendar;charset=utf-8\r\n" . "\r\n" + . $veventWithManagedAttachUrl + . "\r\n--boundary\r\n" + . "Content-Type: text/calendar;charset=utf-8\r\n" . "\r\n" + . $veventWithManagedAttachBinary + . "\r\n--boundary--\r\n" + ) || die; - xlog "Fetch email via JMAP"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['calendarEvents'], - }, 'R2' ], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + xlog "Fetch email via JMAP"; + my $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['calendarEvents'], + }, + 'R2' + ], + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/calendars', + ] + ); - xlog "Assert both events have the same blobId"; - my @linksFromUrl = values %{$res->[1][1]{list}[0]{calendarEvents}{2}[0]{links}}; - $self->assert_num_equals(1, scalar @linksFromUrl); - my @linksFromBinary = values %{$res->[1][1]{list}[0]{calendarEvents}{3}[0]{links}}; - $self->assert_num_equals(1, scalar @linksFromBinary); - $self->assert_str_equals($linksFromUrl[0]->{blobId}, $linksFromBinary[0]->{blobId}); + xlog "Assert both events have the same blobId"; + my @linksFromUrl + = values %{ $res->[1][1]{list}[0]{calendarEvents}{2}[0]{links} }; + $self->assert_num_equals(1, scalar @linksFromUrl); + my @linksFromBinary + = values %{ $res->[1][1]{list}[0]{calendarEvents}{3}[0]{links} }; + $self->assert_num_equals(1, scalar @linksFromBinary); + $self->assert_str_equals($linksFromUrl[0]->{blobId}, + $linksFromBinary[0]->{blobId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_links_blobid_attachbinary b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_links_blobid_attachbinary index 75eb1b386c..c7c3c99f6b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_links_blobid_attachbinary +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_links_blobid_attachbinary @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_email_get_calendarevents_links_blobid_attachbinary - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - xlog "Create event via CalDAV"; - my $rawIcal = <<'EOF'; + xlog "Create event via CalDAV"; + my $rawIcal = <<'EOF'; BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.10.4//EN @@ -29,47 +28,53 @@ SEQUENCE:0 END:VEVENT END:VCALENDAR EOF - $rawIcal =~ s/\r?\n/\r\n/gs; + $rawIcal =~ s/\r?\n/\r\n/gs; - xlog "Make email"; - $self->make_message('test', - mime_type => 'multipart/related', - mime_boundary => 'boundary', - body => "" - . "\r\n--boundary\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "test" - . "\r\n--boundary\r\n" - . "Content-Type: text/calendar;charset=utf-8\r\n" - . "\r\n" - . $rawIcal - . "\r\n--boundary--\r\n" - ) || die; + xlog "Make email"; + $self->make_message( + 'test', + mime_type => 'multipart/related', + mime_boundary => 'boundary', + body => "" + . "\r\n--boundary\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "test" + . "\r\n--boundary\r\n" + . "Content-Type: text/calendar;charset=utf-8\r\n" . "\r\n" + . $rawIcal + . "\r\n--boundary--\r\n" + ) || die; - xlog "Fetch email via JMAP"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['calendarEvents'], - }, 'R2' ], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + xlog "Fetch email via JMAP"; + my $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['calendarEvents'], + }, + 'R2' + ], + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/calendars', + ] + ); - my $link = (values %{$res->[1][1]{list}[0]{calendarEvents}{2}[0]{links}})[0]; - $self->assert_not_null($link); - $self->assert_not_null($link->{blobId}); - $res = $jmap->Download('cassandane', uri_escape($link->{blobId})); - $self->assert_str_equals("hello", $res->{content}); + my $link + = (values %{ $res->[1][1]{list}[0]{calendarEvents}{2}[0]{links} })[0]; + $self->assert_not_null($link); + $self->assert_not_null($link->{blobId}); + $res = $jmap->Download('cassandane', uri_escape($link->{blobId})); + $self->assert_str_equals("hello", $res->{content}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_standalone_instances b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_standalone_instances index e482bd76da..b4c314b15f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_standalone_instances +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_standalone_instances @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_get_calendarevents_standalone_instances - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $body = <<'EOF'; + my $body = <<'EOF'; --boundary_1 Content-Type: text/plain @@ -44,37 +43,46 @@ END:VEVENT END:VCALENDAR --boundary_1-- EOF - $body =~ s/\r?\n/\r\n/gs; + $body =~ s/\r?\n/\r\n/gs; - $self->make_message('test', mime_type => 'multipart/related', - mime_boundary => 'boundary_1', body => $body) or die; + $self->make_message( + 'test', + mime_type => 'multipart/related', + mime_boundary => 'boundary_1', + body => $body + ) or die; - xlog $self, 'get email'; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ - 'attachments', 'calendarEvents' - ], - }, 'R2'], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:calendars', - 'urn:ietf:params:jmap:principals', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/calendars', - ]); + xlog $self, 'get email'; + my $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => [ 'attachments', 'calendarEvents' ], + }, + 'R2' + ], + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:calendars', + 'urn:ietf:params:jmap:principals', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/calendars', + ] + ); - $self->assert_num_equals(2, - scalar @{$res->[1][1]{list}[0]{calendarEvents}{2}}); - $self->assert_str_equals('instance1', - $res->[1][1]{list}[0]{calendarEvents}{2}[0]{title}); - $self->assert_str_equals('instance2', - $res->[1][1]{list}[0]{calendarEvents}{2}[1]{title}); + $self->assert_num_equals(2, + scalar @{ $res->[1][1]{list}[0]{calendarEvents}{2} }); + $self->assert_str_equals('instance1', + $res->[1][1]{list}[0]{calendarEvents}{2}[0]{title}); + $self->assert_str_equals('instance2', + $res->[1][1]{list}[0]{calendarEvents}{2}[1]{title}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_utc b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_utc index c91c668afe..92aa7c6fc1 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_utc +++ b/cassandane/tiny-tests/JMAPEmail/email_get_calendarevents_utc @@ -2,89 +2,92 @@ use Cassandane::Tiny; sub test_email_get_calendarevents_utc - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # calendarEvents property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # calendarEvents property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $uid1 = "d9e7f7d6-ce1a-4a71-94c0-b4edd41e5959"; + my $uid1 = "d9e7f7d6-ce1a-4a71-94c0-b4edd41e5959"; - $self->make_message("foo", - mime_type => "multipart/related", - mime_boundary => "boundary_1", - body => "" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "txt body" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/calendar;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" - . "BEGIN:VCALENDAR\r\n" - . "VERSION:2.0\r\n" - . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" - . "CALSCALE:GREGORIAN\r\n" - . "BEGIN:VTIMEZONE\r\n" - . "TZID:UTC\r\n" - . "BEGIN:STANDARD\r\n" - . "DTSTART:16010101T000000\r\n" - . "TZOFFSETFROM:+0000\r\n" - . "TZOFFSETTO:+0000\r\n" - . "END:STANDARD\r\n" - . "BEGIN:DAYLIGHT\r\n" - . "DTSTART:16010101T000000\r\n" - . "TZOFFSETFROM:+0000\r\n" - . "TZOFFSETTO:+0000\r\n" - . "END:DAYLIGHT\r\n" - . "END:VTIMEZONE\r\n" - . "BEGIN:VEVENT\r\n" - . "CREATED:20180518T090306Z\r\n" - . "DTEND;TZID=UTC:20180518T100000\r\n" - . "DTSTAMP:20180518T090306Z\r\n" - . "DTSTART;TZID=UTC:20180518T090000\r\n" - . "LAST-MODIFIED:20180518T090306Z\r\n" - . "SEQUENCE:1\r\n" - . "SUMMARY:Foo\r\n" - . "TRANSP:OPAQUE\r\n" - . "UID:$uid1\r\n" - . "END:VEVENT\r\n" - . "END:VCALENDAR\r\n" - . "\r\n--boundary_1--\r\n" - ) || die; + $self->make_message( + "foo", + mime_type => "multipart/related", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "txt body" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/calendar;charset=utf-8\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" + . "BEGIN:VCALENDAR\r\n" + . "VERSION:2.0\r\n" + . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" + . "CALSCALE:GREGORIAN\r\n" + . "BEGIN:VTIMEZONE\r\n" + . "TZID:UTC\r\n" + . "BEGIN:STANDARD\r\n" + . "DTSTART:16010101T000000\r\n" + . "TZOFFSETFROM:+0000\r\n" + . "TZOFFSETTO:+0000\r\n" + . "END:STANDARD\r\n" + . "BEGIN:DAYLIGHT\r\n" + . "DTSTART:16010101T000000\r\n" + . "TZOFFSETFROM:+0000\r\n" + . "TZOFFSETTO:+0000\r\n" + . "END:DAYLIGHT\r\n" + . "END:VTIMEZONE\r\n" + . "BEGIN:VEVENT\r\n" + . "CREATED:20180518T090306Z\r\n" + . "DTEND;TZID=UTC:20180518T100000\r\n" + . "DTSTAMP:20180518T090306Z\r\n" + . "DTSTART;TZID=UTC:20180518T090000\r\n" + . "LAST-MODIFIED:20180518T090306Z\r\n" + . "SEQUENCE:1\r\n" + . "SUMMARY:Foo\r\n" + . "TRANSP:OPAQUE\r\n" + . "UID:$uid1\r\n" + . "END:VEVENT\r\n" + . "END:VCALENDAR\r\n" + . "\r\n--boundary_1--\r\n" + ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['textBody', 'attachments', 'calendarEvents'], - }, 'R2' ], - ]); - my $msg = $res->[1][1]{list}[0]; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ 'textBody', 'attachments', 'calendarEvents' ], + }, + 'R2' + ], + ]); + my $msg = $res->[1][1]{list}[0]; - $self->assert_num_equals(1, scalar @{$msg->{attachments}}); - $self->assert_str_equals('text/calendar', $msg->{attachments}[0]{type}); + $self->assert_num_equals(1, scalar @{ $msg->{attachments} }); + $self->assert_str_equals('text/calendar', $msg->{attachments}[0]{type}); - $self->assert_num_equals(1, scalar keys %{$msg->{calendarEvents}}); - my $partId = $msg->{attachments}[0]{partId}; + $self->assert_num_equals(1, scalar keys %{ $msg->{calendarEvents} }); + my $partId = $msg->{attachments}[0]{partId}; - my %jsevents_by_uid = map { $_->{uid} => $_ } @{$msg->{calendarEvents}{$partId}}; - $self->assert_num_equals(1, scalar keys %jsevents_by_uid); - my $jsevent1 = $jsevents_by_uid{$uid1}; + my %jsevents_by_uid + = map { $_->{uid} => $_ } @{ $msg->{calendarEvents}{$partId} }; + $self->assert_num_equals(1, scalar keys %jsevents_by_uid); + my $jsevent1 = $jsevents_by_uid{$uid1}; - $self->assert_not_null($jsevent1); - $self->assert_str_equals("Foo", $jsevent1->{title}); - $self->assert_str_equals('2018-05-18T09:00:00', $jsevent1->{start}); - $self->assert_str_equals('Etc/UTC', $jsevent1->{timeZone}); - $self->assert_str_equals('PT1H', $jsevent1->{duration}); + $self->assert_not_null($jsevent1); + $self->assert_str_equals("Foo", $jsevent1->{title}); + $self->assert_str_equals('2018-05-18T09:00:00', $jsevent1->{start}); + $self->assert_str_equals('Etc/UTC', $jsevent1->{timeZone}); + $self->assert_str_equals('PT1H', $jsevent1->{duration}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_cid b/cassandane/tiny-tests/JMAPEmail/email_get_cid index e1350952c6..cb66894788 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_cid +++ b/cassandane/tiny-tests/JMAPEmail/email_get_cid @@ -2,61 +2,54 @@ use Cassandane::Tiny; sub test_email_get_cid - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - $self->make_message("msg1", - mime_type => "multipart/mixed", - mime_boundary => "boundary", - body => "" - . "--boundary\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body" - . "\r\n" - . "--boundary\r\n" - . "Content-Type: image/png\r\n" - . "Content-Id: <1234567890\@local>\r\n" - . "\r\n" - . "data" - . "\r\n" - . "--boundary\r\n" - . "Content-Type: image/png\r\n" - . "Content-Id: <1234567890>\r\n" - . "\r\n" - . "data" - . "\r\n" - . "--boundary\r\n" - . "Content-Type: image/png\r\n" - . "Content-Id: 1234567890\r\n" - . "\r\n" - . "data" - . "\r\n" - . "--boundary--\r\n" - ) || die; + $self->make_message( + "msg1", + mime_type => "multipart/mixed", + mime_boundary => "boundary", + body => "" + . "--boundary\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "body" . "\r\n" + . "--boundary\r\n" + . "Content-Type: image/png\r\n" + . "Content-Id: <1234567890\@local>\r\n" . "\r\n" . "data" . "\r\n" + . "--boundary\r\n" + . "Content-Type: image/png\r\n" + . "Content-Id: <1234567890>\r\n" . "\r\n" . "data" . "\r\n" + . "--boundary\r\n" + . "Content-Type: image/png\r\n" + . "Content-Id: 1234567890\r\n" . "\r\n" . "data" . "\r\n" + . "--boundary--\r\n" + ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'bodyStructure' ], - bodyProperties => ['partId', 'cid'], - }, 'R2'], - ]); - my $bodyStructure = $res->[1][1]{list}[0]{bodyStructure}; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['bodyStructure'], + bodyProperties => [ 'partId', 'cid' ], + }, + 'R2' + ], + ]); + my $bodyStructure = $res->[1][1]{list}[0]{bodyStructure}; - $self->assert_null($bodyStructure->{subParts}[0]{cid}); - $self->assert_str_equals('1234567890@local', $bodyStructure->{subParts}[1]{cid}); - $self->assert_str_equals('1234567890', $bodyStructure->{subParts}[2]{cid}); - $self->assert_str_equals('1234567890', $bodyStructure->{subParts}[3]{cid}); + $self->assert_null($bodyStructure->{subParts}[0]{cid}); + $self->assert_str_equals('1234567890@local', + $bodyStructure->{subParts}[1]{cid}); + $self->assert_str_equals('1234567890', $bodyStructure->{subParts}[2]{cid}); + $self->assert_str_equals('1234567890', $bodyStructure->{subParts}[3]{cid}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_createdmodseq b/cassandane/tiny-tests/JMAPEmail/email_get_createdmodseq index 6747ce8e6d..804b301a8d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_createdmodseq +++ b/cassandane/tiny-tests/JMAPEmail/email_get_createdmodseq @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_email_get_createdmodseq - :min_version_3_9 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/mail'); + : min_version_3_9 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/mail'); - xlog $self, "Append duplicate messages"; - $mimeMessage = <<'EOF'; + xlog $self, "Append duplicate messages"; + $mimeMessage = <<'EOF'; From: To: to@local Subject: test @@ -20,32 +19,34 @@ Content-Type: text/plain test EOF - $mimeMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', '17-Aug-2023 15:13:54 +0200', $mimeMessage) || die $@; - $imap->append('INBOX', '17-Aug-2023 15:13:54 +0200', $mimeMessage) || die $@; + $mimeMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', '17-Aug-2023 15:13:54 +0200', $mimeMessage) || die $@; + $imap->append('INBOX', '17-Aug-2023 15:13:54 +0200', $mimeMessage) || die $@; - $imap->select('INBOX'); - $fetch = $imap->fetch('1:2', ['INTERNALDATE', 'CREATEDMODSEQ']); - $self->assert_str_equals( - $fetch->{1}{internaldate}, $fetch->{2}{internaldate} - ); - $self->assert_num_lt( - $fetch->{2}{createdmodseq}[0], $fetch->{1}{createdmodseq}[0] - ); + $imap->select('INBOX'); + $fetch = $imap->fetch('1:2', [ 'INTERNALDATE', 'CREATEDMODSEQ' ]); + $self->assert_str_equals($fetch->{1}{internaldate}, + $fetch->{2}{internaldate}); + $self->assert_num_lt($fetch->{2}{createdmodseq}[0], + $fetch->{1}{createdmodseq}[0]); - # The createdModseq must be the lower modseq. - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['createdModseq'], - }, 'R2' ], - ]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_num_equals($fetch->{1}{createdmodseq}[0], - $res->[1][1]{list}[0]{createdModseq}); + # The createdModseq must be the lower modseq. + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['createdModseq'], + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_num_equals($fetch->{1}{createdmodseq}[0], + $res->[1][1]{list}[0]{createdModseq}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_deliveredto b/cassandane/tiny-tests/JMAPEmail/email_get_deliveredto index 1822bfc694..a3fa41b9b6 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_deliveredto +++ b/cassandane/tiny-tests/JMAPEmail/email_get_deliveredto @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_email_get_deliveredto - :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: To: to@local Bcc: bcc@local @@ -20,10 +19,10 @@ Content-Type: text/plain msg1 EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - $rawMessage = <<'EOF'; + $rawMessage = <<'EOF'; From: To: to@local Bcc: bcc@local @@ -36,10 +35,10 @@ Content-Type: text/plain msg2 EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - $rawMessage = <<'EOF'; + $rawMessage = <<'EOF'; From: To: to@local Subject: msg3 @@ -49,48 +48,57 @@ Content-Type: text/plain msg3 EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', 'https://cyrusimap.org/ns/jmap/mail', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { }, - sort => [{ - property => 'subject', - }], - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['subject', 'deliveredTo'], - }, 'R2'], - ], $using); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => {}, + sort => [ { + property => 'subject', + } ], + }, + 'R1' + ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => [ 'subject', 'deliveredTo' ], + }, + 'R2' + ], + ], + $using + ); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); - # This test assumes that Email/get returns the emails in order - # of the ids property request argument. + # This test assumes that Email/get returns the emails in order + # of the ids property request argument. - $self->assert_str_equals('msg1', $res->[1][1]{list}[0]{subject}); - $self->assert_str_equals('x-delivered-to@local', - $res->[1][1]{list}[0]{deliveredTo}); + $self->assert_str_equals('msg1', $res->[1][1]{list}[0]{subject}); + $self->assert_str_equals('x-delivered-to@local', + $res->[1][1]{list}[0]{deliveredTo}); - $self->assert_str_equals('msg2', $res->[1][1]{list}[1]{subject}); - $self->assert_str_equals('x-original-delivered-to@local', - $res->[1][1]{list}[1]{deliveredTo}); + $self->assert_str_equals('msg2', $res->[1][1]{list}[1]{subject}); + $self->assert_str_equals('x-original-delivered-to@local', + $res->[1][1]{list}[1]{deliveredTo}); - $self->assert_str_equals('msg3', $res->[1][1]{list}[2]{subject}); - $self->assert_null($res->[1][1]{list}[2]{deliveredTo}); + $self->assert_str_equals('msg3', $res->[1][1]{list}[2]{subject}); + $self->assert_null($res->[1][1]{list}[2]{deliveredTo}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_detect_iso_8859_1 b/cassandane/tiny-tests/JMAPEmail/email_get_detect_iso_8859_1 index 35cb80e4e4..706f847e6b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_detect_iso_8859_1 +++ b/cassandane/tiny-tests/JMAPEmail/email_get_detect_iso_8859_1 @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_email_get_detect_iso_8859_1 - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :needs_dependency_chardet -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : needs_dependency_chardet { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: Here is some ISO-8859-1 text that claims to be ascii @@ -21,28 +20,43 @@ Content-Transfer-Encoding: base64 Ikvkc2Ugc2NobGllc3N0IGRlbiBNYWdlbiIsIGj2cnRlIGljaCBkZW4gU2NobG/faGVycm4gc2FnZW4uCg== EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; - my $inboxid = $self->getinbox()->{id}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; + my $inboxid = $self->getinbox()->{id}; - xlog $self, "import and get email from blob $blobid"; - my $res = $jmap->CallMethods([['Email/import', { + xlog $self, "import and get email from blob $blobid"; + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$inboxid => JSON::true}, - }, + "1" => { + blobId => $blobid, + mailboxIds => { $inboxid => JSON::true }, + }, }, - }, "R1"], ["Email/get", { - ids => ["#1"], - properties => ['textBody', 'bodyValues'], + }, + "R1" + ], + [ + "Email/get", + { + ids => ["#1"], + properties => [ 'textBody', 'bodyValues' ], fetchTextBodyValues => JSON::true, - }, "R2" ]]); + }, + "R2" + ] + ]); - $self->assert_num_equals(0, - index($res->[1][1]{list}[0]{bodyValues}{1}{value}, - "\"K\N{LATIN SMALL LETTER A WITH DIAERESIS}se") - ); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{bodyValues}{1}{isEncodingProblem}); + $self->assert_num_equals( + 0, + index( + $res->[1][1]{list}[0]{bodyValues}{1}{value}, + "\"K\N{LATIN SMALL LETTER A WITH DIAERESIS}se" + ) + ); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{bodyValues}{1}{isEncodingProblem}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_detect_utf32 b/cassandane/tiny-tests/JMAPEmail/email_get_detect_utf32 index d4d3c8ecc3..8fdfb79d6f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_detect_utf32 +++ b/cassandane/tiny-tests/JMAPEmail/email_get_detect_utf32 @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_get_detect_utf32 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: Here are some base64-encoded UTF-32LE bytes without BOM. @@ -74,30 +73,45 @@ AACLMAAAaDAAAAEwAAB2UQAAbjAAAEJmAACkWwAAbjAAADZiAACSMAAAB2MAAEhRAABnMAAAszAA AMgwAAAzMAAANTAAAGgwAAAVjwAATzAAAOlTAABPMAAAgjAAAG4wAABMMAAAQjAAAIswAAACMAAA DQAAAAoA EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; - my $inboxid = $self->getinbox()->{id}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; + my $inboxid = $self->getinbox()->{id}; - xlog $self, "import and get email from blob $blobid"; - my $res = $jmap->CallMethods([['Email/import', { + xlog $self, "import and get email from blob $blobid"; + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$inboxid => JSON::true}, - }, + "1" => { + blobId => $blobid, + mailboxIds => { $inboxid => JSON::true }, + }, }, - }, "R1"], ["Email/get", { - ids => ["#1"], - properties => ['textBody', 'bodyValues', 'preview'], + }, + "R1" + ], + [ + "Email/get", + { + ids => ["#1"], + properties => [ 'textBody', 'bodyValues', 'preview' ], fetchTextBodyValues => JSON::true, - }, "R2" ]]); + }, + "R2" + ] + ]); - $self->assert_num_equals(0, - index($res->[1][1]{list}[0]{bodyValues}{1}{value}, - "\N{HIRAGANA LETTER A}" . - "\N{HIRAGANA LETTER ME}" . - "\N{HIRAGANA LETTER RI}") - ); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{bodyValues}{1}{isEncodingProblem}); + $self->assert_num_equals( + 0, + index( + $res->[1][1]{list}[0]{bodyValues}{1}{value}, + "\N{HIRAGANA LETTER A}" + . "\N{HIRAGANA LETTER ME}" + . "\N{HIRAGANA LETTER RI}" + ) + ); + $self->assert_equals(JSON::true, + $res->[1][1]{list}[0]{bodyValues}{1}{isEncodingProblem}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_encoding_utf8 b/cassandane/tiny-tests/JMAPEmail/email_get_encoding_utf8 index e06e8e7b41..9108008bc1 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_encoding_utf8 +++ b/cassandane/tiny-tests/JMAPEmail/email_get_encoding_utf8 @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_email_get_encoding_utf8 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # Some clients erroneously declare encoding to be UTF-8. - my $email = <<'EOF'; + # Some clients erroneously declare encoding to be UTF-8. + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -19,30 +18,41 @@ Content-Transfer-Encoding: UTF-8 This is a test. EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; - my $inboxid = $self->getinbox()->{id}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; + my $inboxid = $self->getinbox()->{id}; - xlog $self, "import and get email from blob $blobid"; - my $res = $jmap->CallMethods([['Email/import', { + xlog $self, "import and get email from blob $blobid"; + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$inboxid => JSON::true}, - }, + "1" => { + blobId => $blobid, + mailboxIds => { $inboxid => JSON::true }, + }, }, - }, "R1"], ["Email/get", { - ids => ["#1"], - properties => ['bodyStructure', 'bodyValues'], + }, + "R1" + ], + [ + "Email/get", + { + ids => ["#1"], + properties => [ 'bodyStructure', 'bodyValues' ], fetchAllBodyValues => JSON::true, - }, "R2" ]]); + }, + "R2" + ] + ]); - $self->assert_str_equals("Email/import", $res->[0][0]); - $self->assert_str_equals("Email/get", $res->[1][0]); + $self->assert_str_equals("Email/import", $res->[0][0]); + $self->assert_str_equals("Email/get", $res->[1][0]); - my $msg = $res->[1][1]{list}[0]; - my $partId = $msg->{bodyStructure}{partId}; - my $bodyValue = $msg->{bodyValues}{$partId}; - $self->assert_str_equals("This is a test.\n", $bodyValue->{value}); + my $msg = $res->[1][1]{list}[0]; + my $partId = $msg->{bodyStructure}{partId}; + my $bodyValue = $msg->{bodyValues}{$partId}; + $self->assert_str_equals("This is a test.\n", $bodyValue->{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_fixbrokenmessageids b/cassandane/tiny-tests/JMAPEmail/email_get_fixbrokenmessageids index 6b662dbe25..4538a62a87 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_fixbrokenmessageids +++ b/cassandane/tiny-tests/JMAPEmail/email_get_fixbrokenmessageids @@ -2,38 +2,36 @@ use Cassandane::Tiny; sub test_email_get_fixbrokenmessageids - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ + : min_version_3_1 : needs_component_sieve : needs_component_jmap { - # See issue https://github.com/cyrusimap/cyrus-imapd/issues/2601 + # See issue https://github.com/cyrusimap/cyrus-imapd/issues/2601 - my ($self) = @_; - my $jmap = $self->{jmap}; + my ($self) = @_; + my $jmap = $self->{jmap}; - # An email with a folded reference id. - my %params = ( - extra_headers => [ - ['references', "<123\r\n\t456\@lo cal>" ], - ], - ); - $self->make_message("Email A", %params) || die; + # An email with a folded reference id. + my %params + = (extra_headers => [ [ 'references', "<123\r\n\t456\@lo cal>" ], ],); + $self->make_message("Email A", %params) || die; - xlog $self, "get email"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ - 'references' - ], - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $email = $res->[1][1]->{list}[0]; + xlog $self, "get email"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['references'], + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $email = $res->[1][1]->{list}[0]; - $self->assert_str_equals('123456@local', $email->{references}[0]); + $self->assert_str_equals('123456@local', $email->{references}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_hasattachment b/cassandane/tiny-tests/JMAPEmail/email_get_hasattachment index 7f8310beac..854397ea1e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_hasattachment +++ b/cassandane/tiny-tests/JMAPEmail/email_get_hasattachment @@ -2,14 +2,15 @@ use Cassandane::Tiny; sub test_email_get_hasattachment - :min_version_3_5 :needs_component_sieve :needs_component_jmap :AltNamespace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_sieve : needs_component_jmap : + AltNamespace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $imap->create("matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + $imap->create("matches") or die; + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -23,9 +24,9 @@ if fileinto "matches"; } EOF - ); + ); - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: from@local To: to@local Subject: test @@ -53,24 +54,28 @@ Sent from my supercalifragilisticexpialidocious device --e523eb44-40ae-463e-9261-2f935700196d-- EOF - $rawMessage =~ s/\r?\n/\r\n/gs; + $rawMessage =~ s/\r?\n/\r\n/gs; - my $msg = Cassandane::Message->new(); - $msg->set_lines(split /\n/, $rawMessage); - $self->{instance}->deliver($msg); + my $msg = Cassandane::Message->new(); + $msg->set_lines(split /\n/, $rawMessage); + $self->{instance}->deliver($msg); - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['hasAttachment'], - }, 'R2'], - ]); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['hasAttachment'], + }, + 'R2' + ], + ]); - $self->assert_num_equals(1, $imap->message_count('matches')); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{hasAttachment}); + $self->assert_num_equals(1, $imap->message_count('matches')); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{hasAttachment}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_header_all b/cassandane/tiny-tests/JMAPEmail/email_get_header_all index 8db1aa7fe6..0ade5066c9 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_header_all +++ b/cassandane/tiny-tests/JMAPEmail/email_get_header_all @@ -2,33 +2,39 @@ use Cassandane::Tiny; sub test_email_get_header_all - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Generate an email in INBOX via IMAP"; - my %exp_inbox; - my %params = ( - extra_headers => [ - ['x-tra', "foo"], - ['x-tra', "bar"], - ], - body => "hello", - ); - $self->make_message("Email A", %params) || die; + xlog $self, "Generate an email in INBOX via IMAP"; + my %exp_inbox; + my %params = ( + extra_headers => [ [ 'x-tra', "foo" ], [ 'x-tra', "bar" ], ], + body => "hello", + ); + $self->make_message("Email A", %params) || die; - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids, properties => ['header:x-tra:all', 'header:x-tra:asRaw:all'] }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get email"; + $res = $jmap->CallMethods( + [ [ + 'Email/get', + { + ids => $ids, + properties => [ 'header:x-tra:all', 'header:x-tra:asRaw:all' ] + }, + "R1" + ] ] + ); + my $msg = $res->[0][1]{list}[0]; - $self->assert_deep_equals([' foo', ' bar'], $msg->{'header:x-tra:all'}); - $self->assert_deep_equals([' foo', ' bar'], $msg->{'header:x-tra:asRaw:all'}); + $self->assert_deep_equals([ ' foo', ' bar' ], $msg->{'header:x-tra:all'}); + $self->assert_deep_equals([ ' foo', ' bar' ], + $msg->{'header:x-tra:asRaw:all'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_header_last_value b/cassandane/tiny-tests/JMAPEmail/email_get_header_last_value index 23b47bf743..433cce1f1e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_header_last_value +++ b/cassandane/tiny-tests/JMAPEmail/email_get_header_last_value @@ -2,36 +2,42 @@ use Cassandane::Tiny; sub test_email_get_header_last_value - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - $self->make_message("msg", extra_headers => [ - ['x-tra', 'Fri, 21 Nov 1997 09:55:06 -0600'], - ['x-tra', 'Thu, 22 Aug 2019 23:12:06 -0600'], - ]) || die; + $self->make_message( + "msg", + extra_headers => [ + [ 'x-tra', 'Fri, 21 Nov 1997 09:55:06 -0600' ], + [ 'x-tra', 'Thu, 22 Aug 2019 23:12:06 -0600' ], + ] + ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['header:x-tra:asDate'] - }, 'R2'], - ]); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min >= 4)) { - $self->assert_str_equals('2019-08-22T23:12:06-06:00', - $res->[1][1]{list}[0]{'header:x-tra:asDate'}); - } else { - $self->assert_str_equals('2019-08-23T05:12:06Z', - $res->[1][1]{list}[0]{'header:x-tra:asDate'}); - } + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['header:x-tra:asDate'] + }, + 'R2' + ], + ]); + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min >= 4)) { + $self->assert_str_equals('2019-08-22T23:12:06-06:00', + $res->[1][1]{list}[0]{'header:x-tra:asDate'}); + } else { + $self->assert_str_equals('2019-08-23T05:12:06Z', + $res->[1][1]{list}[0]{'header:x-tra:asDate'}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_headers_multipart b/cassandane/tiny-tests/JMAPEmail/email_get_headers_multipart index 35057d1cc1..5b5db29389 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_headers_multipart +++ b/cassandane/tiny-tests/JMAPEmail/email_get_headers_multipart @@ -2,53 +2,59 @@ use Cassandane::Tiny; sub test_email_get_headers_multipart - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); - - my $htmlBody = "

This is the html part.

"; - my $textBody = "This is the plain text part."; - - my $body = "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; - $body .= "\r\n"; - $body .= $textBody; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; - $body .= "\r\n"; - $body .= $htmlBody; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; - $exp_sub{A} = $self->make_message("foo", - mime_type => "multipart/alternative", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body, - extra_headers => [['X-Spam-Hits', 'SPAMA, SPAMB, SPAMC']], - ); - - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; - - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { - ids => $ids, - properties => [ "header:x-spam-hits:asRaw", "header:x-spam-hits:asText" ], - }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; - - $self->assert_str_equals(' SPAMA, SPAMB, SPAMC', $msg->{"header:x-spam-hits:asRaw"}); - $self->assert_str_equals('SPAMA, SPAMB, SPAMC', $msg->{"header:x-spam-hits:asText"}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); + + my $htmlBody = "

This is the html part.

"; + my $textBody = "This is the plain text part."; + + my $body = "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $body .= "\r\n"; + $body .= $textBody; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; + $body .= "\r\n"; + $body .= $htmlBody; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; + $exp_sub{A} = $self->make_message( + "foo", + mime_type => "multipart/alternative", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body, + extra_headers => [ [ 'X-Spam-Hits', 'SPAMA, SPAMB, SPAMC' ] ], + ); + + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; + + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => $ids, + properties => [ "header:x-spam-hits:asRaw", "header:x-spam-hits:asText" ], + }, + "R1" + ] ]); + my $msg = $res->[0][1]{list}[0]; + + $self->assert_str_equals(' SPAMA, SPAMB, SPAMC', + $msg->{"header:x-spam-hits:asRaw"}); + $self->assert_str_equals('SPAMA, SPAMB, SPAMC', + $msg->{"header:x-spam-hits:asText"}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_imagesize b/cassandane/tiny-tests/JMAPEmail/email_get_imagesize index 2093cc74e9..24f781a5c8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_imagesize +++ b/cassandane/tiny-tests/JMAPEmail/email_get_imagesize @@ -2,81 +2,81 @@ use Cassandane::Tiny; sub test_email_get_imagesize - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - # This is a FastMail-extension + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + # This is a FastMail-extension - my ($self) = @_; - my $jmap = $self->{jmap}; + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - $store->set_folder('INBOX'); + my $store = $self->{store}; + my $talk = $store->get_client(); + $store->set_folder('INBOX'); - # Part 1 has no imagesize defined, part 2 defines no EXIF - # orientation, part 3 defines all image size properties. - my $imageSize = { - '2' => [1,2], - '3' => [1,2,3], - }; + # Part 1 has no imagesize defined, part 2 defines no EXIF + # orientation, part 3 defines all image size properties. + my $imageSize = { + '2' => [ 1, 2 ], + '3' => [ 1, 2, 3 ], + }; - # Generate an email with image MIME parts. - xlog $self, "Generate an email via IMAP"; - my $msg = $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: image/png\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=" - . "\r\n--sub\r\n" - . "Content-Type: image/png\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=" - . "\r\n--sub\r\n" - . "Content-Type: image/png\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=" - . "\r\n--sub--\r\n", - ); - xlog $self, "set imagesize annotation"; - my $annot = '/vendor/messagingengine.com/imagesize'; - my $ret = $talk->store('1', 'annotation', [ - $annot, ['value.shared', { Quote => encode_json($imageSize) }] - ]); - if (not $ret) { - xlog $self, "Could not set $annot annotation. Aborting."; - return; - } + # Generate an email with image MIME parts. + xlog $self, "Generate an email via IMAP"; + my $msg = $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: image/png\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=" + . "\r\n--sub\r\n" + . "Content-Type: image/png\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=" + . "\r\n--sub\r\n" + . "Content-Type: image/png\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=" + . "\r\n--sub--\r\n", + ); + xlog $self, "set imagesize annotation"; + my $annot = '/vendor/messagingengine.com/imagesize'; + my $ret = $talk->store('1', 'annotation', + [ $annot, [ 'value.shared', { Quote => encode_json($imageSize) } ] ]); + if (not $ret) { + xlog $self, "Could not set $annot annotation. Aborting."; + return; + } - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { - ids => $ids, - properties => ['bodyStructure'], - bodyProperties => ['partId', 'imageSize' ], - }, "R1"]]); - my $email = $res->[0][1]{list}[0]; + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => $ids, + properties => ['bodyStructure'], + bodyProperties => [ 'partId', 'imageSize' ], + }, + "R1" + ] ]); + my $email = $res->[0][1]{list}[0]; - my $part = $email->{bodyStructure}{subParts}[0]; - $self->assert_str_equals('1', $part->{partId}); - $self->assert_null($part->{imageSize}); + my $part = $email->{bodyStructure}{subParts}[0]; + $self->assert_str_equals('1', $part->{partId}); + $self->assert_null($part->{imageSize}); - $part = $email->{bodyStructure}{subParts}[1]; - $self->assert_str_equals('2', $part->{partId}); - $self->assert_deep_equals($imageSize->{2}, $part->{imageSize}); + $part = $email->{bodyStructure}{subParts}[1]; + $self->assert_str_equals('2', $part->{partId}); + $self->assert_deep_equals($imageSize->{2}, $part->{imageSize}); - $part = $email->{bodyStructure}{subParts}[2]; - $self->assert_str_equals('3', $part->{partId}); - $self->assert_deep_equals($imageSize->{3}, $part->{imageSize}); + $part = $email->{bodyStructure}{subParts}[2]; + $self->assert_str_equals('3', $part->{partId}); + $self->assert_deep_equals($imageSize->{3}, $part->{imageSize}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_isdeleted b/cassandane/tiny-tests/JMAPEmail/email_get_isdeleted index 04ec4e1151..fc4b4d5f7c 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_isdeleted +++ b/cassandane/tiny-tests/JMAPEmail/email_get_isdeleted @@ -2,48 +2,51 @@ use Cassandane::Tiny; sub test_email_get_isdeleted - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - # This is a FastMail-extension - - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - $store->set_folder('INBOX'); - - my $msg = $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: text/x-me-removed-file\r\n" - . "\r\n" - . "deleted" - . "\r\n--sub--\r\n", - ); - - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; - - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { - ids => $ids, - properties => ['bodyStructure'], - bodyProperties => ['partId', 'isDeleted' ], - }, "R1"]]); - my $email = $res->[0][1]{list}[0]; - - my $part = $email->{bodyStructure}{subParts}[0]; - $self->assert_str_equals('1', $part->{partId}); - $self->assert_equals(JSON::false, $part->{isDeleted}); - - $part = $email->{bodyStructure}{subParts}[1]; - $self->assert_str_equals('2', $part->{partId}); - $self->assert_equals(JSON::true, $part->{isDeleted}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + # This is a FastMail-extension + + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + $store->set_folder('INBOX'); + + my $msg = $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: text/x-me-removed-file\r\n" . "\r\n" + . "deleted" + . "\r\n--sub--\r\n", + ); + + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; + + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => $ids, + properties => ['bodyStructure'], + bodyProperties => [ 'partId', 'isDeleted' ], + }, + "R1" + ] ]); + my $email = $res->[0][1]{list}[0]; + + my $part = $email->{bodyStructure}{subParts}[0]; + $self->assert_str_equals('1', $part->{partId}); + $self->assert_equals(JSON::false, $part->{isDeleted}); + + $part = $email->{bodyStructure}{subParts}[1]; + $self->assert_str_equals('2', $part->{partId}); + $self->assert_equals(JSON::true, $part->{isDeleted}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_iso2022jp_body b/cassandane/tiny-tests/JMAPEmail/email_get_iso2022jp_body index 599c59e9bf..3e587b13f1 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_iso2022jp_body +++ b/cassandane/tiny-tests/JMAPEmail/email_get_iso2022jp_body @@ -2,33 +2,35 @@ use Cassandane::Tiny; sub test_email_get_iso2022jp_body - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - open(my $F, 'data/mime/iso-2022-jp.eml') || die $!; - $imap->append('INBOX', $F) || die $@; - close($F); + open(my $F, 'data/mime/iso-2022-jp.eml') || die $!; + $imap->append('INBOX', $F) || die $@; + close($F); - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['bodyValues', 'preview'], - fetchAllBodyValues => JSON::true, - }, 'R2'], - ]); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => [ 'bodyValues', 'preview' ], + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); -use utf8; - $self->assert_str_equals("シニアソフトウェアエンジニア\n", - $res->[1][1]{list}[0]{bodyValues}{1}{value}); - $self->assert_str_equals("シニアソフトウェアエンジニア ", - $res->[1][1]{list}[0]{preview}); -no utf8; + use utf8; + $self->assert_str_equals("シニアソフトウェアエンジニア\n", + $res->[1][1]{list}[0]{bodyValues}{1}{value}); + $self->assert_str_equals("シニアソフトウェアエンジニア ", $res->[1][1]{list}[0]{preview}); + no utf8; } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_keywords b/cassandane/tiny-tests/JMAPEmail/email_get_keywords index 12577c43d8..5eb7683ded 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_keywords +++ b/cassandane/tiny-tests/JMAPEmail/email_get_keywords @@ -2,78 +2,76 @@ use Cassandane::Tiny; sub test_email_get_keywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Create IMAP mailbox and message A"; - $talk->create('INBOX.A') || die; - $store->set_folder('INBOX.A'); - $self->make_message('A') || die; + xlog $self, "Create IMAP mailbox and message A"; + $talk->create('INBOX.A') || die; + $store->set_folder('INBOX.A'); + $self->make_message('A') || die; - xlog $self, "Create IMAP mailbox B and copy message A to B"; - $talk->create('INBOX.B') || die; - $talk->copy('1:*', 'INBOX.B'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "Create IMAP mailbox B and copy message A to B"; + $talk->create('INBOX.B') || die; + $talk->copy('1:*', 'INBOX.B'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids'} - }, 'R2' ] - ]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - my $jmapmsg = $res->[1][1]{list}[0]; - $self->assert_not_null($jmapmsg); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } + }, + 'R2' + ] + ]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + my $jmapmsg = $res->[1][1]{list}[0]; + $self->assert_not_null($jmapmsg); - # Keywords are empty by default - my $keywords = {}; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + # Keywords are empty by default + my $keywords = {}; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); - xlog $self, "Set \\Seen on message A"; - $store->set_folder('INBOX.A'); - $talk->store('1', '+flags', '(\\Seen)'); + xlog $self, "Set \\Seen on message A"; + $store->set_folder('INBOX.A'); + $talk->store('1', '+flags', '(\\Seen)'); - # Seen must only be set if ALL messages are seen. - $res = $jmap->CallMethods([ - ['Email/get', { 'ids' => [ $jmapmsg->{id} ] }, 'R2' ] - ]); - $jmapmsg = $res->[0][1]{list}[0]; - $keywords = {}; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + # Seen must only be set if ALL messages are seen. + $res = $jmap->CallMethods([ + [ 'Email/get', { 'ids' => [ $jmapmsg->{id} ] }, 'R2' ] ]); + $jmapmsg = $res->[0][1]{list}[0]; + $keywords = {}; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); - xlog $self, "Set \\Seen on message B"; - $store->set_folder('INBOX.B'); - $store->_select(); - $talk->store('1', '+flags', '(\\Seen)'); + xlog $self, "Set \\Seen on message B"; + $store->set_folder('INBOX.B'); + $store->_select(); + $talk->store('1', '+flags', '(\\Seen)'); - # Seen must only be set if ALL messages are seen. - $res = $jmap->CallMethods([ - ['Email/get', { 'ids' => [ $jmapmsg->{id} ] }, 'R2' ] - ]); - $jmapmsg = $res->[0][1]{list}[0]; - $keywords = { - '$seen' => JSON::true, - }; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + # Seen must only be set if ALL messages are seen. + $res = $jmap->CallMethods([ + [ 'Email/get', { 'ids' => [ $jmapmsg->{id} ] }, 'R2' ] ]); + $jmapmsg = $res->[0][1]{list}[0]; + $keywords = { '$seen' => JSON::true, }; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); - xlog $self, "Set \\Flagged on message B"; - $store->set_folder('INBOX.B'); - $store->_select(); - $talk->store('1', '+flags', '(\\Flagged)'); + xlog $self, "Set \\Flagged on message B"; + $store->set_folder('INBOX.B'); + $store->_select(); + $talk->store('1', '+flags', '(\\Flagged)'); - # Any other keyword is set if set on any IMAP message of this email. - $res = $jmap->CallMethods([ - ['Email/get', { 'ids' => [ $jmapmsg->{id} ] }, 'R2' ] - ]); - $jmapmsg = $res->[0][1]{list}[0]; - $keywords = { - '$seen' => JSON::true, - '$flagged' => JSON::true, - }; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + # Any other keyword is set if set on any IMAP message of this email. + $res = $jmap->CallMethods([ + [ 'Email/get', { 'ids' => [ $jmapmsg->{id} ] }, 'R2' ] ]); + $jmapmsg = $res->[0][1]{list}[0]; + $keywords = { + '$seen' => JSON::true, + '$flagged' => JSON::true, + }; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_keywords_case_insensitive b/cassandane/tiny-tests/JMAPEmail/email_get_keywords_case_insensitive index bbef9070cb..28a2c94c4b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_keywords_case_insensitive +++ b/cassandane/tiny-tests/JMAPEmail/email_get_keywords_case_insensitive @@ -2,35 +2,38 @@ use Cassandane::Tiny; sub test_email_get_keywords_case_insensitive - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Create IMAP mailbox and message A"; - $talk->create('INBOX.A') || die; - $store->set_folder('INBOX.A'); - $self->make_message('A') || die; + xlog $self, "Create IMAP mailbox and message A"; + $talk->create('INBOX.A') || die; + $store->set_folder('INBOX.A'); + $self->make_message('A') || die; - xlog $self, "Set flag Foo and Flagged on message A"; - $store->set_folder('INBOX.A'); - $talk->store('1', '+flags', '(Foo \\Flagged)'); + xlog $self, "Set flag Foo and Flagged on message A"; + $store->set_folder('INBOX.A'); + $talk->store('1', '+flags', '(Foo \\Flagged)'); - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids'}, - properties => ['keywords'], - }, 'R2' ] - ]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - my $jmapmsg = $res->[1][1]{list}[0]; - my $keywords = { - 'foo' => JSON::true, - '$flagged' => JSON::true, - }; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['keywords'], + }, + 'R2' + ] + ]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + my $jmapmsg = $res->[1][1]{list}[0]; + my $keywords = { + 'foo' => JSON::true, + '$flagged' => JSON::true, + }; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_maxbodyvaluebytes_utf8 b/cassandane/tiny-tests/JMAPEmail/email_get_maxbodyvaluebytes_utf8 index 57367a971e..acfe6746b1 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_maxbodyvaluebytes_utf8 +++ b/cassandane/tiny-tests/JMAPEmail/email_get_maxbodyvaluebytes_utf8 @@ -2,50 +2,53 @@ use Cassandane::Tiny; sub test_email_get_maxbodyvaluebytes_utf8 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - # A body containing a three-byte, two-byte and one-byte UTF-8 char - my $body = "\N{EURO SIGN}\N{CENT SIGN}\N{DOLLAR SIGN}"; - my @wantbodies = ( - [1, ""], - [2, ""], - [3, "\N{EURO SIGN}"], - [4, "\N{EURO SIGN}"], - [5, "\N{EURO SIGN}\N{CENT SIGN}"], - [6, "\N{EURO SIGN}\N{CENT SIGN}\N{DOLLAR SIGN}"], - ); + # A body containing a three-byte, two-byte and one-byte UTF-8 char + my $body = "\N{EURO SIGN}\N{CENT SIGN}\N{DOLLAR SIGN}"; + my @wantbodies = ( + [ 1, "" ], + [ 2, "" ], + [ 3, "\N{EURO SIGN}" ], + [ 4, "\N{EURO SIGN}" ], + [ 5, "\N{EURO SIGN}\N{CENT SIGN}" ], + [ 6, "\N{EURO SIGN}\N{CENT SIGN}\N{DOLLAR SIGN}" ], + ); - utf8::encode($body); - my %params = ( - mime_charset => "utf-8", - body => $body - ); - $self->make_message("1", %params) || die; + utf8::encode($body); + my %params = ( + mime_charset => "utf-8", + body => $body + ); + $self->make_message("1", %params) || die; - xlog $self, "get email id"; - my $res = $jmap->CallMethods([['Email/query', {}, 'R1']]); - my $id = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ] ]); + my $id = $res->[0][1]->{ids}[0]; - for my $tc ( @wantbodies ) { - my $nbytes = $tc->[0]; - my $wantbody = $tc->[1]; + for my $tc (@wantbodies) { + my $nbytes = $tc->[0]; + my $wantbody = $tc->[1]; - xlog $self, "get email"; - my $res = $jmap->CallMethods([ - ['Email/get', { - ids => [ $id ], - properties => [ 'bodyValues' ], - fetchAllBodyValues => JSON::true, - maxBodyValueBytes => $nbytes + 0, - }, "R1"], - ]); - my $msg = $res->[0][1]->{list}[0]; - $self->assert_str_equals($wantbody, $msg->{bodyValues}{'1'}{value}); - } + xlog $self, "get email"; + my $res = $jmap->CallMethods([ + [ + 'Email/get', + { + ids => [$id], + properties => ['bodyValues'], + fetchAllBodyValues => JSON::true, + maxBodyValueBytes => $nbytes + 0, + }, + "R1" + ], + ]); + my $msg = $res->[0][1]->{list}[0]; + $self->assert_str_equals($wantbody, $msg->{bodyValues}{'1'}{value}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_mimeencode b/cassandane/tiny-tests/JMAPEmail/email_get_mimeencode index 43a398b151..c19bb08e20 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_mimeencode +++ b/cassandane/tiny-tests/JMAPEmail/email_get_mimeencode @@ -2,69 +2,77 @@ use Cassandane::Tiny; sub test_email_get_mimeencode - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - my $body = "a body"; + my $body = "a body"; - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => -10)); + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => -10)); - # Thanks to http://dogmamix.com/MimeHeadersDecoder/ for examples + # Thanks to http://dogmamix.com/MimeHeadersDecoder/ for examples - xlog $self, "Generate an email in INBOX via IMAP"; - my %exp_inbox; - my %params = ( - date => $maildate, - from => Cassandane::Address->new( - name => "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?=", - localpart => "keld", - domain => "local" - ), - to => Cassandane::Address->new( - name => "=?US-ASCII?Q?Tom To?=", - localpart => 'tom', - domain => 'local' - ), - messageid => 'fake.123456789@local', - extra_headers => [ - ['x-tra', "foo bar\r\n baz"], - ['sender', "Bla "], - ['x-mood', '=?UTF-8?Q?I feel =E2=98=BA?='], - ], - body => $body - ); + xlog $self, "Generate an email in INBOX via IMAP"; + my %exp_inbox; + my %params = ( + date => $maildate, + from => Cassandane::Address->new( + name => "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?=", + localpart => "keld", + domain => "local" + ), + to => Cassandane::Address->new( + name => "=?US-ASCII?Q?Tom To?=", + localpart => 'tom', + domain => 'local' + ), + messageid => 'fake.123456789@local', + extra_headers => [ + [ 'x-tra', "foo bar\r\n baz" ], + [ 'sender', "Bla " ], + [ 'x-mood', '=?UTF-8?Q?I feel =E2=98=BA?=' ], + ], + body => $body + ); - $self->make_message( - "=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= " . - "=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=", - %params ) || die; + $self->make_message( + "=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= " + . "=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=", + %params + ) || die; - xlog $self, "get email list"; - $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'subject', 'header:x-mood:asText', 'from', 'to' ], - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $msg = $res->[1][1]->{list}[0]; + xlog $self, "get email list"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => [ 'subject', 'header:x-mood:asText', 'from', 'to' ], + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $msg = $res->[1][1]->{list}[0]; - $self->assert_str_equals("If you can read this you understand the example.", $msg->{subject}); - $self->assert_str_equals("I feel \N{WHITE SMILING FACE}", $msg->{'header:x-mood:asText'}); - $self->assert_str_equals("Keld J\N{LATIN SMALL LETTER O WITH STROKE}rn Simonsen", $msg->{from}[0]{name}); - $self->assert_str_equals("Tom To", $msg->{to}[0]{name}); + $self->assert_str_equals("If you can read this you understand the example.", + $msg->{subject}); + $self->assert_str_equals("I feel \N{WHITE SMILING FACE}", + $msg->{'header:x-mood:asText'}); + $self->assert_str_equals( + "Keld J\N{LATIN SMALL LETTER O WITH STROKE}rn Simonsen", + $msg->{from}[0]{name}); + $self->assert_str_equals("Tom To", $msg->{to}[0]{name}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_multimailboxes b/cassandane/tiny-tests/JMAPEmail/email_get_multimailboxes index 3a508d5e91..ad4ea68d77 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_multimailboxes +++ b/cassandane/tiny-tests/JMAPEmail/email_get_multimailboxes @@ -2,42 +2,49 @@ use Cassandane::Tiny; sub test_email_get_multimailboxes - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - my $now = DateTime->now(); - - xlog $self, "Generate an email in INBOX via IMAP"; - my $res = $self->make_message("foo") || die; - my $uid = $res->{attrs}->{uid}; - my $msg; - - xlog $self, "get email"; - $res = $jmap->CallMethods([ - ['Email/query', {}, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2'], - ]); - $msg = $res->[1][1]{list}[0]; - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - - xlog $self, "Create target mailbox"; - $talk->create("INBOX.target"); - - xlog $self, "Copy email into INBOX.target"; - $talk->copy($uid, "INBOX.target"); - - xlog $self, "get email"; - $res = $jmap->CallMethods([ - ['Email/query', {}, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2'], - ]); - $msg = $res->[1][1]{list}[0]; - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(2, scalar keys %{$msg->{mailboxIds}}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + my $now = DateTime->now(); + + xlog $self, "Generate an email in INBOX via IMAP"; + my $res = $self->make_message("foo") || die; + my $uid = $res->{attrs}->{uid}; + my $msg; + + xlog $self, "get email"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + $msg = $res->[1][1]{list}[0]; + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + + xlog $self, "Create target mailbox"; + $talk->create("INBOX.target"); + + xlog $self, "Copy email into INBOX.target"; + $talk->copy($uid, "INBOX.target"); + + xlog $self, "get email"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + $msg = $res->[1][1]{list}[0]; + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(2, scalar keys %{ $msg->{mailboxIds} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_multimailboxes_expunged b/cassandane/tiny-tests/JMAPEmail/email_get_multimailboxes_expunged index 66c5a668ad..0afa16c79a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_multimailboxes_expunged +++ b/cassandane/tiny-tests/JMAPEmail/email_get_multimailboxes_expunged @@ -2,68 +2,79 @@ use Cassandane::Tiny; sub test_email_get_multimailboxes_expunged - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :DelayedExpunge -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : DelayedExpunge { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $now = DateTime->now(); + my $now = DateTime->now(); - xlog $self, "Generate an email in INBOX via IMAP"; - my $res = $self->make_message("foo") || die; - my $uid = $res->{attrs}->{uid}; - my $msg; + xlog $self, "Generate an email in INBOX via IMAP"; + my $res = $self->make_message("foo") || die; + my $uid = $res->{attrs}->{uid}; + my $msg; - xlog $self, "get email"; - $res = $jmap->CallMethods([ - ['Email/query', {}, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2'], - ]); - $msg = $res->[1][1]{list}[0]; - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); + xlog $self, "get email"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + $msg = $res->[1][1]{list}[0]; + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); - xlog $self, "Create target mailbox"; - $talk->create("INBOX.target"); + xlog $self, "Create target mailbox"; + $talk->create("INBOX.target"); - xlog $self, "Copy email into INBOX.target"; - $talk->copy($uid, "INBOX.target"); + xlog $self, "Copy email into INBOX.target"; + $talk->copy($uid, "INBOX.target"); - xlog $self, "get email"; - $res = $jmap->CallMethods([ - ['Email/query', {}, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2'], - ]); - $msg = $res->[1][1]{list}[0]; - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(2, scalar keys %{$msg->{mailboxIds}}); - my $val = join(',', sort keys %{$msg->{mailboxIds}}); + xlog $self, "get email"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + $msg = $res->[1][1]{list}[0]; + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(2, scalar keys %{ $msg->{mailboxIds} }); + my $val = join(',', sort keys %{ $msg->{mailboxIds} }); - xlog $self, "Move the message to target2"; - $talk->create("INBOX.target2"); - $talk->copy($uid, "INBOX.target2"); + xlog $self, "Move the message to target2"; + $talk->create("INBOX.target2"); + $talk->copy($uid, "INBOX.target2"); - xlog $self, "and move it back again!"; - $talk->select("INBOX.target2"); - $talk->move("1:*", "INBOX"); + xlog $self, "and move it back again!"; + $talk->select("INBOX.target2"); + $talk->move("1:*", "INBOX"); - # and finally delete the SECOND copy by UID sorting - xlog $self, "and delete one of them"; - $talk->select("INBOX"); - $talk->store('2', "+flags", "\\Deleted"); - $talk->expunge(); + # and finally delete the SECOND copy by UID sorting + xlog $self, "and delete one of them"; + $talk->select("INBOX"); + $talk->store('2', "+flags", "\\Deleted"); + $talk->expunge(); - xlog $self, "check that email is still in both mailboxes"; - $res = $jmap->CallMethods([ - ['Email/query', {}, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2'], - ]); - $msg = $res->[1][1]{list}[0]; - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(2, scalar keys %{$msg->{mailboxIds}}); - $self->assert_str_equals($val, join(',', sort keys %{$msg->{mailboxIds}})); + xlog $self, "check that email is still in both mailboxes"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + $msg = $res->[1][1]{list}[0]; + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(2, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_str_equals($val, join(',', sort keys %{ $msg->{mailboxIds} })); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_preview b/cassandane/tiny-tests/JMAPEmail/email_get_preview index 992075b162..a9fb06f649 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_preview +++ b/cassandane/tiny-tests/JMAPEmail/email_get_preview @@ -2,32 +2,30 @@ use Cassandane::Tiny; sub test_email_get_preview - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; - xlog $self, "Generate an email in $inbox via IMAP"; - my %exp_sub; - $store->set_folder($inbox); - $store->_select(); - $self->{gen}->set_next_uid(1); + xlog $self, "Generate an email in $inbox via IMAP"; + my %exp_sub; + $store->set_folder($inbox); + $store->_select(); + $self->{gen}->set_next_uid(1); - my $body = "A plain\r\ntext email."; - $exp_sub{A} = $self->make_message("foo", - body => $body - ); + my $body = "A plain\r\ntext email."; + $exp_sub{A} = $self->make_message("foo", body => $body); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); - xlog $self, "get emails"; - $res = $jmap->CallMethods([['Email/get', { ids => $res->[0][1]->{ids} }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get emails"; + $res = $jmap->CallMethods( + [ [ 'Email/get', { ids => $res->[0][1]->{ids} }, "R1" ] ]); + my $msg = $res->[0][1]{list}[0]; - $self->assert_str_equals('A plain text email.', $msg->{preview}); + $self->assert_str_equals('A plain text email.', $msg->{preview}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_preview_html b/cassandane/tiny-tests/JMAPEmail/email_get_preview_html index 7c4e0b29c7..4a7c2fddd8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_preview_html +++ b/cassandane/tiny-tests/JMAPEmail/email_get_preview_html @@ -2,26 +2,31 @@ use Cassandane::Tiny; sub test_email_get_preview_html - :min_version_3_7 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - $self->make_message('test', mime_type => 'text/html', - body => 'hello

world

!'); + $self->make_message( + 'test', + mime_type => 'text/html', + body => 'hello

world

!' + ); - my $res = $jmap->CallMethods([ - ['Email/query', {}, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['preview'], - }, 'R2'] - ]); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['preview'], + }, + 'R2' + ] + ]); - $self->assert_str_equals("hello world !", - $res->[1][1]{list}[0]->{preview}); + $self->assert_str_equals("hello world !", $res->[1][1]{list}[0]->{preview}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_previous_calendarevent b/cassandane/tiny-tests/JMAPEmail/email_get_previous_calendarevent index a6db270911..6630e82411 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_previous_calendarevent +++ b/cassandane/tiny-tests/JMAPEmail/email_get_previous_calendarevent @@ -2,24 +2,22 @@ use Cassandane::Tiny; sub test_email_get_previous_calendarevent - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $instance = $self->{instance}; + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $instance = $self->{instance}; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; - - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: from@local To: to@local Subject: test @@ -73,31 +71,39 @@ END:VCALENDAR --c4683f7a320d4d20902b000486fbdf9b-- EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - properties => ['calendarEvents', 'previousCalendarEvent'], - }, 'R2'], - ], $using); + my $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + properties => [ 'calendarEvents', 'previousCalendarEvent' ], + }, + 'R2' + ], + ], + $using + ); - $self->assert_str_equals('test', - $res->[1][1]{list}[0]{previousCalendarEvent}{title}); - $self->assert_str_equals('updatedTest', - $res->[1][1]{list}[0]{calendarEvents}{2}[0]{title}); + $self->assert_str_equals('test', + $res->[1][1]{list}[0]{previousCalendarEvent}{title}); + $self->assert_str_equals('updatedTest', + $res->[1][1]{list}[0]{calendarEvents}{2}[0]{title}); - $self->assert_str_equals('40d6fe3c-6a51-489e-823e-3ea22f427a3e', - $res->[1][1]{list}[0]{previousCalendarEvent}{uid}); - $self->assert_str_equals('40d6fe3c-6a51-489e-823e-3ea22f427a3e', - $res->[1][1]{list}[0]{calendarEvents}{2}[0]{uid}); + $self->assert_str_equals('40d6fe3c-6a51-489e-823e-3ea22f427a3e', + $res->[1][1]{list}[0]{previousCalendarEvent}{uid}); + $self->assert_str_equals( + '40d6fe3c-6a51-489e-823e-3ea22f427a3e', + $res->[1][1]{list}[0]{calendarEvents}{2}[0]{uid} + ); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_references b/cassandane/tiny-tests/JMAPEmail/email_get_references index 2b2f7174dd..4531400367 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_references +++ b/cassandane/tiny-tests/JMAPEmail/email_get_references @@ -2,30 +2,36 @@ use Cassandane::Tiny; sub test_email_get_references - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $rawReferences = ', '; - my $parsedReferences = [ 'bar', 'baz' ]; + my $rawReferences = ', '; + my $parsedReferences = [ 'bar', 'baz' ]; - $self->make_message("foo", - mime_type => 'text/plain', - extra_headers => [ - ['References', $rawReferences], + $self->make_message( + "foo", + mime_type => 'text/plain', + extra_headers => [ [ 'References', $rawReferences ], ], + body => 'foo', + ) || die; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ + 'references', 'header:references', + 'header:references:asMessageIds' ], - body => 'foo', - ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['references', 'header:references', 'header:references:asMessageIds'], - }, 'R2' ], - ]); - my $msg = $res->[1][1]{list}[0]; - $self->assert_str_equals(' ' . $rawReferences, $msg->{'header:references'}); - $self->assert_deep_equals($parsedReferences, $msg->{'header:references:asMessageIds'}); - $self->assert_deep_equals($parsedReferences, $msg->{references}); + }, + 'R2' + ], + ]); + my $msg = $res->[1][1]{list}[0]; + $self->assert_str_equals(' ' . $rawReferences, $msg->{'header:references'}); + $self->assert_deep_equals($parsedReferences, + $msg->{'header:references:asMessageIds'}); + $self->assert_deep_equals($parsedReferences, $msg->{references}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_shared b/cassandane/tiny-tests/JMAPEmail/email_get_shared index 7f36206a80..a4989bcf47 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_get_shared @@ -2,69 +2,82 @@ use Cassandane::Tiny; sub test_email_get_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Share account - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lr") or die; + # Share account + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lr") or die; - # Create mailbox A - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lr") or die; + # Create mailbox A + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lr") or die; - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - $self->make_message("Email", store => $self->{adminstore}) or die; + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + $self->make_message("Email", store => $self->{adminstore}) or die; - # Copy message to unshared mailbox B - $admintalk->create("user.other.B") or die; - $admintalk->setacl("user.other.B", "cassandane", "") or die; - $admintalk->copy(1, "user.other.B"); + # Copy message to unshared mailbox B + $admintalk->create("user.other.B") or die; + $admintalk->setacl("user.other.B", "cassandane", "") or die; + $admintalk->copy(1, "user.other.B"); - my @fetchEmailMethods = [ - ['Email/query', { - accountId => 'other', - collapseThreads => JSON::true, - }, "R1"], - ['Email/get', { - accountId => 'other', - properties => ['mailboxIds'], - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]; + my @fetchEmailMethods = [ + [ + 'Email/query', + { + accountId => 'other', + collapseThreads => JSON::true, + }, + "R1" + ], + [ + 'Email/get', + { + accountId => 'other', + properties => ['mailboxIds'], + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]; - # Fetch Email - my $res = $jmap->CallMethods(@fetchEmailMethods); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_num_equals(1, scalar keys %{$res->[1][1]{list}[0]{mailboxIds}}); - my $emailId = $res->[1][1]{list}[0]{id}; + # Fetch Email + my $res = $jmap->CallMethods(@fetchEmailMethods); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_num_equals(1, + scalar keys %{ $res->[1][1]{list}[0]{mailboxIds} }); + my $emailId = $res->[1][1]{list}[0]{id}; - # Share mailbox B - $admintalk->setacl("user.other.B", "cassandane", "lr") or die; - $res = $jmap->CallMethods(@fetchEmailMethods); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_num_equals(2, scalar keys %{$res->[1][1]{list}[0]{mailboxIds}}); + # Share mailbox B + $admintalk->setacl("user.other.B", "cassandane", "lr") or die; + $res = $jmap->CallMethods(@fetchEmailMethods); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_num_equals(2, + scalar keys %{ $res->[1][1]{list}[0]{mailboxIds} }); - # Unshare mailboxes A and B - $admintalk->setacl("user.other.A", "cassandane", "") or die; - $admintalk->setacl("user.other.B", "cassandane", "") or die; - $res = $jmap->CallMethods([['Email/get', { - accountId => 'other', - ids => [$emailId], - }, 'R1']]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); - $self->assert_str_equals($emailId, $res->[0][1]{notFound}[0]); + # Unshare mailboxes A and B + $admintalk->setacl("user.other.A", "cassandane", "") or die; + $admintalk->setacl("user.other.B", "cassandane", "") or die; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + ids => [$emailId], + }, + 'R1' + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); + $self->assert_str_equals($emailId, $res->[0][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_size b/cassandane/tiny-tests/JMAPEmail/email_get_size index 7ae051a0bc..da627711f6 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_size +++ b/cassandane/tiny-tests/JMAPEmail/email_get_size @@ -2,24 +2,28 @@ use Cassandane::Tiny; sub test_email_get_size - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - $self->make_message("foo", - mime_type => 'text/plain; charset="UTF-8"', - mime_encoding => 'quoted-printable', - body => '=C2=A1Hola, se=C3=B1or!', - ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['bodyStructure', 'size'], - }, 'R2' ], - ]); + $self->make_message( + "foo", + mime_type => 'text/plain; charset="UTF-8"', + mime_encoding => 'quoted-printable', + body => '=C2=A1Hola, se=C3=B1or!', + ) || die; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ 'bodyStructure', 'size' ], + }, + 'R2' + ], + ]); - my $msg = $res->[1][1]{list}[0]; - $self->assert_num_equals(15, $msg->{bodyStructure}{size}); + my $msg = $res->[1][1]{list}[0]; + $self->assert_num_equals(15, $msg->{bodyStructure}{size}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_trustedsender b/cassandane/tiny-tests/JMAPEmail/email_get_trustedsender index 6a78c5f7ef..b778169b0f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_trustedsender +++ b/cassandane/tiny-tests/JMAPEmail/email_get_trustedsender @@ -2,81 +2,102 @@ use Cassandane::Tiny; sub test_email_get_trustedsender - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - # This is a FastMail-extension + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + # This is a FastMail-extension - my ($self) = @_; - my $jmap = $self->{jmap}; + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - $store->set_folder('INBOX'); + my $store = $self->{store}; + my $talk = $store->get_client(); + $store->set_folder('INBOX'); - my $msg = $self->make_message("foo"); + my $msg = $self->make_message("foo"); - xlog $self, "Assert trustedSender isn't set"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => [ 'id', 'trustedSender', 'keywords' ], - }, 'R2'], - ]); - my $emailId = $res->[0][1]{ids}[0]; - my $email = $res->[1][1]{list}[0]; - $self->assert_null($email->{trustedSender}); + xlog $self, "Assert trustedSender isn't set"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ 'id', 'trustedSender', 'keywords' ], + }, + 'R2' + ], + ]); + my $emailId = $res->[0][1]{ids}[0]; + my $email = $res->[1][1]{list}[0]; + $self->assert_null($email->{trustedSender}); - xlog $self, "Set IsTrusted flag"; - $talk->store('1', '+flags', '($IsTrusted)'); + xlog $self, "Set IsTrusted flag"; + $talk->store('1', '+flags', '($IsTrusted)'); - xlog $self, "Assert trustedSender isn't set"; - $res = $jmap->CallMethods([['Email/get', { - ids => [$emailId], properties => [ 'id', 'trustedSender', 'keywords' ], - }, 'R1']]); - $email = $res->[0][1]{list}[0]; - $self->assert_null($email->{trustedSender}); + xlog $self, "Assert trustedSender isn't set"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'id', 'trustedSender', 'keywords' ], + }, + 'R1' + ] ]); + $email = $res->[0][1]{list}[0]; + $self->assert_null($email->{trustedSender}); - xlog $self, "Set zero-length trusted annotation"; - my $annot = '/vendor/messagingengine.com/trusted'; - my $ret = $talk->store('1', 'annotation', [ - $annot, ['value.shared', { Quote => '' }] - ]); - if (not $ret) { - xlog $self, "Could not set $annot annotation. Aborting."; - return; - } + xlog $self, "Set zero-length trusted annotation"; + my $annot = '/vendor/messagingengine.com/trusted'; + my $ret = $talk->store('1', 'annotation', + [ $annot, [ 'value.shared', { Quote => '' } ] ]); + if (not $ret) { + xlog $self, "Could not set $annot annotation. Aborting."; + return; + } - xlog $self, "Assert trustedSender isn't set"; - $res = $jmap->CallMethods([['Email/get', { - ids => [$emailId], properties => [ 'id', 'trustedSender', 'keywords' ], - }, 'R1']]); - $email = $res->[0][1]{list}[0]; - $self->assert_null($email->{trustedSender}); + xlog $self, "Assert trustedSender isn't set"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'id', 'trustedSender', 'keywords' ], + }, + 'R1' + ] ]); + $email = $res->[0][1]{list}[0]; + $self->assert_null($email->{trustedSender}); - xlog $self, "Set trusted annotation"; - $ret = $talk->store('1', 'annotation', [ - $annot, ['value.shared', { Quote => 'bar' }] - ]); - if (not $ret) { - xlog $self, "Could not set $annot annotation. Aborting."; - return; - } + xlog $self, "Set trusted annotation"; + $ret = $talk->store('1', 'annotation', + [ $annot, [ 'value.shared', { Quote => 'bar' } ] ]); + if (not $ret) { + xlog $self, "Could not set $annot annotation. Aborting."; + return; + } - xlog $self, "Assert trustedSender is set"; - $res = $jmap->CallMethods([['Email/get', { - ids => [$emailId], properties => [ 'id', 'trustedSender', 'keywords' ], - }, 'R1']]); - $email = $res->[0][1]{list}[0]; - $self->assert_str_equals('bar', $email->{trustedSender}); + xlog $self, "Assert trustedSender is set"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'id', 'trustedSender', 'keywords' ], + }, + 'R1' + ] ]); + $email = $res->[0][1]{list}[0]; + $self->assert_str_equals('bar', $email->{trustedSender}); - xlog $self, "Remove IsTrusted flag"; - $talk->store('1', '-flags', '($IsTrusted)'); + xlog $self, "Remove IsTrusted flag"; + $talk->store('1', '-flags', '($IsTrusted)'); - xlog $self, "Assert trustedSender isn't set"; - $res = $jmap->CallMethods([['Email/get', { - ids => [$emailId], properties => [ 'id', 'trustedSender', 'keywords' ], - }, 'R1']]); - $email = $res->[0][1]{list}[0]; - $self->assert_null($email->{trustedSender}); + xlog $self, "Assert trustedSender isn't set"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'id', 'trustedSender', 'keywords' ], + }, + 'R1' + ] ]); + $email = $res->[0][1]{list}[0]; + $self->assert_null($email->{trustedSender}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_utf8_domain b/cassandane/tiny-tests/JMAPEmail/email_get_utf8_domain index ebc2a923e9..15fb83cc88 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_utf8_domain +++ b/cassandane/tiny-tests/JMAPEmail/email_get_utf8_domain @@ -2,37 +2,43 @@ use Cassandane::Tiny; sub test_email_get_utf8_domain - :min_version_3_9 :needs_component_jmap :NoMunge8Bit :RFC2047_UTF8 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_component_jmap : NoMunge8Bit : RFC2047_UTF8 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - open(my $F, "data/mime/utf8-domain.bin") || die $!; - $imap->append('INBOX', $F) || die $@; - close($F); + open(my $F, "data/mime/utf8-domain.bin") || die $!; + $imap->append('INBOX', $F) || die $@; + close($F); - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['from', 'subject'], - }, 'R2'], - ]); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => [ 'from', 'subject' ], + }, + 'R2' + ], + ]); -use utf8; - $self->assert_deep_equals([{ - name => 'J. Besteiro', - email => 'jb@xn--julin-0qa.example.com', - }], $res->[1][1]{list}[0]{from}); -no utf8; + use utf8; + $self->assert_deep_equals( + [ { + name => 'J. Besteiro', + email => 'jb@xn--julin-0qa.example.com', + } ], + $res->[1][1]{list}[0]{from} + ); + no utf8; - $imap->select('INBOX'); - $res = $imap->fetch('1:*', 'ENVELOPE'); - $self->assert_str_equals('"J. Besteiro" ', - $res->{1}{envelope}{From}); + $imap->select('INBOX'); + $res = $imap->fetch('1:*', 'ENVELOPE'); + $self->assert_str_equals('"J. Besteiro" ', + $res->{1}{envelope}{From}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_get_utf8body_base64_with_replacement_char b/cassandane/tiny-tests/JMAPEmail/email_get_utf8body_base64_with_replacement_char index a60ba74ee1..6a86ba8d22 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_get_utf8body_base64_with_replacement_char +++ b/cassandane/tiny-tests/JMAPEmail/email_get_utf8body_base64_with_replacement_char @@ -2,33 +2,37 @@ use Cassandane::Tiny; sub test_email_get_utf8body_base64_with_replacement_char - :min_version_3_5 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - # MIME message contains a correctly encoded emoji and one UTF-8 - # replacement character. The latter must not cause Cyrus to - # attempt guessing the source charset or report an encoding error. - open(my $F, '<', 'data/mime/utf8-base64-replacement.eml') || die $!; - $imap->append('INBOX', $F) || die $@; - close($F); + # MIME message contains a correctly encoded emoji and one UTF-8 + # replacement character. The latter must not cause Cyrus to + # attempt guessing the source charset or report an encoding error. + open(my $F, '<', 'data/mime/utf8-base64-replacement.eml') || die $!; + $imap->append('INBOX', $F) || die $@; + close($F); - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - properties => ['bodyValues'], - }, 'R2'], - ]); - $self->assert_equals(JSON::false, - $res->[1][1]{list}[0]{bodyValues}{1}{isEncodingProblem}); - $self->assert_str_equals("Hello \N{GRINNING FACE}, World \N{REPLACEMENT CHARACTER} !\n", - $res->[1][1]{list}[0]{bodyValues}{1}{value}); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + properties => ['bodyValues'], + }, + 'R2' + ], + ]); + $self->assert_equals(JSON::false, + $res->[1][1]{list}[0]{bodyValues}{1}{isEncodingProblem}); + $self->assert_str_equals( + "Hello \N{GRINNING FACE}, World \N{REPLACEMENT CHARACTER} !\n", + $res->[1][1]{list}[0]{bodyValues}{1}{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import b/cassandane/tiny-tests/JMAPEmail/email_import index 01cbf2c364..37ae54bea7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import +++ b/cassandane/tiny-tests/JMAPEmail/email_import @@ -2,100 +2,122 @@ use Cassandane::Tiny; sub test_email_import - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $inbox = $self->getinbox()->{id}; - $self->assert_not_null($inbox); + my $inbox = $self->getinbox()->{id}; + $self->assert_not_null($inbox); - # Generate an embedded email to get a blob id - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "Content-Disposition: inline\r\n" . "\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . "Return-Path: \r\n" - . "Mime-Version: 1.0\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Transfer-Encoding: 7bit\r\n" - . "Subject: bar\r\n" - . "From: Ava T. Nguyen \r\n" - . "Message-ID: \r\n" - . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "An embedded email" - . "\r\n--sub--\r\n", - ) || die; + # Generate an embedded email to get a blob id + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "Return-Path: \r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" + . "Subject: bar\r\n" + . "From: Ava T. Nguyen \r\n" + . "Message-ID: \r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: Test User \r\n" . "\r\n" + . "An embedded email" + . "\r\n--sub--\r\n", + ) || die; - xlog $self, "get blobId"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['attachments'], - }, 'R2' ], - ]); - my $blobid = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; - $self->assert_not_null($blobid); + xlog $self, "get blobId"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['attachments'], + }, + 'R2' + ], + ]); + my $blobid = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; + $self->assert_not_null($blobid); - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $drafts = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($drafts); + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + my $drafts = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($drafts); - xlog $self, "import and get email from blob $blobid"; - $res = $jmap->CallMethods([['Email/import', { + xlog $self, "import and get email from blob $blobid"; + $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$drafts => JSON::true}, - keywords => { '$draft' => JSON::true }, - }, + "1" => { + blobId => $blobid, + mailboxIds => { $drafts => JSON::true }, + keywords => { '$draft' => JSON::true }, + }, }, - }, "R1"], ["Email/get", { ids => ["#1"] }, "R2" ]]); + }, + "R1" + ], + [ "Email/get", { ids => ["#1"] }, "R2" ] + ]); - $self->assert_str_equals("Email/import", $res->[0][0]); - my $msg = $res->[0][1]->{created}{"1"}; - $self->assert_not_null($msg); + $self->assert_str_equals("Email/import", $res->[0][0]); + my $msg = $res->[0][1]->{created}{"1"}; + $self->assert_not_null($msg); - $self->assert_str_equals("Email/get", $res->[1][0]); - $self->assert_str_equals($msg->{id}, $res->[1][1]{list}[0]->{id}); + $self->assert_str_equals("Email/get", $res->[1][0]); + $self->assert_str_equals($msg->{id}, $res->[1][1]{list}[0]->{id}); - xlog $self, "load email"; - $res = $jmap->CallMethods([['Email/get', { ids => [$msg->{id}] }, "R1"]]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{list}[0]->{mailboxIds}}); - $self->assert_not_null($res->[0][1]{list}[0]->{mailboxIds}{$drafts}); + xlog $self, "load email"; + $res + = $jmap->CallMethods([ [ 'Email/get', { ids => [ $msg->{id} ] }, "R1" ] ]); + $self->assert_num_equals(1, + scalar keys %{ $res->[0][1]{list}[0]->{mailboxIds} }); + $self->assert_not_null($res->[0][1]{list}[0]->{mailboxIds}{$drafts}); - xlog $self, "import existing email (expect email exists error)"; - $res = $jmap->CallMethods([['Email/import', { - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$drafts => JSON::true, $inbox => JSON::true}, - keywords => { '$draft' => JSON::true }, - }, + xlog $self, "import existing email (expect email exists error)"; + $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $drafts => JSON::true, $inbox => JSON::true }, + keywords => { '$draft' => JSON::true }, }, - }, "R1"]]); - $self->assert_str_equals("Email/import", $res->[0][0]); - $self->assert_str_equals("alreadyExists", $res->[0][1]->{notCreated}{"1"}{type}); - $self->assert_not_null($res->[0][1]->{notCreated}{"1"}{existingId}); + }, + }, + "R1" + ] ]); + $self->assert_str_equals("Email/import", $res->[0][0]); + $self->assert_str_equals("alreadyExists", + $res->[0][1]->{notCreated}{"1"}{type}); + $self->assert_not_null($res->[0][1]->{notCreated}{"1"}{existingId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_encoded_contenttype b/cassandane/tiny-tests/JMAPEmail/email_import_encoded_contenttype index fef50a9634..5eaa14bf07 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_encoded_contenttype +++ b/cassandane/tiny-tests/JMAPEmail/email_import_encoded_contenttype @@ -2,20 +2,19 @@ use Cassandane::Tiny; sub test_email_import_encoded_contenttype - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - # Very old macOS Mail.app versions encode the complete - # Content-Type header value, when they really should - # just encode its file name parameter value. - # See: https://github.com/cyrusimap/cyrus-imapd/issues/2622 + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + # Very old macOS Mail.app versions encode the complete + # Content-Type header value, when they really should + # just encode its file name parameter value. + # See: https://github.com/cyrusimap/cyrus-imapd/issues/2622 - my ($self) = @_; - my $jmap = $self->{jmap}; + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $email = <<'EOF'; + my $email = <<'EOF'; From: example@example.com To: example@example.biz Subject: This is a test @@ -37,21 +36,34 @@ data --123456789-- EOF - $email =~ s/\r?\n/\r\n/gs; - my $blobId = $jmap->Upload($email, "message/rfc822")->{blobId}; + $email =~ s/\r?\n/\r\n/gs; + my $blobId = $jmap->Upload($email, "message/rfc822")->{blobId}; - my $inboxId = $self->getinbox()->{id}; + my $inboxId = $self->getinbox()->{id}; - my $res = $jmap->CallMethods([['Email/import', { + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobId, - mailboxIds => {$inboxId => JSON::true}, - }, + "1" => { + blobId => $blobId, + mailboxIds => { $inboxId => JSON::true }, + }, }, - }, "R1"], ["Email/get", { ids => ["#1", "#2"], properties => ['bodyStructure'] }, "R2" ]]); + }, + "R1" + ], + [ + "Email/get", { ids => [ "#1", "#2" ], properties => ['bodyStructure'] }, + "R2" + ] + ]); - my $msg = $res->[1][1]{list}[0]; - $self->assert_equals('image/png', $msg->{bodyStructure}{subParts}[1]{type}); - $self->assert_equals("k\N{LATIN SMALL LETTER A WITH DIAERESIS}fer.png", $msg->{bodyStructure}{subParts}[1]{name}); + my $msg = $res->[1][1]{list}[0]; + $self->assert_equals('image/png', $msg->{bodyStructure}{subParts}[1]{type}); + $self->assert_equals( + "k\N{LATIN SMALL LETTER A WITH DIAERESIS}fer.png", + $msg->{bodyStructure}{subParts}[1]{name} + ); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_error b/cassandane/tiny-tests/JMAPEmail/email_import_error index d4435cde64..20c2c4fcff 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_error +++ b/cassandane/tiny-tests/JMAPEmail/email_import_error @@ -2,36 +2,43 @@ use Cassandane::Tiny; sub test_email_import_error - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - my $res = $jmap->CallMethods([['Email/import', { emails => "nope" }, 'R1' ]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - $self->assert_str_equals('emails', $res->[0][1]{arguments}[0]); + my $res + = $jmap->CallMethods([ [ 'Email/import', { emails => "nope" }, 'R1' ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + $self->assert_str_equals('emails', $res->[0][1]{arguments}[0]); - $res = $jmap->CallMethods([['Email/import', { emails => { 1 => "nope" }}, 'R1' ]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - $self->assert_str_equals('emails/1', $res->[0][1]{arguments}[0]); + $res = $jmap->CallMethods( + [ [ 'Email/import', { emails => { 1 => "nope" } }, 'R1' ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + $self->assert_str_equals('emails/1', $res->[0][1]{arguments}[0]); - $res = $jmap->CallMethods([['Email/import', { - emails => { - "1" => { - blobId => "nope", - mailboxIds => {$inboxid => JSON::true}, - }, + $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => "nope", + mailboxIds => { $inboxid => JSON::true }, }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - $self->assert_str_equals('Email/import', $res->[0][0]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notCreated}{1}{type}); - $self->assert_str_equals('blobId', $res->[0][1]{notCreated}{1}{properties}[0]); + $self->assert_str_equals('Email/import', $res->[0][0]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{1}{type}); + $self->assert_str_equals('blobId', + $res->[0][1]{notCreated}{1}{properties}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_has_attachment b/cassandane/tiny-tests/JMAPEmail/email_import_has_attachment index 5792a81cb5..ca0462a144 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_has_attachment +++ b/cassandane/tiny-tests/JMAPEmail/email_import_has_attachment @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_email_import_has_attachment - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $emailSimple = <<'EOF'; + my $emailSimple = <<'EOF'; From: example@example.com To: example@example.biz Subject: This is a test @@ -20,10 +19,10 @@ MIME-Version: 1.0 This is a very simple message. EOF - $emailSimple =~ s/\r?\n/\r\n/gs; - my $blobIdSimple = $jmap->Upload($emailSimple, "message/rfc822")->{blobId}; + $emailSimple =~ s/\r?\n/\r\n/gs; + my $blobIdSimple = $jmap->Upload($emailSimple, "message/rfc822")->{blobId}; - my $emailMixed = <<'EOF'; + my $emailMixed = <<'EOF'; From: example@example.com To: example@example.biz Subject: This is a test @@ -45,26 +44,33 @@ data --123456789-- EOF - $emailMixed =~ s/\r?\n/\r\n/gs; - my $blobIdMixed = $jmap->Upload($emailMixed, "message/rfc822")->{blobId}; + $emailMixed =~ s/\r?\n/\r\n/gs; + my $blobIdMixed = $jmap->Upload($emailMixed, "message/rfc822")->{blobId}; - my $inboxId = $self->getinbox()->{id}; + my $inboxId = $self->getinbox()->{id}; - my $res = $jmap->CallMethods([['Email/import', { + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobIdSimple, - mailboxIds => {$inboxId => JSON::true}, - }, - "2" => { - blobId => $blobIdMixed, - mailboxIds => {$inboxId => JSON::true}, - }, + "1" => { + blobId => $blobIdSimple, + mailboxIds => { $inboxId => JSON::true }, + }, + "2" => { + blobId => $blobIdMixed, + mailboxIds => { $inboxId => JSON::true }, + }, }, - }, "R1"], ["Email/get", { ids => ["#1", "#2"] }, "R2" ]]); + }, + "R1" + ], + [ "Email/get", { ids => [ "#1", "#2" ] }, "R2" ] + ]); - my $msgSimple = $res->[1][1]{list}[0]; - $self->assert_equals(JSON::false, $msgSimple->{hasAttachment}); - my $msgMixed = $res->[1][1]{list}[1]; - $self->assert_equals(JSON::true, $msgMixed->{hasAttachment}); + my $msgSimple = $res->[1][1]{list}[0]; + $self->assert_equals(JSON::false, $msgSimple->{hasAttachment}); + my $msgMixed = $res->[1][1]{list}[1]; + $self->assert_equals(JSON::true, $msgMixed->{hasAttachment}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_issue2918 b/cassandane/tiny-tests/JMAPEmail/email_import_issue2918 index fc8999a454..10eeef6269 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_issue2918 +++ b/cassandane/tiny-tests/JMAPEmail/email_import_issue2918 @@ -2,26 +2,28 @@ use Cassandane::Tiny; sub test_email_import_issue2918 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $binary = slurp_file(abs_path('data/mime/issue2918.eml')); - my $data = $jmap->Upload($binary, "message/rfc822"); - my $blobId = $data->{blobId}; + my $binary = slurp_file(abs_path('data/mime/issue2918.eml')); + my $data = $jmap->Upload($binary, "message/rfc822"); + my $blobId = $data->{blobId}; - # Not crashing here is enough. + # Not crashing here is enough. - my $res = $jmap->CallMethods([ - ['Email/import', { - emails => { - "1" => { - blobId => $blobId, - mailboxIds => { - '$inbox' => JSON::true}, - }, - }, - }, "R1"] - ]); + my $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobId, + mailboxIds => { + '$inbox' => JSON::true + }, + }, + }, + }, + "R1" + ] ]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_issue3122 b/cassandane/tiny-tests/JMAPEmail/email_import_issue3122 index 3e16f9326b..635b6a53c9 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_issue3122 +++ b/cassandane/tiny-tests/JMAPEmail/email_import_issue3122 @@ -2,26 +2,28 @@ use Cassandane::Tiny; sub test_email_import_issue3122 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $binary = slurp_file(abs_path('data/mime/msg1.eml')); - my $data = $jmap->Upload($binary, "message/rfc822"); - my $blobId = $data->{blobId}; + my $binary = slurp_file(abs_path('data/mime/msg1.eml')); + my $data = $jmap->Upload($binary, "message/rfc822"); + my $blobId = $data->{blobId}; - # Not crashing here is enough. + # Not crashing here is enough. - my $res = $jmap->CallMethods([ - ['Email/import', { - emails => { - "1" => { - blobId => $blobId, - mailboxIds => { - '$inbox' => JSON::true}, - }, - }, - }, "R1"] - ]); + my $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobId, + mailboxIds => { + '$inbox' => JSON::true + }, + }, + }, + }, + "R1" + ] ]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_mailboxid_by_role b/cassandane/tiny-tests/JMAPEmail/email_import_mailboxid_by_role index 9ee7012099..0481a578b8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_mailboxid_by_role +++ b/cassandane/tiny-tests/JMAPEmail/email_import_mailboxid_by_role @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_import_mailboxid_by_role - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -18,38 +17,51 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $draftsMboxId = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsMboxId); + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + my $draftsMboxId = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftsMboxId); - xlog $self, "import email from blob $blobid"; - $res = eval { - $jmap->CallMethods([['Email/import', { - emails => { - "1" => { - blobId => $blobid, - mailboxIds => { - '$drafts'=> JSON::true - }, - keywords => { - '$draft' => JSON::true, - }, - }, + xlog $self, "import email from blob $blobid"; + $res = eval { + $jmap->CallMethods([ + [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { + '$drafts' => JSON::true + }, + keywords => { + '$draft' => JSON::true, + }, }, - }, "R1"], ['Email/get', {ids => ["#1"]}, "R2"]]); - }; + }, + }, + "R1" + ], + [ 'Email/get', { ids => ["#1"] }, "R2" ] + ]); + }; - $self->assert_str_equals("Email/import", $res->[0][0]); - $self->assert_not_null($res->[1][1]{list}[0]->{mailboxIds}{$draftsMboxId}); + $self->assert_str_equals("Email/import", $res->[0][0]); + $self->assert_not_null($res->[1][1]{list}[0]->{mailboxIds}{$draftsMboxId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_no_keywords b/cassandane/tiny-tests/JMAPEmail/email_import_no_keywords index c2a4bd485c..cdd979647d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_no_keywords +++ b/cassandane/tiny-tests/JMAPEmail/email_import_no_keywords @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_import_no_keywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -18,22 +17,25 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; - my $mboxid = $self->getinbox()->{id}; + my $mboxid = $self->getinbox()->{id}; - my $req = ['Email/import', { - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$mboxid => JSON::true}, - }, - }, - }, "R1" - ]; - xlog $self, "import email from blob $blobid"; - my $res = eval { $jmap->CallMethods([$req]) }; - $self->assert(exists $res->[0][1]->{created}{"1"}); + my $req = [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $mboxid => JSON::true }, + }, + }, + }, + "R1" + ]; + xlog $self, "import email from blob $blobid"; + my $res = eval { $jmap->CallMethods([$req]) }; + $self->assert(exists $res->[0][1]->{created}{"1"}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_received_at b/cassandane/tiny-tests/JMAPEmail/email_import_received_at index 688c0350e6..2a6c8c8996 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_received_at +++ b/cassandane/tiny-tests/JMAPEmail/email_import_received_at @@ -2,86 +2,94 @@ use Cassandane::Tiny; sub test_email_import_received_at - :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my ($maj, $min) = Cassandane::Instance->get_version(); + : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my ($maj, $min) = Cassandane::Instance->get_version(); - my @testCases = ({ - desc => 'receivedAt set by client', - headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n", - receivedAt => '2022-02-01T12:00:00Z', - wantSentAt => '2022-01-01T01:00:00+11:00', - wantReceivedAt => '2022-02-01T12:00:00Z', - }, { - desc => 'receivedAt set by client', - headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" . - "Received: from foo ([192.168.0.1]) by bar (Baz); Mon, 15 Aug 2022 07:49:01 -0400\r\n", - receivedAt => '2022-02-01T12:00:00Z', - wantSentAt => '2022-01-01T01:00:00+11:00', - wantReceivedAt => '2022-02-01T12:00:00Z', - }, { - desc => 'receivedAt from Received header', - creationId => 'receivedAtFromReceivedHeader', - headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" . - "Received: from foo ([192.168.0.1]) by bar (Baz); Mon, 15 Aug 2022 07:49:01 -0400\r\n", - wantSentAt => '2022-01-01T01:00:00+11:00', - wantReceivedAt => '2022-08-15T11:49:01Z', - skipVersionBefore => qw(3,7), - }, { - desc => 'receivedAt from first Received header', - headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" . - "Received: from rcv1 ([192.168.0.1]) by bar (Baz); Mon, 15 Aug 2022 07:49:01 -0400\r\n" . - "Received: from rcv2 ([192.168.0.2]) by tux (Qux); Sat, 13 Aug 2022 12:01:10 -0200\r\n" . - "Received: from rcv3 ([192.168.0.3]) by baz (Hkl); Tue, 16 Aug 2022 13:01:10 -0200\r\n", - wantSentAt => '2022-01-01T01:00:00+11:00', - wantReceivedAt => '2022-08-15T11:49:01Z', - skipVersionBefore => qw(3,7), - }, { - desc => 'receivedAt from Date header', - headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n", - wantSentAt => '2022-01-01T01:00:00+11:00', - wantReceivedAt => '2021-12-31T14:00:00Z', - skipVersionBefore => qw(3,7), - }, { - desc => 'not set', - wantSentAt => undef, - wantReceivedAt => undef, - }, { - desc => 'receivedAt from first valid Received header', - headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" . - "Received: from rcv1 ([192.168.0.1]) by bar (Baz); invalid datetime\r\n" . - "Received: from rcv2 ([192.168.0.2]) by tux (Qux); Sat, 13 Aug 2022 12:01:10 -0200\r\n" . - "Received: from rcv3 ([192.168.0.3]) by baz (Hkl); Sat, 13 Aug 2022 12:00:45 -0200\r\n", - wantSentAt => '2022-01-01T01:00:00+11:00', - wantReceivedAt => '2022-08-13T14:01:10Z', - skipVersionBefore => qw(3,9), - }, { - desc => 'receivedAt from X-DeliveredInternalDate header', - headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" . - "Received: from rcv1 ([192.168.0.2]) by tux (Qux); Sat, 13 Aug 2022 12:01:10 -0200\r\n" . - "Received: from rcv2 ([192.168.0.3]) by baz (Hkl); Sat, 13 Aug 2022 12:00:45 -0200\r\n" . - "X-DeliveredInternalDate: Mon, 15 Aug 2022 13:00:45 -0200\r\n", - , - wantSentAt => '2022-01-01T01:00:00+11:00', - wantReceivedAt => '2022-08-15T15:00:45Z', - skipVersionBefore => qw(3,9), - }); + my @testCases = ( + { + desc => 'receivedAt set by client', + headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n", + receivedAt => '2022-02-01T12:00:00Z', + wantSentAt => '2022-01-01T01:00:00+11:00', + wantReceivedAt => '2022-02-01T12:00:00Z', + }, + { + desc => 'receivedAt set by client', + headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" + . "Received: from foo ([192.168.0.1]) by bar (Baz); Mon, 15 Aug 2022 07:49:01 -0400\r\n", + receivedAt => '2022-02-01T12:00:00Z', + wantSentAt => '2022-01-01T01:00:00+11:00', + wantReceivedAt => '2022-02-01T12:00:00Z', + }, + { + desc => 'receivedAt from Received header', + creationId => 'receivedAtFromReceivedHeader', + headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" + . "Received: from foo ([192.168.0.1]) by bar (Baz); Mon, 15 Aug 2022 07:49:01 -0400\r\n", + wantSentAt => '2022-01-01T01:00:00+11:00', + wantReceivedAt => '2022-08-15T11:49:01Z', + skipVersionBefore => qw(3,7), + }, + { + desc => 'receivedAt from first Received header', + headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" + . "Received: from rcv1 ([192.168.0.1]) by bar (Baz); Mon, 15 Aug 2022 07:49:01 -0400\r\n" + . "Received: from rcv2 ([192.168.0.2]) by tux (Qux); Sat, 13 Aug 2022 12:01:10 -0200\r\n" + . "Received: from rcv3 ([192.168.0.3]) by baz (Hkl); Tue, 16 Aug 2022 13:01:10 -0200\r\n", + wantSentAt => '2022-01-01T01:00:00+11:00', + wantReceivedAt => '2022-08-15T11:49:01Z', + skipVersionBefore => qw(3,7), + }, + { + desc => 'receivedAt from Date header', + headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n", + wantSentAt => '2022-01-01T01:00:00+11:00', + wantReceivedAt => '2021-12-31T14:00:00Z', + skipVersionBefore => qw(3,7), + }, + { + desc => 'not set', + wantSentAt => undef, + wantReceivedAt => undef, + }, + { + desc => 'receivedAt from first valid Received header', + headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" + . "Received: from rcv1 ([192.168.0.1]) by bar (Baz); invalid datetime\r\n" + . "Received: from rcv2 ([192.168.0.2]) by tux (Qux); Sat, 13 Aug 2022 12:01:10 -0200\r\n" + . "Received: from rcv3 ([192.168.0.3]) by baz (Hkl); Sat, 13 Aug 2022 12:00:45 -0200\r\n", + wantSentAt => '2022-01-01T01:00:00+11:00', + wantReceivedAt => '2022-08-13T14:01:10Z', + skipVersionBefore => qw(3,9), + }, + { + desc => 'receivedAt from X-DeliveredInternalDate header', + headers => "Date: Sat, 1 Jan 2022 01:00:00 +1100\r\n" + . "Received: from rcv1 ([192.168.0.2]) by tux (Qux); Sat, 13 Aug 2022 12:01:10 -0200\r\n" + . "Received: from rcv2 ([192.168.0.3]) by baz (Hkl); Sat, 13 Aug 2022 12:00:45 -0200\r\n" + . "X-DeliveredInternalDate: Mon, 15 Aug 2022 13:00:45 -0200\r\n", - while (my ($i, $tc) = each @testCases) { + wantSentAt => '2022-01-01T01:00:00+11:00', + wantReceivedAt => '2022-08-15T15:00:45Z', + skipVersionBefore => qw(3,9), + } + ); + + while (my ($i, $tc) = each @testCases) { - my ($needMaj, $needMin) = $tc->{skipVersionBefore} || qw(0,0); - if ($maj < $needMaj || ($maj == $needMaj && $min < $needMin)) { - xlog "maj=$maj needMaj=$needMaj min=$min needMin=$needMin"; - xlog $self, "Skipping test $tc->{creationId}"; - next; - } + my ($needMaj, $needMin) = $tc->{skipVersionBefore} || qw(0,0); + if ($maj < $needMaj || ($maj == $needMaj && $min < $needMin)) { + xlog "maj=$maj needMaj=$needMaj min=$min needMin=$needMin"; + xlog $self, "Skipping test $tc->{creationId}"; + next; + } - xlog $self, "Running test $tc->{creationId}"; + xlog $self, "Running test $tc->{creationId}"; - my $mime = $tc->{headers} || ''; - $mime .= <<'EOF'; + my $mime = $tc->{headers} || ''; + $mime .= <<'EOF'; From: sender@local To: receiver@local Subject: test @@ -89,54 +97,60 @@ MIME-Version: 1.0 Content-Type: text/plain; charset='UTF-8' Content-Transfer-Encoding: quoted-printable EOF - $mime =~ s/\r?\n/\r\n/gs; - $mime .= "\r\n"; - $mime .= $tc->{desc} || 'foo'; + $mime =~ s/\r?\n/\r\n/gs; + $mime .= "\r\n"; + $mime .= $tc->{desc} || 'foo'; - xlog $self, "Upload blob"; - my $blobId = ($jmap->Upload($mime, 'message/rfc822'))->{blobId}; - $self->assert_not_null($blobId); + xlog $self, "Upload blob"; + my $blobId = ($jmap->Upload($mime, 'message/rfc822'))->{blobId}; + $self->assert_not_null($blobId); - my $creationId = "email" . ($i + 1); + my $creationId = "email" . ($i + 1); - xlog $self, "Import $creationId" . (": $tc->{desc}" || ''); - my $res = $jmap->CallMethods([ - ['Email/import', { - emails => { - $creationId => { - blobId => $blobId, - mailboxIds => { - '$inbox' => JSON::true - }, - receivedAt => $tc->{receivedAt}, - }, - }, - }, 'R1'], - ['Email/get', { - ids => [ - "#$creationId", - ], - properties => ['receivedAt', 'sentAt'], - }, 'R2'] - ]); + xlog $self, "Import $creationId" . (": $tc->{desc}" || ''); + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { + emails => { + $creationId => { + blobId => $blobId, + mailboxIds => { + '$inbox' => JSON::true + }, + receivedAt => $tc->{receivedAt}, + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [ "#$creationId", ], + properties => [ 'receivedAt', 'sentAt' ], + }, + 'R2' + ] + ]); - $self->assert_not_null($res->[0][1]{created}{$creationId}); + $self->assert_not_null($res->[0][1]{created}{$creationId}); - xlog $self, "Assert sentAt"; - if ($tc->{wantSentAt}) { - $self->assert_str_equals($tc->{wantSentAt}, - $res->[1][1]{list}[0]{sentAt}); - } else { - $self->assert_null($res->[1][1]{list}[0]{sentAt}); - } + xlog $self, "Assert sentAt"; + if ($tc->{wantSentAt}) { + $self->assert_str_equals($tc->{wantSentAt}, + $res->[1][1]{list}[0]{sentAt}); + } else { + $self->assert_null($res->[1][1]{list}[0]{sentAt}); + } - xlog $self, "Assert receivedAt"; - $self->assert_not_null($res->[1][1]{list}[0]{receivedAt}); - if ($tc->{wantReceivedAt}) { - $self->assert_str_equals($tc->{wantReceivedAt}, - $res->[1][1]{list}[0]{receivedAt}); - } else { - $self->assert_not_null($res->[1][1]{list}[0]{receivedAt}); - } + xlog $self, "Assert receivedAt"; + $self->assert_not_null($res->[1][1]{list}[0]{receivedAt}); + if ($tc->{wantReceivedAt}) { + $self->assert_str_equals($tc->{wantReceivedAt}, + $res->[1][1]{list}[0]{receivedAt}); + } else { + $self->assert_not_null($res->[1][1]{list}[0]{receivedAt}); } + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_shared b/cassandane/tiny-tests/JMAPEmail/email_import_shared index f3256ee949..7a7e31c56d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_import_shared @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_email_import_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); - # Create user and share mailbox - xlog $self, "create shared mailbox"; - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lkrwpsintex") or die; + # Create user and share mailbox + xlog $self, "create shared mailbox"; + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lkrwpsintex") or die; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -24,25 +23,28 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822", "foo"); - my $blobid = $data->{blobId}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822", "foo"); + my $blobid = $data->{blobId}; - my $mboxid = $self->getinbox({accountId => 'foo'})->{id}; + my $mboxid = $self->getinbox({ accountId => 'foo' })->{id}; - my $req = ['Email/import', { - accountId => 'foo', - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$mboxid => JSON::true}, - keywords => { }, - }, - }, - }, "R1" - ]; + my $req = [ + 'Email/import', + { + accountId => 'foo', + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $mboxid => JSON::true }, + keywords => {}, + }, + }, + }, + "R1" + ]; - xlog $self, "import email from blob $blobid"; - my $res = eval { $jmap->CallMethods([$req]) }; - $self->assert(exists $res->[0][1]->{created}{"1"}); + xlog $self, "import email from blob $blobid"; + my $res = eval { $jmap->CallMethods([$req]) }; + $self->assert(exists $res->[0][1]->{created}{"1"}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_singlecopy b/cassandane/tiny-tests/JMAPEmail/email_import_singlecopy index 484b7e1733..56c3a7a897 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_singlecopy +++ b/cassandane/tiny-tests/JMAPEmail/email_import_singlecopy @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_import_singlecopy - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -18,51 +17,61 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsmbox); + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftsmbox); - xlog $self, "import email from blob $blobid"; - $res = eval { - $jmap->CallMethods([['Email/import', { - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$draftsmbox => JSON::true}, - keywords => { - '$draft' => JSON::true, - }, - }, + xlog $self, "import email from blob $blobid"; + $res = eval { + $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $draftsmbox => JSON::true }, + keywords => { + '$draft' => JSON::true, }, - }, "R1"]]); - }; + }, + }, + }, + "R1" + ] ]); + }; - $self->assert_str_equals("Email/import", $res->[0][0]); - my $msg = $res->[0][1]->{created}{"1"}; - $self->assert_not_null($msg); + $self->assert_str_equals("Email/import", $res->[0][0]); + my $msg = $res->[0][1]->{created}{"1"}; + $self->assert_not_null($msg); - my $basedir = $self->{instance}->{basedir}; - my $jpath = $self->{instance}->folder_to_directory('user.cassandane.#jmap'); - my $dpath = $self->{instance}->folder_to_directory('user.cassandane.drafts'); + my $basedir = $self->{instance}->{basedir}; + my $jpath = $self->{instance}->folder_to_directory('user.cassandane.#jmap'); + my $dpath = $self->{instance}->folder_to_directory('user.cassandane.drafts'); - my @jstat = stat("$jpath/1."); - my @dstat = stat("$dpath/1."); + my @jstat = stat("$jpath/1."); + my @dstat = stat("$dpath/1."); - xlog $self, "sizes match"; - $self->assert_num_equals($jstat[7], $dstat[7]); - xlog $self, "same device"; - $self->assert_num_equals($jstat[0], $dstat[0]); - xlog $self, "same inode"; # single instance store - $self->assert_num_equals($jstat[1], $dstat[1]); + xlog $self, "sizes match"; + $self->assert_num_equals($jstat[7], $dstat[7]); + xlog $self, "same device"; + $self->assert_num_equals($jstat[0], $dstat[0]); + xlog $self, "same inode"; # single instance store + $self->assert_num_equals($jstat[1], $dstat[1]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_snooze b/cassandane/tiny-tests/JMAPEmail/email_import_snooze index 6b94ab0e76..97c1a2520a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_snooze +++ b/cassandane/tiny-tests/JMAPEmail/email_import_snooze @@ -2,93 +2,108 @@ use Cassandane::Tiny; sub test_email_import_snooze - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd - :needs_component_sieve :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd + : needs_component_sieve : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # snoozed property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # snoozed property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $inbox = $self->getinbox()->{id}; - $self->assert_not_null($inbox); + my $inbox = $self->getinbox()->{id}; + $self->assert_not_null($inbox); - # Generate an embedded email to get a blob id - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "Content-Disposition: inline\r\n" . "\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . "Return-Path: \r\n" - . "Mime-Version: 1.0\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Transfer-Encoding: 7bit\r\n" - . "Subject: bar\r\n" - . "From: Ava T. Nguyen \r\n" - . "Message-ID: \r\n" - . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "An embedded email" - . "\r\n--sub--\r\n", - ) || die; + # Generate an embedded email to get a blob id + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "Return-Path: \r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" + . "Subject: bar\r\n" + . "From: Ava T. Nguyen \r\n" + . "Message-ID: \r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: Test User \r\n" . "\r\n" + . "An embedded email" + . "\r\n--sub--\r\n", + ) || die; - xlog $self, "get blobId"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['attachments'], - }, 'R2' ], - ]); - my $blobid = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; - $self->assert_not_null($blobid); + xlog $self, "get blobId"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['attachments'], + }, + 'R2' + ], + ]); + my $blobid = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; + $self->assert_not_null($blobid); - xlog $self, "create snooze mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "snoozed", - parentId => undef, - role => "snoozed" - }}}, "R1"] - ]); - my $snoozed = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($snoozed); + xlog $self, "create snooze mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "snoozed", + parentId => undef, + role => "snoozed" + } + } + }, + "R1" + ] ]); + my $snoozed = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($snoozed); - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => 30)); - my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => 30)); + my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - xlog $self, "import and get email from blob $blobid"; - $res = $jmap->CallMethods([['Email/import', { + xlog $self, "import and get email from blob $blobid"; + $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$snoozed => JSON::true}, - snoozed => { "until" => "$datestr" }, - }, + "1" => { + blobId => $blobid, + mailboxIds => { $snoozed => JSON::true }, + snoozed => { "until" => "$datestr" }, + }, }, - }, "R1"], ["Email/get", { ids => ["#1"] }, "R2" ]]); + }, + "R1" + ], + [ "Email/get", { ids => ["#1"] }, "R2" ] + ]); - $self->assert_str_equals("Email/import", $res->[0][0]); - my $msg = $res->[0][1]->{created}{"1"}; - $self->assert_not_null($msg); + $self->assert_str_equals("Email/import", $res->[0][0]); + my $msg = $res->[0][1]->{created}{"1"}; + $self->assert_not_null($msg); - $self->assert_str_equals("Email/get", $res->[1][0]); - $self->assert_str_equals($msg->{id}, $res->[1][1]{list}[0]->{id}); - $self->assert_str_equals($datestr, $res->[1][1]{list}[0]->{snoozed}{'until'}); + $self->assert_str_equals("Email/get", $res->[1][0]); + $self->assert_str_equals($msg->{id}, $res->[1][1]{list}[0]->{id}); + $self->assert_str_equals($datestr, $res->[1][1]{list}[0]->{snoozed}{'until'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_state b/cassandane/tiny-tests/JMAPEmail/email_import_state index b196ce03ce..bf96690d95 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_state +++ b/cassandane/tiny-tests/JMAPEmail/email_import_state @@ -2,113 +2,133 @@ use Cassandane::Tiny; sub test_email_import_state - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $inbox = $self->getinbox()->{id}; - $self->assert_not_null($inbox); + my $inbox = $self->getinbox()->{id}; + $self->assert_not_null($inbox); - # Generate an embedded email to get a blob id - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "Content-Disposition: inline\r\n" . "\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . "Return-Path: \r\n" - . "Mime-Version: 1.0\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Transfer-Encoding: 7bit\r\n" - . "Subject: bar\r\n" - . "From: Ava T. Nguyen \r\n" - . "Message-ID: \r\n" - . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "An embedded email" - . "\r\n--sub--\r\n", - ) || die; + # Generate an embedded email to get a blob id + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "Return-Path: \r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" + . "Subject: bar\r\n" + . "From: Ava T. Nguyen \r\n" + . "Message-ID: \r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: Test User \r\n" . "\r\n" + . "An embedded email" + . "\r\n--sub--\r\n", + ) || die; - xlog $self, "get blobId"; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['attachments'], - }, 'R2' ], - ]); - my $blobid = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; - $self->assert_not_null($blobid); + xlog $self, "get blobId"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['attachments'], + }, + 'R2' + ], + ]); + my $blobid = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; + $self->assert_not_null($blobid); - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $drafts = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($drafts); + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + my $drafts = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($drafts); - my $state = $res->[0][1]{newState}; - $self->assert_not_null($state); + my $state = $res->[0][1]{newState}; + $self->assert_not_null($state); - xlog $self, "attempt to import from blob $blobid with bogus state token"; - $res = $jmap->CallMethods([['Email/import', { - ifInState => 'bogus', - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$drafts => JSON::true}, - keywords => { '$draft' => JSON::true }, - }, + xlog $self, "attempt to import from blob $blobid with bogus state token"; + $res = $jmap->CallMethods([ [ + 'Email/import', + { + ifInState => 'bogus', + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $drafts => JSON::true }, + keywords => { '$draft' => JSON::true }, }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); - xlog $self, "import email from blob $blobid with valid state token"; - $res = $jmap->CallMethods([['Email/import', { - ifInState => $state, - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$drafts => JSON::true}, - keywords => { '$draft' => JSON::true }, - }, + xlog $self, "import email from blob $blobid with valid state token"; + $res = $jmap->CallMethods([ [ + 'Email/import', + { + ifInState => $state, + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $drafts => JSON::true }, + keywords => { '$draft' => JSON::true }, }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - $self->assert_str_equals("Email/import", $res->[0][0]); - $self->assert_not_null($res->[0][1]->{created}{"1"}); - $self->assert_str_equals($state, $res->[0][1]{oldState}); - $self->assert_not_null($res->[0][1]{newState}); + $self->assert_str_equals("Email/import", $res->[0][0]); + $self->assert_not_null($res->[0][1]->{created}{"1"}); + $self->assert_str_equals($state, $res->[0][1]{oldState}); + $self->assert_not_null($res->[0][1]{newState}); - xlog $self, "attempt to import from blob $blobid with stale state token"; - $res = $jmap->CallMethods([['Email/import', { - ifInState => $state, - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$drafts => JSON::true}, - keywords => { '$draft' => JSON::true }, - }, + xlog $self, "attempt to import from blob $blobid with stale state token"; + $res = $jmap->CallMethods([ [ + 'Email/import', + { + ifInState => $state, + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $drafts => JSON::true }, + keywords => { '$draft' => JSON::true }, }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('stateMismatch', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_zerobyte b/cassandane/tiny-tests/JMAPEmail/email_import_zerobyte index a32787a14c..8ad9f8ae34 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_zerobyte +++ b/cassandane/tiny-tests/JMAPEmail/email_import_zerobyte @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_email_import_zerobyte - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # A bogus email with an unencoded zero byte - my $email = <<"EOF"; + # A bogus email with an unencoded zero byte + my $email = <<"EOF"; From: \"Some Example Sender\" \r\n To: baseball\@local\r\n Subject: test email\r\n @@ -19,31 +18,41 @@ Content-Type: text/plain; charset="UTF-8"\r\n This is a test email with a \x{0}-byte.\r\n EOF - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsmbox); + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftsmbox); - xlog $self, "import email from blob $blobid"; - $res = $jmap->CallMethods([['Email/import', { - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$draftsmbox => JSON::true}, - keywords => { - '$draft' => JSON::true, - }, - }, - }, - }, "R1"]]); - $self->assert_str_equals("invalidEmail", $res->[0][1]{notCreated}{1}{type}); + xlog $self, "import email from blob $blobid"; + $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $draftsmbox => JSON::true }, + keywords => { + '$draft' => JSON::true, + }, + }, + }, + }, + "R1" + ] ]); + $self->assert_str_equals("invalidEmail", $res->[0][1]{notCreated}{1}{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_matchmime b/cassandane/tiny-tests/JMAPEmail/email_matchmime index 4fa360c7a4..935a5e51c7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_matchmime +++ b/cassandane/tiny-tests/JMAPEmail/email_matchmime @@ -2,19 +2,18 @@ use Cassandane::Tiny; sub test_email_matchmime - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd - :needs_component_sieve :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd + : needs_component_sieve : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # Email/matchMime method - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # Email/matchMime method + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $email = <<'EOF'; + my $email = <<'EOF'; From: sender@local To: recipient@local Subject: test email @@ -26,44 +25,59 @@ Content-Transfer-Encoding: quoted-printable Some body. EOF - $email =~ s/\r?\n/\r\n/gs; + $email =~ s/\r?\n/\r\n/gs; - my $res = $jmap->CallMethods([ - ['Email/matchMime', { - mime => $email, - filter => { - subject => "test", - header => [ "X-tra", 'baz' ], - }, - }, "R1"], - ]); + my $res = $jmap->CallMethods([ + [ + 'Email/matchMime', + { + mime => $email, + filter => { + subject => "test", + header => [ "X-tra", 'baz' ], + }, + }, + "R1" + ], + ]); - $self->assert_equals(JSON::true, $res->[0][1]{matches}); + $self->assert_equals(JSON::true, $res->[0][1]{matches}); - $res = $jmap->CallMethods([ - ['Email/matchMime', { - mime => $email, - filter => { - operator => 'AND', - conditions => [{ - text => "body", - }, { - header => [ "X-tra" ], - }], + $res = $jmap->CallMethods([ + [ + 'Email/matchMime', + { + mime => $email, + filter => { + operator => 'AND', + conditions => [ + { + text => "body", }, - }, "R1"], - ]); + { + header => ["X-tra"], + } + ], + }, + }, + "R1" + ], + ]); - $self->assert_equals(JSON::true, $res->[0][1]{matches}); + $self->assert_equals(JSON::true, $res->[0][1]{matches}); - $res = $jmap->CallMethods([ - ['Email/matchMime', { - mime => $email, - filter => { - hasAttachment => JSON::true, - }, - }, "R1"], - ]); + $res = $jmap->CallMethods([ + [ + 'Email/matchMime', + { + mime => $email, + filter => { + hasAttachment => JSON::true, + }, + }, + "R1" + ], + ]); - $self->assert_equals(JSON::false, $res->[0][1]{matches}); + $self->assert_equals(JSON::false, $res->[0][1]{matches}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_move_shared b/cassandane/tiny-tests/JMAPEmail/email_move_shared index 5a02a85e2c..bbe9e7745b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_move_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_move_shared @@ -2,66 +2,81 @@ use Cassandane::Tiny; sub test_email_move_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Share account - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lr") or die; + # Share account + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lr") or die; - # Create mailbox A - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lrswipkxtecdan") or die; + # Create mailbox A + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lrswipkxtecdan") or die; - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - $self->make_message("Email", store => $self->{adminstore}) or die; + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + $self->make_message("Email", store => $self->{adminstore}) or die; - # Create mailbox B - $admintalk->create("user.other.B") or die; - $admintalk->setacl("user.other.B", "cassandane", "lrswipkxtecdan") or die; + # Create mailbox B + $admintalk->create("user.other.B") or die; + $admintalk->setacl("user.other.B", "cassandane", "lrswipkxtecdan") or die; - my @fetchEmailMethods = ( - ['Email/query', { - accountId => 'other', - collapseThreads => JSON::true, - }, "R1"], - ['Email/get', { - accountId => 'other', - properties => ['mailboxIds'], - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ); + my @fetchEmailMethods = ( + [ + 'Email/query', + { + accountId => 'other', + collapseThreads => JSON::true, + }, + "R1" + ], + [ + 'Email/get', + { + accountId => 'other', + properties => ['mailboxIds'], + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ); - # Fetch Email - my $res = $jmap->CallMethods([@fetchEmailMethods, ['Mailbox/get', { accountId => 'other' }, 'R3']]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_num_equals(1, scalar keys %{$res->[1][1]{list}[0]{mailboxIds}}); - my $emailId = $res->[1][1]{list}[0]{id}; - my %mbids = map { $_->{name} => $_->{id} } @{$res->[2][1]{list}}; + # Fetch Email + my $res = $jmap->CallMethods( + [ @fetchEmailMethods, [ 'Mailbox/get', { accountId => 'other' }, 'R3' ] ]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_num_equals(1, + scalar keys %{ $res->[1][1]{list}[0]{mailboxIds} }); + my $emailId = $res->[1][1]{list}[0]{id}; + my %mbids = map { $_->{name} => $_->{id} } @{ $res->[2][1]{list} }; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "mailboxIds/$mbids{A}" => undef, - "mailboxIds/$mbids{B}" => $JSON::true, - }}, - accountId => 'other', - }, 'R1'], - ]); + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$mbids{A}" => undef, + "mailboxIds/$mbids{B}" => $JSON::true, + } + }, + accountId => 'other', + }, + 'R1' + ], + ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_move_shared_fromsharedseen b/cassandane/tiny-tests/JMAPEmail/email_move_shared_fromsharedseen index a6d908eeb0..9c603d20ff 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_move_shared_fromsharedseen +++ b/cassandane/tiny-tests/JMAPEmail/email_move_shared_fromsharedseen @@ -9,75 +9,97 @@ use Cassandane::Tiny; # IOERROR: append_addseen failed to open DB for foo@example.com sub test_email_move_shared_fromsharedseen - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Share account - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lr") or die; + # Share account + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lr") or die; - # Create mailbox A - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lrswipkxtecdan") or die; - $admintalk->setmetadata("user.other.A", "/shared/vendor/cmu/cyrus-imapd/sharedseen", "true"); + # Create mailbox A + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lrswipkxtecdan") or die; + $admintalk->setmetadata("user.other.A", + "/shared/vendor/cmu/cyrus-imapd/sharedseen", "true"); - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - $self->make_message("Email", store => $self->{adminstore}) or die; + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + $self->make_message("Email", store => $self->{adminstore}) or die; - # Create mailbox B - $admintalk->create("user.other.B") or die; - $admintalk->setacl("user.other.B", "cassandane", "lrswipkxtecdan") or die; + # Create mailbox B + $admintalk->create("user.other.B") or die; + $admintalk->setacl("user.other.B", "cassandane", "lrswipkxtecdan") or die; - my @fetchEmailMethods = ( - ['Email/query', { - accountId => 'other', - collapseThreads => JSON::true, - }, "R1"], - ['Email/get', { - accountId => 'other', - properties => ['mailboxIds'], - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ); + my @fetchEmailMethods = ( + [ + 'Email/query', + { + accountId => 'other', + collapseThreads => JSON::true, + }, + "R1" + ], + [ + 'Email/get', + { + accountId => 'other', + properties => ['mailboxIds'], + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ); - # Fetch Email - my $res = $jmap->CallMethods([@fetchEmailMethods, ['Mailbox/get', { accountId => 'other' }, 'R3']]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_num_equals(1, scalar keys %{$res->[1][1]{list}[0]{mailboxIds}}); - my $emailId = $res->[1][1]{list}[0]{id}; - my %mbids = map { $_->{name} => $_->{id} } @{$res->[2][1]{list}}; + # Fetch Email + my $res = $jmap->CallMethods( + [ @fetchEmailMethods, [ 'Mailbox/get', { accountId => 'other' }, 'R3' ] ]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_num_equals(1, + scalar keys %{ $res->[1][1]{list}[0]{mailboxIds} }); + my $emailId = $res->[1][1]{list}[0]{id}; + my %mbids = map { $_->{name} => $_->{id} } @{ $res->[2][1]{list} }; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "keywords/\$seen" => $JSON::true, - }}, - accountId => 'other', - }, 'R1'], - ['Email/set', { - update => { $emailId => { - "mailboxIds/$mbids{A}" => undef, - "mailboxIds/$mbids{B}" => $JSON::true, - }}, - accountId => 'other', - }, 'R2'], - ]); + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + "keywords/\$seen" => $JSON::true, + } + }, + accountId => 'other', + }, + 'R1' + ], + [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$mbids{A}" => undef, + "mailboxIds/$mbids{B}" => $JSON::true, + } + }, + accountId => 'other', + }, + 'R2' + ], + ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - $self->assert_not_null($res->[1][1]{updated}); - $self->assert_null($res->[1][1]{notUpdated}); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + $self->assert_not_null($res->[1][1]{updated}); + $self->assert_null($res->[1][1]{notUpdated}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse b/cassandane/tiny-tests/JMAPEmail/email_parse index bc227c9d21..d2e110cb8e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse +++ b/cassandane/tiny-tests/JMAPEmail/email_parse @@ -2,74 +2,86 @@ use Cassandane::Tiny; sub test_email_parse - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . "Return-Path: \r\n" - . "Mime-Version: 1.0\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Transfer-Encoding: 7bit\r\n" - . "Subject: bar\r\n" - . "From: Ava T. Nguyen \r\n" - . "Message-ID: \r\n" - . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "An embedded email" - . "\r\n--sub--\r\n", - ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['attachments'], - }, 'R2' ], - ]); - my $blobId = $res->[1][1]{list}[0]{attachments}[0]{blobId}; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "Return-Path: \r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" + . "Subject: bar\r\n" + . "From: Ava T. Nguyen \r\n" + . "Message-ID: \r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: Test User \r\n" . "\r\n" + . "An embedded email" + . "\r\n--sub--\r\n", + ) || die; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['attachments'], + }, + 'R2' + ], + ]); + my $blobId = $res->[1][1]{list}[0]{attachments}[0]{blobId}; - my @props = $self->defaultprops_for_email_get(); - push @props, "bodyStructure"; - push @props, "bodyValues"; + my @props = $self->defaultprops_for_email_get(); + push @props, "bodyStructure"; + push @props, "bodyValues"; - $res = $jmap->CallMethods([['Email/parse', { - blobIds => [ $blobId ], properties => \@props, fetchAllBodyValues => JSON::true, - }, 'R1']]); - my $email = $res->[0][1]{parsed}{$blobId}; - $self->assert_not_null($email); + $res = $jmap->CallMethods([ [ + 'Email/parse', + { + blobIds => [$blobId], + properties => \@props, + fetchAllBodyValues => JSON::true, + }, + 'R1' + ] ]); + my $email = $res->[0][1]{parsed}{$blobId}; + $self->assert_not_null($email); - $self->assert_null($email->{id}); - $self->assert_null($email->{threadId}); - $self->assert_null($email->{mailboxIds}); - $self->assert_deep_equals({}, $email->{keywords}); - $self->assert_deep_equals(['fake.1475639947.6507@local'], $email->{messageId}); - $self->assert_deep_equals([{name=>'Ava T. Nguyen', email=>'Ava.Nguyen@local'}], $email->{from}); - $self->assert_deep_equals([{name=>'Test User', email=>'test@local'}], $email->{to}); - $self->assert_null($email->{cc}); - $self->assert_null($email->{bcc}); - $self->assert_null($email->{references}); - $self->assert_null($email->{sender}); - $self->assert_null($email->{replyTo}); - $self->assert_str_equals('bar', $email->{subject}); - $self->assert_str_equals('2016-10-05T14:59:07+11:00', $email->{sentAt}); - $self->assert_not_null($email->{blobId}); - $self->assert_str_equals('text/plain', $email->{bodyStructure}{type}); - $self->assert_null($email->{bodyStructure}{subParts}); - $self->assert_num_equals(1, scalar @{$email->{textBody}}); - $self->assert_num_equals(1, scalar @{$email->{htmlBody}}); - $self->assert_num_equals(0, scalar @{$email->{attachments}}); + $self->assert_null($email->{id}); + $self->assert_null($email->{threadId}); + $self->assert_null($email->{mailboxIds}); + $self->assert_deep_equals({}, $email->{keywords}); + $self->assert_deep_equals(['fake.1475639947.6507@local'], + $email->{messageId}); + $self->assert_deep_equals( + [ { name => 'Ava T. Nguyen', email => 'Ava.Nguyen@local' } ], + $email->{from}); + $self->assert_deep_equals([ { name => 'Test User', email => 'test@local' } ], + $email->{to}); + $self->assert_null($email->{cc}); + $self->assert_null($email->{bcc}); + $self->assert_null($email->{references}); + $self->assert_null($email->{sender}); + $self->assert_null($email->{replyTo}); + $self->assert_str_equals('bar', $email->{subject}); + $self->assert_str_equals('2016-10-05T14:59:07+11:00', $email->{sentAt}); + $self->assert_not_null($email->{blobId}); + $self->assert_str_equals('text/plain', $email->{bodyStructure}{type}); + $self->assert_null($email->{bodyStructure}{subParts}); + $self->assert_num_equals(1, scalar @{ $email->{textBody} }); + $self->assert_num_equals(1, scalar @{ $email->{htmlBody} }); + $self->assert_num_equals(0, scalar @{ $email->{attachments} }); - my $bodyValue = $email->{bodyValues}{$email->{bodyStructure}{partId}}; - $self->assert_str_equals('An embedded email', $bodyValue->{value}); + my $bodyValue = $email->{bodyValues}{ $email->{bodyStructure}{partId} }; + $self->assert_str_equals('An embedded email', $bodyValue->{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_base64 b/cassandane/tiny-tests/JMAPEmail/email_parse_base64 index f822d7fcf4..6f87d56e77 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_base64 +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_base64 @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_email_parse_base64 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $rawEmail = <<'EOF'; + my $rawEmail = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -21,48 +20,56 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $rawEmail =~ s/\r?\n/\r\n/gs; + $rawEmail =~ s/\r?\n/\r\n/gs; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: message/rfc822\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . MIME::Base64::encode_base64($rawEmail, "\r\n") - . "\r\n--sub--\r\n", - ) || die; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: message/rfc822\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . MIME::Base64::encode_base64($rawEmail, "\r\n") + . "\r\n--sub--\r\n", + ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['attachments'], - }, 'R2' ], - ]); - my $blobId = $res->[1][1]{list}[0]{attachments}[0]{blobId}; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['attachments'], + }, + 'R2' + ], + ]); + my $blobId = $res->[1][1]{list}[0]{attachments}[0]{blobId}; - my @props = $self->defaultprops_for_email_get(); - push @props, "bodyStructure"; - push @props, "bodyValues"; + my @props = $self->defaultprops_for_email_get(); + push @props, "bodyStructure"; + push @props, "bodyValues"; - $res = $jmap->CallMethods([['Email/parse', { - blobIds => [ $blobId ], - properties => \@props, - fetchAllBodyValues => JSON::true, - }, 'R1']]); + $res = $jmap->CallMethods([ [ + 'Email/parse', + { + blobIds => [$blobId], + properties => \@props, + fetchAllBodyValues => JSON::true, + }, + 'R1' + ] ]); - my $email = $res->[0][1]{parsed}{$blobId}; - $self->assert_not_null($email); - $self->assert_deep_equals( - [{ - name => 'Some Example Sender', - email => 'example@example.com' - }], - $email->{from} - ); - my $bodyValue = $email->{bodyValues}{$email->{bodyStructure}{partId}}; - $self->assert_str_equals("This is a test email.\n", $bodyValue->{value}); + my $email = $res->[0][1]{parsed}{$blobId}; + $self->assert_not_null($email); + $self->assert_deep_equals( + [ { + name => 'Some Example Sender', + email => 'example@example.com' + } ], + $email->{from} + ); + my $bodyValue = $email->{bodyValues}{ $email->{bodyStructure}{partId} }; + $self->assert_str_equals("This is a test email.\n", $bodyValue->{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_blob822 b/cassandane/tiny-tests/JMAPEmail/email_parse_blob822 index e1627cae7c..24d481c4a2 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_blob822 +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_blob822 @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_email_parse_blob822 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $rawEmail = <<'EOF'; + my $rawEmail = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -21,24 +20,30 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $rawEmail =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($rawEmail, "application/data"); - my $blobId = $data->{blobId}; - - my @props = $self->defaultprops_for_email_get(); - push @props, "bodyStructure"; - push @props, "bodyValues"; - - my $res = $jmap->CallMethods([['Email/parse', { - blobIds => [ $blobId ], - properties => \@props, - fetchAllBodyValues => JSON::true, - }, 'R1']]); - my $email = $res->[0][1]{parsed}{$blobId}; - - $self->assert_not_null($email); - $self->assert_deep_equals([{name=>'Some Example Sender', email=>'example@example.com'}], $email->{from}); - - my $bodyValue = $email->{bodyValues}{$email->{bodyStructure}{partId}}; - $self->assert_str_equals("This is a test email.\n", $bodyValue->{value}); + $rawEmail =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($rawEmail, "application/data"); + my $blobId = $data->{blobId}; + + my @props = $self->defaultprops_for_email_get(); + push @props, "bodyStructure"; + push @props, "bodyValues"; + + my $res = $jmap->CallMethods([ [ + 'Email/parse', + { + blobIds => [$blobId], + properties => \@props, + fetchAllBodyValues => JSON::true, + }, + 'R1' + ] ]); + my $email = $res->[0][1]{parsed}{$blobId}; + + $self->assert_not_null($email); + $self->assert_deep_equals( + [ { name => 'Some Example Sender', email => 'example@example.com' } ], + $email->{from}); + + my $bodyValue = $email->{bodyValues}{ $email->{bodyStructure}{partId} }; + $self->assert_str_equals("This is a test email.\n", $bodyValue->{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_blob822_lenient b/cassandane/tiny-tests/JMAPEmail/email_parse_blob822_lenient index 8e2f9c9a79..bb10ce1d24 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_blob822_lenient +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_blob822_lenient @@ -2,35 +2,39 @@ use Cassandane::Tiny; sub test_email_parse_blob822_lenient - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - # This isn't a valid RFC822 message, as it neither contains - # a Date nor a From header. But there's wild stuff out there, - # so let's be lenient. - my $rawEmail = <<'EOF'; + # This isn't a valid RFC822 message, as it neither contains + # a Date nor a From header. But there's wild stuff out there, + # so let's be lenient. + my $rawEmail = <<'EOF'; To: foo@bar.local MIME-Version: 1.0 Some illegit mail. EOF - $rawEmail =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($rawEmail, "application/data"); - my $blobId = $data->{blobId}; + $rawEmail =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($rawEmail, "application/data"); + my $blobId = $data->{blobId}; - my $res = $jmap->CallMethods([['Email/parse', { - blobIds => [ $blobId ], - fetchAllBodyValues => JSON::true, - }, 'R1']]); - my $email = $res->[0][1]{parsed}{$blobId}; + my $res = $jmap->CallMethods([ [ + 'Email/parse', + { + blobIds => [$blobId], + fetchAllBodyValues => JSON::true, + }, + 'R1' + ] ]); + my $email = $res->[0][1]{parsed}{$blobId}; - $self->assert_not_null($email); - $self->assert_null($email->{from}); - $self->assert_null($email->{sentAt}); - $self->assert_deep_equals([{name=>undef, email=>'foo@bar.local'}], $email->{to}); + $self->assert_not_null($email); + $self->assert_null($email->{from}); + $self->assert_null($email->{sentAt}); + $self->assert_deep_equals([ { name => undef, email => 'foo@bar.local' } ], + $email->{to}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_charset b/cassandane/tiny-tests/JMAPEmail/email_parse_charset index 2c75b13ad2..db2e86c27e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_charset +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_charset @@ -2,71 +2,84 @@ use Cassandane::Tiny; sub test_email_parse_charset - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - # LF in raw headers will be replaced to CRLF later. + # LF in raw headers will be replaced to CRLF later. - my @testCases = ({ - desc => "Canonical charset parameter", - rawHeader => "text/plain; charset=utf-8", - wantContentType => 'text/plain', - wantCharset => 'utf-8', - }, { - desc => "Folded charset parameter", - rawHeader => "text/plain;\n charset=\n utf-8", - wantContentType => 'text/plain', - wantCharset => 'utf-8', - }, { - desc => "Aliased charset parameter", - rawHeader => "text/plain; charset=latin1", - wantContentType => 'text/plain', - wantCharset => 'latin1', - }); + my @testCases = ( + { + desc => "Canonical charset parameter", + rawHeader => "text/plain; charset=utf-8", + wantContentType => 'text/plain', + wantCharset => 'utf-8', + }, + { + desc => "Folded charset parameter", + rawHeader => "text/plain;\n charset=\n utf-8", + wantContentType => 'text/plain', + wantCharset => 'utf-8', + }, + { + desc => "Aliased charset parameter", + rawHeader => "text/plain; charset=latin1", + wantContentType => 'text/plain', + wantCharset => 'latin1', + } + ); - foreach (@testCases) { - xlog $self, "Running test: $_->{desc}"; - my $rawEmail = "" - . "From: foo\@local\n" - . "To: bar\@local\n" - . "Subject: test email\n" - . "Date: Wed, 7 Dec 2016 00:21:50 -0500\n" - . "Content-Type: " . $_->{rawHeader} . "\n" - . "MIME-Version: 1.0\n" - . "\n" - . "This is a test email.\n"; + foreach (@testCases) { + xlog $self, "Running test: $_->{desc}"; + my $rawEmail + = "" + . "From: foo\@local\n" + . "To: bar\@local\n" + . "Subject: test email\n" + . "Date: Wed, 7 Dec 2016 00:21:50 -0500\n" + . "Content-Type: " + . $_->{rawHeader} . "\n" + . "MIME-Version: 1.0\n" . "\n" + . "This is a test email.\n"; - $rawEmail =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($rawEmail, "application/octet-stream"); - my $blobId = $data->{blobId}; + $rawEmail =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($rawEmail, "application/octet-stream"); + my $blobId = $data->{blobId}; - my $res = $jmap->CallMethods([ - ['Email/import', { - emails => { - 1 => { - mailboxIds => { - '$inbox' => JSON::true, - }, - blobId => $blobId, - }, - }, - }, 'R1'], - ['Email/get', { - ids => ['#1'], - properties => ['bodyStructure'], - bodyProperties => ['charset'], - }, '$2'], - ]); - my $email = $res->[1][1]{list}[0]; - if (defined $_->{wantCharset}) { - $self->assert_str_equals($_->{wantCharset}, $email->{bodyStructure}{charset}); - } else { - $self->assert_null($email->{bodyStructure}{charset}); - } + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { + emails => { + 1 => { + mailboxIds => { + '$inbox' => JSON::true, + }, + blobId => $blobId, + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#1'], + properties => ['bodyStructure'], + bodyProperties => ['charset'], + }, + '$2' + ], + ]); + my $email = $res->[1][1]{list}[0]; + if (defined $_->{wantCharset}) { + $self->assert_str_equals($_->{wantCharset}, + $email->{bodyStructure}{charset}); + } else { + $self->assert_null($email->{bodyStructure}{charset}); } + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_contenttype_default b/cassandane/tiny-tests/JMAPEmail/email_parse_contenttype_default index 1771d5fd58..de8cd8de15 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_contenttype_default +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_contenttype_default @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_email_parse_contenttype_default - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $emailWithoutContentType = <<'EOF'; + my $emailWithoutContentType = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -20,7 +19,7 @@ MIME-Version: 1.0 This is a test email. EOF - my $emailWithoutCharset = <<'EOF'; + my $emailWithoutCharset = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -31,7 +30,7 @@ MIME-Version: 1.0 This is a test email. EOF - my $emailWithNonTextContentType = <<'EOF'; + my $emailWithNonTextContentType = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -42,7 +41,7 @@ MIME-Version: 1.0 This is a test email. EOF - my $emailWithBogusContentTypeParams = <<'EOF'; + my $emailWithBogusContentTypeParams = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -53,46 +52,57 @@ MIME-Version: 1.0 This is a test email. EOF + my @testCases = ( + { + desc => "Email without Content-Type header", + rawEmail => $emailWithoutContentType, + wantContentType => 'text/plain', + wantCharset => 'us-ascii', + }, + { + desc => "Email without charset parameter", + rawEmail => $emailWithoutCharset, + wantContentType => 'text/plain', + wantCharset => 'us-ascii', + }, + { + desc => "Email with non-text Content-Type", + rawEmail => $emailWithNonTextContentType, + wantContentType => 'application/data', + wantCharset => undef, + }, + { + desc => "Email with bogus Content-Type params", + rawEmail => $emailWithBogusContentTypeParams, + wantContentType => 'text/html', + wantCharset => 'utf-8', + } + ); - my @testCases = ({ - desc => "Email without Content-Type header", - rawEmail => $emailWithoutContentType, - wantContentType => 'text/plain', - wantCharset => 'us-ascii', - }, { - desc => "Email without charset parameter", - rawEmail => $emailWithoutCharset, - wantContentType => 'text/plain', - wantCharset => 'us-ascii', - }, { - desc => "Email with non-text Content-Type", - rawEmail => $emailWithNonTextContentType, - wantContentType => 'application/data', - wantCharset => undef, - }, { - desc => "Email with bogus Content-Type params", - rawEmail => $emailWithBogusContentTypeParams, - wantContentType => 'text/html', - wantCharset => 'utf-8', - }); + foreach (@testCases) { + xlog $self, "Running test: $_->{desc}"; + my $rawEmail = $_->{rawEmail}; + $rawEmail =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($rawEmail, "application/data"); + my $blobId = $data->{blobId}; - foreach (@testCases) { - xlog $self, "Running test: $_->{desc}"; - my $rawEmail = $_->{rawEmail}; - $rawEmail =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($rawEmail, "application/data"); - my $blobId = $data->{blobId}; + my $res = $jmap->CallMethods([ [ + 'Email/parse', + { + blobIds => [$blobId], + properties => ['bodyStructure'], + }, + 'R1' + ] ]); + my $email = $res->[0][1]{parsed}{$blobId}; + $self->assert_str_equals($_->{wantContentType}, + $email->{bodyStructure}{type}); - my $res = $jmap->CallMethods([['Email/parse', { - blobIds => [ $blobId ], - properties => ['bodyStructure'], - }, 'R1']]); - my $email = $res->[0][1]{parsed}{$blobId}; - $self->assert_str_equals($_->{wantContentType}, $email->{bodyStructure}{type}); - if (defined $_->{wantCharset}) { - $self->assert_str_equals($_->{wantCharset}, $email->{bodyStructure}{charset}); - } else { - $self->assert_null($email->{bodyStructure}{charset}); - } + if (defined $_->{wantCharset}) { + $self->assert_str_equals($_->{wantCharset}, + $email->{bodyStructure}{charset}); + } else { + $self->assert_null($email->{bodyStructure}{charset}); } + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_digest b/cassandane/tiny-tests/JMAPEmail/email_parse_digest index b406f9730d..1169aebf60 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_digest +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_digest @@ -2,43 +2,46 @@ use Cassandane::Tiny; sub test_email_parse_digest - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - $self->make_message("foo", - mime_type => "multipart/digest", - mime_boundary => "sub", - body => "" - . "\r\n--sub\r\n" - . "\r\n" - . "Return-Path: \r\n" - . "Mime-Version: 1.0\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Transfer-Encoding: 7bit\r\n" - . "Subject: bar\r\n" - . "From: Ava T. Nguyen \r\n" - . "Message-ID: \r\n" - . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "An embedded email" - . "\r\n--sub--\r\n", - ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['bodyStructure'] - }, 'R2' ], - ]); - my $blobId = $res->[1][1]{list}[0]{bodyStructure}{subParts}[0]{blobId}; - $self->assert_not_null($blobId); + $self->make_message( + "foo", + mime_type => "multipart/digest", + mime_boundary => "sub", + body => "" + . "\r\n--sub\r\n" . "\r\n" + . "Return-Path: \r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" + . "Subject: bar\r\n" + . "From: Ava T. Nguyen \r\n" + . "Message-ID: \r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: Test User \r\n" . "\r\n" + . "An embedded email" + . "\r\n--sub--\r\n", + ) || die; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['bodyStructure'] + }, + 'R2' + ], + ]); + my $blobId = $res->[1][1]{list}[0]{bodyStructure}{subParts}[0]{blobId}; + $self->assert_not_null($blobId); - $res = $jmap->CallMethods([['Email/parse', { blobIds => [ $blobId ] }, 'R1']]); - $self->assert_not_null($res->[0][1]{parsed}{$blobId}); + $res + = $jmap->CallMethods([ [ 'Email/parse', { blobIds => [$blobId] }, 'R1' ] ]); + $self->assert_not_null($res->[0][1]{parsed}{$blobId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_embedded_toplevel b/cassandane/tiny-tests/JMAPEmail/email_parse_embedded_toplevel index e57d15369f..8b0cc78913 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_embedded_toplevel +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_embedded_toplevel @@ -2,75 +2,93 @@ use Cassandane::Tiny; sub test_email_parse_embedded_toplevel - :min_version_3_3 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 1 => { - mailboxIds => { - '$inbox' => JSON::true, - }, - subject => 'test1', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'A text body', - }, - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 1 => { + mailboxIds => { + '$inbox' => JSON::true, + }, + subject => 'test1', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'A text body', + }, }, - }, 'R1'], - ]); - my $blobId = $res->[0][1]{created}{1}{blobId}; - $self->assert_not_null($blobId); + }, + }, + }, + 'R1' + ], + ]); + my $blobId = $res->[0][1]{created}{1}{blobId}; + $self->assert_not_null($blobId); - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 2 => { - mailboxIds => { - '$inbox' => JSON::true, - }, - subject => 'test2', - bodyStructure => { - subParts => [{ - type => 'text/plain', - partId => 'part1', - }, { - type => 'message/rfc822', - blobId => $blobId, - }], - }, - bodyValues => { - part1 => { - value => 'A text body', - }, - }, + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 2 => { + mailboxIds => { + '$inbox' => JSON::true, + }, + subject => 'test2', + bodyStructure => { + subParts => [ + { + type => 'text/plain', + partId => 'part1', }, + { + type => 'message/rfc822', + blobId => $blobId, + } + ], + }, + bodyValues => { + part1 => { + value => 'A text body', + }, }, - }, 'R1'], - ['Email/get', { - ids => ['#2'], - properties => ['bodyStructure'], - bodyProperties => ['blobId'], - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}{2}); - $self->assert_str_equals($blobId, - $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]{blobId}); + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#2'], + properties => ['bodyStructure'], + bodyProperties => ['blobId'], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{2}); + $self->assert_str_equals($blobId, + $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]{blobId}); - $res = $jmap->CallMethods([ - ['Email/parse', { - blobIds => [ $blobId ], - properties => ['blobId'], - }, 'R1'], - ]); - $self->assert_str_equals($blobId, $res->[0][1]{parsed}{$blobId}{blobId}); + $res = $jmap->CallMethods([ + [ + 'Email/parse', + { + blobIds => [$blobId], + properties => ['blobId'], + }, + 'R1' + ], + ]); + $self->assert_str_equals($blobId, $res->[0][1]{parsed}{$blobId}{blobId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_encoding b/cassandane/tiny-tests/JMAPEmail/email_parse_encoding index dfee093975..70f3674e53 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_encoding +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_encoding @@ -2,19 +2,18 @@ use Cassandane::Tiny; sub test_email_parse_encoding - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $decodedBody = "\N{LATIN SMALL LETTER A WITH GRAVE} la carte"; - my $encodedBody = '=C3=A0 la carte'; - $encodedBody =~ s/\r?\n/\r\n/gs; + my $decodedBody = "\N{LATIN SMALL LETTER A WITH GRAVE} la carte"; + my $encodedBody = '=C3=A0 la carte'; + $encodedBody =~ s/\r?\n/\r\n/gs; - my $Header = <<'EOF'; + my $Header = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -23,71 +22,81 @@ MIME-Version: 1.0 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable EOF - $Header =~ s/\r?\n/\r\n/gs; - my $emailBlob = $Header . "\r\n" . $encodedBody; + $Header =~ s/\r?\n/\r\n/gs; + my $emailBlob = $Header . "\r\n" . $encodedBody; - my $email; - my $res; - my $partId; + my $email; + my $res; + my $partId; - $self->make_message("foo", - mime_type => "multipart/mixed;boundary=1234567", - body => "" - . "--1234567\r\n" - . "Content-Type: text/plain; charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" - . $encodedBody - . "\r\n--1234567\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . "X-Header: ignore\r\n" # make this blob id unique - . $emailBlob - . "\r\n--1234567--\r\n" - ); + $self->make_message( + "foo", + mime_type => "multipart/mixed;boundary=1234567", + body => "" + . "--1234567\r\n" + . "Content-Type: text/plain; charset=utf-8\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" + . $encodedBody + . "\r\n--1234567\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "X-Header: ignore\r\n" # make this blob id unique + . $emailBlob . "\r\n--1234567--\r\n" + ); - # Assert content decoding for top-level message. - xlog $self, "get email"; - $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['bodyValues', 'bodyStructure', 'textBody'], - bodyProperties => ['partId', 'blobId'], - fetchAllBodyValues => JSON::true, - }, 'R2'], - ]); - $self->assert_num_equals(scalar @{$res->[0][1]->{ids}}, 1); - $email = $res->[1][1]->{list}[0]; - $partId = $email->{textBody}[0]{partId}; - $self->assert_str_equals($decodedBody, $email->{bodyValues}{$partId}{value}); - - # Assert content decoding for embedded message. - xlog $self, "parse embedded email"; - my $embeddedBlobId = $email->{bodyStructure}{subParts}[1]{blobId}; - $res = $jmap->CallMethods([['Email/parse', { - blobIds => [ $email->{bodyStructure}{subParts}[1]{blobId} ], - properties => ['bodyValues', 'textBody'], + # Assert content decoding for top-level message. + xlog $self, "get email"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => [ 'bodyValues', 'bodyStructure', 'textBody' ], + bodyProperties => [ 'partId', 'blobId' ], fetchAllBodyValues => JSON::true, - }, 'R1']]); - $email = $res->[0][1]{parsed}{$embeddedBlobId}; - $partId = $email->{textBody}[0]{partId}; - $self->assert_str_equals($decodedBody, $email->{bodyValues}{$partId}{value}); + }, + 'R2' + ], + ]); + $self->assert_num_equals(scalar @{ $res->[0][1]->{ids} }, 1); + $email = $res->[1][1]->{list}[0]; + $partId = $email->{textBody}[0]{partId}; + $self->assert_str_equals($decodedBody, $email->{bodyValues}{$partId}{value}); - # Assert content decoding for message blob. - my $data = $jmap->Upload($emailBlob, "application/data"); - my $blobId = $data->{blobId}; + # Assert content decoding for embedded message. + xlog $self, "parse embedded email"; + my $embeddedBlobId = $email->{bodyStructure}{subParts}[1]{blobId}; + $res = $jmap->CallMethods([ [ + 'Email/parse', + { + blobIds => [ $email->{bodyStructure}{subParts}[1]{blobId} ], + properties => [ 'bodyValues', 'textBody' ], + fetchAllBodyValues => JSON::true, + }, + 'R1' + ] ]); + $email = $res->[0][1]{parsed}{$embeddedBlobId}; + $partId = $email->{textBody}[0]{partId}; + $self->assert_str_equals($decodedBody, $email->{bodyValues}{$partId}{value}); - $res = $jmap->CallMethods([['Email/parse', { - blobIds => [ $blobId ], - properties => ['bodyValues', 'textBody'], - fetchAllBodyValues => JSON::true, - }, 'R1']]); - $email = $res->[0][1]{parsed}{$blobId}; - $partId = $email->{textBody}[0]{partId}; - $self->assert_str_equals($decodedBody, $email->{bodyValues}{$partId}{value}); + # Assert content decoding for message blob. + my $data = $jmap->Upload($emailBlob, "application/data"); + my $blobId = $data->{blobId}; + + $res = $jmap->CallMethods([ [ + 'Email/parse', + { + blobIds => [$blobId], + properties => [ 'bodyValues', 'textBody' ], + fetchAllBodyValues => JSON::true, + }, + 'R1' + ] ]); + $email = $res->[0][1]{parsed}{$blobId}; + $partId = $email->{textBody}[0]{partId}; + $self->assert_str_equals($decodedBody, $email->{bodyValues}{$partId}{value}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_inmemory_blob b/cassandane/tiny-tests/JMAPEmail/email_parse_inmemory_blob index 792e95a395..cea5ce66ae 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_inmemory_blob +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_inmemory_blob @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_email_parse_inmemory_blob - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # XXX: replace with the upstream one once RFC 9404 is finished - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); + # XXX: replace with the upstream one once RFC 9404 is finished + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/blob'); - my $mimeMsg = <<'EOF'; + my $mimeMsg = <<'EOF'; From: To: to@local Bcc: bcc@local @@ -21,25 +20,33 @@ Content-Type: text/plain hello EOF - $mimeMsg =~ s/\r?\n/\r\n/gs; + $mimeMsg =~ s/\r?\n/\r\n/gs; - # XXX: can't use a result reference in array - my $blobId = 'G67501cd2e1eaaf65d25e6f3b49554d2193f06ee8'; + # XXX: can't use a result reference in array + my $blobId = 'G67501cd2e1eaaf65d25e6f3b49554d2193f06ee8'; - $res = $jmap->CallMethods([ - ['Blob/upload', { - create => { - b1 => { - data => [{'data:asText' => $mimeMsg}] - }, - }, - }, 'R1'], - ['Email/parse', { - blobIds => [$blobId], - properties => ['subject', 'bodyStructure'], - }, 'R2'], - ]); - $self->assert_str_equals('Blob/upload', $res->[0][0]); - $self->assert_not_null($res->[0][1]{created}{b1}{id}); - $self->assert_str_equals('test', $res->[1][1]{parsed}{$blobId}{subject}); + $res = $jmap->CallMethods([ + [ + 'Blob/upload', + { + create => { + b1 => { + data => [ { 'data:asText' => $mimeMsg } ] + }, + }, + }, + 'R1' + ], + [ + 'Email/parse', + { + blobIds => [$blobId], + properties => [ 'subject', 'bodyStructure' ], + }, + 'R2' + ], + ]); + $self->assert_str_equals('Blob/upload', $res->[0][0]); + $self->assert_not_null($res->[0][1]{created}{b1}{id}); + $self->assert_str_equals('test', $res->[1][1]{parsed}{$blobId}{subject}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_notparsable b/cassandane/tiny-tests/JMAPEmail/email_parse_notparsable index a93873299f..30c8544d31 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_notparsable +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_notparsable @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_email_parse_notparsable - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $rawEmail = "" - ."To:foo\@bar.local\r\n" - ."Date: Date: Wed, 7 Dec 2016 00:21:50 -0500\r\n" - ."\r\n" - ."Some\nbogus\nbody"; + my $rawEmail + = "" + . "To:foo\@bar.local\r\n" + . "Date: Date: Wed, 7 Dec 2016 00:21:50 -0500\r\n" . "\r\n" + . "Some\nbogus\nbody"; - my $data = $jmap->Upload($rawEmail, "application/data"); - my $blobId = $data->{blobId}; + my $data = $jmap->Upload($rawEmail, "application/data"); + my $blobId = $data->{blobId}; - my $res = $jmap->CallMethods([['Email/parse', { blobIds => [ $blobId ] }, 'R1']]); - $self->assert_str_equals($blobId, $res->[0][1]{notParsable}[0]); + my $res + = $jmap->CallMethods([ [ 'Email/parse', { blobIds => [$blobId] }, 'R1' ] ]); + $self->assert_str_equals($blobId, $res->[0][1]{notParsable}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_parse_replyto b/cassandane/tiny-tests/JMAPEmail/email_parse_replyto index 9dc85c8987..5eef5514f3 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_parse_replyto +++ b/cassandane/tiny-tests/JMAPEmail/email_parse_replyto @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_email_parse_replyto - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: To: to@local Reply-To: replyto@local @@ -39,34 +38,41 @@ attachedbody --6c3338934661485f87537c19b5f9d933-- EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; - my $res = $jmap->CallMethods([ - ['Email/query', { - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['bodyStructure'], - }, 'R2'], - ]); - my $emailId = $res->[0][1]{ids}[0]; - $self->assert_not_null($emailId); + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['bodyStructure'], + }, + 'R2' + ], + ]); + my $emailId = $res->[0][1]{ids}[0]; + $self->assert_not_null($emailId); - my $blobId = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]{blobId}; - $self->assert_not_null($blobId); + my $blobId = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]{blobId}; + $self->assert_not_null($blobId); - $res = $jmap->CallMethods([ - ['Email/parse', { - blobIds => [$blobId], - properties => ['from', 'replyTo'], - }, 'R1'], - ]); - $self->assert_str_equals('attachedfrom@local', - $res->[0][1]{parsed}{$blobId}{from}[0]{email}); - $self->assert_str_equals('attachedreplyto@local', - $res->[0][1]{parsed}{$blobId}{replyTo}[0]{email}); + $res = $jmap->CallMethods([ + [ + 'Email/parse', + { + blobIds => [$blobId], + properties => [ 'from', 'replyTo' ], + }, + 'R1' + ], + ]); + $self->assert_str_equals('attachedfrom@local', + $res->[0][1]{parsed}{$blobId}{from}[0]{email}); + $self->assert_str_equals('attachedreplyto@local', + $res->[0][1]{parsed}{$blobId}{replyTo}[0]{email}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query b/cassandane/tiny-tests/JMAPEmail/email_query index 4ffadbecc2..9a3f86fae0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query +++ b/cassandane/tiny-tests/JMAPEmail/email_query @@ -2,69 +2,70 @@ use Cassandane::Tiny; sub test_email_query - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $account = undef; - my $store = $self->{store}; - my $mboxprefix = "INBOX"; - my $talk = $store->get_client(); - - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => $account }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; - - xlog $self, "create mailboxes"; - $talk->create("$mboxprefix.A") || die; - $talk->create("$mboxprefix.B") || die; - $talk->create("$mboxprefix.C") || die; - - $res = $jmap->CallMethods([['Mailbox/get', { accountId => $account }, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxa = $m{"A"}->{id}; - my $mboxb = $m{"B"}->{id}; - my $mboxc = $m{"C"}->{id}; - $self->assert_not_null($mboxa); - $self->assert_not_null($mboxb); - $self->assert_not_null($mboxc); - - xlog $self, "create emails"; - my %params; - $store->set_folder("$mboxprefix.A"); - my $dtfoo = DateTime->new( - year => 2016, - month => 11, - day => 1, - hour => 7, - time_zone => 'Etc/UTC', - ); - my $bodyfoo = "A rather short email"; - %params = ( - date => $dtfoo, - body => $bodyfoo, - store => $store, - ); - $res = $self->make_message("foo", %params) || die; - $talk->copy(1, "$mboxprefix.C") || die; - - $store->set_folder("$mboxprefix.B"); - my $dtbar = DateTime->new( - year => 2016, - month => 3, - day => 1, - hour => 19, - time_zone => 'Etc/UTC', - ); - my $bodybar = "" + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $account = undef; + my $store = $self->{store}; + my $mboxprefix = "INBOX"; + my $talk = $store->get_client(); + + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { accountId => $account }, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; + + xlog $self, "create mailboxes"; + $talk->create("$mboxprefix.A") || die; + $talk->create("$mboxprefix.B") || die; + $talk->create("$mboxprefix.C") || die; + + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { accountId => $account }, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxa = $m{"A"}->{id}; + my $mboxb = $m{"B"}->{id}; + my $mboxc = $m{"C"}->{id}; + $self->assert_not_null($mboxa); + $self->assert_not_null($mboxb); + $self->assert_not_null($mboxc); + + xlog $self, "create emails"; + my %params; + $store->set_folder("$mboxprefix.A"); + my $dtfoo = DateTime->new( + year => 2016, + month => 11, + day => 1, + hour => 7, + time_zone => 'Etc/UTC', + ); + my $bodyfoo = "A rather short email"; + %params = ( + date => $dtfoo, + body => $bodyfoo, + store => $store, + ); + $res = $self->make_message("foo", %params) || die; + $talk->copy(1, "$mboxprefix.C") || die; + + $store->set_folder("$mboxprefix.B"); + my $dtbar = DateTime->new( + year => 2016, + month => 3, + day => 1, + hour => 19, + time_zone => 'Etc/UTC', + ); + my $bodybar + = "" . "In the context of electronic mail, emails are viewed as having an\r\n" . "envelope and contents. The envelope contains whatever information is\r\n" . "needed to accomplish transmission and delivery. (See [RFC5321] for a\r\n" . "discussion of the envelope.) The contents comprise the object to be\r\n" . "delivered to the recipient. This specification applies only to the\r\n" . "format and some of the semantics of email contents. It contains no\r\n" - . "specification of the information in the envelope.i\r\n" - . "\r\n" + . "specification of the information in the envelope.i\r\n" . "\r\n" . "However, some email systems may use information from the contents\r\n" . "to create the envelope. It is intended that this specification\r\n" . "facilitate the acquisition of such information by programs.\r\n" @@ -76,273 +77,363 @@ sub test_email_query . "differ from the one specified in this specification, local storage is\r\n" . "outside of the scope of this specification.\r\n"; - %params = ( - date => $dtbar, - body => $bodybar, - extra_headers => [ - ['x-tra', "baz"], + %params = ( + date => $dtbar, + body => $bodybar, + extra_headers => [ [ 'x-tra', "baz" ], ], + store => $store, + ); + $self->make_message("bar", %params) || die; + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "fetch emails without filter"; + $res = $jmap->CallMethods([ + [ 'Email/query', { accountId => $account }, 'R1' ], + [ + 'Email/get', + { + accountId => $account, + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } + }, + 'R2' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_num_equals(2, scalar @{ $res->[1][1]->{list} }); + + %m = map { $_->{subject} => $_ } @{ $res->[1][1]{list} }; + my $foo = $m{"foo"}->{id}; + my $bar = $m{"bar"}->{id}; + $self->assert_not_null($foo); + $self->assert_not_null($bar); + + xlog $self, "filter text"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + text => "foo", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter NOT text"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + operator => "NOT", + conditions => [ { text => "foo" } ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailbox A"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + inMailbox => $mboxa, + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailboxes"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + operator => 'OR', + conditions => [ + { + inMailbox => $mboxa, + }, + { + inMailbox => $mboxc, + }, + ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailboxes with not in"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + inMailboxOtherThan => [$mboxb], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailboxes"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + operator => 'AND', + conditions => [ + { + inMailbox => $mboxa, + }, + { + inMailbox => $mboxb, + }, + { + inMailbox => $mboxc, + }, + ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "filter not in mailbox A"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + operator => 'NOT', + conditions => [ + { + inMailbox => $mboxa, + }, ], - store => $store, - ); - $self->make_message("bar", %params) || die; - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "fetch emails without filter"; - $res = $jmap->CallMethods([ - ['Email/query', { accountId => $account }, 'R1'], - ['Email/get', { - accountId => $account, - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } - }, 'R2'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_num_equals(2, scalar @{$res->[1][1]->{list}}); - - %m = map { $_->{subject} => $_ } @{$res->[1][1]{list}}; - my $foo = $m{"foo"}->{id}; - my $bar = $m{"bar"}->{id}; - $self->assert_not_null($foo); - $self->assert_not_null($bar); - - xlog $self, "filter text"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - text => "foo", - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter NOT text"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - operator => "NOT", - conditions => [ {text => "foo"} ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailbox A"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - inMailbox => $mboxa, - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailboxes"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - operator => 'OR', - conditions => [ - { - inMailbox => $mboxa, - }, - { - inMailbox => $mboxc, - }, - ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailboxes with not in"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - inMailboxOtherThan => [$mboxb], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailboxes"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - operator => 'AND', - conditions => [ - { - inMailbox => $mboxa, - }, - { - inMailbox => $mboxb, - }, - { - inMailbox => $mboxc, - }, - ], - }, - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "filter not in mailbox A"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - operator => 'NOT', - conditions => [ - { - inMailbox => $mboxa, - }, - ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by before"; - my $dtbefore = $dtfoo->clone()->subtract(seconds => 1); - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - before => $dtbefore->strftime('%Y-%m-%dT%TZ'), - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by after", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by before"; + my $dtbefore = $dtfoo->clone()->subtract(seconds => 1); + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + before => $dtbefore->strftime('%Y-%m-%dT%TZ'), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by after", my $dtafter = $dtbar->clone()->add(seconds => 1); - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - after => $dtafter->strftime('%Y-%m-%dT%TZ'), - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by after and before", - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - after => $dtafter->strftime('%Y-%m-%dT%TZ'), - before => $dtbefore->strftime('%Y-%m-%dT%TZ'), - }, - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "filter by minSize"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - minSize => length($bodybar), - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by maxSize"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - maxSize => length($bodybar), - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by header"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - header => [ "x-tra" ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by header and value"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - header => [ "x-tra", "bam" ], - }, - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "sort by ascending receivedAt"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "receivedAt" }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by descending receivedAt"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "receivedAt", isAscending => JSON::false }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by ascending sentAt"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "sentAt" }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by descending sentAt"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "sentAt", isAscending => JSON::false }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by ascending size"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "size" }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by descending size"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "size", isAscending => JSON::false }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by ascending id"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "id" }], - }, "R1"]]); - my @ids = sort ($foo, $bar); - $self->assert_deep_equals(\@ids, $res->[0][1]->{ids}); - - xlog $self, "sort by descending id"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "id", isAscending => JSON::false }], - }, "R1"]]); - @ids = reverse sort ($foo, $bar); - $self->assert_deep_equals(\@ids, $res->[0][1]->{ids}); - - xlog $self, "delete mailboxes"; - $talk->delete("$mboxprefix.A") or die; - $talk->delete("$mboxprefix.B") or die; - $talk->delete("$mboxprefix.C") or die; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + after => $dtafter->strftime('%Y-%m-%dT%TZ'), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by after and before", + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + after => $dtafter->strftime('%Y-%m-%dT%TZ'), + before => $dtbefore->strftime('%Y-%m-%dT%TZ'), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "filter by minSize"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + minSize => length($bodybar), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by maxSize"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + maxSize => length($bodybar), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by header"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + header => ["x-tra"], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by header and value"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + header => [ "x-tra", "bam" ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "sort by ascending receivedAt"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "receivedAt" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by descending receivedAt"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "receivedAt", isAscending => JSON::false } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by ascending sentAt"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "sentAt" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by descending sentAt"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "sentAt", isAscending => JSON::false } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by ascending size"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "size" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by descending size"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "size", isAscending => JSON::false } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by ascending id"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "id" } ], + }, + "R1" + ] ]); + my @ids = sort ($foo, $bar); + $self->assert_deep_equals(\@ids, $res->[0][1]->{ids}); + + xlog $self, "sort by descending id"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "id", isAscending => JSON::false } ], + }, + "R1" + ] ]); + @ids = reverse sort ($foo, $bar); + $self->assert_deep_equals(\@ids, $res->[0][1]->{ids}); + + xlog $self, "delete mailboxes"; + $talk->delete("$mboxprefix.A") or die; + $talk->delete("$mboxprefix.B") or die; + $talk->delete("$mboxprefix.C") or die; } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_acl b/cassandane/tiny-tests/JMAPEmail/email_query_acl index 68df898027..b7b9bb0af9 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_acl +++ b/cassandane/tiny-tests/JMAPEmail/email_query_acl @@ -2,60 +2,63 @@ use Cassandane::Tiny; sub test_email_query_acl - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - my $admintalk = $self->{adminstore}->get_client(); - - # Create user and share mailbox - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lr") or die; - - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', { accountId => 'foo' }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "Create email in shared account"; - $self->{adminstore}->set_folder('user.foo'); - $self->make_message("Email foo", store => $self->{adminstore}) or die; - - xlog $self, "get email list in main account"; - $res = $jmap->CallMethods([['Email/query', { }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "get email list in shared account"; - $res = $jmap->CallMethods([['Email/query', { accountId => 'foo' }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $id = $res->[0][1]->{ids}[0]; - - xlog $self, "Create email in main account"; - $self->make_message("Email cassandane") or die; - - xlog $self, "get email list in main account"; - $res = $jmap->CallMethods([['Email/query', { }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_not_equals($id, $res->[0][1]->{ids}[0]); - - xlog $self, "get email list in shared account"; - $res = $jmap->CallMethods([['Email/query', { accountId => 'foo' }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($id, $res->[0][1]->{ids}[0]); - - xlog $self, "create but do not share mailbox"; - $admintalk->create("user.foo.box1") or die; - $admintalk->setacl("user.foo.box1", "cassandane", "") or die; - - xlog $self, "create email in private mailbox"; - $self->{adminstore}->set_folder('user.foo.box1'); - $self->make_message("Email private foo", store => $self->{adminstore}) or die; - - xlog $self, "get email list in shared account"; - $res = $jmap->CallMethods([['Email/query', { accountId => 'foo' }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($id, $res->[0][1]->{ids}[0]); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + my $admintalk = $self->{adminstore}->get_client(); + + # Create user and share mailbox + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lr") or die; + + xlog $self, "get email list"; + my $res + = $jmap->CallMethods([ [ 'Email/query', { accountId => 'foo' }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "Create email in shared account"; + $self->{adminstore}->set_folder('user.foo'); + $self->make_message("Email foo", store => $self->{adminstore}) or die; + + xlog $self, "get email list in main account"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "get email list in shared account"; + $res + = $jmap->CallMethods([ [ 'Email/query', { accountId => 'foo' }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $id = $res->[0][1]->{ids}[0]; + + xlog $self, "Create email in main account"; + $self->make_message("Email cassandane") or die; + + xlog $self, "get email list in main account"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_not_equals($id, $res->[0][1]->{ids}[0]); + + xlog $self, "get email list in shared account"; + $res + = $jmap->CallMethods([ [ 'Email/query', { accountId => 'foo' }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($id, $res->[0][1]->{ids}[0]); + + xlog $self, "create but do not share mailbox"; + $admintalk->create("user.foo.box1") or die; + $admintalk->setacl("user.foo.box1", "cassandane", "") or die; + + xlog $self, "create email in private mailbox"; + $self->{adminstore}->set_folder('user.foo.box1'); + $self->make_message("Email private foo", store => $self->{adminstore}) or die; + + xlog $self, "get email list in shared account"; + $res + = $jmap->CallMethods([ [ 'Email/query', { accountId => 'foo' }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($id, $res->[0][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_addedDates b/cassandane/tiny-tests/JMAPEmail/email_query_addedDates index eb7b8e4b84..ea11b1f492 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_addedDates +++ b/cassandane/tiny-tests/JMAPEmail/email_query_addedDates @@ -2,147 +2,180 @@ use Cassandane::Tiny; sub test_email_query_addedDates - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # addedDates property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # addedDates property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - xlog $self, "Create Trash folder"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "trash" => { - name => "Trash", - parentId => undef, - role => "trash" - } - } - }, "R1"], - ]); - my $trashId = $res->[0][1]{created}{trash}{id}; - $self->assert_not_null($trashId); + xlog $self, "Create Trash folder"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "trash" => { + name => "Trash", + parentId => undef, + role => "trash" + } + } + }, + "R1" + ], + ]); + my $trashId = $res->[0][1]{created}{trash}{id}; + $self->assert_not_null($trashId); - xlog $self, "create messages"; - $self->make_message('uid1') || die; - $self->make_message('uid2') || die; - sleep 1; - $self->make_message('uid3') || die; - $self->make_message('uid4') || die; + xlog $self, "create messages"; + $self->make_message('uid1') || die; + $self->make_message('uid2') || die; + sleep 1; + $self->make_message('uid3') || die; + $self->make_message('uid4') || die; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ - property => 'subject', - isAscending => JSON::true - }], - }, 'R1'], - ]); - my $emailId1 = $res->[0][1]{ids}[0]; - my $emailId2 = $res->[0][1]{ids}[1]; - my $emailId3 = $res->[0][1]{ids}[2]; - my $emailId4 = $res->[0][1]{ids}[3]; - $self->assert_not_null($emailId1); - $self->assert_not_null($emailId2); - $self->assert_not_null($emailId3); - $self->assert_not_null($emailId4); + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { + property => 'subject', + isAscending => JSON::true + } ], + }, + 'R1' + ], + ]); + my $emailId1 = $res->[0][1]{ids}[0]; + my $emailId2 = $res->[0][1]{ids}[1]; + my $emailId3 = $res->[0][1]{ids}[2]; + my $emailId4 = $res->[0][1]{ids}[3]; + $self->assert_not_null($emailId1); + $self->assert_not_null($emailId2); + $self->assert_not_null($emailId3); + $self->assert_not_null($emailId4); - # Move email2 to mailbox using role as id - sleep 1; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId2 => { - "mailboxIds/$inboxid" => undef, - "mailboxIds/$trashId" => JSON::true - } - }, - }, 'R1'], - ]); + # Move email2 to mailbox using role as id + sleep 1; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId2 => { + "mailboxIds/$inboxid" => undef, + "mailboxIds/$trashId" => JSON::true + } + }, + }, + 'R1' + ], + ]); - # Move email1 to mailbox using role as id - sleep 1; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId1 => { - "mailboxIds/$inboxid" => undef, - "mailboxIds/$trashId" => JSON::true - } - }, - }, 'R1'], - ]); + # Move email1 to mailbox using role as id + sleep 1; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId1 => { + "mailboxIds/$inboxid" => undef, + "mailboxIds/$trashId" => JSON::true + } + }, + }, + 'R1' + ], + ]); - # Copy email4 to mailbox using role as id - sleep 1; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId4 => { - "mailboxIds/$trashId" => JSON::true, - keywords => { '$flagged' => JSON::true } - } - }, - }, 'R1'], - ]); + # Copy email4 to mailbox using role as id + sleep 1; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId4 => { + "mailboxIds/$trashId" => JSON::true, + keywords => { '$flagged' => JSON::true } + } + }, + }, + 'R1' + ], + ]); - # Copy email3 to mailbox using role as id - sleep 1; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId3 => { - "mailboxIds/$trashId" => JSON::true - } - }, - }, 'R1'], - ]); + # Copy email3 to mailbox using role as id + sleep 1; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId3 => { + "mailboxIds/$trashId" => JSON::true + } + }, + }, + 'R1' + ], + ]); - xlog $self, "query emails sorted by addedDates"; - $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ - property => 'addedDates', - mailboxId => "$trashId", - isAscending => JSON::true - }], - }, 'R1'], - ]); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[1]); - $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($emailId3, $res->[0][1]->{ids}[3]); - $self->assert_str_equals($emailId4, $res->[0][1]->{ids}[2]); + xlog $self, "query emails sorted by addedDates"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { + property => 'addedDates', + mailboxId => "$trashId", + isAscending => JSON::true + } ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[1]); + $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($emailId3, $res->[0][1]->{ids}[3]); + $self->assert_str_equals($emailId4, $res->[0][1]->{ids}[2]); - $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ - property => 'someInThreadHaveKeyword', - keyword => '$flagged', - isAscending => JSON::false, - }, - { - property => 'addedDates', - mailboxId => "$trashId", - isAscending => JSON::false, - }], - }, 'R1'], - ]); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[2]); - $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[3]); - $self->assert_str_equals($emailId3, $res->[0][1]->{ids}[1]); - $self->assert_str_equals($emailId4, $res->[0][1]->{ids}[0]); + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ + { + property => 'someInThreadHaveKeyword', + keyword => '$flagged', + isAscending => JSON::false, + }, + { + property => 'addedDates', + mailboxId => "$trashId", + isAscending => JSON::false, + } + ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[2]); + $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[3]); + $self->assert_str_equals($emailId3, $res->[0][1]->{ids}[1]); + $self->assert_str_equals($emailId4, $res->[0][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_angleuri b/cassandane/tiny-tests/JMAPEmail/email_query_angleuri index 9dea8278bf..1929a36121 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_angleuri +++ b/cassandane/tiny-tests/JMAPEmail/email_query_angleuri @@ -2,25 +2,24 @@ use Cassandane::Tiny; sub test_email_query_angleuri - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - - # Search indexing attempts to strip HTML tags also from plain - # text bodies if they are part of a multipart/alternative. - # This is because ill-behaving email implementations tend to - # put HTML in these, too. - - # Unfortunately, legit email clients might enclose URLs - # in angle-brackets when they convert anchor hrefs in - # HTML bodies to plain text alternatives. This caused - # Cyrus to also strip such URLs from plain text, instead - # of indexing them for text search. - - xlog $self, "Append message with angle-bracket URI in plain text"; - my $mimeMessage = <<'EOF'; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + + # Search indexing attempts to strip HTML tags also from plain + # text bodies if they are part of a multipart/alternative. + # This is because ill-behaving email implementations tend to + # put HTML in these, too. + + # Unfortunately, legit email clients might enclose URLs + # in angle-brackets when they convert anchor hrefs in + # HTML bodies to plain text alternatives. This caused + # Cyrus to also strip such URLs from plain text, instead + # of indexing them for text search. + + xlog $self, "Append message with angle-bracket URI in plain text"; + my $mimeMessage = <<'EOF'; From: from@local To: to@local Subject: needle_in_plain @@ -41,11 +40,11 @@ Content-Type: text/html;charset=utf-8 --c4683f7a320d4d20902b000486fbdf9b-- EOF - $mimeMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $mimeMessage) || die $@; + $mimeMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $mimeMessage) || die $@; - xlog $self, "Append message with angle-bracket URI in HTML text"; - $mimeMessage = <<'EOF'; + xlog $self, "Append message with angle-bracket URI in HTML text"; + $mimeMessage = <<'EOF'; From: from@local To: to@local Subject: needle_in_html @@ -66,29 +65,37 @@ Content-Type: text/html;charset=utf-8 --c4683f7a320d4d20902b000486fbdf9b-- EOF - $mimeMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $mimeMessage) || die $@; - - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "Assert angle-bracket URI is indexed for plain text, but not HTML"; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => 'needle', - }, - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - properties => ['subject'], - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals('needle_in_plain', - $res->[1][1]{list}[0]{subject}); + $mimeMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $mimeMessage) || die $@; + + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, + "Assert angle-bracket URI is indexed for plain text, but not HTML"; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + body => 'needle', + }, + }, + 'R1' + ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + properties => ['subject'], + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals('needle_in_plain', $res->[1][1]{list}[0]{subject}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_attachmentname b/cassandane/tiny-tests/JMAPEmail/email_query_attachmentname index d0aca9b113..6bbc351b31 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_attachmentname +++ b/cassandane/tiny-tests/JMAPEmail/email_query_attachmentname @@ -2,66 +2,84 @@ use Cassandane::Tiny; sub test_email_query_attachmentname - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - # create an email with an attachment - my $binary = slurp_file(abs_path('data/logo.gif')); - my $data = $jmap->Upload($binary, "image/gif"); + # create an email with an attachment + my $binary = slurp_file(abs_path('data/logo.gif')); + my $data = $jmap->Upload($binary, "image/gif"); - $res = $jmap->CallMethods([ - ['Email/set', { create => { - "1" => { - mailboxIds => {$draftsmbox => JSON::true}, - from => [ { name => "", email => "sam\@acme.local" } ] , - to => [ { name => "", email => "bugs\@acme.local" } ], - subject => "msg1", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "foo" } }, - attachments => [{ - blobId => $data->{blobId}, - name => "R\N{LATIN SMALL LETTER U WITH DIAERESIS}bezahl.txt", - }], - keywords => { '$draft' => JSON::true }, - }, - }}, 'R2'], - ]); - my $id1 = $res->[0][1]{created}{"1"}{id}; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + "1" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "", email => "sam\@acme.local" } ], + to => [ { name => "", email => "bugs\@acme.local" } ], + subject => "msg1", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "foo" } }, + attachments => [ { + blobId => $data->{blobId}, + name => "R\N{LATIN SMALL LETTER U WITH DIAERESIS}bezahl.txt", + } ], + keywords => { '$draft' => JSON::true }, + }, + } + }, + 'R2' + ], + ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + ]; - xlog $self, "filter attachmentName"; - $res = $jmap->CallMethods([['Email/query', { + xlog $self, "filter attachmentName"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - attachmentName => "r\N{LATIN SMALL LETTER U WITH DIAERESIS}bezahl", + attachmentName => "r\N{LATIN SMALL LETTER U WITH DIAERESIS}bezahl", }, - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($id1, $res->[0][1]->{ids}[0]); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($id1, $res->[0][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_attachments b/cassandane/tiny-tests/JMAPEmail/email_query_attachments index f11c91d962..6caaf86e91 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_attachments +++ b/cassandane/tiny-tests/JMAPEmail/email_query_attachments @@ -2,110 +2,149 @@ use Cassandane::Tiny; sub test_email_query_attachments - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - # create an email with an attachment - my $binary = slurp_file(abs_path('data/logo.gif')); - my $data = $jmap->Upload($binary, "image/gif"); + # create an email with an attachment + my $binary = slurp_file(abs_path('data/logo.gif')); + my $data = $jmap->Upload($binary, "image/gif"); - $res = $jmap->CallMethods([ - ['Email/set', { create => { - "1" => { - mailboxIds => {$draftsmbox => JSON::true}, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo", - textBody => [{ partId => '1' }], - bodyValues => {'1' => { value => "I'm givin' ya one last chance ta surrenda!" }}, - attachments => [{ - blobId => $data->{blobId}, - name => "logo.gif", - }], - keywords => { '$draft' => JSON::true }, - }, - "2" => { - mailboxIds => {$draftsmbox => JSON::true}, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo 2", - textBody => [{ partId => '1' }], - bodyValues => {'1' => { value => "I'm givin' ya *one* last chance ta surrenda!" }}, - attachments => [{ - blobId => $data->{blobId}, - name => "somethingelse.gif", - }], - keywords => { '$draft' => JSON::true }, - }, - } }, 'R2'], - ]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + "1" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { + '1' => { value => "I'm givin' ya one last chance ta surrenda!" } + }, + attachments => [ { + blobId => $data->{blobId}, + name => "logo.gif", + } ], + keywords => { '$draft' => JSON::true }, + }, + "2" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo 2", + textBody => [ { partId => '1' } ], + bodyValues => { + '1' => { value => "I'm givin' ya *one* last chance ta surrenda!" } + }, + attachments => [ { + blobId => $data->{blobId}, + name => "somethingelse.gif", + } ], + keywords => { '$draft' => JSON::true }, + }, + } + }, + 'R2' + ], + ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + ]; - xlog $self, "filter attachmentName"; - $res = $jmap->CallMethods([['Email/query', { + xlog $self, "filter attachmentName"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - attachmentName => "logo", + attachmentName => "logo", }, - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($id1, $res->[0][1]->{ids}[0]); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($id1, $res->[0][1]->{ids}[0]); - xlog $self, "filter attachmentName"; - $res = $jmap->CallMethods([['Email/query', { + xlog $self, "filter attachmentName"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - attachmentName => "somethingelse.gif", + attachmentName => "somethingelse.gif", }, - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($id2, $res->[0][1]->{ids}[0]); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($id2, $res->[0][1]->{ids}[0]); - xlog $self, "filter attachmentName"; - $res = $jmap->CallMethods([['Email/query', { + xlog $self, "filter attachmentName"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - attachmentName => "gif", + attachmentName => "gif", }, - }, "R1"]], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); - xlog $self, "filter text"; - $res = $jmap->CallMethods([['Email/query', { + xlog $self, "filter text"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - text => "logo", + text => "logo", }, - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($id1, $res->[0][1]->{ids}[0]); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($id1, $res->[0][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype b/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype index 5a66e5f2c5..5cf2d8c25e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype +++ b/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_email_query_attachmenttype - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $blobId = $jmap->Upload('some_data', "application/octet")->{blobId}; + my $blobId = $jmap->Upload('some_data', "application/octet")->{blobId}; - my $rfc822Msg = <<'EOF'; + my $rfc822Msg = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -21,157 +20,184 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $rfc822Msg =~ s/\r?\n/\r\n/gs; - my $rfc822MsgBlobId = $jmap->Upload($rfc822Msg, "message/rfc822")->{blobId}; - $self->assert_not_null($rfc822MsgBlobId); + $rfc822Msg =~ s/\r?\n/\r\n/gs; + my $rfc822MsgBlobId = $jmap->Upload($rfc822Msg, "message/rfc822")->{blobId}; + $self->assert_not_null($rfc822MsgBlobId); - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { + my $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { "1" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "sam\@acme.local" } ] , - to => [ { name => "", email => "bugs\@acme.local" } ], - subject => "foo", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "foo" } }, - attachments => [{ + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "sam\@acme.local" } ], + to => [ { name => "", email => "bugs\@acme.local" } ], + subject => "foo", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "foo" } }, + attachments => [ { blobId => $blobId, - type => 'image/gif', - }], - }, - "2" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "tweety\@acme.local" } ] , - to => [ { name => "", email => "duffy\@acme.local" } ], - subject => "bar", - textBody => [{ partId => '1' }], + type => 'image/gif', + } ], + }, + "2" => { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "tweety\@acme.local" } ], + to => [ { name => "", email => "duffy\@acme.local" } ], + subject => "bar", + textBody => [ { partId => '1' } ], bodyValues => { '1' => { value => "bar" } }, - }, - "3" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "elmer\@acme.local" } ] , - to => [ { name => "", email => "porky\@acme.local" } ], - subject => "baz", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "baz" } }, - attachments => [{ + }, + "3" => { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "elmer\@acme.local" } ], + to => [ { name => "", email => "porky\@acme.local" } ], + subject => "baz", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "baz" } }, + attachments => [ { blobId => $blobId, - type => 'application/msword', - }], - }, - "4" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "elmer\@acme.local" } ] , - to => [ { name => "", email => "porky\@acme.local" } ], - subject => "baz", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "baz" } }, - attachments => [{ + type => 'application/msword', + } ], + }, + "4" => { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "elmer\@acme.local" } ], + to => [ { name => "", email => "porky\@acme.local" } ], + subject => "baz", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "baz" } }, + attachments => [ { blobId => $blobId, - type => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - }], - }, - "5" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "elmer\@acme.local" } ] , - to => [ { name => "", email => "porky\@acme.local" } ], - subject => "embeddedmsg", + type => + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + } ], + }, + "5" => { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "elmer\@acme.local" } ], + to => [ { name => "", email => "porky\@acme.local" } ], + subject => "embeddedmsg", bodyStructure => { - subParts => [{ - partId => "text", - type => "text/plain" - },{ - blobId => $rfc822MsgBlobId, - disposition => "attachment", - type => "message/rfc822" - }], - type => "multipart/mixed", + subParts => [ + { + partId => "text", + type => "text/plain" + }, + { + blobId => $rfc822MsgBlobId, + disposition => "attachment", + type => "message/rfc822" + } + ], + type => "multipart/mixed", }, bodyValues => { - text => { - value => "Hello World", - }, + text => { + value => "Hello World", + }, }, + } } - }}, 'R1'] - ]); - my $idGif = $res->[0][1]{created}{"1"}{id}; - my $idTxt = $res->[0][1]{created}{"2"}{id}; - my $idDoc = $res->[0][1]{created}{"3"}{id}; - my $idWord = $res->[0][1]{created}{"4"}{id}; - my $idRfc822Msg = $res->[0][1]{created}{"5"}{id}; - $self->assert_not_null($idGif); - $self->assert_not_null($idTxt); - $self->assert_not_null($idDoc); - $self->assert_not_null($idWord); + }, + 'R1' + ] ]); + my $idGif = $res->[0][1]{created}{"1"}{id}; + my $idTxt = $res->[0][1]{created}{"2"}{id}; + my $idDoc = $res->[0][1]{created}{"3"}{id}; + my $idWord = $res->[0][1]{created}{"4"}{id}; + my $idRfc822Msg = $res->[0][1]{created}{"5"}{id}; + $self->assert_not_null($idGif); + $self->assert_not_null($idTxt); + $self->assert_not_null($idDoc); + $self->assert_not_null($idWord); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my @testCases = ({ - filter => { - attachmentType => 'image/gif', - }, - wantIds => [$idGif], - }, { - filter => { + my @testCases = ( + { + filter => { + attachmentType => 'image/gif', + }, + wantIds => [$idGif], + }, + { + filter => { + attachmentType => 'image', + }, + wantIds => [$idGif], + }, + { + filter => { + attachmentType => 'application/msword', + }, + wantIds => [$idDoc], + }, + { + filter => { +# this should be application/vnd... but Xapian has a 64 character limit on terms +# indexed, so application_vndopenxmlformatsofficedocumentwordprocessingmldocument +# never got indexed + attachmentType => + 'vnd.openxmlformats-officedocument.wordprocessingml.document', + }, + wantIds => [$idWord], + }, + { + filter => { + attachmentType => 'document', + }, + wantIds => [ $idDoc, $idWord ], + }, + { + filter => { + operator => 'NOT', + conditions => [ + { attachmentType => 'image', - }, - wantIds => [$idGif], - }, { - filter => { - attachmentType => 'application/msword', - }, - wantIds => [$idDoc], - }, { - filter => { - # this should be application/vnd... but Xapian has a 64 character limit on terms - # indexed, so application_vndopenxmlformatsofficedocumentwordprocessingmldocument - # never got indexed - attachmentType => 'vnd.openxmlformats-officedocument.wordprocessingml.document', - }, - wantIds => [$idWord], - }, { - filter => { + }, + { attachmentType => 'document', - }, - wantIds => [$idDoc, $idWord], - }, { - filter => { - operator => 'NOT', - conditions => [{ - attachmentType => 'image', - }, { - attachmentType => 'document', - }], - }, - wantIds => [$idTxt, $idRfc822Msg], - }, { - filter => { - attachmentType => 'email', - }, - wantIds => [$idRfc822Msg], - }); + } + ], + }, + wantIds => [ $idTxt, $idRfc822Msg ], + }, + { + filter => { + attachmentType => 'email', + }, + wantIds => [$idRfc822Msg], + } + ); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + ]; - foreach (@testCases) { - my $filter = $_->{filter}; - my $wantIds = $_->{wantIds}; - $res = $jmap->CallMethods([['Email/query', { - filter => $filter, - }, "R1"]], $using); - my @wantIds = sort @{$wantIds}; - my @gotIds = sort @{$res->[0][1]->{ids}}; - $self->assert_deep_equals(\@wantIds, \@gotIds); - } + foreach (@testCases) { + my $filter = $_->{filter}; + my $wantIds = $_->{wantIds}; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => $filter, + }, + "R1" + ] ], + $using + ); + my @wantIds = sort @{$wantIds}; + my @gotIds = sort @{ $res->[0][1]->{ids} }; + $self->assert_deep_equals(\@wantIds, \@gotIds); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype_legacy b/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype_legacy index 683dbd3bbb..875f5dc68d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype_legacy +++ b/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype_legacy @@ -2,123 +2,141 @@ use Cassandane::Tiny; sub test_email_query_attachmenttype_legacy - :min_version_3_1 :max_version_3_4 - :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : max_version_3_4 + : needs_component_sieve : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $blobId = $jmap->Upload('some_data', "application/octet")->{blobId}; + my $blobId = $jmap->Upload('some_data', "application/octet")->{blobId}; - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { + my $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { "1" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "sam\@acme.local" } ] , - to => [ { name => "", email => "bugs\@acme.local" } ], - subject => "foo", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "foo" } }, - attachments => [{ + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "sam\@acme.local" } ], + to => [ { name => "", email => "bugs\@acme.local" } ], + subject => "foo", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "foo" } }, + attachments => [ { blobId => $blobId, - type => 'image/gif', - }], - }, - "2" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "tweety\@acme.local" } ] , - to => [ { name => "", email => "duffy\@acme.local" } ], - subject => "bar", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "bar" } }, - }, - "3" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "elmer\@acme.local" } ] , - to => [ { name => "", email => "porky\@acme.local" } ], - subject => "baz", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "baz" } }, - attachments => [{ - blobId => $blobId, - type => 'application/msword', - }], - }, - "4" => { - mailboxIds => {$inboxid => JSON::true}, - from => [ { name => "", email => "elmer\@acme.local" } ] , - to => [ { name => "", email => "porky\@acme.local" } ], - subject => "baz", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "baz" } }, - attachments => [{ - blobId => $blobId, - type => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - }], - }, - }}, 'R1'] - ]); - my $idGif = $res->[0][1]{created}{"1"}{id}; - my $idTxt = $res->[0][1]{created}{"2"}{id}; - my $idDoc = $res->[0][1]{created}{"3"}{id}; - my $idWord = $res->[0][1]{created}{"4"}{id}; - $self->assert_not_null($idGif); - $self->assert_not_null($idTxt); - $self->assert_not_null($idDoc); - $self->assert_not_null($idWord); - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my @testCases = ({ - filter => { - attachmentType => 'image/gif', + type => 'image/gif', + } ], }, - wantIds => [$idGif], - }, { - filter => { - attachmentType => 'image', - }, - wantIds => [$idGif], - }, { - filter => { - attachmentType => 'application/msword', + "2" => { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "tweety\@acme.local" } ], + to => [ { name => "", email => "duffy\@acme.local" } ], + subject => "bar", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "bar" } }, }, - wantIds => [$idDoc], - }, { - filter => { - # this should be application/vnd... but Xapian has a 64 character limit on terms - # indexed, so application_vndopenxmlformatsofficedocumentwordprocessingmldocument - # never got indexed - attachmentType => 'vnd.openxmlformats-officedocument.wordprocessingml.document', + "3" => { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "elmer\@acme.local" } ], + to => [ { name => "", email => "porky\@acme.local" } ], + subject => "baz", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "baz" } }, + attachments => [ { + blobId => $blobId, + type => 'application/msword', + } ], }, - wantIds => [$idWord], - }, { - filter => { - attachmentType => 'document', + "4" => { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "", email => "elmer\@acme.local" } ], + to => [ { name => "", email => "porky\@acme.local" } ], + subject => "baz", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "baz" } }, + attachments => [ { + blobId => $blobId, + type => + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + } ], }, - wantIds => [$idDoc, $idWord], - }); + } + }, + 'R1' + ] ]); + my $idGif = $res->[0][1]{created}{"1"}{id}; + my $idTxt = $res->[0][1]{created}{"2"}{id}; + my $idDoc = $res->[0][1]{created}{"3"}{id}; + my $idWord = $res->[0][1]{created}{"4"}{id}; + $self->assert_not_null($idGif); + $self->assert_not_null($idTxt); + $self->assert_not_null($idDoc); + $self->assert_not_null($idWord); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - ]; + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - foreach (@testCases) { - my $filter = $_->{filter}; - my $wantIds = $_->{wantIds}; - $res = $jmap->CallMethods([['Email/query', { - filter => $filter, - }, "R1"]], $using); - my @wantIds = sort @{$wantIds}; - my @gotIds = sort @{$res->[0][1]->{ids}}; - $self->assert_deep_equals(\@wantIds, \@gotIds); + my @testCases = ( + { + filter => { + attachmentType => 'image/gif', + }, + wantIds => [$idGif], + }, + { + filter => { + attachmentType => 'image', + }, + wantIds => [$idGif], + }, + { + filter => { + attachmentType => 'application/msword', + }, + wantIds => [$idDoc], + }, + { + filter => { +# this should be application/vnd... but Xapian has a 64 character limit on terms +# indexed, so application_vndopenxmlformatsofficedocumentwordprocessingmldocument +# never got indexed + attachmentType => + 'vnd.openxmlformats-officedocument.wordprocessingml.document', + }, + wantIds => [$idWord], + }, + { + filter => { + attachmentType => 'document', + }, + wantIds => [ $idDoc, $idWord ], } + ); + + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + ]; + + foreach (@testCases) { + my $filter = $_->{filter}; + my $wantIds = $_->{wantIds}; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => $filter, + }, + "R1" + ] ], + $using + ); + my @wantIds = sort @{$wantIds}; + my @gotIds = sort @{ $res->[0][1]->{ids} }; + $self->assert_deep_equals(\@wantIds, \@gotIds); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype_wildcards b/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype_wildcards index eb4fc3f2fd..2f09145994 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype_wildcards +++ b/cassandane/tiny-tests/JMAPEmail/email_query_attachmenttype_wildcards @@ -2,122 +2,138 @@ use Cassandane::Tiny; sub test_email_query_attachmenttype_wildcards - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $self->make_message("msg1", - mime_type => "multipart/mixed", - mime_boundary => "123456789", - body => "" - . "--123456789\r\n" - . "Content-Type: text/plain\r\n" - . "msg1" - . "\r\n--123456789\r\n" - . "Content-Type: application/rtf\r\n" - . "\r\n" - . "data" - . "\r\n--123456789--\r\n", - ); - - $self->make_message("msg2", - mime_type => "multipart/mixed", - mime_boundary => "123456789", - body => "" - . "--123456789\r\n" - . "Content-Type: text/plain\r\n" - . "msg1" - . "\r\n--123456789\r\n" - . "Content-Type: text/rtf\r\n" - . "\r\n" - . "data" - . "\r\n--123456789--\r\n", - ); + $self->make_message( + "msg1", + mime_type => "multipart/mixed", + mime_boundary => "123456789", + body => "" + . "--123456789\r\n" + . "Content-Type: text/plain\r\n" . "msg1" + . "\r\n--123456789\r\n" + . "Content-Type: application/rtf\r\n" . "\r\n" . "data" + . "\r\n--123456789--\r\n", + ); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->make_message( + "msg2", + mime_type => "multipart/mixed", + mime_boundary => "123456789", + body => "" + . "--123456789\r\n" + . "Content-Type: text/plain\r\n" . "msg1" + . "\r\n--123456789\r\n" + . "Content-Type: text/rtf\r\n" . "\r\n" . "data" + . "\r\n--123456789--\r\n", + ); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'subject' ], - }, 'R2'], - ]); - $self->assert_num_equals(2, scalar @{$res->[1][1]{list}}); - my %emails = map { $_->{subject} => $_->{id} } @{$res->[1][1]{list}}; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my @tests = ({ - filter => { - attachmentType => 'text/plain', - }, - wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], - }, { - filter => { - attachmentType => 'application/rtf', - }, - wantIds => [ $emails{'msg1'} ], - }, { - filter => { - attachmentType => 'text/rtf', - }, - wantIds => [ $emails{'msg2'} ], - }, { - filter => { - attachmentType => 'text', - }, - wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], - }, { - filter => { - attachmentType => 'application', + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' }, - wantIds => [ $emails{'msg1'} ], - }, { - filter => { - attachmentType => 'plain', - }, - wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], - }, { - filter => { - attachmentType => 'rtf', - }, - wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], - }, { - filter => { - attachmentType => 'application/*', - }, - wantIds => [ $emails{'msg1'} ], - }, { - filter => { - attachmentType => '*/rtf', - }, - wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], - }); + properties => ['subject'], + }, + 'R2' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[1][1]{list} }); + my %emails = map { $_->{subject} => $_->{id} } @{ $res->[1][1]{list} }; - foreach (@tests) { - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => $_->{filter}, - }, 'R1'], - ], $using); - my @gotIds = sort @{$res->[0][1]->{ids}}; - my @wantIds = sort @{$_->{wantIds}}; - $self->assert_deep_equals(\@wantIds, \@gotIds); + my @tests = ( + { + filter => { + attachmentType => 'text/plain', + }, + wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], + }, + { + filter => { + attachmentType => 'application/rtf', + }, + wantIds => [ $emails{'msg1'} ], + }, + { + filter => { + attachmentType => 'text/rtf', + }, + wantIds => [ $emails{'msg2'} ], + }, + { + filter => { + attachmentType => 'text', + }, + wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], + }, + { + filter => { + attachmentType => 'application', + }, + wantIds => [ $emails{'msg1'} ], + }, + { + filter => { + attachmentType => 'plain', + }, + wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], + }, + { + filter => { + attachmentType => 'rtf', + }, + wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], + }, + { + filter => { + attachmentType => 'application/*', + }, + wantIds => [ $emails{'msg1'} ], + }, + { + filter => { + attachmentType => '*/rtf', + }, + wantIds => [ $emails{'msg1'}, $emails{'msg2'} ], } + ); + + foreach (@tests) { + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => $_->{filter}, + }, + 'R1' + ], + ], + $using + ); + my @gotIds = sort @{ $res->[0][1]->{ids} }; + my @wantIds = sort @{ $_->{wantIds} }; + $self->assert_deep_equals(\@wantIds, \@gotIds); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_bcc b/cassandane/tiny-tests/JMAPEmail/email_query_bcc index 6667ee277d..8a025b18fb 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_bcc +++ b/cassandane/tiny-tests/JMAPEmail/email_query_bcc @@ -2,79 +2,99 @@ use Cassandane::Tiny; sub test_email_query_bcc - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $account = undef; - my $store = $self->{store}; - my $mboxprefix = "INBOX"; - my $talk = $store->get_client(); + my $account = undef; + my $store = $self->{store}; + my $mboxprefix = "INBOX"; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => $account }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { accountId => $account }, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - xlog $self, "create email1"; - my $bcc1 = Cassandane::Address->new(localpart => 'needle', domain => 'local'); - my $msg1 = $self->make_message('msg1', bcc => $bcc1); + xlog $self, "create email1"; + my $bcc1 = Cassandane::Address->new(localpart => 'needle', domain => 'local'); + my $msg1 = $self->make_message('msg1', bcc => $bcc1); - my $bcc2 = Cassandane::Address->new(localpart => 'beetle', domain => 'local'); - my $msg2 = $self->make_message('msg2', bcc => $bcc2); + my $bcc2 = Cassandane::Address->new(localpart => 'beetle', domain => 'local'); + my $msg2 = $self->make_message('msg2', bcc => $bcc2); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "fetch emails without filter"; - $res = $jmap->CallMethods([ - ['Email/query', { accountId => $account }, 'R1'], - ['Email/get', { - accountId => $account, - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } - }, 'R2'], - ]); + xlog $self, "fetch emails without filter"; + $res = $jmap->CallMethods([ + [ 'Email/query', { accountId => $account }, 'R1' ], + [ + 'Email/get', + { + accountId => $account, + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } + }, + 'R2' + ], + ]); - my %m = map { $_->{subject} => $_ } @{$res->[1][1]{list}}; - my $emailId1 = $m{"msg1"}->{id}; - my $emailId2 = $m{"msg2"}->{id}; - $self->assert_not_null($emailId1); - $self->assert_not_null($emailId2); + my %m = map { $_->{subject} => $_ } @{ $res->[1][1]{list} }; + my $emailId1 = $m{"msg1"}->{id}; + my $emailId2 = $m{"msg2"}->{id}; + $self->assert_not_null($emailId1); + $self->assert_not_null($emailId2); - xlog $self, "filter text"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - text => "needle", - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); + xlog $self, "filter text"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + text => "needle", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); - xlog $self, "filter NOT text"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - operator => "NOT", - conditions => [ {text => "needle"} ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[0]); + xlog $self, "filter NOT text"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + operator => "NOT", + conditions => [ { text => "needle" } ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[0]); - xlog $self, "filter bcc"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - bcc => "needle", - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); + xlog $self, "filter bcc"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + bcc => "needle", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); - xlog $self, "filter NOT bcc"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - operator => "NOT", - conditions => [ {bcc => "needle"} ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[0]); + xlog $self, "filter NOT bcc"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + operator => "NOT", + conditions => [ { bcc => "needle" } ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_body_sieve b/cassandane/tiny-tests/JMAPEmail/email_query_body_sieve index c344cb4d49..dec9161f5e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_body_sieve +++ b/cassandane/tiny-tests/JMAPEmail/email_query_body_sieve @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_email_query_body_sieve - :min_version_3_7 :needs_component_sieve :needs_component_jmap - :JMAPExtensions :AltNamespace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_7 : needs_component_sieve : needs_component_jmap + : JMAPExtensions : AltNamespace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $imap->create("matches") or die; + $imap->create("matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -25,28 +25,28 @@ if fileinto "matches"; } EOF - ); + ); - xlog "Deliver matching message"; - my $msg1 = $self->{gen}->generate( - subject => 'xxxyyyzzz', - body => "a msg with a wizzbang in it" - ); - $self->{instance}->deliver($msg1); + xlog "Deliver matching message"; + my $msg1 = $self->{gen}->generate( + subject => 'xxxyyyzzz', + body => "a msg with a wizzbang in it" + ); + $self->{instance}->deliver($msg1); - xlog "Assert that message got moved into INBOX.matches"; - $self->{store}->set_folder('matches'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog "Assert that message got moved into INBOX.matches"; + $self->{store}->set_folder('matches'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Deliver a non-matching message"; - my $msg2 = $self->{gen}->generate( - subject => 'zzzyyyyxxx', - body => "a more boring msg" - ); - $self->{instance}->deliver($msg2); - $msg2->set_attribute(uid => 1); + xlog $self, "Deliver a non-matching message"; + my $msg2 = $self->{gen}->generate( + subject => 'zzzyyyyxxx', + body => "a more boring msg" + ); + $self->{instance}->deliver($msg2); + $msg2->set_attribute(uid => 1); - xlog "Assert that message got moved into INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg2 }, check_guid => 0); + xlog "Assert that message got moved into INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg2 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_cached b/cassandane/tiny-tests/JMAPEmail/email_query_cached index 1fb359f1fa..fccea774a7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_cached +++ b/cassandane/tiny-tests/JMAPEmail/email_query_cached @@ -2,73 +2,76 @@ use Cassandane::Tiny; sub test_email_query_cached - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPQueryCacheMaxAge1s :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPQueryCacheMaxAge1s : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - xlog $self, "create emails"; - $res = $self->make_message("foo 1") || die; - $res = $self->make_message("foo 2") || die; + xlog $self, "create emails"; + $res = $self->make_message("foo 1") || die; + $res = $self->make_message("foo 2") || die; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $query1 = { - filter => { - subject => 'foo', - }, - sort => [{ property => 'subject' }], - }; + my $query1 = { + filter => { + subject => 'foo', + }, + sort => [ { property => 'subject' } ], + }; - my $query2 = { - filter => { - subject => 'foo', - }, - sort => [{ property => 'subject', isAscending => JSON::false }], - }; + my $query2 = { + filter => { + subject => 'foo', + }, + sort => [ { property => 'subject', isAscending => JSON::false } ], + }; - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; - xlog $self, "run query #1"; - $res = $jmap->CallMethods([['Email/query', $query1, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); + xlog $self, "run query #1"; + $res = $jmap->CallMethods([ [ 'Email/query', $query1, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[0][1]->{performance}{details}{isCached}); - xlog $self, "re-run query #1"; - $res = $jmap->CallMethods([['Email/query', $query1, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::true, $res->[0][1]->{performance}{details}{isCached}); + xlog $self, "re-run query #1"; + $res = $jmap->CallMethods([ [ 'Email/query', $query1, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::true, + $res->[0][1]->{performance}{details}{isCached}); - xlog $self, "run query #2"; - $res = $jmap->CallMethods([['Email/query', $query2, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); + xlog $self, "run query #2"; + $res = $jmap->CallMethods([ [ 'Email/query', $query2, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[0][1]->{performance}{details}{isCached}); - xlog $self, "re-run query #2"; - $res = $jmap->CallMethods([['Email/query', $query2, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::true, $res->[0][1]->{performance}{details}{isCached}); + xlog $self, "re-run query #2"; + $res = $jmap->CallMethods([ [ 'Email/query', $query2, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::true, + $res->[0][1]->{performance}{details}{isCached}); - xlog $self, "change Email state"; - $res = $self->make_message("foo 3") || die; + xlog $self, "change Email state"; + $res = $self->make_message("foo 3") || die; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "re-run query #2"; - $res = $jmap->CallMethods([['Email/query', $query2, 'R1']], $using); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); + xlog $self, "re-run query #2"; + $res = $jmap->CallMethods([ [ 'Email/query', $query2, 'R1' ] ], $using); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[0][1]->{performance}{details}{isCached}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_cached_collapsed_uncollapsed b/cassandane/tiny-tests/JMAPEmail/email_query_cached_collapsed_uncollapsed index c287069715..4f8e9efc79 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_cached_collapsed_uncollapsed +++ b/cassandane/tiny-tests/JMAPEmail/email_query_cached_collapsed_uncollapsed @@ -2,53 +2,65 @@ use Cassandane::Tiny; sub test_email_query_cached_collapsed_uncollapsed - :min_version_3_7 :needs_component_sieve :needs_component_jmap - :JMAPQueryCacheMaxAge1s :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_sieve : needs_component_jmap + : JMAPQueryCacheMaxAge1s : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - xlog $self, "create emails"; - $res = $self->make_message("foo 1") || die; - $res = $self->make_message("foo 2") || die; + xlog $self, "create emails"; + $res = $self->make_message("foo 1") || die; + $res = $self->make_message("foo 2") || die; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + ]; - - xlog $self, "Query uncollapsed threads"; - $res = $jmap->CallMethods([['Email/query', { + xlog $self, "Query uncollapsed threads"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - subject => 'foo', + subject => 'foo', }, - sort => [{ property => 'subject' }], + sort => [ { property => 'subject' } ], collapseThreads => JSON::false, - limit => 1, - }, 'R1']], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - #$self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); + limit => 1, + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); +#$self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); - xlog $self, "Query collapsed threads"; - $res = $jmap->CallMethods([['Email/query', { + xlog $self, "Query collapsed threads"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - subject => 'foo', + subject => 'foo', }, - sort => [{ property => 'subject' }], + sort => [ { property => 'subject' } ], collapseThreads => JSON::true, - limit => 1, - }, 'R1']], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); + limit => 1, + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_cached_evict b/cassandane/tiny-tests/JMAPEmail/email_query_cached_evict index e0bf5632f0..1ba3e95605 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_cached_evict +++ b/cassandane/tiny-tests/JMAPEmail/email_query_cached_evict @@ -2,54 +2,81 @@ use Cassandane::Tiny; sub test_email_query_cached_evict - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPQueryCacheMaxAge1s :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPQueryCacheMaxAge1s : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - $self->make_message("foo") || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->make_message("foo") || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - text => 'foo', - }, - }, 'R1'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + text => 'foo', + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[0][1]->{performance}{details}{isCached}); - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - text => 'foo', - }, - }, 'R1'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::true, $res->[0][1]->{performance}{details}{isCached}); + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + text => 'foo', + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::true, + $res->[0][1]->{performance}{details}{isCached}); - sleep(2); + sleep(2); - $res = $jmap->CallMethods([ - ['Identity/get', { - # evict cache - }, 'R1'], - ['Email/query', { - filter => { - text => 'foo', - }, - }, 'R2'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[1][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[1][1]->{performance}{details}{isCached}); + $res = $jmap->CallMethods( + [ + [ + 'Identity/get', + { + # evict cache + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + text => 'foo', + }, + }, + 'R2' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[1][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[1][1]->{performance}{details}{isCached}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_cached_legacy b/cassandane/tiny-tests/JMAPEmail/email_query_cached_legacy index 02492c7db2..58f3197a17 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_cached_legacy +++ b/cassandane/tiny-tests/JMAPEmail/email_query_cached_legacy @@ -2,83 +2,88 @@ use Cassandane::Tiny; sub test_email_query_cached_legacy - :min_version_3_1 :max_version_3_4 :needs_component_jmap - :needs_component_sieve :JMAPSearchDBLegacy :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; - - xlog $self, "create emails"; - $res = $self->make_message("foo 1") || die; - $res = $self->make_message("foo 2") || die; - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my $query1 = { - filter => { - subject => 'foo', - }, - sort => [{ property => 'subject' }], - }; - - my $query2 = { - filter => { - subject => 'foo', - }, - sort => [{ property => 'subject', isAscending => JSON::false }], - }; - - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; - - xlog $self, "run query #1"; - $res = $jmap->CallMethods([['Email/query', $query1, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); - - xlog $self, "re-run query #1"; - $res = $jmap->CallMethods([['Email/query', $query1, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::true, $res->[0][1]->{performance}{details}{isCached}); - - xlog $self, "run query #2"; - $res = $jmap->CallMethods([['Email/query', $query2, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); - - xlog $self, "re-run query #1 (still cached)"; - $res = $jmap->CallMethods([['Email/query', $query1, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::true, $res->[0][1]->{performance}{details}{isCached}); - - xlog $self, "re-run query #2 (still cached)"; - $res = $jmap->CallMethods([['Email/query', $query2, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::true, $res->[0][1]->{performance}{details}{isCached}); - - xlog $self, "change Email state"; - $res = $self->make_message("foo 3") || die; - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "re-run query #1 (cache invalidated)"; - $res = $jmap->CallMethods([['Email/query', $query1, 'R1']], $using); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); - - xlog $self, "re-run query #2 (cache invalidated)"; - $res = $jmap->CallMethods([['Email/query', $query2, 'R1']], $using); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals(JSON::false, $res->[0][1]->{performance}{details}{isCached}); + : min_version_3_1 : max_version_3_4 : needs_component_jmap + : needs_component_sieve : JMAPSearchDBLegacy : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; + + xlog $self, "create emails"; + $res = $self->make_message("foo 1") || die; + $res = $self->make_message("foo 2") || die; + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my $query1 = { + filter => { + subject => 'foo', + }, + sort => [ { property => 'subject' } ], + }; + + my $query2 = { + filter => { + subject => 'foo', + }, + sort => [ { property => 'subject', isAscending => JSON::false } ], + }; + + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; + + xlog $self, "run query #1"; + $res = $jmap->CallMethods([ [ 'Email/query', $query1, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[0][1]->{performance}{details}{isCached}); + + xlog $self, "re-run query #1"; + $res = $jmap->CallMethods([ [ 'Email/query', $query1, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::true, + $res->[0][1]->{performance}{details}{isCached}); + + xlog $self, "run query #2"; + $res = $jmap->CallMethods([ [ 'Email/query', $query2, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[0][1]->{performance}{details}{isCached}); + + xlog $self, "re-run query #1 (still cached)"; + $res = $jmap->CallMethods([ [ 'Email/query', $query1, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::true, + $res->[0][1]->{performance}{details}{isCached}); + + xlog $self, "re-run query #2 (still cached)"; + $res = $jmap->CallMethods([ [ 'Email/query', $query2, 'R1' ] ], $using); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::true, + $res->[0][1]->{performance}{details}{isCached}); + + xlog $self, "change Email state"; + $res = $self->make_message("foo 3") || die; + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "re-run query #1 (cache invalidated)"; + $res = $jmap->CallMethods([ [ 'Email/query', $query1, 'R1' ] ], $using); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[0][1]->{performance}{details}{isCached}); + + xlog $self, "re-run query #2 (cache invalidated)"; + $res = $jmap->CallMethods([ [ 'Email/query', $query2, 'R1' ] ], $using); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals(JSON::false, + $res->[0][1]->{performance}{details}{isCached}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_cjk_fullwidth b/cassandane/tiny-tests/JMAPEmail/email_query_cjk_fullwidth index b1529f637b..fd660c0cfd 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_cjk_fullwidth +++ b/cassandane/tiny-tests/JMAPEmail/email_query_cjk_fullwidth @@ -2,69 +2,88 @@ use Cassandane::Tiny; sub test_email_query_cjk_fullwidth - :min_version_3_9 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; -use utf8; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => 'foo@local' }], - to => [{ email => 'bar@local' }], - subject => $_->{id}, - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => <<'EOF' + use utf8; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => 'foo@local' } ], + to => [ { email => 'bar@local' } ], + subject => $_->{id}, + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => <<'EOF' 三菱UFJファクター株式会社 EOF - }, - }, - }, + }, }, - }, 'R1'], - ]); - my $email1Id = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($email1Id); + }, + }, + }, + 'R1' + ], + ]); + my $email1Id = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($email1Id); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => "UFJ", - }, - }, 'R1'], - ['Email/query', { - filter => { - body => "三菱UFJファクター株式会社", - }, - }, 'R2'], - ['Email/query', { - filter => { - body => "三菱UFJ", - }, - }, 'R3'], - ['Email/query', { - filter => { - body => "三菱", - }, - }, 'R4'], - ]); - $self->assert_deep_equals([$email1Id], $res->[0][1]{ids}); - $self->assert_deep_equals([$email1Id], $res->[1][1]{ids}); - $self->assert_deep_equals([$email1Id], $res->[2][1]{ids}); - $self->assert_deep_equals([$email1Id], $res->[3][1]{ids}); + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + body => "UFJ", + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + body => "三菱UFJファクター株式会社", + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + body => "三菱UFJ", + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + body => "三菱", + }, + }, + 'R4' + ], + ]); + $self->assert_deep_equals([$email1Id], $res->[0][1]{ids}); + $self->assert_deep_equals([$email1Id], $res->[1][1]{ids}); + $self->assert_deep_equals([$email1Id], $res->[2][1]{ids}); + $self->assert_deep_equals([$email1Id], $res->[3][1]{ids}); -no utf8; + no utf8; } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_collapse b/cassandane/tiny-tests/JMAPEmail/email_query_collapse index bee7392877..845d437850 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_collapse +++ b/cassandane/tiny-tests/JMAPEmail/email_query_collapse @@ -2,53 +2,58 @@ use Cassandane::Tiny; sub test_email_query_collapse - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; - - my $imaptalk = $self->{store}->get_client(); - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user("test"); - $admintalk->setacl("user.test", "cassandane", "lrwkx") or die; - - # run tests for both the main and "test" account - foreach (undef, "test") { - my $account = $_; - my $store = defined $account ? $self->{adminstore} : $self->{store}; - my $mboxprefix = defined $account ? "user.$account" : "INBOX"; - my $talk = $store->get_client(); - - my %params = (store => $store); - $store->set_folder($mboxprefix); - - xlog $self, "generating email A"; - $exp{A} = $self->make_message("Email A", %params); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B", %params); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "generating email C referencing A"; - %params = ( - references => [ $exp{A} ], - store => $store, - ); - $exp{C} = $self->make_message("Re: Email A", %params); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "list uncollapsed threads"; - $res = $jmap->CallMethods([['Email/query', { accountId => $account }, "R1"]]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - - $res = $jmap->CallMethods([['Email/query', { accountId => $account, collapseThreads => JSON::true }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - } + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; + + my $imaptalk = $self->{store}->get_client(); + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + my $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->create_user("test"); + $admintalk->setacl("user.test", "cassandane", "lrwkx") or die; + + # run tests for both the main and "test" account + foreach (undef, "test") { + my $account = $_; + my $store = defined $account ? $self->{adminstore} : $self->{store}; + my $mboxprefix = defined $account ? "user.$account" : "INBOX"; + my $talk = $store->get_client(); + + my %params = (store => $store); + $store->set_folder($mboxprefix); + + xlog $self, "generating email A"; + $exp{A} = $self->make_message("Email A", %params); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B", %params); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "generating email C referencing A"; + %params = ( + references => [ $exp{A} ], + store => $store, + ); + $exp{C} = $self->make_message("Re: Email A", %params); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "list uncollapsed threads"; + $res = $jmap->CallMethods( + [ [ 'Email/query', { accountId => $account }, "R1" ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { accountId => $account, collapseThreads => JSON::true }, "R1" + ] ] + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_contactgroup_filter_no_dnf b/cassandane/tiny-tests/JMAPEmail/email_query_contactgroup_filter_no_dnf index 43e6ad58e6..e8b39a9902 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_contactgroup_filter_no_dnf +++ b/cassandane/tiny-tests/JMAPEmail/email_query_contactgroup_filter_no_dnf @@ -2,74 +2,104 @@ use Cassandane::Tiny; sub test_email_query_contactgroup_filter_no_dnf - :min_version_3_4 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_4 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/contacts', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/contacts', + ]; - my $ncontacts = 100; - my $createContacts = {}; - for (my $i = 1; $i <= $ncontacts; $i++) { - $createContacts->{"contact$i"} = { - emails => [{ - type => 'personal', - value => "contact$i\@local", - }], - }; - } + my $ncontacts = 100; + my $createContacts = {}; + for (my $i = 1; $i <= $ncontacts; $i++) { + $createContacts->{"contact$i"} = { + emails => [ { + type => 'personal', + value => "contact$i\@local", + } ], + }; + } - my @contactCreationIds = map { "#$_" } keys %{$createContacts}; + my @contactCreationIds = map { "#$_" } keys %{$createContacts}; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => $createContacts, - }, 'R1'], - ['ContactGroup/set', { - create => { - contactGroup => { - name => 'contactGroup', - contactIds => \@contactCreationIds, - }, - } - }, 'R2'], - ], $using); - $self->assert_num_equals($ncontacts, scalar keys %{$res->[0][1]{created}}); - my $contactGroupId = $res->[1][1]{created}{contactGroup}{id}; - $self->assert_not_null($contactGroupId); + my $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + create => $createContacts, + }, + 'R1' + ], + [ + 'ContactGroup/set', + { + create => { + contactGroup => { + name => 'contactGroup', + contactIds => \@contactCreationIds, + }, + } + }, + 'R2' + ], + ], + $using + ); + $self->assert_num_equals($ncontacts, scalar keys %{ $res->[0][1]{created} }); + my $contactGroupId = $res->[1][1]{created}{contactGroup}{id}; + $self->assert_not_null($contactGroupId); - $self->make_message("msg-contact", from => Cassandane::Address->new( - localpart => 'contact1', domain => 'local' - )) or die; - $self->make_message("msg-nocontact", from => Cassandane::Address->new( - localpart => 'nocontact', domain => 'local' - )) or die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->make_message( + "msg-contact", + from => Cassandane::Address->new( + localpart => 'contact1', + domain => 'local' + ) + ) or die; + $self->make_message( + "msg-nocontact", + from => Cassandane::Address->new( + localpart => 'nocontact', + domain => 'local' + ) + ) or die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [{ - fromContactGroupId => $contactGroupId, - }], - }, - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - properties => ['subject'], - }, 'R2'] - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals('msg-nocontact', $res->[1][1]{list}[0]{subject}); + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { + fromContactGroupId => $contactGroupId, + } ], + }, + }, + 'R1' + ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + properties => ['subject'], + }, + 'R2' + ] + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals('msg-nocontact', $res->[1][1]{list}[0]{subject}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_convflags_seen_in_trash b/cassandane/tiny-tests/JMAPEmail/email_query_convflags_seen_in_trash index 58fc964f8d..2fcc9003f0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_convflags_seen_in_trash +++ b/cassandane/tiny-tests/JMAPEmail/email_query_convflags_seen_in_trash @@ -2,146 +2,188 @@ use Cassandane::Tiny; sub test_email_query_convflags_seen_in_trash - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxTrash => { - name => 'Trash', - }, - } - }, 'R2'], - ]); - my $mboxTrash = $res->[0][1]{created}{mboxTrash}{id}; - $self->assert_not_null($mboxTrash); + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxTrash => { + name => 'Trash', + }, + } + }, + 'R2' + ], + ]); + my $mboxTrash = $res->[0][1]{created}{mboxTrash}{id}; + $self->assert_not_null($mboxTrash); - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - emailInInbox => { - mailboxIds => { - '$inbox' => JSON::true, - }, - messageId => ['emailInInbox@local'], - subject => 'test', - keywords => { - '$seen' => JSON::true, - }, - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test inbox', - } - }, - }, + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + emailInInbox => { + mailboxIds => { + '$inbox' => JSON::true, }, - }, 'R1'], - ['Email/set', { - create => { - emailInTrash => { - mailboxIds => { - $mboxTrash => JSON::true, - }, - messageId => ['emailInThrash@local'], - subject => 'Re: test', - references => ['emailInInbox@local'], - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test trash', - } - }, - }, + messageId => ['emailInInbox@local'], + subject => 'test', + keywords => { + '$seen' => JSON::true, }, - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}{emailInInbox}{id}); - $self->assert_not_null($res->[1][1]{created}{emailInTrash}{id}); - $self->assert_str_equals($res->[0][1]{created}{emailInInbox}{threadId}, - $res->[1][1]{created}{emailInTrash}{threadId}); - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [{ - allInThreadHaveKeyword => '$seen', - }], + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test inbox', + } }, - }, 'R1'], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - operator => 'NOT', - conditions => [{ - allInThreadHaveKeyword => '$seen', - }], - }, { - inMailboxOtherThan => [ $mboxTrash ], - }], + }, + }, + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + emailInTrash => { + mailboxIds => { + $mboxTrash => JSON::true, }, - }, 'R2'], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - body => 'test', - }, { - operator => 'NOT', - conditions => [{ - allInThreadHaveKeyword => '$seen', - }], - }], + messageId => ['emailInThrash@local'], + subject => 'Re: test', + references => ['emailInInbox@local'], + bodyStructure => { + type => 'text/plain', + partId => 'part1', }, - }, 'R3'], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - body => 'test', - }, { - operator => 'NOT', - conditions => [{ - allInThreadHaveKeyword => '$seen', - }], - }, { - inMailboxOtherThan => [ $mboxTrash ], - }], + bodyValues => { + part1 => { + value => 'test trash', + } }, - }, 'R4'], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]); + }, + }, + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{emailInInbox}{id}); + $self->assert_not_null($res->[1][1]{created}{emailInTrash}{id}); + $self->assert_str_equals( + $res->[0][1]{created}{emailInInbox}{threadId}, + $res->[1][1]{created}{emailInTrash}{threadId} + ); + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { + allInThreadHaveKeyword => '$seen', + } ], + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + operator => 'NOT', + conditions => [ { + allInThreadHaveKeyword => '$seen', + } ], + }, + { + inMailboxOtherThan => [$mboxTrash], + } + ], + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + body => 'test', + }, + { + operator => 'NOT', + conditions => [ { + allInThreadHaveKeyword => '$seen', + } ], + } + ], + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + body => 'test', + }, + { + operator => 'NOT', + conditions => [ { + allInThreadHaveKeyword => '$seen', + } ], + }, + { + inMailboxOtherThan => [$mboxTrash], + } + ], + }, + }, + 'R4' + ], + ], + [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ] + ); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(2, scalar @{$res->[2][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[3][1]{ids}}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(2, scalar @{ $res->[2][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[3][1]{ids} }); - $self->assert_equals(JSON::false, - $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, - $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, - $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, - $res->[3][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::true, + $res->[3][1]{performance}{details}{isGuidSearch}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_dash b/cassandane/tiny-tests/JMAPEmail/email_query_dash index f8d92186cd..3c19070274 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_dash +++ b/cassandane/tiny-tests/JMAPEmail/email_query_dash @@ -2,48 +2,52 @@ use Cassandane::Tiny; sub test_email_query_dash - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $self->make_message("something - otherthing", body => 'test') || die; - $self->make_message("something", body => 'test') || die; - $self->make_message("otherthing", body => 'test') || die; + $self->make_message("something - otherthing", body => 'test') || die; + $self->make_message("something", body => 'test') || die; + $self->make_message("otherthing", body => 'test') || die; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Running query with guidsearch"; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - "operator" => "AND", - "conditions" => [ - { - "subject" => "something" - }, - { - "subject" => "-" - }, - { - "subject" => "otherthing" - } - ], + xlog "Running query with guidsearch"; + my $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + "operator" => "AND", + "conditions" => [ + { + "subject" => "something" }, - }, 'R1'] - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + { + "subject" => "-" + }, + { + "subject" => "otherthing" + } + ], + }, + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_dash_sieve b/cassandane/tiny-tests/JMAPEmail/email_query_dash_sieve index f16b613d39..7eacf9c196 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_dash_sieve +++ b/cassandane/tiny-tests/JMAPEmail/email_query_dash_sieve @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_email_query_dash_sieve - :min_version_3_3 :needs_component_jmap :JMAPExtensions - :needs_component_sieve -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_jmap : JMAPExtensions + : needs_component_sieve { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog "Running query in sieve"; - $imap->create("INBOX.matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + xlog "Running query in sieve"; + $imap->create("INBOX.matches") or die; + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -36,20 +36,23 @@ if fileinto "INBOX.matches"; } EOF - ); + ); - my $msg1 = $self->{gen}->generate( - subject => 'something - otherthing', body => '' - ); - $self->{instance}->deliver($msg1); - my $msg2 = $self->{gen}->generate( - subject => 'something', body => '' - ); - my $msg3 = $self->{gen}->generate( - subject => 'otherthing', body => '' - ); - $self->{instance}->deliver($msg1); - $self->{store}->set_fetch_attributes('uid'); - $self->{store}->set_folder('INBOX.matches'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + my $msg1 = $self->{gen}->generate( + subject => 'something - otherthing', + body => '' + ); + $self->{instance}->deliver($msg1); + my $msg2 = $self->{gen}->generate( + subject => 'something', + body => '' + ); + my $msg3 = $self->{gen}->generate( + subject => 'otherthing', + body => '' + ); + $self->{instance}->deliver($msg1); + $self->{store}->set_fetch_attributes('uid'); + $self->{store}->set_folder('INBOX.matches'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_deliveredto b/cassandane/tiny-tests/JMAPEmail/email_query_deliveredto index 4b1b1f48ea..936e552bdc 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_deliveredto +++ b/cassandane/tiny-tests/JMAPEmail/email_query_deliveredto @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_email_query_deliveredto - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: To: to@local Bcc: bcc@local @@ -21,10 +20,10 @@ Content-Type: text/plain match1 EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - $rawMessage = <<'EOF'; + $rawMessage = <<'EOF'; From: To: to@local Bcc: bcc@local @@ -36,10 +35,10 @@ Content-Type: text/plain match2 EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - $rawMessage = <<'EOF'; + $rawMessage = <<'EOF'; From: To: to@local Subject: nomatch @@ -49,59 +48,70 @@ Content-Type: text/plain nomatch EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', 'https://cyrusimap.org/ns/jmap/mail', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { }, - sort => [{ - property => 'subject', - }], - }, 'R1'], - ], $using); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - my $match1Id = $res->[0][1]{ids}[0]; - $self->assert_not_null($match1Id); - my $match2Id = $res->[0][1]{ids}[1]; - $self->assert_not_null($match2Id); - my $noMatchId = $res->[0][1]{ids}[2]; - $self->assert_not_null($noMatchId); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => {}, + sort => [ { + property => 'subject', + } ], + }, + 'R1' + ], + ], + $using + ); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + my $match1Id = $res->[0][1]{ids}[0]; + $self->assert_not_null($match1Id); + my $match2Id = $res->[0][1]{ids}[1]; + $self->assert_not_null($match2Id); + my $noMatchId = $res->[0][1]{ids}[2]; + $self->assert_not_null($noMatchId); - xlog "Query with JMAP search"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - deliveredTo => 'deliveredto@local', - }, - sort => [{ - property => 'subject', - }], - }, 'R1'], - ], $using); - $self->assert_deep_equals([$match1Id,$match2Id], $res->[0][1]{ids}); + xlog "Query with JMAP search"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + deliveredTo => 'deliveredto@local', + }, + sort => [ { + property => 'subject', + } ], + }, + 'R1' + ], + ], + $using + ); + $self->assert_deep_equals([ $match1Id, $match2Id ], $res->[0][1]{ids}); - xlog "Query with IMAP search"; - $imap->select('INBOX'); - my $uids = $imap->search( - 'deliveredto', { Quote => 'deliveredto@local' }, - ) || die; - $self->assert_deep_equals([1,2], $uids); + xlog "Query with IMAP search"; + $imap->select('INBOX'); + my $uids = $imap->search('deliveredto', { Quote => 'deliveredto@local' },) + || die; + $self->assert_deep_equals([ 1, 2 ], $uids); - xlog "Query with fuzzy IMAP search"; - $imap->select('INBOX'); - $uids = $imap->search( - 'fuzzy', 'deliveredto', { Quote => 'deliveredto@local' }, - ) || die; - $self->assert_deep_equals([1,2], $uids); + xlog "Query with fuzzy IMAP search"; + $imap->select('INBOX'); + $uids + = $imap->search('fuzzy', 'deliveredto', { Quote => 'deliveredto@local' },) + || die; + $self->assert_deep_equals([ 1, 2 ], $uids); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_dnfcomplexity b/cassandane/tiny-tests/JMAPEmail/email_query_dnfcomplexity index 8bf0566bb9..c38968652e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_dnfcomplexity +++ b/cassandane/tiny-tests/JMAPEmail/email_query_dnfcomplexity @@ -2,23 +2,22 @@ use Cassandane::Tiny; sub test_email_query_dnfcomplexity - :min_version_3_4 :needs_component_sieve :needs_component_jmap - :JMAPExtensions :SearchNormalizationMax20000 :SearchMaxTime1Sec -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_4 : needs_component_sieve : needs_component_jmap + : JMAPExtensions : SearchNormalizationMax20000 : SearchMaxTime1Sec { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: To: to@local Reply-To: replyto@local @@ -49,62 +48,82 @@ ZGF0YQ== --6c3338934661485f87537c19b5f9d933-- EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - xlog $self, 'run squatter'; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, 'run squatter'; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { - position => 0, - calculateTotal => JSON::false, - limit => 30, - findAllInThread => JSON::true, - collapseThreads => JSON::true, - sort => [{ - property => 'receivedAt', - isAscending => JSON::false - }], - filter => { - operator => 'AND', - conditions => [{ - hasAttachment => JSON::true - }, { - operator => 'NOT', - conditions => [{ - hasAttachment => JSON::true, - attachmentType => 'pdf' - }, { - hasAttachment => JSON::true, - attachmentType => 'presentation' - }, { - hasAttachment => JSON::true, - attachmentType => 'email' - }, { - hasAttachment => JSON::true, - attachmentType => 'spreadsheet' - }, { - attachmentType => 'document', - hasAttachment => JSON::true - }, { - attachmentType => 'image', - hasAttachment => JSON::true - }, { - attachmentType => 'presentation', - hasAttachment => JSON::true - }, { - attachmentType => 'document', - hasAttachment => JSON::true - }, { - hasAttachment => JSON::true, - attachmentType => 'pdf' - }], - }], - }, - }, 'R0'], - ], $using); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + position => 0, + calculateTotal => JSON::false, + limit => 30, + findAllInThread => JSON::true, + collapseThreads => JSON::true, + sort => [ { + property => 'receivedAt', + isAscending => JSON::false + } ], + filter => { + operator => 'AND', + conditions => [ + { + hasAttachment => JSON::true + }, + { + operator => 'NOT', + conditions => [ + { + hasAttachment => JSON::true, + attachmentType => 'pdf' + }, + { + hasAttachment => JSON::true, + attachmentType => 'presentation' + }, + { + hasAttachment => JSON::true, + attachmentType => 'email' + }, + { + hasAttachment => JSON::true, + attachmentType => 'spreadsheet' + }, + { + attachmentType => 'document', + hasAttachment => JSON::true + }, + { + attachmentType => 'image', + hasAttachment => JSON::true + }, + { + attachmentType => 'presentation', + hasAttachment => JSON::true + }, + { + attachmentType => 'document', + hasAttachment => JSON::true + }, + { + hasAttachment => JSON::true, + attachmentType => 'pdf' + } + ], + } + ], + }, + }, + 'R0' + ], + ], + $using + ); - $self->assert_str_equals('unsupportedFilter', $res->[0][1]{type}); - $self->assert_str_equals('search too complex', $res->[0][1]{description}); + $self->assert_str_equals('unsupportedFilter', $res->[0][1]{type}); + $self->assert_str_equals('search too complex', $res->[0][1]{description}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_emailaddress b/cassandane/tiny-tests/JMAPEmail/email_query_emailaddress index e7b1b72452..47a028367d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_emailaddress +++ b/cassandane/tiny-tests/JMAPEmail/email_query_emailaddress @@ -2,50 +2,52 @@ use Cassandane::Tiny; sub test_email_query_emailaddress - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog "Append emails"; - $self->make_message("msg1", - from => Cassandane::Address->new( - localpart => 'from', - domain => 'local', - ), - to => Cassandane::Address->new( - name => "Jon Doe", - localpart => "foo.bar", - domain => "xxx.example.com" - ), - body => "msg1" - ) || die; - $self->make_message("msg2", - from => Cassandane::Address->new( - localpart => 'from', - domain => 'local', - ), - to => Cassandane::Address->new( - name => "Jane Doe", - localpart => "foo.baz+bla", - domain => "yyy.example.com" - ), - body => "msg2" - ) || die; - $self->make_message("msg3", - from => Cassandane::Address->new( - localpart => 'from', - domain => 'local', - ), - to => Cassandane::Address->new( - localpart => '"tu x"', - domain => "example.com" - ), - body => "msg3" - ) || die; - my $raw = <<'EOF'; + xlog "Append emails"; + $self->make_message( + "msg1", + from => Cassandane::Address->new( + localpart => 'from', + domain => 'local', + ), + to => Cassandane::Address->new( + name => "Jon Doe", + localpart => "foo.bar", + domain => "xxx.example.com" + ), + body => "msg1" + ) || die; + $self->make_message( + "msg2", + from => Cassandane::Address->new( + localpart => 'from', + domain => 'local', + ), + to => Cassandane::Address->new( + name => "Jane Doe", + localpart => "foo.baz+bla", + domain => "yyy.example.com" + ), + body => "msg2" + ) || die; + $self->make_message( + "msg3", + from => Cassandane::Address->new( + localpart => 'from', + domain => 'local', + ), + to => Cassandane::Address->new( + localpart => '"tu x"', + domain => "example.com" + ), + body => "msg3" + ) || die; + my $raw = <<'EOF'; From: To: toa@example.com, RecipientB Subject: msg4 @@ -55,105 +57,137 @@ Content-Type: text/plain msg4 EOF - $raw =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $raw) || die $@; + $raw =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $raw) || die $@; - xlog "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-Z'); + xlog "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-Z'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ - property => 'subject', - }], - }, 'R1'], + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + sort => [ { + property => 'subject', + } ], + }, + 'R1' + ], - ], $using); - my @ids = @{$res->[0][1]{ids}}; - $self->assert_num_equals(4, scalar @ids); + ], + $using + ); + my @ids = @{ $res->[0][1]{ids} }; + $self->assert_num_equals(4, scalar @ids); - my @tests = ({ - to => '@xxx.example.com', - wantIds => [$ids[0]], - }, { - to => '@*.example.com', - wantIds => [$ids[0], $ids[1]], - }, { - to => '@*example.com', - wantIds => [$ids[0], $ids[1], $ids[2], $ids[3]], - }, { - to => 'foo*@*example.com', - wantIds => [$ids[0], $ids[1]], - }, { - to => 'foo.bar@*.com', - wantIds => [$ids[0]], - }, { - to => 'foo.baz+*@yyy.example.com', - wantIds => [$ids[1]], - }, { - to => 'foo.baz+bla@yyy.example.com', - wantIds => [$ids[1]], - }, { - to => 'foo*@*example.com', - wantIds => [$ids[0], $ids[1]], - }, { - to => 'foo.bar@', - wantIds => [$ids[0]], - }, { - to => 'foo.ba*@', - wantIds => [$ids[0], $ids[1]], - }, { - to => 'doe', - wantIds => [$ids[0], $ids[1]], - }, { - to => 'jane doe', - wantIds => [$ids[1]], - }, { - to => 'foo* example', - wantIds => [$ids[0], $ids[1]], - }, { - to => 'foo* yyy', - wantIds => [$ids[1]], - }, { - to => 'example.com', - wantIds => [$ids[0], $ids[1], $ids[2], $ids[3]], - }, { - to => '"tu x"@example.com', - wantIds => [$ids[2]], - }, { - to => 'tux@example.com', - wantIds => [], - }, { - to => 'Jane Doe ', - wantIds => [$ids[1]], - }, { - to => 'Doe ', - wantIds => [$ids[0], $ids[1]], - }, { - to => 'tob@example.com', - wantIds => [$ids[3]], - }); - - foreach (@tests) { - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - to => $_->{to}, - }, - sort => [{ - property => 'subject', - }], - }, 'R1'], - ]); - $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + my @tests = ( + { + to => '@xxx.example.com', + wantIds => [ $ids[0] ], + }, + { + to => '@*.example.com', + wantIds => [ $ids[0], $ids[1] ], + }, + { + to => '@*example.com', + wantIds => [ $ids[0], $ids[1], $ids[2], $ids[3] ], + }, + { + to => 'foo*@*example.com', + wantIds => [ $ids[0], $ids[1] ], + }, + { + to => 'foo.bar@*.com', + wantIds => [ $ids[0] ], + }, + { + to => 'foo.baz+*@yyy.example.com', + wantIds => [ $ids[1] ], + }, + { + to => 'foo.baz+bla@yyy.example.com', + wantIds => [ $ids[1] ], + }, + { + to => 'foo*@*example.com', + wantIds => [ $ids[0], $ids[1] ], + }, + { + to => 'foo.bar@', + wantIds => [ $ids[0] ], + }, + { + to => 'foo.ba*@', + wantIds => [ $ids[0], $ids[1] ], + }, + { + to => 'doe', + wantIds => [ $ids[0], $ids[1] ], + }, + { + to => 'jane doe', + wantIds => [ $ids[1] ], + }, + { + to => 'foo* example', + wantIds => [ $ids[0], $ids[1] ], + }, + { + to => 'foo* yyy', + wantIds => [ $ids[1] ], + }, + { + to => 'example.com', + wantIds => [ $ids[0], $ids[1], $ids[2], $ids[3] ], + }, + { + to => '"tu x"@example.com', + wantIds => [ $ids[2] ], + }, + { + to => 'tux@example.com', + wantIds => [], + }, + { + to => 'Jane Doe ', + wantIds => [ $ids[1] ], + }, + { + to => 'Doe ', + wantIds => [ $ids[0], $ids[1] ], + }, + { + to => 'tob@example.com', + wantIds => [ $ids[3] ], } + ); + + foreach (@tests) { + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + to => $_->{to}, + }, + sort => [ { + property => 'subject', + } ], + }, + 'R1' + ], + ]); + $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_empty b/cassandane/tiny-tests/JMAPEmail/email_query_empty index 08506c7ec2..dd016762b7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_empty +++ b/cassandane/tiny-tests/JMAPEmail/email_query_empty @@ -2,21 +2,20 @@ use Cassandane::Tiny; sub test_email_query_empty - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # See - # https://github.com/cyrusimap/cyrus-imapd/issues/2266 - # and - # https://github.com/cyrusimap/cyrus-imapd/issues/2287 + # See + # https://github.com/cyrusimap/cyrus-imapd/issues/2266 + # and + # https://github.com/cyrusimap/cyrus-imapd/issues/2287 - my $res = $jmap->CallMethods([['Email/query', { }, "R1"]]); - $self->assert(ref($res->[0][1]->{ids}) eq 'ARRAY'); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + $self->assert(ref($res->[0][1]->{ids}) eq 'ARRAY'); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); - $res = $jmap->CallMethods([['Email/query', { limit => 0 }, "R1"]]); - $self->assert(ref($res->[0][1]->{ids}) eq 'ARRAY'); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); + $res = $jmap->CallMethods([ [ 'Email/query', { limit => 0 }, "R1" ] ]); + $self->assert(ref($res->[0][1]->{ids}) eq 'ARRAY'); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_empty_filter_conds b/cassandane/tiny-tests/JMAPEmail/email_query_empty_filter_conds index 45cfa72eb8..500070ac9a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_empty_filter_conds +++ b/cassandane/tiny-tests/JMAPEmail/email_query_empty_filter_conds @@ -2,46 +2,65 @@ use Cassandane::Tiny; sub test_email_query_empty_filter_conds - :min_version_3_7 :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_7 : needs_component_sieve : needs_component_jmap : + JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $self->make_message('test'); + $self->make_message('test'); - xlog $self, 'run squatter'; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, 'run squatter'; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [{ }], - }, - }, 'R0'], - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [], - }, - }, 'R1'], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ }], - }, - }, 'R2'], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [], - }, - }, 'R3'], - ], $using); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ {} ], + }, + }, + 'R0' + ], + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [], + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ {} ], + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [], + }, + }, + 'R3' + ], + ], + $using + ); - $self->assert_num_equals(0, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[3][1]{ids}}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[3][1]{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_findallinthread b/cassandane/tiny-tests/JMAPEmail/email_query_findallinthread index 6bd0c1fc1d..12d4599cf4 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_findallinthread +++ b/cassandane/tiny-tests/JMAPEmail/email_query_findallinthread @@ -2,186 +2,230 @@ use Cassandane::Tiny; sub test_email_query_findallinthread - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog "Create three top-level thread emails"; - my %createEmails; - for (my $i = 1; $i <= 3; $i++) { - $createEmails{$i} = { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => "$i\@local" }], - to => [{ email => "$i\@local" }], - messageId => ["email$i\@local"], - subject => "email$i", - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email$i body", - }, - }, - } - } - my $res = $jmap->CallMethods([ - ['Email/set', { - create => \%createEmails, - }, 'R1'], - ]); - $self->assert_num_equals(3, scalar keys %{$res->[0][1]{created}}); - my $emailId1 = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($emailId1); - my $threadId1 = $res->[0][1]{created}{1}{threadId}; - $self->assert_not_null($threadId1); - my $emailId2 = $res->[0][1]{created}{2}{id}; - $self->assert_not_null($emailId2); - my $threadId2 = $res->[0][1]{created}{2}{threadId}; - $self->assert_not_null($threadId2); - my $emailId3 = $res->[0][1]{created}{3}{id}; - $self->assert_not_null($emailId3); - my $threadId3 = $res->[0][1]{created}{3}{threadId}; - $self->assert_not_null($threadId3); + xlog "Create three top-level thread emails"; + my %createEmails; + for (my $i = 1; $i <= 3; $i++) { + $createEmails{$i} = { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => "$i\@local" } ], + to => [ { email => "$i\@local" } ], + messageId => ["email$i\@local"], + subject => "email$i", + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email$i body", + }, + }, + }; + } + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => \%createEmails, + }, + 'R1' + ], + ]); + $self->assert_num_equals(3, scalar keys %{ $res->[0][1]{created} }); + my $emailId1 = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($emailId1); + my $threadId1 = $res->[0][1]{created}{1}{threadId}; + $self->assert_not_null($threadId1); + my $emailId2 = $res->[0][1]{created}{2}{id}; + $self->assert_not_null($emailId2); + my $threadId2 = $res->[0][1]{created}{2}{threadId}; + $self->assert_not_null($threadId2); + my $emailId3 = $res->[0][1]{created}{3}{id}; + $self->assert_not_null($emailId3); + my $threadId3 = $res->[0][1]{created}{3}{threadId}; + $self->assert_not_null($threadId3); - xlog "Create reference emails to top-level emails"; - %createEmails = (); - foreach (qw/21 22 31/) { - my $ref = substr($_, 0, 1); - $createEmails{$_} = { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => "$_\@local" }], - to => [{ email => "$_\@local" }], - messageId => ["email$_\@local"], - subject => "Re: email$ref", - references => ["email$ref\@local"], - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email$_ body", - }, - }, - } - } - $res = $jmap->CallMethods([ - ['Email/set', { - create => \%createEmails, - }, 'R1'], - ]); - $self->assert_num_equals(3, scalar keys %{$res->[0][1]{created}}); - my $emailId21 = $res->[0][1]{created}{21}{id}; - $self->assert_not_null($emailId21); - my $emailId22 = $res->[0][1]{created}{22}{id}; - $self->assert_not_null($emailId22); - my $emailId31 = $res->[0][1]{created}{31}{id}; - $self->assert_not_null($emailId31); + xlog "Create reference emails to top-level emails"; + %createEmails = (); + foreach (qw/21 22 31/) { + my $ref = substr($_, 0, 1); + $createEmails{$_} = { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => "$_\@local" } ], + to => [ { email => "$_\@local" } ], + messageId => ["email$_\@local"], + subject => "Re: email$ref", + references => ["email$ref\@local"], + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email$_ body", + }, + }, + }; + } + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => \%createEmails, + }, + 'R1' + ], + ]); + $self->assert_num_equals(3, scalar keys %{ $res->[0][1]{created} }); + my $emailId21 = $res->[0][1]{created}{21}{id}; + $self->assert_not_null($emailId21); + my $emailId22 = $res->[0][1]{created}{22}{id}; + $self->assert_not_null($emailId22); + my $emailId31 = $res->[0][1]{created}{31}{id}; + $self->assert_not_null($emailId31); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query emails"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => 'body', - }, - sort => [{ - property => 'id', - }], - collapseThreads => JSON::true, - findAllInThread => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { - body => 'body', - }, - sort => [{ - property => 'id', - }], - collapseThreads => JSON::true, - findAllInThread => JSON::true, - disableGuidSearch => JSON::true, - }, 'R2'], - ], $using); + xlog "Query emails"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + body => 'body', + }, + sort => [ { + property => 'id', + } ], + collapseThreads => JSON::true, + findAllInThread => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + body => 'body', + }, + sort => [ { + property => 'id', + } ], + collapseThreads => JSON::true, + findAllInThread => JSON::true, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + ], + $using + ); - my @emailIdsThread1 = sort ($emailId1); - my @emailIdsThread2 = sort ($emailId2, $emailId21, $emailId22); - my @emailIdsThread3 = sort ($emailId3, $emailId31); + my @emailIdsThread1 = sort ($emailId1); + my @emailIdsThread2 = sort ($emailId2, $emailId21, $emailId22); + my @emailIdsThread3 = sort ($emailId3, $emailId31); - my $wantThreadIdToEmailIds = { - $threadId1 => \@emailIdsThread1, - $threadId2 => \@emailIdsThread2, - $threadId3 => \@emailIdsThread3, - }; + my $wantThreadIdToEmailIds = { + $threadId1 => \@emailIdsThread1, + $threadId2 => \@emailIdsThread2, + $threadId3 => \@emailIdsThread3, + }; - my %gotThreadIdToEmailIds; - while (my ($threadId, $emailIds) = each %{$res->[0][1]{threadIdToEmailIds}}) { - my @emailIds = sort @{$emailIds}; - $gotThreadIdToEmailIds{$threadId} = \@emailIds; - } - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals($wantThreadIdToEmailIds, \%gotThreadIdToEmailIds); + my %gotThreadIdToEmailIds; + while (my ($threadId, $emailIds) = each %{ $res->[0][1]{threadIdToEmailIds} }) + { + my @emailIds = sort @{$emailIds}; + $gotThreadIdToEmailIds{$threadId} = \@emailIds; + } + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals($wantThreadIdToEmailIds, \%gotThreadIdToEmailIds); - %gotThreadIdToEmailIds = (); - while (my ($threadId, $emailIds) = each %{$res->[1][1]{threadIdToEmailIds}}) { - my @emailIds = sort @{$emailIds}; - $gotThreadIdToEmailIds{$threadId} = \@emailIds; - } - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals($wantThreadIdToEmailIds, \%gotThreadIdToEmailIds); + %gotThreadIdToEmailIds = (); + while (my ($threadId, $emailIds) = each %{ $res->[1][1]{threadIdToEmailIds} }) + { + my @emailIds = sort @{$emailIds}; + $gotThreadIdToEmailIds{$threadId} = \@emailIds; + } + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals($wantThreadIdToEmailIds, \%gotThreadIdToEmailIds); - xlog "Assert empty result"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => 'nope', - }, - findAllInThread => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { - body => 'nope', - }, - findAllInThread => JSON::true, - disableGuidSearch => JSON::true, - }, 'R2'], - ], $using); - $self->assert_deep_equals({}, $res->[0][1]{threadIdToEmailIds}); - $self->assert_deep_equals({}, $res->[1][1]{threadIdToEmailIds}); + xlog "Assert empty result"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + body => 'nope', + }, + findAllInThread => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + body => 'nope', + }, + findAllInThread => JSON::true, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + ], + $using + ); + $self->assert_deep_equals({}, $res->[0][1]{threadIdToEmailIds}); + $self->assert_deep_equals({}, $res->[1][1]{threadIdToEmailIds}); - xlog "Assert threadIdToEmailIds isn't set if not requested"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => 'body', - }, - }, 'R1'], - ['Email/query', { - filter => { - body => 'body', - }, - disableGuidSearch => JSON::true, - }, 'R2'], - ], $using); - $self->assert_null($res->[0][1]{threadIdToEmailIds}); - $self->assert_null($res->[1][1]{threadIdToEmailIds}); + xlog "Assert threadIdToEmailIds isn't set if not requested"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + body => 'body', + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + body => 'body', + }, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + ], + $using + ); + $self->assert_null($res->[0][1]{threadIdToEmailIds}); + $self->assert_null($res->[1][1]{threadIdToEmailIds}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_fix_multiple_recipients b/cassandane/tiny-tests/JMAPEmail/email_query_fix_multiple_recipients index e14f94b2e5..247a7e1e6f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_fix_multiple_recipients +++ b/cassandane/tiny-tests/JMAPEmail/email_query_fix_multiple_recipients @@ -2,23 +2,22 @@ use Cassandane::Tiny; sub test_email_query_fix_multiple_recipients - :min_version_3_4 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_4 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: from@local To: unquoted@local, "quot@ed" Subject: test @@ -28,17 +27,21 @@ Content-Type: text/plain; charset="UTF-8" test EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - to => 'unquoted@local', - }, - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + to => 'unquoted@local', + }, + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_from b/cassandane/tiny-tests/JMAPEmail/email_query_from index 34ad5efc83..f70e3c0837 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_from +++ b/cassandane/tiny-tests/JMAPEmail/email_query_from @@ -2,54 +2,70 @@ use Cassandane::Tiny; sub test_email_query_from - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - # Create test messages. - $self->make_message('uid1', from => Cassandane::Address->new( - name => 'B', - localpart => 'local', - domain => 'hostA' - )); - $self->make_message('uid2', from => Cassandane::Address->new( - name => 'A', - localpart => 'local', - domain => 'hostA' - )); - $self->make_message('uid3', from => Cassandane::Address->new( - localpart => 'local', - domain => 'hostY' - )); - $self->make_message('uid4', from => Cassandane::Address->new( - localpart => 'local', - domain => 'hostX' - )); + # Create test messages. + $self->make_message( + 'uid1', + from => Cassandane::Address->new( + name => 'B', + localpart => 'local', + domain => 'hostA' + ) + ); + $self->make_message( + 'uid2', + from => Cassandane::Address->new( + name => 'A', + localpart => 'local', + domain => 'hostA' + ) + ); + $self->make_message( + 'uid3', + from => Cassandane::Address->new( + localpart => 'local', + domain => 'hostY' + ) + ); + $self->make_message( + 'uid4', + from => Cassandane::Address->new( + localpart => 'local', + domain => 'hostX' + ) + ); - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => 'subject' }], - }, 'R1'], - ]); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{ids}}); - my $emailId1 = $res->[0][1]{ids}[0]; - my $emailId2 = $res->[0][1]{ids}[1]; - my $emailId3 = $res->[0][1]{ids}[2]; - my $emailId4 = $res->[0][1]{ids}[3]; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { property => 'subject' } ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{ids} }); + my $emailId1 = $res->[0][1]{ids}[0]; + my $emailId2 = $res->[0][1]{ids}[1]; + my $emailId3 = $res->[0][1]{ids}[2]; + my $emailId4 = $res->[0][1]{ids}[3]; - $res = $jmap->CallMethods([ - ['Email/query', { - sort => [ - { property => 'from' }, - { property => 'subject'} - ], - }, 'R1'], - ]); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId2, $res->[0][1]{ids}[0]); - $self->assert_str_equals($emailId1, $res->[0][1]{ids}[1]); - $self->assert_str_equals($emailId4, $res->[0][1]{ids}[2]); - $self->assert_str_equals($emailId3, $res->[0][1]{ids}[3]); + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { property => 'from' }, { property => 'subject' } ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId2, $res->[0][1]{ids}[0]); + $self->assert_str_equals($emailId1, $res->[0][1]{ids}[1]); + $self->assert_str_equals($emailId4, $res->[0][1]{ids}[2]); + $self->assert_str_equals($emailId3, $res->[0][1]{ids}[3]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_fromanycontact_ignore_localpartonly b/cassandane/tiny-tests/JMAPEmail/email_query_fromanycontact_ignore_localpartonly index 1aee402edd..7d60680d36 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_fromanycontact_ignore_localpartonly +++ b/cassandane/tiny-tests/JMAPEmail/email_query_fromanycontact_ignore_localpartonly @@ -2,39 +2,46 @@ use Cassandane::Tiny; sub test_email_query_fromanycontact_ignore_localpartonly - :min_version_3_3 :needs_component_jmap :JMAPExtensions - :needs_component_sieve -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_jmap : JMAPExtensions + : needs_component_sieve { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog "Create contact with localpart-only mail address"; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/contacts', - ]; + xlog "Create contact with localpart-only mail address"; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/contacts', + ]; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - emails => [{ - type => 'personal', - value => 'email', - }], - }, - } - }, 'R1'], - ], $using); - my $contactId1 = $res->[0][1]{created}{contact1}{id}; - $self->assert_not_null($contactId1); + my $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + create => { + contact1 => { + emails => [ { + type => 'personal', + value => 'email', + } ], + }, + } + }, + 'R1' + ], + ], + $using + ); + my $contactId1 = $res->[0][1]{created}{contact1}{id}; + $self->assert_not_null($contactId1); - xlog "Assert JMAP sieve ignores localpart-only contacts"; - $imap->create("INBOX.matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + xlog "Assert JMAP sieve ignores localpart-only contacts"; + $imap->create("INBOX.matches") or die; + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -53,29 +60,35 @@ if fileinto "INBOX.matches"; } EOF - ); + ); - my $msg1 = $self->{gen}->generate(from => Cassandane::Address->new( - localpart => 'email', domain => 'local' - )); - $self->{instance}->deliver($msg1); - $self->{store}->set_fetch_attributes('uid'); - $self->{store}->set_folder('INBOX.matches'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + my $msg1 = $self->{gen}->generate( + from => Cassandane::Address->new( + localpart => 'email', + domain => 'local' + ) + ); + $self->{instance}->deliver($msg1); + $self->{store}->set_fetch_attributes('uid'); + $self->{store}->set_folder('INBOX.matches'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog "Assert Email/query ignores localpart-only contacts"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [{ - fromAnyContact => JSON::true - }] - }, - sort => [ - { property => "subject" } - ], - }, 'R1'] - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + xlog "Assert Email/query ignores localpart-only contacts"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { + fromAnyContact => JSON::true + } ] + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_fromanycontact_shared b/cassandane/tiny-tests/JMAPEmail/email_query_fromanycontact_shared index f28262c58f..064a15f8eb 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_fromanycontact_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_query_fromanycontact_shared @@ -2,109 +2,146 @@ use Cassandane::Tiny; sub test_email_query_fromanycontact_shared - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $admin = $self->{adminstore}->get_client(); + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $admin = $self->{adminstore}->get_client(); - xlog "Create shared addressbook"; - $admin->create("user.other"); - my $http = $self->{instance}->get_service("http"); - my $otherCarddav = Net::CardDAVTalk->new( - user => "other", - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/', - expandurl => 1, - ); - my $otherJmap = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $http->host(), - port => $http->port(), - scheme => 'http', - url => '/jmap/', - ); - $admin->create("user.other.#addressbooks.Shared", ['TYPE', 'ADDRESSBOOK']); - $admin->setacl("user.other.#addressbooks.Shared", "cassandane", "lr") or die; - $imap->subscribe("user.other.#addressbooks.Shared"); + xlog "Create shared addressbook"; + $admin->create("user.other"); + my $http = $self->{instance}->get_service("http"); + my $otherCarddav = Net::CardDAVTalk->new( + user => "other", + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/', + expandurl => 1, + ); + my $otherJmap = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $http->host(), + port => $http->port(), + scheme => 'http', + url => '/jmap/', + ); + $admin->create("user.other.#addressbooks.Shared", [ 'TYPE', 'ADDRESSBOOK' ]); + $admin->setacl("user.other.#addressbooks.Shared", "cassandane", "lr") or die; + $imap->subscribe("user.other.#addressbooks.Shared"); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/contacts', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/contacts', + ]; - xlog "Create contact in shared addressbook"; - my $res = $otherJmap->CallMethods([ - ['Contact/set', { - create => { - sharedContact => { - emails => [{ - type => 'personal', - value => 'sharedcontact@local', - }], - addressbookId => 'Shared', - }, + xlog "Create contact in shared addressbook"; + my $res = $otherJmap->CallMethods( + [ + [ + 'Contact/set', + { + create => { + sharedContact => { + emails => [ { + type => 'personal', + value => 'sharedcontact@local', + } ], + addressbookId => 'Shared', }, - }, 'R1'], - ], $using); - $self->assert_not_null($res->[0][1]{created}{sharedContact}{id}); + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert_not_null($res->[0][1]{created}{sharedContact}{id}); - xlog "Create contact in own addressbook"; - $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - ownContact => { - emails => [{ - type => 'personal', - value => 'ownContact@local', - }], - }, + xlog "Create contact in own addressbook"; + $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + create => { + ownContact => { + emails => [ { + type => 'personal', + value => 'ownContact@local', + } ], }, - }, 'R1'], - ], $using); - $self->assert_not_null($res->[0][1]{created}{ownContact}{id}); + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert_not_null($res->[0][1]{created}{ownContact}{id}); - xlog "Create emails"; - $self->make_message("msg1", from => Cassandane::Address->new( - localpart => 'sharedContact', domain => 'local' - )) or die; - $self->make_message("msg2", from => Cassandane::Address->new( - localpart => 'ownContact', domain => 'local' - )) or die; - $self->make_message("msg3", from => Cassandane::Address->new( - localpart => 'noContact', domain => 'local' - )) or die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => "subject" }], - }, 'R1'] - ], $using); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - my $emailIds = $res->[0][1]{ids}; + xlog "Create emails"; + $self->make_message( + "msg1", + from => Cassandane::Address->new( + localpart => 'sharedContact', + domain => 'local' + ) + ) or die; + $self->make_message( + "msg2", + from => Cassandane::Address->new( + localpart => 'ownContact', + domain => 'local' + ) + ) or die; + $self->make_message( + "msg3", + from => Cassandane::Address->new( + localpart => 'noContact', + domain => 'local' + ) + ) or die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + my $emailIds = $res->[0][1]{ids}; - xlog "Assert Email/query"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - fromAnyContact => JSON::true, - }, - sort => [{ property => "subject" }], - }, 'R1'] - ], $using); - $self->assert_deep_equals([$emailIds->[0], $emailIds->[1]], $res->[0][1]{ids}); + xlog "Assert Email/query"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + fromAnyContact => JSON::true, + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_deep_equals([ $emailIds->[0], $emailIds->[1] ], + $res->[0][1]{ids}); - xlog "Assert Sieve"; - $imap->create("INBOX.matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + xlog "Assert Sieve"; + $imap->create("INBOX.matches") or die; + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -118,9 +155,9 @@ if fileinto "INBOX.matches"; } EOF - ); + ); - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: sharedContact@local To: to@local Subject: sieve1 @@ -130,10 +167,10 @@ Content-Type: text/plain; charset="UTF-8" hello EOF - $rawMessage =~ s/\r?\n/\r\n/gs; + $rawMessage =~ s/\r?\n/\r\n/gs; - my $msg = Cassandane::Message->new(); - $msg->set_lines(split /\n/, $rawMessage); - $self->{instance}->deliver($msg); - $self->assert_num_equals(1, $imap->message_count('INBOX.matches')); + my $msg = Cassandane::Message->new(); + $msg->set_lines(split /\n/, $rawMessage); + $self->{instance}->deliver($msg); + $self->assert_num_equals(1, $imap->message_count('INBOX.matches')); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_fromcontactgroupid b/cassandane/tiny-tests/JMAPEmail/email_query_fromcontactgroupid index 77e7fdd590..53862daaad 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_fromcontactgroupid +++ b/cassandane/tiny-tests/JMAPEmail/email_query_fromcontactgroupid @@ -2,251 +2,345 @@ use Cassandane::Tiny; sub test_email_query_fromcontactgroupid - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user.cassandane.#addressbooks.Addrbook1", ['TYPE', 'ADDRESSBOOK']) or die; - $admintalk->create("user.cassandane.#addressbooks.Addrbook2", ['TYPE', 'ADDRESSBOOK']) or die; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user.cassandane.#addressbooks.Addrbook1", + [ 'TYPE', 'ADDRESSBOOK' ]) + or die; + $admintalk->create("user.cassandane.#addressbooks.Addrbook2", + [ 'TYPE', 'ADDRESSBOOK' ]) + or die; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/contacts', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/contacts', + ]; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - emails => [{ - type => 'personal', - value => 'contact1@local', - }], - }, - contact2 => { - emails => [{ - type => 'personal', - value => 'contact2@local', - }] - }, - } - }, 'R1'], - ['ContactGroup/set', { - create => { - contactGroup1 => { - name => 'contactGroup1', - contactIds => ['#contact1', '#contact2'], - addressbookId => 'Addrbook1', - }, - contactGroup2 => { - name => 'contactGroup2', - contactIds => ['#contact1'], - addressbookId => 'Addrbook2', - } + my $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + create => { + contact1 => { + emails => [ { + type => 'personal', + value => 'contact1@local', + } ], + }, + contact2 => { + emails => [ { + type => 'personal', + value => 'contact2@local', + } ] + }, + } + }, + 'R1' + ], + [ + 'ContactGroup/set', + { + create => { + contactGroup1 => { + name => 'contactGroup1', + contactIds => [ '#contact1', '#contact2' ], + addressbookId => 'Addrbook1', + }, + contactGroup2 => { + name => 'contactGroup2', + contactIds => ['#contact1'], + addressbookId => 'Addrbook2', } - }, 'R2'], - ], $using); - my $contactId1 = $res->[0][1]{created}{contact1}{id}; - $self->assert_not_null($contactId1); - my $contactId2 = $res->[0][1]{created}{contact2}{id}; - $self->assert_not_null($contactId2); - my $contactGroupId1 = $res->[1][1]{created}{contactGroup1}{id}; - $self->assert_not_null($contactGroupId1); - my $contactGroupId2 = $res->[1][1]{created}{contactGroup2}{id}; - $self->assert_not_null($contactGroupId2); + } + }, + 'R2' + ], + ], + $using + ); + my $contactId1 = $res->[0][1]{created}{contact1}{id}; + $self->assert_not_null($contactId1); + my $contactId2 = $res->[0][1]{created}{contact2}{id}; + $self->assert_not_null($contactId2); + my $contactGroupId1 = $res->[1][1]{created}{contactGroup1}{id}; + $self->assert_not_null($contactGroupId1); + my $contactGroupId2 = $res->[1][1]{created}{contactGroup2}{id}; + $self->assert_not_null($contactGroupId2); - $self->make_message("msg1", from => Cassandane::Address->new( - localpart => 'contact1', domain => 'local' - )) or die; - $self->make_message("msg2", from => Cassandane::Address->new( - localpart => 'contact2', domain => 'local' - )) or die; - $self->make_message("msg3", from => Cassandane::Address->new( - localpart => 'neither', domain => 'local' - )) or die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->make_message( + "msg1", + from => Cassandane::Address->new( + localpart => 'contact1', + domain => 'local' + ) + ) or die; + $self->make_message( + "msg2", + from => Cassandane::Address->new( + localpart => 'contact2', + domain => 'local' + ) + ) or die; + $self->make_message( + "msg3", + from => Cassandane::Address->new( + localpart => 'neither', + domain => 'local' + ) + ) or die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => "subject" }], - }, 'R1'] - ], $using); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - my $emailId1 = $res->[0][1]{ids}[0]; - my $emailId2 = $res->[0][1]{ids}[1]; - my $emailId3 = $res->[0][1]{ids}[2]; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + my $emailId1 = $res->[0][1]{ids}[0]; + my $emailId2 = $res->[0][1]{ids}[1]; + my $emailId3 = $res->[0][1]{ids}[2]; - # Filter by contact group. - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - fromContactGroupId => $contactGroupId1 - }, - sort => [ - { property => "subject" } - ], - }, 'R1'] - ], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); - $self->assert_str_equals($emailId2, $res->[0][1]{ids}[1]); + # Filter by contact group. + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + fromContactGroupId => $contactGroupId1 + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); + $self->assert_str_equals($emailId2, $res->[0][1]{ids}[1]); - # Filter by fromAnyContact - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - fromAnyContact => $JSON::true - }, - sort => [ - { property => "subject" } - ], - }, 'R1'] - ], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); - $self->assert_str_equals($emailId2, $res->[0][1]{ids}[1]); + # Filter by fromAnyContact + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + fromAnyContact => $JSON::true + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); + $self->assert_str_equals($emailId2, $res->[0][1]{ids}[1]); - # Filter by contact group and addressbook. - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - fromContactGroupId => $contactGroupId2 - }, - sort => [ - { property => "subject" } - ], - addressbookId => 'Addrbook2' - }, 'R1'] - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); + # Filter by contact group and addressbook. + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + fromContactGroupId => $contactGroupId2 + }, + sort => [ { property => "subject" } ], + addressbookId => 'Addrbook2' + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); + # Negate filter by contact group. + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { + fromContactGroupId => $contactGroupId1 + } ] + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($emailId3, $res->[0][1]{ids}[0]); - # Negate filter by contact group. - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [{ - fromContactGroupId => $contactGroupId1 - }] - }, - sort => [ - { property => "subject" } - ], - }, 'R1'] - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($emailId3, $res->[0][1]{ids}[0]); + # Reject unknown contact groups. + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + fromContactGroupId => 'doesnotexist', + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - # Reject unknown contact groups. - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - fromContactGroupId => 'doesnotexist', - }, - sort => [ - { property => "subject" } - ], - }, 'R1'] - ], $using); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + # Reject contact groups in wrong addressbook. + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + fromContactGroupId => $contactGroupId1 + }, + sort => [ { property => "subject" } ], + addressbookId => 'Addrbook2', + }, + 'R1' + ] ], + $using + ); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - # Reject contact groups in wrong addressbook. - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - fromContactGroupId => $contactGroupId1 - }, - sort => [ - { property => "subject" } - ], - addressbookId => 'Addrbook2', - }, 'R1'] - ], $using); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + # Reject unknown addressbooks. + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + fromContactGroupId => $contactGroupId1, + }, + sort => [ { property => "subject" } ], + addressbookId => 'doesnotexist', + }, + 'R1' + ] ], + $using + ); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - # Reject unknown addressbooks. - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - fromContactGroupId => $contactGroupId1, + # Support also to, cc, bcc + $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + create => { + contact3 => { + emails => [ { + type => 'personal', + value => 'contact3@local', + } ] }, - sort => [ - { property => "subject" } - ], - addressbookId => 'doesnotexist', - }, 'R1'] - ], $using); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - - # Support also to, cc, bcc - $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact3 => { - emails => [{ - type => 'personal', - value => 'contact3@local', - }] - }, - } - }, 'R1'], - ['ContactGroup/set', { - update => { - $contactGroupId1 => { - contactIds => ['#contact3'], - } + } + }, + 'R1' + ], + [ + 'ContactGroup/set', + { + update => { + $contactGroupId1 => { + contactIds => ['#contact3'], } - }, 'R1'], - ], $using); - $self->assert_not_null($res->[0][1]{created}{contact3}); - $self->make_message("msg4", to => Cassandane::Address->new( - localpart => 'contact3', domain => 'local' - )) or die; - $self->make_message("msg5", cc => Cassandane::Address->new( - localpart => 'contact3', domain => 'local' - )) or die; - $self->make_message("msg6", bcc => Cassandane::Address->new( - localpart => 'contact3', domain => 'local' - )) or die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => "subject" }], - }, 'R1'] - ], $using); - $self->assert_num_equals(6, scalar @{$res->[0][1]{ids}}); - my $emailId4 = $res->[0][1]{ids}[3]; - my $emailId5 = $res->[0][1]{ids}[4]; - my $emailId6 = $res->[0][1]{ids}[5]; + } + }, + 'R1' + ], + ], + $using + ); + $self->assert_not_null($res->[0][1]{created}{contact3}); + $self->make_message( + "msg4", + to => Cassandane::Address->new( + localpart => 'contact3', + domain => 'local' + ) + ) or die; + $self->make_message( + "msg5", + cc => Cassandane::Address->new( + localpart => 'contact3', + domain => 'local' + ) + ) or die; + $self->make_message( + "msg6", + bcc => Cassandane::Address->new( + localpart => 'contact3', + domain => 'local' + ) + ) or die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(6, scalar @{ $res->[0][1]{ids} }); + my $emailId4 = $res->[0][1]{ids}[3]; + my $emailId5 = $res->[0][1]{ids}[4]; + my $emailId6 = $res->[0][1]{ids}[5]; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - toContactGroupId => $contactGroupId1 - }, - }, 'R1'], - ['Email/query', { - filter => { - ccContactGroupId => $contactGroupId1 - }, - }, 'R2'], - ['Email/query', { - filter => { - bccContactGroupId => $contactGroupId1 - }, - }, 'R3'] - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($emailId4, $res->[0][1]{ids}[0]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{ids}}); - $self->assert_str_equals($emailId5, $res->[1][1]{ids}[0]); - $self->assert_num_equals(1, scalar @{$res->[2][1]{ids}}); - $self->assert_str_equals($emailId6, $res->[2][1]{ids}[0]); + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + toContactGroupId => $contactGroupId1 + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + ccContactGroupId => $contactGroupId1 + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + bccContactGroupId => $contactGroupId1 + }, + }, + 'R3' + ] + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($emailId4, $res->[0][1]{ids}[0]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{ids} }); + $self->assert_str_equals($emailId5, $res->[1][1]{ids}[0]); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{ids} }); + $self->assert_str_equals($emailId6, $res->[2][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch index 6f36f141dd..c6c2798296 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch @@ -2,57 +2,71 @@ use Cassandane::Tiny; sub test_email_query_guidsearch - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - for (my $i = 0; $i < 10; $i++) { - $self->make_message("msg$i", to => Cassandane::Address->new( - localpart => "recipient$i", - domain => 'example.com' - )) || die; - } + for (my $i = 0; $i < 10; $i++) { + $self->make_message( + "msg$i", + to => Cassandane::Address->new( + localpart => "recipient$i", + domain => 'example.com' + ) + ) || die; + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog "Running query with guidsearch"; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - to => '@example.com', - }, - }, 'R1'] - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - my $guidSearchIds = $res->[0][1]{ids}; - $self->assert_num_equals(10, scalar @{$guidSearchIds}); + xlog "Running query with guidsearch"; + my $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + }, + 'R1' + ] ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + my $guidSearchIds = $res->[0][1]{ids}; + $self->assert_num_equals(10, scalar @{$guidSearchIds}); - xlog "Running query without guidsearch"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - to => '@example.com', - }, - disableGuidSearch => JSON::true, - }, 'R1'] - ], $using); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - my $uidSearchIds = $res->[0][1]{ids}; - $self->assert_num_equals(10, scalar @{$uidSearchIds}); + xlog "Running query without guidsearch"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + disableGuidSearch => JSON::true, + }, + 'R1' + ] ], + $using + ); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + my $uidSearchIds = $res->[0][1]{ids}; + $self->assert_num_equals(10, scalar @{$uidSearchIds}); - xlog "Comparing results"; - $self->assert_deep_equals($guidSearchIds, $uidSearchIds); + xlog "Comparing results"; + $self->assert_deep_equals($guidSearchIds, $uidSearchIds); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_collapsethreads b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_collapsethreads index a543eb7e9d..c060d24623 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_collapsethreads +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_collapsethreads @@ -2,128 +2,154 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_collapsethreads - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; + my $emailCount = 3; + my %createEmails; + for (my $i = 1; $i <= $emailCount; $i++) { + my $extraBody = ' diy reseller' unless ($i % 2); + $createEmails{$i} = { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => "foo$i\@bar" } ], + to => [ { email => "bar$i\@example.com" } ], + messageId => ["email$i\@local"], + subject => "email$i", + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email$i body" . $extraBody + }, + }, + }; + } + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => \%createEmails, + }, + 'R1' + ], + ]); + $self->assert_num_equals($emailCount, scalar keys %{ $res->[0][1]{created} }); - my $emailCount = 3; - my %createEmails; - for (my $i = 1; $i <= $emailCount; $i++) { - my $extraBody = ' diy reseller' unless ($i % 2); - $createEmails{$i} = { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => "foo$i\@bar" }], - to => [{ email => "bar$i\@example.com" }], - messageId => ["email$i\@local"], - subject => "email$i", - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email$i body" . $extraBody - }, - }, - } + for (my $i = 1; $i <= $emailCount; $i++) { + my %createEmails = (); + my $threadCount = ($i % 7) + 3; # clamp to max 10 thread emails + for (my $j = 1; $j <= $threadCount; $j++) { + my $extraBody = ' nyi reseller' unless ($j % 2); + $createEmails{$j} = { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => "foo$i" . "ref$j\@bar" } ], + to => [ { email => "bar$i" . "ref$j\@example.com" } ], + messageId => [ "email$i" . "ref$j\@local" ], + references => ["email$i\@local"], + subject => "Re: email$i", + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email$i" . "ref$j body" . $extraBody + }, + }, + }; } - my $res = $jmap->CallMethods([ - ['Email/set', { - create => \%createEmails, - }, 'R1'], + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => \%createEmails, + }, + 'R1' + ], ]); - $self->assert_num_equals($emailCount, scalar keys %{$res->[0][1]{created}}); - - for (my $i = 1; $i <= $emailCount; $i++) { - my %createEmails = (); - my $threadCount = ($i % 7) + 3; # clamp to max 10 thread emails - for (my $j = 1; $j <= $threadCount; $j++) { - my $extraBody = ' nyi reseller' unless ($j % 2); - $createEmails{$j} = { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => "foo$i" . "ref$j\@bar" }], - to => [{ email => "bar$i" . "ref$j\@example.com" }], - messageId => ["email$i" . "ref$j\@local"], - references => ["email$i\@local"], - subject => "Re: email$i", - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email$i" ."ref$j body" . $extraBody - }, - }, - } - } - $res = $jmap->CallMethods([ - ['Email/set', { - create => \%createEmails, - }, 'R1'], - ]); - $self->assert_num_equals($threadCount, scalar keys %{$res->[0][1]{created}}); - } + $self->assert_num_equals($threadCount, + scalar keys %{ $res->[0][1]{created} }); + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query collapsed threads"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - text => 'nyi', - }, { - text => 'reseller', - }], - }, - sort => [{ - property => 'receivedAt', - isAscending => JSON::false, - }], - collapseThreads => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - text => 'nyi', - }, { - text => 'reseller', - }], - }, - sort => [{ - property => 'receivedAt', - isAscending => JSON::false, - }], - collapseThreads => JSON::true, - disableGuidSearch => JSON::true, - }, 'R2'], - ], $using); + xlog "Query collapsed threads"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + text => 'nyi', + }, + { + text => 'reseller', + } + ], + }, + sort => [ { + property => 'receivedAt', + isAscending => JSON::false, + } ], + collapseThreads => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + text => 'nyi', + }, + { + text => 'reseller', + } + ], + }, + sort => [ { + property => 'receivedAt', + isAscending => JSON::false, + } ], + collapseThreads => JSON::true, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + ], + $using + ); - my $guidSearchIds; - my @wantIds; + my $guidSearchIds; + my @wantIds; - # Check GUID search results - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals($res->[1][1]{ids}, $res->[0][1]{ids}); + # Check GUID search results + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals($res->[1][1]{ids}, $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inbox b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inbox index 6b3418943d..56a0ebb1f7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inbox +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inbox @@ -2,23 +2,22 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_inbox - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog "Create message in mailbox A"; - my $email = <<'EOF'; + xlog "Create message in mailbox A"; + my $email = <<'EOF'; From: from@local To: to@local Subject: email1 @@ -28,134 +27,185 @@ Content-Type: text/plain; charset="UTF-8" email1 EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobId = $data->{blobId}; - $self->assert_not_null($blobId); + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobId = $data->{blobId}; + $self->assert_not_null($blobId); - my $res = $jmap->CallMethods([ - ['Mailbox/query', { - }, "R1"], - ['Mailbox/set', { - create => { - mboxA => { - name => "A", - } + my $res = $jmap->CallMethods( + [ + [ 'Mailbox/query', {}, "R1" ], + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => "A", } - }, "R2"] - ], $using); - my $inboxId = $res->[0][1]{ids}[0]; - $self->assert_not_null($inboxId); - my $mboxId = $res->[1][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxId); + } + }, + "R2" + ] + ], + $using + ); + my $inboxId = $res->[0][1]{ids}[0]; + $self->assert_not_null($inboxId); + my $mboxId = $res->[1][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxId); - $res = $jmap->CallMethods([ - ['Email/import', { - emails => { - email1 => { - blobId => $blobId, - mailboxIds => { - $mboxId => JSON::true - }, - }, + $res = $jmap->CallMethods( + [ + [ + 'Email/import', + { + emails => { + email1 => { + blobId => $blobId, + mailboxIds => { + $mboxId => JSON::true + }, }, - }, "R1"], - ], $using); - $self->assert_str_equals("Email/import", $res->[0][0]); - my $email1Id = $res->[0][1]->{created}{email1}{id}; - $self->assert_not_null($email1Id); + }, + }, + "R1" + ], + ], + $using + ); + $self->assert_str_equals("Email/import", $res->[0][0]); + my $email1Id = $res->[0][1]->{created}{email1}{id}; + $self->assert_not_null($email1Id); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query inMailbox=inbox"; - $res = $jmap->CallMethods([ - ['Email/get', { - ids => [$email1Id], - properties => ['mailboxIds'], - }, "R1"], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - inMailbox => $inboxId, - }], - }, - }, "R2"], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - inMailbox => $inboxId, - subject => 'email1', - }], - }, - }, "R3"], - ], $using); - $self->assert_deep_equals({ - $mboxId => JSON::true, - }, $res->[0][1]{list}[0]{mailboxIds}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals([], $res->[1][1]{ids}); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj < 3 || ($maj ==3 && $min < 5)) { - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - } - else { - # Due to improved JMAP Email query optimizer - $self->assert_equals(JSON::false, $res->[2][1]{performance}{details}{isGuidSearch}); - } + xlog "Query inMailbox=inbox"; + $res = $jmap->CallMethods( + [ + [ + 'Email/get', + { + ids => [$email1Id], + properties => ['mailboxIds'], + }, + "R1" + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ { + inMailbox => $inboxId, + } ], + }, + }, + "R2" + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ { + inMailbox => $inboxId, + subject => 'email1', + } ], + }, + }, + "R3" + ], + ], + $using + ); + $self->assert_deep_equals( + { + $mboxId => JSON::true, + }, + $res->[0][1]{list}[0]{mailboxIds} + ); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals([], $res->[1][1]{ids}); + my ($maj, $min) = Cassandane::Instance->get_version(); - $self->assert_deep_equals([], $res->[2][1]{ids}); - - xlog "Create message in inbox"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email2 => { - mailboxIds => { - $inboxId => JSON::true, - }, - subject => 'email2', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'email2', - } - }, - }, - }, - }, 'R1'], - ]); - my $email2Id = $res->[0][1]->{created}{email2}{id}; - $self->assert_not_null($email2Id); + if ($maj < 3 || ($maj == 3 && $min < 5)) { + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + } else { + # Due to improved JMAP Email query optimizer + $self->assert_equals(JSON::false, + $res->[2][1]{performance}{details}{isGuidSearch}); + } - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->assert_deep_equals([], $res->[2][1]{ids}); - xlog "Rerun query inMailbox=inbox"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - inMailbox => $inboxId, - }], + xlog "Create message in inbox"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email2 => { + mailboxIds => { + $inboxId => JSON::true, }, - }, "R1"], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - inMailbox => $inboxId, - subject => 'email2', - }], + subject => 'email2', + bodyStructure => { + type => 'text/plain', + partId => 'part1', }, - }, "R1"], - ], $using); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals([$email2Id], $res->[0][1]{ids}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals([$email2Id], $res->[1][1]{ids}); + bodyValues => { + part1 => { + value => 'email2', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $email2Id = $res->[0][1]->{created}{email2}{id}; + $self->assert_not_null($email2Id); + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog "Rerun query inMailbox=inbox"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ { + inMailbox => $inboxId, + } ], + }, + }, + "R1" + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ { + inMailbox => $inboxId, + subject => 'email2', + } ], + }, + }, + "R1" + ], + ], + $using + ); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals([$email2Id], $res->[0][1]{ids}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals([$email2Id], $res->[1][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inmailbox b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inmailbox index 65592edd4a..1c960d3138 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inmailbox +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inmailbox @@ -2,272 +2,324 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_inmailbox - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; - $imap->create("INBOX.B") or die; - $imap->create("INBOX.C") or die; - $imap->create("INBOX.D") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ], $using); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $mboxByName{'A'}->{id}; - my $mboxIdB = $mboxByName{'B'}->{id}; - my $mboxIdC = $mboxByName{'C'}->{id}; - my $mboxIdD = $mboxByName{'D'}->{id}; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; + $imap->create("INBOX.B") or die; + $imap->create("INBOX.C") or die; + $imap->create("INBOX.D") or die; + my $res = $jmap->CallMethods( + [ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ], + $using + ); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $mboxByName{'A'}->{id}; + my $mboxIdB = $mboxByName{'B'}->{id}; + my $mboxIdC = $mboxByName{'C'}->{id}; + my $mboxIdD = $mboxByName{'D'}->{id}; - xlog $self, "create emails"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'mA' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'A', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mB' => { - mailboxIds => { - $mboxIdB => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'B', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mC' => { - mailboxIds => { - $mboxIdC => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'C', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mD' => { - mailboxIds => { - $mboxIdD => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'D', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mAB' => { - mailboxIds => { - $mboxIdA => JSON::true, - $mboxIdB => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'AB', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mCD' => { - mailboxIds => { - $mboxIdC => JSON::true, - $mboxIdD => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'CD', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mABCD' => { - mailboxIds => { - $mboxIdA => JSON::true, - $mboxIdB => JSON::true, - $mboxIdC => JSON::true, - $mboxIdD => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'ABCD', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog $self, "create emails"; + $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + 'mA' => { + mailboxIds => { + $mboxIdA => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'A', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, }, - }, 'R1'], - ], $using); - my $emailIdA = $res->[0][1]->{created}{mA}{id}; - $self->assert_not_null($emailIdA); - my $emailIdB = $res->[0][1]->{created}{mB}{id}; - $self->assert_not_null($emailIdB); - my $emailIdC = $res->[0][1]->{created}{mC}{id}; - $self->assert_not_null($emailIdC); - my $emailIdD = $res->[0][1]->{created}{mD}{id}; - $self->assert_not_null($emailIdD); - my $emailIdAB = $res->[0][1]->{created}{mAB}{id}; - $self->assert_not_null($emailIdAB); - my $emailIdCD = $res->[0][1]->{created}{mCD}{id}; - $self->assert_not_null($emailIdCD); - my $emailIdABCD = $res->[0][1]->{created}{mABCD}{id}; - $self->assert_not_null($emailIdABCD); + 'mB' => { + mailboxIds => { + $mboxIdB => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'B', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mC' => { + mailboxIds => { + $mboxIdC => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'C', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mD' => { + mailboxIds => { + $mboxIdD => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'D', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mAB' => { + mailboxIds => { + $mboxIdA => JSON::true, + $mboxIdB => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'AB', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mCD' => { + mailboxIds => { + $mboxIdC => JSON::true, + $mboxIdD => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'CD', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mABCD' => { + mailboxIds => { + $mboxIdA => JSON::true, + $mboxIdB => JSON::true, + $mboxIdC => JSON::true, + $mboxIdD => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'ABCD', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ], + $using + ); + my $emailIdA = $res->[0][1]->{created}{mA}{id}; + $self->assert_not_null($emailIdA); + my $emailIdB = $res->[0][1]->{created}{mB}{id}; + $self->assert_not_null($emailIdB); + my $emailIdC = $res->[0][1]->{created}{mC}{id}; + $self->assert_not_null($emailIdC); + my $emailIdD = $res->[0][1]->{created}{mD}{id}; + $self->assert_not_null($emailIdD); + my $emailIdAB = $res->[0][1]->{created}{mAB}{id}; + $self->assert_not_null($emailIdAB); + my $emailIdCD = $res->[0][1]->{created}{mCD}{id}; + $self->assert_not_null($emailIdCD); + my $emailIdABCD = $res->[0][1]->{created}{mABCD}{id}; + $self->assert_not_null($emailIdABCD); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my @wantIds; + my @wantIds; - xlog $self, "query emails in mailbox A"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - from => 'foo@local', - inMailbox => $mboxIdA, - }, - sort => [{ - property => 'id', - isAscending => JSON::true, - }], - }, 'R1'], - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - @wantIds = sort ($emailIdA, $emailIdAB, $emailIdABCD); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + xlog $self, "query emails in mailbox A"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + from => 'foo@local', + inMailbox => $mboxIdA, + }, + sort => [ { + property => 'id', + isAscending => JSON::true, + } ], + }, + 'R1' + ], + ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + @wantIds = sort ($emailIdA, $emailIdAB, $emailIdABCD); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - xlog $self, "query emails in mailbox A and B"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - from => 'foo@local', - inMailbox => $mboxIdA, - }, { - from => 'foo@local', - inMailbox => $mboxIdB, - }], - }, - sort => [{ - property => 'id', - isAscending => JSON::true, - }], - }, 'R1'], - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - @wantIds = sort ($emailIdAB, $emailIdABCD); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + xlog $self, "query emails in mailbox A and B"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + from => 'foo@local', + inMailbox => $mboxIdA, + }, + { + from => 'foo@local', + inMailbox => $mboxIdB, + } + ], + }, + sort => [ { + property => 'id', + isAscending => JSON::true, + } ], + }, + 'R1' + ], + ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + @wantIds = sort ($emailIdAB, $emailIdABCD); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - xlog $self, "query emails in mailboxes other than A,B"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - from => 'foo@local', - inMailboxOtherThan => [$mboxIdA, $mboxIdB], - }, - sort => [{ - property => 'id', - isAscending => JSON::true, - }], - }, 'R1'], - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - @wantIds = sort ($emailIdC, $emailIdD, $emailIdCD, $emailIdABCD); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + xlog $self, "query emails in mailboxes other than A,B"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + from => 'foo@local', + inMailboxOtherThan => [ $mboxIdA, $mboxIdB ], + }, + sort => [ { + property => 'id', + isAscending => JSON::true, + } ], + }, + 'R1' + ], + ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + @wantIds = sort ($emailIdC, $emailIdD, $emailIdCD, $emailIdABCD); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inmailboxotherthan b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inmailboxotherthan index 6cb58d5a5a..71d3644bb7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inmailboxotherthan +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_inmailboxotherthan @@ -2,112 +2,131 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_inmailboxotherthan - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ], $using); + my $res = $jmap->CallMethods( + [ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ], + $using + ); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxA = $mboxByName{'A'}->{id}; - $self->assert_not_null($mboxA); - my $inbox = $mboxByName{'Inbox'}->{id}; - $self->assert_not_null($inbox); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxA = $mboxByName{'A'}->{id}; + $self->assert_not_null($mboxA); + my $inbox = $mboxByName{'Inbox'}->{id}; + $self->assert_not_null($inbox); - xlog $self, "create emails"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'msgInbox' => { - mailboxIds => { - $inbox => JSON::true, - }, - from => [{ - name => '', email => 'from@local' - }], - to => [{ - name => '', email => 'to@local' - }], - subject => 'msgInbox', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'msgA' => { - mailboxIds => { - $mboxA => JSON::true, - }, - from => [{ - name => '', email => 'from@local' - }], - to => [{ - name => '', email => 'to@local' - }], - subject => 'msgA', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog $self, "create emails"; + $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + 'msgInbox' => { + mailboxIds => { + $inbox => JSON::true, + }, + from => [ { + name => '', + email => 'from@local' + } ], + to => [ { + name => '', + email => 'to@local' + } ], + subject => 'msgInbox', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, }, - }, 'R1'], - ], $using); - my $emailInbox = $res->[0][1]->{created}{msgInbox}{id}; - $self->assert_not_null($emailInbox); - my $emailA = $res->[0][1]->{created}{msgA}{id}; - $self->assert_not_null($emailA); + 'msgA' => { + mailboxIds => { + $mboxA => JSON::true, + }, + from => [ { + name => '', + email => 'from@local' + } ], + to => [ { + name => '', + email => 'to@local' + } ], + subject => 'msgA', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ], + $using + ); + my $emailInbox = $res->[0][1]->{created}{msgInbox}{id}; + $self->assert_not_null($emailInbox); + my $emailA = $res->[0][1]->{created}{msgA}{id}; + $self->assert_not_null($emailA); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Running query with guidsearch"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - body => 'test', - inMailboxOtherThan => [ - $inbox, - ], - }], - }, - collapseThreads => JSON::true, - findAllInThread => JSON::true, - }, 'R1'] - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - my @wantIds = sort ($emailA); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + xlog "Running query with guidsearch"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ { + body => 'test', + inMailboxOtherThan => [ $inbox, ], + } ], + }, + collapseThreads => JSON::true, + findAllInThread => JSON::true, + }, + 'R1' + ] ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + my @wantIds = sort ($emailA); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_keywords b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_keywords index ae6f42f48e..3e0f6d5530 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_keywords +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_keywords @@ -2,146 +2,174 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_keywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "create emails"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'mA' => { - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - mailboxIds => { - '$inbox' => JSON::true, - }, - subject => 'Answered', - keywords => { - '$Answered' => JSON::true, - }, - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mD' => { - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - mailboxIds => { - '$inbox' => JSON::true, - }, - subject => 'Draft', - keywords => { - '$Draft' => JSON::true, - }, - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mF' => { - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - mailboxIds => { - '$inbox' => JSON::true, - }, - subject => 'Flagged', - keywords => { - '$Flagged' => JSON::true, - }, - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog $self, "create emails"; + my $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + 'mA' => { + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + mailboxIds => { + '$inbox' => JSON::true, + }, + subject => 'Answered', + keywords => { + '$Answered' => JSON::true, + }, + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, }, - }, 'R1'], - ], $using); - my $emailIdA = $res->[0][1]->{created}{mA}{id}; - $self->assert_not_null($emailIdA); - my $emailIdD = $res->[0][1]->{created}{mD}{id}; - $self->assert_not_null($emailIdD); - my $emailIdF = $res->[0][1]->{created}{mF}{id}; - $self->assert_not_null($emailIdF); + 'mD' => { + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + mailboxIds => { + '$inbox' => JSON::true, + }, + subject => 'Draft', + keywords => { + '$Draft' => JSON::true, + }, + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mF' => { + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + mailboxIds => { + '$inbox' => JSON::true, + }, + subject => 'Flagged', + keywords => { + '$Flagged' => JSON::true, + }, + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ], + $using + ); + my $emailIdA = $res->[0][1]->{created}{mA}{id}; + $self->assert_not_null($emailIdA); + my $emailIdD = $res->[0][1]->{created}{mD}{id}; + $self->assert_not_null($emailIdD); + my $emailIdF = $res->[0][1]->{created}{mF}{id}; + $self->assert_not_null($emailIdF); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my @wantIds; + my @wantIds; - xlog $self, "query draft emails"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - from => 'foo@local', - hasKeyword => '$draft', - }, - sort => [{ - property => 'id', - isAscending => JSON::true, - }], - }, 'R1'], - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - @wantIds = sort ($emailIdD); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + xlog $self, "query draft emails"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + from => 'foo@local', + hasKeyword => '$draft', + }, + sort => [ { + property => 'id', + isAscending => JSON::true, + } ], + }, + 'R1' + ], + ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + @wantIds = sort ($emailIdD); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - xlog $self, "query anything but draft emails"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - from => 'foo@local', - notKeyword => '$draft', - }, - sort => [{ - property => 'id', - isAscending => JSON::true, - }], - }, 'R1'], - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - @wantIds = sort ($emailIdA, $emailIdF); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + xlog $self, "query anything but draft emails"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + from => 'foo@local', + notKeyword => '$draft', + }, + sort => [ { + property => 'id', + isAscending => JSON::true, + } ], + }, + 'R1' + ], + ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + @wantIds = sort ($emailIdA, $emailIdF); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_mixedfilter b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_mixedfilter index 586a83824c..06b51a0aa0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_mixedfilter +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_mixedfilter @@ -2,161 +2,191 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_mixedfilter - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; - $imap->create("INBOX.B") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ], $using); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $mboxByName{'A'}->{id}; - my $mboxIdB = $mboxByName{'B'}->{id}; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; + $imap->create("INBOX.B") or die; + my $res = $jmap->CallMethods( + [ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ], + $using + ); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $mboxByName{'A'}->{id}; + my $mboxIdB = $mboxByName{'B'}->{id}; - xlog $self, "create emails"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'mAfoo' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'from@local' - }], - to => [{ - name => '', email => 'to@local' - }], - subject => 'foo', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mAbar' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'from@local' - }], - to => [{ - name => '', email => 'to@local' - }], - subject => 'bar', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mBfoo' => { - mailboxIds => { - $mboxIdB => JSON::true, - }, - from => [{ - name => '', email => 'from@local' - }], - to => [{ - name => '', email => 'to@local' - }], - subject => 'foo', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mBbar' => { - mailboxIds => { - $mboxIdB => JSON::true, - }, - from => [{ - name => '', email => 'from@local' - }], - to => [{ - name => '', email => 'to@local' - }], - subject => 'bar', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog $self, "create emails"; + $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + 'mAfoo' => { + mailboxIds => { + $mboxIdA => JSON::true, + }, + from => [ { + name => '', + email => 'from@local' + } ], + to => [ { + name => '', + email => 'to@local' + } ], + subject => 'foo', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, }, - }, 'R1'], - ], $using); - my $emailIdAfoo = $res->[0][1]->{created}{mAfoo}{id}; - $self->assert_not_null($emailIdAfoo); - my $emailIdAbar = $res->[0][1]->{created}{mAbar}{id}; - $self->assert_not_null($emailIdAbar); - my $emailIdBfoo = $res->[0][1]->{created}{mBfoo}{id}; - $self->assert_not_null($emailIdBfoo); - my $emailIdBbar = $res->[0][1]->{created}{mBbar}{id}; - $self->assert_not_null($emailIdBbar); + 'mAbar' => { + mailboxIds => { + $mboxIdA => JSON::true, + }, + from => [ { + name => '', + email => 'from@local' + } ], + to => [ { + name => '', + email => 'to@local' + } ], + subject => 'bar', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mBfoo' => { + mailboxIds => { + $mboxIdB => JSON::true, + }, + from => [ { + name => '', + email => 'from@local' + } ], + to => [ { + name => '', + email => 'to@local' + } ], + subject => 'foo', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mBbar' => { + mailboxIds => { + $mboxIdB => JSON::true, + }, + from => [ { + name => '', + email => 'from@local' + } ], + to => [ { + name => '', + email => 'to@local' + } ], + subject => 'bar', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ], + $using + ); + my $emailIdAfoo = $res->[0][1]->{created}{mAfoo}{id}; + $self->assert_not_null($emailIdAfoo); + my $emailIdAbar = $res->[0][1]->{created}{mAbar}{id}; + $self->assert_not_null($emailIdAbar); + my $emailIdBfoo = $res->[0][1]->{created}{mBfoo}{id}; + $self->assert_not_null($emailIdBfoo); + my $emailIdBbar = $res->[0][1]->{created}{mBbar}{id}; + $self->assert_not_null($emailIdBbar); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my @wantIds; + my @wantIds; - xlog $self, "query emails with disjunction of mixed criteria"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'OR', - conditions => [{ - subject => 'foo', - }, { - inMailbox => $mboxIdB, - }], - }, - sort => [{ - property => 'id', - isAscending => JSON::true, - }], - }, 'R1'], - ], $using); + xlog $self, "query emails with disjunction of mixed criteria"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'OR', + conditions => [ + { + subject => 'foo', + }, + { + inMailbox => $mboxIdB, + } + ], + }, + sort => [ { + property => 'id', + isAscending => JSON::true, + } ], + }, + 'R1' + ], + ], + $using + ); - # Current Cyrus implementation of GUID search does not support - # disjunctions of Xapian and non-Xapian filters. This might change. - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - @wantIds = sort ($emailIdAfoo, $emailIdBfoo, $emailIdBbar); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + # Current Cyrus implementation of GUID search does not support + # disjunctions of Xapian and non-Xapian filters. This might change. + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + @wantIds = sort ($emailIdAfoo, $emailIdBfoo, $emailIdBbar); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_mixedfilter2 b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_mixedfilter2 index aea21b5201..80aa441f18 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_mixedfilter2 +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_mixedfilter2 @@ -2,177 +2,200 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_mixedfilter2 - :min_version_3_4 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_4 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['id'], - }, 'R1'], - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, - } - }, 'R2'], - ], $using); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); - my $mboxA = $res->[1][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[1][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); + my $res = $jmap->CallMethods( + [ + [ + 'Mailbox/get', + { + properties => ['id'], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + }, + mboxB => { + name => 'B', + }, + } + }, + 'R2' + ], + ], + $using + ); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); + my $mboxA = $res->[1][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[1][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - emailA => { - mailboxIds => { - $mboxA => JSON::true, - }, - subject => 'emailA', - from => [{ - email => 'fromA@local' - }] , - to => [{ - email => 'toA@local' - }] , - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'emailA', - } - }, - }, - emailB => { - mailboxIds => { - $mboxB => JSON::true, - }, - subject => 'emailB', - from => [{ - email => 'fromB@local' - }] , - to => [{ - email => 'toB@local' - }] , - cc => [{ - email => 'ccB@local' - }] , - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'emailB', - } - }, - }, - emailX => { - mailboxIds => { - $inbox => JSON::true, - }, - subject => 'emailX', - from => [{ - email => 'fromA@local' - }] , - to => [{ - email => 'toB@local' - }] , - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'emailX', - } - }, - }, + $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + emailA => { + mailboxIds => { + $mboxA => JSON::true, + }, + subject => 'emailA', + from => [ { + email => 'fromA@local' + } ], + to => [ { + email => 'toA@local' + } ], + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'emailA', + } + }, + }, + emailB => { + mailboxIds => { + $mboxB => JSON::true, + }, + subject => 'emailB', + from => [ { + email => 'fromB@local' + } ], + to => [ { + email => 'toB@local' + } ], + cc => [ { + email => 'ccB@local' + } ], + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'emailB', + } + }, }, - }, 'R1'], - ], $using); - my $emailA = $res->[0][1]{created}{emailA}{id}; - $self->assert_not_null($emailA); - my $emailB = $res->[0][1]{created}{emailB}{id}; - $self->assert_not_null($emailB); - my $emailX = $res->[0][1]{created}{emailX}{id}; - $self->assert_not_null($emailX); + emailX => { + mailboxIds => { + $inbox => JSON::true, + }, + subject => 'emailX', + from => [ { + email => 'fromA@local' + } ], + to => [ { + email => 'toB@local' + } ], + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'emailX', + } + }, + }, + }, + }, + 'R1' + ], + ], + $using + ); + my $emailA = $res->[0][1]{created}{emailA}{id}; + $self->assert_not_null($emailA); + my $emailB = $res->[0][1]{created}{emailB}{id}; + $self->assert_not_null($emailB); + my $emailX = $res->[0][1]{created}{emailX}{id}; + $self->assert_not_null($emailX); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - 'operator' => 'AND', + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + 'operator' => 'AND', + 'conditions' => [ + { + 'operator' => 'OR', 'conditions' => [ - { - 'operator' => 'OR', + { + 'from' => 'fromA@local', + }, + { + 'operator' => 'AND', + 'conditions' => [ + { + 'inMailbox' => $mboxB, + }, + { + 'operator' => 'OR', 'conditions' => [ - { - 'from' => 'fromA@local', - }, - { - 'operator' => 'AND', - 'conditions' => [ - { - 'inMailbox' => $mboxB, - }, - { - 'operator' => 'OR', - 'conditions' => [ - { - 'to' => 'toB@local' - }, - { - 'cc' => 'ccB@local' - }, - { - 'bcc' => 'bccB@local' - }, - { - 'deliveredTo' => 'deliveredToB@local' - } - ] - } - ] - } - ] - }, - { - 'inMailboxOtherThan' => [ - $inbox + { + 'to' => 'toB@local' + }, + { + 'cc' => 'ccB@local' + }, + { + 'bcc' => 'bccB@local' + }, + { + 'deliveredTo' => 'deliveredToB@local' + } ] - } + } + ] + } ] - }, - sort => [{ property => 'id' }], - }, 'R1'], - ], $using); + }, + { + 'inMailboxOtherThan' => [$inbox] + } + ] + }, + sort => [ { property => 'id' } ], + }, + 'R1' + ], + ], + $using + ); - # All DNF-clauses of a guidsearch query with Xapian and non-Xapian criteria - # must contain the same non-Xapian criteria. - # This might change in the future. - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - my @wantIds = sort ( $emailA, $emailB ); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + # All DNF-clauses of a guidsearch query with Xapian and non-Xapian criteria + # must contain the same non-Xapian criteria. + # This might change in the future. + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + my @wantIds = sort ($emailA, $emailB); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_only_email_mailboxes b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_only_email_mailboxes index 5fc1486fe8..cc4f4e4230 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_only_email_mailboxes +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_only_email_mailboxes @@ -2,91 +2,115 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_only_email_mailboxes - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - 'https://cyrusimap.org/ns/jmap/calendars', - 'https://cyrusimap.org/ns/jmap/contacts', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + 'https://cyrusimap.org/ns/jmap/calendars', + 'https://cyrusimap.org/ns/jmap/contacts', + ]; - xlog $self, "create email, calendar event and contact"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - '1' => { - mailboxIds => { - '$inbox' => JSON::true, - }, - from => [{ - name => '', email => 'from@local' - }], - to => [{ - name => '', email => 'to@local' - }], - subject => 'test', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - }, - }, 'R1'], - ['CalendarEvent/set', { - create => { - '2' => { - calendarIds => { - Default => JSON::true - }, - start => '2020-02-25T11:00:00', - timeZone => 'Australia/Melbourne', - title => 'test', + xlog $self, "create email, calendar event and contact"; + my $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + '1' => { + mailboxIds => { + '$inbox' => JSON::true, + }, + from => [ { + name => '', + email => 'from@local' + } ], + to => [ { + name => '', + email => 'to@local' + } ], + subject => 'test', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', } + }, + }, + }, + }, + 'R1' + ], + [ + 'CalendarEvent/set', + { + create => { + '2' => { + calendarIds => { + Default => JSON::true + }, + start => '2020-02-25T11:00:00', + timeZone => 'Australia/Melbourne', + title => 'test', } - }, 'R2'], - ['Contact/set', { - create => { - "3" => { - lastName => "test", - } + } + }, + 'R2' + ], + [ + 'Contact/set', + { + create => { + "3" => { + lastName => "test", } - }, 'R3'], - ], $using); - my $emailId = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($emailId); - my $eventId = $res->[1][1]->{created}{2}{id}; - $self->assert_not_null($eventId); - my $contactId = $res->[2][1]->{created}{3}{id}; - $self->assert_not_null($contactId); + } + }, + 'R3' + ], + ], + $using + ); + my $emailId = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($emailId); + my $eventId = $res->[1][1]->{created}{2}{id}; + $self->assert_not_null($eventId); + my $contactId = $res->[2][1]->{created}{3}{id}; + $self->assert_not_null($contactId); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query emails"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - text => 'test', - }, - }, 'R1'], - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); + xlog "Query emails"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + text => 'test', + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_scanmode b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_scanmode index 54d2c11395..3e5b5ea314 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_scanmode +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_scanmode @@ -2,57 +2,71 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_scanmode - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions :SearchSetForceScanMode -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions : SearchSetForceScanMode { + my ($self) = @_; + my $jmap = $self->{jmap}; - for (my $i = 0; $i < 10; $i++) { - $self->make_message("msg$i", to => Cassandane::Address->new( - localpart => "recipient$i", - domain => 'example.com' - )) || die; - } + for (my $i = 0; $i < 10; $i++) { + $self->make_message( + "msg$i", + to => Cassandane::Address->new( + localpart => "recipient$i", + domain => 'example.com' + ) + ) || die; + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog "Running query with guidsearch"; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - to => '@example.com', - }, - }, 'R1'] - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - my $guidSearchIds = $res->[0][1]{ids}; - $self->assert_num_equals(10, scalar @{$guidSearchIds}); + xlog "Running query with guidsearch"; + my $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + }, + 'R1' + ] ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + my $guidSearchIds = $res->[0][1]{ids}; + $self->assert_num_equals(10, scalar @{$guidSearchIds}); - xlog "Running query without guidsearch"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - to => '@example.com', - }, - disableGuidSearch => JSON::true, - }, 'R1'] - ], $using); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - my $uidSearchIds = $res->[0][1]{ids}; - $self->assert_num_equals(10, scalar @{$uidSearchIds}); + xlog "Running query without guidsearch"; + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + disableGuidSearch => JSON::true, + }, + 'R1' + ] ], + $using + ); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + my $uidSearchIds = $res->[0][1]{ids}; + $self->assert_num_equals(10, scalar @{$uidSearchIds}); - xlog "Comparing results"; - $self->assert_deep_equals($guidSearchIds, $uidSearchIds); + xlog "Comparing results"; + $self->assert_deep_equals($guidSearchIds, $uidSearchIds); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_sort b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_sort index 9858d3b1af..89170c903f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_sort +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_sort @@ -2,193 +2,246 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_sort - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; - - my $emailCount = 10; - - xlog "Creating $emailCount emails (every 5th has same internaldate)"; - my %createEmails; - for (my $i = 0; $i < $emailCount; $i++) { - my $receivedAt = '2019-01-0' . (($i % 5) + 1) . 'T00:00:00Z'; - $createEmails{$i} = { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => "foo$i\@bar" }], - to => [{ email => "bar$i\@example.com" }], - receivedAt => $receivedAt, - subject => "email$i", - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email$i body", - }, - }, - } - } - my $res = $jmap->CallMethods([ - ['Email/set', { - create => \%createEmails, - }, 'R1'], - ]); - $self->assert_num_equals($emailCount, scalar keys %{$res->[0][1]{created}}); - - my @emails; - for (my $i = 0; $i < $emailCount; $i++) { - $emails[$i] = { - id => $res->[0][1]{created}{$i}{id}, - receivedAt => $createEmails{$i}{receivedAt} - }; - } - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog "Sort by id (ascending and descending)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - to => '@example.com', - }, - sort => [{ - property => 'id', - isAscending => JSON::true, - }] - }, 'R1'], - ['Email/query', { - filter => { - to => '@example.com', - }, - sort => [{ - property => 'id', - isAscending => JSON::false, - }] - }, 'R2'], - ['Email/query', { - filter => { - to => '@example.com', - }, - sort => [{ - property => 'id', - isAscending => JSON::true, - }], - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { - to => '@example.com', - }, - sort => [{ - property => 'id', - isAscending => JSON::false, - }], - disableGuidSearch => JSON::true, - }, 'R2'], - ], $using); - - my $guidSearchIds; - my @wantIds; - - # Check GUID search results - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - @wantIds = map { $_->{id} } sort { $a->{id} cmp $b->{id} } @emails; - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isGuidSearch}); - @wantIds = map { $_->{id} } sort { $b->{id} cmp $a->{id} } @emails; - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - - # Check UID search result - $self->assert_equals(JSON::false, $res->[2][1]{performance}{details}{isGuidSearch}); - @wantIds = map { $_->{id} } sort { $a->{id} cmp $b->{id} } @emails; - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); - - $self->assert_equals(JSON::false, $res->[3][1]{performance}{details}{isGuidSearch}); - @wantIds = map { $_->{id} } sort { $b->{id} cmp $a->{id} } @emails; - $self->assert_deep_equals(\@wantIds, $res->[3][1]{ids}); - - xlog "Sort by internaldate (break ties by id) (ascending and descending)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - to => '@example.com', - }, - sort => [{ - property => 'receivedAt', - isAscending => JSON::true, - }] - }, 'R1'], - ['Email/query', { - filter => { - to => '@example.com', - }, - sort => [{ - property => 'receivedAt', - isAscending => JSON::false, - }] - }, 'R2'], - ['Email/query', { - filter => { - to => '@example.com', - }, - sort => [{ - property => 'receivedAt', - isAscending => JSON::true, - }], - disableGuidSearch => JSON::true, - }, 'R3'], - ['Email/query', { - filter => { - to => '@example.com', - }, - sort => [{ - property => 'receivedAt', - isAscending => JSON::false, - }], - disableGuidSearch => JSON::true, - }, 'R4'], - ], $using); - - # Check GUID search results - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - @wantIds = map { $_->{id} } sort { - $a->{receivedAt} cmp $b->{receivedAt} or $b->{id} cmp $a->{id} - } @emails; - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isGuidSearch}); - @wantIds = map { $_->{id} } sort { - $b->{receivedAt} cmp $a->{receivedAt} or $b->{id} cmp $a->{id} - } @emails; - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - - # Check UID search result - $self->assert_equals(JSON::false, $res->[2][1]{performance}{details}{isGuidSearch}); - @wantIds = map { $_->{id} } sort { - $a->{receivedAt} cmp $b->{receivedAt} or $b->{id} cmp $a->{id} - } @emails; - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); - - $self->assert_equals(JSON::false, $res->[3][1]{performance}{details}{isGuidSearch}); - @wantIds = map { $_->{id} } sort { - $b->{receivedAt} cmp $a->{receivedAt} or $b->{id} cmp $a->{id} - } @emails; - $self->assert_deep_equals(\@wantIds, $res->[3][1]{ids}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; + + my $emailCount = 10; + + xlog "Creating $emailCount emails (every 5th has same internaldate)"; + my %createEmails; + for (my $i = 0; $i < $emailCount; $i++) { + my $receivedAt = '2019-01-0' . (($i % 5) + 1) . 'T00:00:00Z'; + $createEmails{$i} = { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => "foo$i\@bar" } ], + to => [ { email => "bar$i\@example.com" } ], + receivedAt => $receivedAt, + subject => "email$i", + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email$i body", + }, + }, + }; + } + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => \%createEmails, + }, + 'R1' + ], + ]); + $self->assert_num_equals($emailCount, scalar keys %{ $res->[0][1]{created} }); + + my @emails; + for (my $i = 0; $i < $emailCount; $i++) { + $emails[$i] = { + id => $res->[0][1]{created}{$i}{id}, + receivedAt => $createEmails{$i}{receivedAt} + }; + } + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog "Sort by id (ascending and descending)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + sort => [ { + property => 'id', + isAscending => JSON::true, + } ] + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + sort => [ { + property => 'id', + isAscending => JSON::false, + } ] + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + sort => [ { + property => 'id', + isAscending => JSON::true, + } ], + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + sort => [ { + property => 'id', + isAscending => JSON::false, + } ], + disableGuidSearch => JSON::true, + }, + 'R2' + ], + ], + $using + ); + + my $guidSearchIds; + my @wantIds; + + # Check GUID search results + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + @wantIds = map { $_->{id} } sort { $a->{id} cmp $b->{id} } @emails; + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isGuidSearch}); + @wantIds = map { $_->{id} } sort { $b->{id} cmp $a->{id} } @emails; + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + + # Check UID search result + $self->assert_equals(JSON::false, + $res->[2][1]{performance}{details}{isGuidSearch}); + @wantIds = map { $_->{id} } sort { $a->{id} cmp $b->{id} } @emails; + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + + $self->assert_equals(JSON::false, + $res->[3][1]{performance}{details}{isGuidSearch}); + @wantIds = map { $_->{id} } sort { $b->{id} cmp $a->{id} } @emails; + $self->assert_deep_equals(\@wantIds, $res->[3][1]{ids}); + + xlog "Sort by internaldate (break ties by id) (ascending and descending)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + sort => [ { + property => 'receivedAt', + isAscending => JSON::true, + } ] + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + sort => [ { + property => 'receivedAt', + isAscending => JSON::false, + } ] + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + sort => [ { + property => 'receivedAt', + isAscending => JSON::true, + } ], + disableGuidSearch => JSON::true, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + to => '@example.com', + }, + sort => [ { + property => 'receivedAt', + isAscending => JSON::false, + } ], + disableGuidSearch => JSON::true, + }, + 'R4' + ], + ], + $using + ); + + # Check GUID search results + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + @wantIds + = map { $_->{id} } + sort { $a->{receivedAt} cmp $b->{receivedAt} or $b->{id} cmp $a->{id} } + @emails; + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isGuidSearch}); + @wantIds + = map { $_->{id} } + sort { $b->{receivedAt} cmp $a->{receivedAt} or $b->{id} cmp $a->{id} } + @emails; + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + + # Check UID search result + $self->assert_equals(JSON::false, + $res->[2][1]{performance}{details}{isGuidSearch}); + @wantIds + = map { $_->{id} } + sort { $a->{receivedAt} cmp $b->{receivedAt} or $b->{id} cmp $a->{id} } + @emails; + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + + $self->assert_equals(JSON::false, + $res->[3][1]{performance}{details}{isGuidSearch}); + @wantIds + = map { $_->{id} } + sort { $b->{receivedAt} cmp $a->{receivedAt} or $b->{id} cmp $a->{id} } + @emails; + $self->assert_deep_equals(\@wantIds, $res->[3][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_threadkeywords b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_threadkeywords index 95ceb1c72b..e27e42427d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_threadkeywords +++ b/cassandane/tiny-tests/JMAPEmail/email_query_guidsearch_threadkeywords @@ -2,147 +2,178 @@ use Cassandane::Tiny; sub test_email_query_guidsearch_threadkeywords - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name'], - }, "R1"] - ], $using); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); + my $res = $jmap->CallMethods( + [ [ + 'Mailbox/get', + { + properties => ['name'], + }, + "R1" + ] ], + $using + ); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); - xlog $self, "create emails"; - my %emails = ( - 'allthread1' => { - subject => 'allthread', - keywords => { - '$flagged' => JSON::true, - }, - messageId => ['allthread@local'], - }, - 'allthread2' => { - subject => 're: allthread', - keywords => { - '$flagged' => JSON::true, - }, - references => ['allthread@local'], - }, - 'somethread1' => { - subject => 'somethread', - keywords => { - '$flagged' => JSON::true, - }, - messageId => ['somethread@local'], - }, - 'somethread2' => { - subject => 're: somethread', - references => ['somethread@local'], - }, - 'nonethread1' => { - subject => 'nonethread', - messageId => ['nonethread@local'], - }, - 'nonethread2' => { - subject => 're: nonethread', - references => ['nonethread@local'], - }, - ); + xlog $self, "create emails"; + my %emails = ( + 'allthread1' => { + subject => 'allthread', + keywords => { + '$flagged' => JSON::true, + }, + messageId => ['allthread@local'], + }, + 'allthread2' => { + subject => 're: allthread', + keywords => { + '$flagged' => JSON::true, + }, + references => ['allthread@local'], + }, + 'somethread1' => { + subject => 'somethread', + keywords => { + '$flagged' => JSON::true, + }, + messageId => ['somethread@local'], + }, + 'somethread2' => { + subject => 're: somethread', + references => ['somethread@local'], + }, + 'nonethread1' => { + subject => 'nonethread', + messageId => ['nonethread@local'], + }, + 'nonethread2' => { + subject => 're: nonethread', + references => ['nonethread@local'], + }, + ); - while (my ($key, $val) = each %emails) { - my $email = { - mailboxIds => { - $inbox => JSON::true, + while (my ($key, $val) = each %emails) { + my $email = { + mailboxIds => { + $inbox => JSON::true, + }, + from => [ { + name => '', + email => 'from@local' + } ], + to => [ { + name => '', + email => 'to@local' + } ], + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }; + $email = { %$email, %$val }; + $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + $key => $email, }, - from => [{ - name => '', email => 'from@local' - }], - to => [{ - name => '', email => 'to@local' - }], - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }; - $email = { %$email, %$val }; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - $key => $email, - }, - }, 'R1'], - ], $using); - $self->assert_not_null($res->[0][1]->{created}{$key}{id}); - $val->{id} = $res->[0][1]->{created}{$key}{id}; - $self->assert_not_null($res->[0][1]->{created}{$key}{threadId}); - $val->{threadId} = $res->[0][1]->{created}{$key}{threadId}; - } + }, + 'R1' + ], + ], + $using + ); + $self->assert_not_null($res->[0][1]->{created}{$key}{id}); + $val->{id} = $res->[0][1]->{created}{$key}{id}; + $self->assert_not_null($res->[0][1]->{created}{$key}{threadId}); + $val->{threadId} = $res->[0][1]->{created}{$key}{threadId}; + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Running query with guidsearch"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => 'test', - allInThreadHaveKeyword => '$flagged', - }, - sort => [{ - property => 'id', - }], - }, 'R1'], - ['Email/query', { - filter => { - body => 'test', - someInThreadHaveKeyword => '$flagged', - }, - sort => [{ - property => 'id', - }], - }, 'R2'], - ['Email/query', { - filter => { - body => 'test', - noneInThreadHaveKeyword => '$flagged', - }, - sort => [{ - property => 'id', - }], - }, 'R3'], - ], $using); + xlog "Running query with guidsearch"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + body => 'test', + allInThreadHaveKeyword => '$flagged', + }, + sort => [ { + property => 'id', + } ], + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + body => 'test', + someInThreadHaveKeyword => '$flagged', + }, + sort => [ { + property => 'id', + } ], + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + body => 'test', + noneInThreadHaveKeyword => '$flagged', + }, + sort => [ { + property => 'id', + } ], + }, + 'R3' + ], + ], + $using + ); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - my @wantIds = sort $emails{allthread1}{id}, $emails{allthread2}{id}; - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + my @wantIds = sort $emails{allthread1}{id}, $emails{allthread2}{id}; + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isGuidSearch}); - @wantIds = sort $emails{somethread1}{id}, $emails{somethread2}{id}, - $emails{allthread1}{id}, $emails{allthread2}{id}; - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isGuidSearch}); + @wantIds = sort $emails{somethread1}{id}, $emails{somethread2}{id}, + $emails{allthread1}{id}, $emails{allthread2}{id}; + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - @wantIds = sort ($emails{nonethread1}{id}, $emails{nonethread2}{id}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + @wantIds = sort ($emails{nonethread1}{id}, $emails{nonethread2}{id}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_header b/cassandane/tiny-tests/JMAPEmail/email_query_header index b63023e247..6f4f2922b8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_header +++ b/cassandane/tiny-tests/JMAPEmail/email_query_header @@ -2,86 +2,100 @@ use Cassandane::Tiny; sub test_email_query_header - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions :NoMunge8Bit :RFC2047_UTF8 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions : NoMunge8Bit : RFC2047_UTF8 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); -use utf8; + use utf8; - $self->make_message("xhdr1", - extra_headers => [['X-hdr', 'val1'], ['X-hdr', 'val2']], - body => "xhdr1" - ) || die; - $self->make_message("xhdr2", - extra_headers => [['X-hdr', 'val1']], - body => "xhdr2" - ) || die; - $self->make_message("xhdr3", - extra_headers => [['X-hdr', " s\xc3\xa4ge "]], - body => "xhdr3" - ) || die; - $self->make_message("subject1", - body => "subject1" - ) || die; + $self->make_message( + "xhdr1", + extra_headers => [ [ 'X-hdr', 'val1' ], [ 'X-hdr', 'val2' ] ], + body => "xhdr1" + ) || die; + $self->make_message( + "xhdr2", + extra_headers => [ [ 'X-hdr', 'val1' ] ], + body => "xhdr2" + ) || die; + $self->make_message( + "xhdr3", + extra_headers => [ [ 'X-hdr', " s\xc3\xa4ge " ] ], + body => "xhdr3" + ) || die; + $self->make_message("subject1", body => "subject1") || die; - xlog "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-Z'); + xlog "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-Z'); - my $res = $jmap->CallMethods([ - ['Email/query', { - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'subject' ], - }, 'R2'], - ]); - my %id = map { $_->{subject} => $_->{id} } @{$res->[1][1]{list}}; - - my @testCases = ({ - desc => 'xhdr equals', - header => ['x-hdr', 'val2', 'equals'], - wantIds => [$id{'xhdr1'}], - }, { - desc => 'xhdr startsWith', - header => ['x-hdr', 'val', 'startsWith'], - wantIds => [$id{'xhdr1'}, $id{'xhdr2'}], - }, { - desc => 'xhdr endsWith', - header => ['x-hdr', 'al1', 'endsWith'], - wantIds => [$id{'xhdr1'}, $id{'xhdr2'}], - }, { - desc => 'xhdr contains', - header => ['x-hdr', 'al', 'contains'], - wantIds => [$id{'xhdr1'}, $id{'xhdr2'}], - }, { - desc => 'xhdr contains utf8 value', - header => ['x-hdr', 'SaGE', 'contains'], - wantIds => [$id{'xhdr3'}], - }, { - desc => 'subject contains ASCII', - header => ['subject', 'ubjec', 'contains'], - wantIds => [$id{'subject1'}], - }); + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['subject'], + }, + 'R2' + ], + ]); + my %id = map { $_->{subject} => $_->{id} } @{ $res->[1][1]{list} }; - foreach (@testCases) { - xlog "Running test: $_->{desc}"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - header => $_->{header}, - }, - sort => [{ property => 'subject' }], - }, 'R1'], - ]); - $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + my @testCases = ( + { + desc => 'xhdr equals', + header => [ 'x-hdr', 'val2', 'equals' ], + wantIds => [ $id{'xhdr1'} ], + }, + { + desc => 'xhdr startsWith', + header => [ 'x-hdr', 'val', 'startsWith' ], + wantIds => [ $id{'xhdr1'}, $id{'xhdr2'} ], + }, + { + desc => 'xhdr endsWith', + header => [ 'x-hdr', 'al1', 'endsWith' ], + wantIds => [ $id{'xhdr1'}, $id{'xhdr2'} ], + }, + { + desc => 'xhdr contains', + header => [ 'x-hdr', 'al', 'contains' ], + wantIds => [ $id{'xhdr1'}, $id{'xhdr2'} ], + }, + { + desc => 'xhdr contains utf8 value', + header => [ 'x-hdr', 'SaGE', 'contains' ], + wantIds => [ $id{'xhdr3'} ], + }, + { + desc => 'subject contains ASCII', + header => [ 'subject', 'ubjec', 'contains' ], + wantIds => [ $id{'subject1'} ], } + ); + + foreach (@testCases) { + xlog "Running test: $_->{desc}"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + header => $_->{header}, + }, + sort => [ { property => 'subject' } ], + }, + 'R1' + ], + ]); + $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + } -no utf8; + no utf8; } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_header_cost b/cassandane/tiny-tests/JMAPEmail/email_query_header_cost index dbb9e22c2a..9aa3073777 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_header_cost +++ b/cassandane/tiny-tests/JMAPEmail/email_query_header_cost @@ -2,41 +2,51 @@ use Cassandane::Tiny; sub test_email_query_header_cost - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions :NoMunge8Bit :RFC2047_UTF8 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions : NoMunge8Bit : RFC2047_UTF8 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $self->make_message() || die; + $self->make_message() || die; - xlog "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-Z'); + xlog "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-Z'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - header => ['x-hdr', 'foo', 'contains'], - }, - }, 'R1'], - ['Email/query', { - filter => { - header => ['subject', 'foo', 'contains'], - }, - }, 'R2'], - ], $using); - $self->assert_deep_equals(['body'], - $res->[0][1]{performance}{details}{filters}); - $self->assert_deep_equals(['cache'], - $res->[1][1]{performance}{details}{filters}); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + header => [ 'x-hdr', 'foo', 'contains' ], + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + header => [ 'subject', 'foo', 'contains' ], + }, + }, + 'R2' + ], + ], + $using + ); + $self->assert_deep_equals(['body'], + $res->[0][1]{performance}{details}{filters}); + $self->assert_deep_equals(['cache'], + $res->[1][1]{performance}{details}{filters}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_header_sieve b/cassandane/tiny-tests/JMAPEmail/email_query_header_sieve index 528b314753..2f6352fd9c 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_header_sieve +++ b/cassandane/tiny-tests/JMAPEmail/email_query_header_sieve @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_email_query_header_sieve - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions :AltNamespace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions : AltNamespace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $imap->create("matches") or die; + $imap->create("matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -25,28 +25,28 @@ if fileinto "matches"; } EOF - ); + ); - xlog "Deliver matching message"; - my $msg1 = $self->{gen}->generate( - subject => 'xxxyyyzzz', - body => "msg1" - ); - $self->{instance}->deliver($msg1); + xlog "Deliver matching message"; + my $msg1 = $self->{gen}->generate( + subject => 'xxxyyyzzz', + body => "msg1" + ); + $self->{instance}->deliver($msg1); - xlog "Assert that message got moved into INBOX.matches"; - $self->{store}->set_folder('matches'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog "Assert that message got moved into INBOX.matches"; + $self->{store}->set_folder('matches'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Deliver a non-matching message"; - my $msg2 = $self->{gen}->generate( - subject => 'zzzyyyyxxx', - body => "msg2" - ); - $self->{instance}->deliver($msg2); - $msg2->set_attribute(uid => 1); + xlog $self, "Deliver a non-matching message"; + my $msg2 = $self->{gen}->generate( + subject => 'zzzyyyyxxx', + body => "msg2" + ); + $self->{instance}->deliver($msg2); + $msg2->set_attribute(uid => 1); - xlog "Assert that message got moved into INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg2 }, check_guid => 0); + xlog "Assert that message got moved into INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg2 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_highpriority b/cassandane/tiny-tests/JMAPEmail/email_query_highpriority index 6c1f233b5b..26950ea823 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_highpriority +++ b/cassandane/tiny-tests/JMAPEmail/email_query_highpriority @@ -2,79 +2,100 @@ use Cassandane::Tiny; sub test_email_query_highpriority - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog "Append emails with and without priority"; - $self->make_message("msg1", - extra_headers => [['x-priority', '1']], - body => "msg1" - ) || die; - $self->make_message("msg2", - extra_headers => [['importance', 'high']], - body => "msg2" - ) || die; - $self->make_message("msg3", - body => "msg3" - ) || die; + xlog "Append emails with and without priority"; + $self->make_message( + "msg1", + extra_headers => [ [ 'x-priority', '1' ] ], + body => "msg1" + ) || die; + $self->make_message( + "msg2", + extra_headers => [ [ 'importance', 'high' ] ], + body => "msg2" + ) || die; + $self->make_message("msg3", body => "msg3") || die; - xlog "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-Z'); + xlog "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-Z'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ - property => 'subject', - }], - }, 'R1'], - ], $using); - my @ids = @{$res->[0][1]{ids}}; - $self->assert_num_equals(3, scalar @ids); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + sort => [ { + property => 'subject', + } ], + }, + 'R1' + ], + ], + $using + ); + my @ids = @{ $res->[0][1]{ids} }; + $self->assert_num_equals(3, scalar @ids); - xlog "Query isHighPriority"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - isHighPriority => JSON::true, - }, - sort => [{ - property => 'subject', - }], - }, 'R1'], - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [{ - isHighPriority => JSON::true, - }], - }, - sort => [{ - property => 'subject', - }], - }, 'R2'], - ['Email/query', { - filter => { - isHighPriority => JSON::false, - }, - sort => [{ - property => 'subject', - }], - }, 'R3'], - ], $using); - $self->assert_deep_equals([$ids[0], $ids[1]], $res->[0][1]{ids}); - $self->assert_deep_equals([$ids[2]], $res->[1][1]{ids}); - $self->assert_deep_equals([$ids[2]], $res->[2][1]{ids}); + xlog "Query isHighPriority"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + isHighPriority => JSON::true, + }, + sort => [ { + property => 'subject', + } ], + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { + isHighPriority => JSON::true, + } ], + }, + sort => [ { + property => 'subject', + } ], + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + isHighPriority => JSON::false, + }, + sort => [ { + property => 'subject', + } ], + }, + 'R3' + ], + ], + $using + ); + $self->assert_deep_equals([ $ids[0], $ids[1] ], $res->[0][1]{ids}); + $self->assert_deep_equals([ $ids[2] ], $res->[1][1]{ids}); + $self->assert_deep_equals([ $ids[2] ], $res->[2][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_inmailbox_null b/cassandane/tiny-tests/JMAPEmail/email_query_inmailbox_null index 8fd0ec6ead..5dbc169b0c 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_inmailbox_null +++ b/cassandane/tiny-tests/JMAPEmail/email_query_inmailbox_null @@ -2,19 +2,19 @@ use Cassandane::Tiny; sub test_email_query_inmailbox_null - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); - xlog $self, "generating email A"; - $self->make_message("Email A") or die; + xlog $self, "generating email A"; + $self->make_message("Email A") or die; - xlog $self, "call Email/query with null inMailbox"; - my $res = $jmap->CallMethods([['Email/query', { filter => { inMailbox => undef } }, "R1"]]); - $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); + xlog $self, "call Email/query with null inMailbox"; + my $res = $jmap->CallMethods( + [ [ 'Email/query', { filter => { inMailbox => undef } }, "R1" ] ]); + $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_inmailboxid_conjunction b/cassandane/tiny-tests/JMAPEmail/email_query_inmailboxid_conjunction index 1e1d8f3bb5..6cf3a96832 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_inmailboxid_conjunction +++ b/cassandane/tiny-tests/JMAPEmail/email_query_inmailboxid_conjunction @@ -2,148 +2,180 @@ use Cassandane::Tiny; sub test_email_query_inmailboxid_conjunction - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; - $imap->create("INBOX.B") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $mboxByName{'A'}->{id}; - my $mboxIdB = $mboxByName{'B'}->{id}; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; + $imap->create("INBOX.B") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $mboxByName{'A'}->{id}; + my $mboxIdB = $mboxByName{'B'}->{id}; - xlog $self, "create emails"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'mAB' => { - mailboxIds => { - $mboxIdA => JSON::true, - $mboxIdB => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'AB', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mA' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'A', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mB' => { - mailboxIds => { - $mboxIdB => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'B', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog $self, "create emails"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'mAB' => { + mailboxIds => { + $mboxIdA => JSON::true, + $mboxIdB => JSON::true, }, - }, 'R1'], - ]); - my $emailIdAB = $res->[0][1]->{created}{mAB}{id}; - $self->assert_not_null($emailIdAB); - my $emailIdA = $res->[0][1]->{created}{mA}{id}; - $self->assert_not_null($emailIdA); - my $emailIdB = $res->[0][1]->{created}{mB}{id}; - $self->assert_not_null($emailIdB); + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'AB', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mA' => { + mailboxIds => { + $mboxIdA => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'A', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mB' => { + mailboxIds => { + $mboxIdB => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'B', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailIdAB = $res->[0][1]->{created}{mAB}{id}; + $self->assert_not_null($emailIdAB); + my $emailIdA = $res->[0][1]->{created}{mA}{id}; + $self->assert_not_null($emailIdA); + my $emailIdB = $res->[0][1]->{created}{mB}{id}; + $self->assert_not_null($emailIdB); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "query emails in mailboxes A AND B"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - inMailbox => $mboxIdA, - }, { - inMailbox => $mboxIdB, - }], - }, - disableGuidSearch => JSON::true, - }, 'R1'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailIdAB, $res->[0][1]->{ids}[0]); + xlog $self, "query emails in mailboxes A AND B"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + inMailbox => $mboxIdA, + }, + { + inMailbox => $mboxIdB, + } + ], + }, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailIdAB, $res->[0][1]->{ids}[0]); - xlog $self, "query emails in mailboxes A AND B (forcing indexed search)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - inMailbox => $mboxIdA, - }, { - inMailbox => $mboxIdB, - }, { - text => "test", - }], - }, - disableGuidSearch => JSON::true, - }, 'R1'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailIdAB, $res->[0][1]->{ids}[0]); + xlog $self, "query emails in mailboxes A AND B (forcing indexed search)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + inMailbox => $mboxIdA, + }, + { + inMailbox => $mboxIdB, + }, + { + text => "test", + } + ], + }, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailIdAB, $res->[0][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_inmailboxotherthan b/cassandane/tiny-tests/JMAPEmail/email_query_inmailboxotherthan index bfe916e13f..eaf1839925 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_inmailboxotherthan +++ b/cassandane/tiny-tests/JMAPEmail/email_query_inmailboxotherthan @@ -2,115 +2,149 @@ use Cassandane::Tiny; sub test_email_query_inmailboxotherthan - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - xlog $self, "create mailboxes"; - $talk->create("INBOX.A") || die; - $talk->create("INBOX.B") || die; - $talk->create("INBOX.C") || die; + xlog $self, "create mailboxes"; + $talk->create("INBOX.A") || die; + $talk->create("INBOX.B") || die; + $talk->create("INBOX.C") || die; - $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $m{"A"}->{id}; - my $mboxIdB = $m{"B"}->{id}; - my $mboxIdC = $m{"C"}->{id}; - $self->assert_not_null($mboxIdA); - $self->assert_not_null($mboxIdB); - $self->assert_not_null($mboxIdC); + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $m{"A"}->{id}; + my $mboxIdB = $m{"B"}->{id}; + my $mboxIdC = $m{"C"}->{id}; + $self->assert_not_null($mboxIdA); + $self->assert_not_null($mboxIdB); + $self->assert_not_null($mboxIdC); - xlog $self, "create emails"; - $store->set_folder("INBOX.A"); - $res = $self->make_message("email1") || die; - $talk->copy(1, "INBOX.B") || die; - $talk->copy(1, "INBOX.C") || die; + xlog $self, "create emails"; + $store->set_folder("INBOX.A"); + $res = $self->make_message("email1") || die; + $talk->copy(1, "INBOX.B") || die; + $talk->copy(1, "INBOX.C") || die; - $store->set_folder("INBOX.B"); - $self->make_message("email2") || die; + $store->set_folder("INBOX.B"); + $self->make_message("email2") || die; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "fetch emails without filter"; - $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - } - }, 'R2'], - ], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_num_equals(2, scalar @{$res->[1][1]->{list}}); + xlog $self, "fetch emails without filter"; + $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + } + }, + 'R2' + ], + ], + $using + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_num_equals(2, scalar @{ $res->[1][1]->{list} }); - %m = map { $_->{subject} => $_ } @{$res->[1][1]{list}}; - my $emailId1 = $m{"email1"}->{id}; - my $emailId2 = $m{"email2"}->{id}; - $self->assert_not_null($emailId1); - $self->assert_not_null($emailId2); + %m = map { $_->{subject} => $_ } @{ $res->[1][1]{list} }; + my $emailId1 = $m{"email1"}->{id}; + my $emailId2 = $m{"email2"}->{id}; + $self->assert_not_null($emailId1); + $self->assert_not_null($emailId2); - $res = $jmap->CallMethods([['Email/query', { + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - inMailboxOtherThan => [$mboxIdB], + inMailboxOtherThan => [$mboxIdB], }, - sort => [{ property => 'subject' }], + sort => [ { property => 'subject' } ], disableGuidSearch => JSON::true, - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); - $res = $jmap->CallMethods([['Email/query', { + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - inMailboxOtherThan => [$mboxIdA], + inMailboxOtherThan => [$mboxIdA], }, - sort => [{ property => 'subject' }], + sort => [ { property => 'subject' } ], disableGuidSearch => JSON::true, - }, "R1"]], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[1]); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[1]); - $res = $jmap->CallMethods([['Email/query', { + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - inMailboxOtherThan => [$mboxIdA, $mboxIdC], + inMailboxOtherThan => [ $mboxIdA, $mboxIdC ], }, - sort => [{ property => 'subject' }], + sort => [ { property => 'subject' } ], disableGuidSearch => JSON::true, - }, "R1"]], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[1]); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[1]); - $res = $jmap->CallMethods([['Email/query', { + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { filter => { - operator => 'NOT', - conditions => [{ - inMailboxOtherThan => [$mboxIdB], - }], + operator => 'NOT', + conditions => [ { + inMailboxOtherThan => [$mboxIdB], + } ], }, - sort => [{ property => 'subject' }], + sort => [ { property => 'subject' } ], disableGuidSearch => JSON::true, - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[0]); + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_issue2905 b/cassandane/tiny-tests/JMAPEmail/email_query_issue2905 index 05ca170f5f..84c6ddb864 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_issue2905 +++ b/cassandane/tiny-tests/JMAPEmail/email_query_issue2905 @@ -2,112 +2,129 @@ use Cassandane::Tiny; sub test_email_query_issue2905 - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPQueryCacheMaxAge1s -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPQueryCacheMaxAge1s { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "create emails"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "email1", - keywords => { - '$flagged' => JSON::true - }, - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email1 body", - }, - }, - }, - email2 => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => q{foo2@bar} }], - to => [{ email => q{bar2@foo} }], - subject => "email2", - keywords => { - '$flagged' => JSON::true - }, - bodyStructure => { - partId => '2', - }, - bodyValues => { - "2" => { - value => "email2 body", - }, - }, - }, + xlog $self, "create emails"; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true }, - }, 'R1'], - ]); - my $emailId1 = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId1); - my $emailId2 = $res->[0][1]{created}{email2}{id}; - $self->assert_not_null($emailId2); - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - $res = $jmap->CallMethods([ - # Run query with mutable search - ['Email/query', { - filter => { - hasKeyword => '$flagged', + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "email1", + keywords => { + '$flagged' => JSON::true + }, + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email1 body", + }, + }, + }, + email2 => { + mailboxIds => { + '$inbox' => JSON::true }, - }, 'R1'], - # Remove $flagged keyword from email2 - ['Email/set', { - update => { - $emailId2 => { - 'keywords/$flagged' => undef, - }, + from => [ { email => q{foo2@bar} } ], + to => [ { email => q{bar2@foo} } ], + subject => "email2", + keywords => { + '$flagged' => JSON::true }, - }, 'R2'], - # Re-run query with mutable search - ['Email/query', { - filter => { - hasKeyword => '$flagged', + bodyStructure => { + partId => '2', }, - }, 'R3'], - ]); + bodyValues => { + "2" => { + value => "email2 body", + }, + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailId1 = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId1); + my $emailId2 = $res->[0][1]{created}{email2}{id}; + $self->assert_not_null($emailId2); - # Assert first query. - my $queryState = $res->[0][1]->{queryState}; - $self->assert_not_null($queryState); - $self->assert_equals(JSON::false, $res->[0][1]->{canCalculateChanges}); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - # Assert email update. - $self->assert(exists $res->[1][1]->{updated}{$emailId2}); + $res = $jmap->CallMethods([ + # Run query with mutable search + [ + 'Email/query', + { + filter => { + hasKeyword => '$flagged', + }, + }, + 'R1' + ], + # Remove $flagged keyword from email2 + [ + 'Email/set', + { + update => { + $emailId2 => { + 'keywords/$flagged' => undef, + }, + }, + }, + 'R2' + ], + # Re-run query with mutable search + [ + 'Email/query', + { + filter => { + hasKeyword => '$flagged', + }, + }, + 'R3' + ], + ]); - # Assert second query. - $self->assert_str_not_equals($queryState, $res->[2][1]->{queryState}); - $self->assert_equals(JSON::false, $res->[2][1]->{canCalculateChanges}); + # Assert first query. + my $queryState = $res->[0][1]->{queryState}; + $self->assert_not_null($queryState); + $self->assert_equals(JSON::false, $res->[0][1]->{canCalculateChanges}); - $res = $jmap->CallMethods([ - ['Email/queryChanges', { - sinceQueryState => $queryState, - filter => { - hasKeyword => '$flagged', - }, - }, 'R1'] - ]); + # Assert email update. + $self->assert(exists $res->[1][1]->{updated}{$emailId2}); + + # Assert second query. + $self->assert_str_not_equals($queryState, $res->[2][1]->{queryState}); + $self->assert_equals(JSON::false, $res->[2][1]->{canCalculateChanges}); + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sinceQueryState => $queryState, + filter => { + hasKeyword => '$flagged', + }, + }, + 'R1' + ] ]); - # Assert queryChanges error. - $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); + # Assert queryChanges error. + $self->assert_str_equals('cannotCalculateChanges', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_keywords b/cassandane/tiny-tests/JMAPEmail/email_query_keywords index 2c60183a8e..5fca4da81b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_keywords +++ b/cassandane/tiny-tests/JMAPEmail/email_query_keywords @@ -2,99 +2,132 @@ use Cassandane::Tiny; sub test_email_query_keywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - xlog $self, "create email"; - $res = $self->make_message("foo") || die; + xlog $self, "create email"; + $res = $self->make_message("foo") || die; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "fetch emails without filter"; - $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $fooid = $res->[0][1]->{ids}[0]; + xlog $self, "fetch emails without filter"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ], ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $fooid = $res->[0][1]->{ids}[0]; - xlog $self, "fetch emails with \$seen flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - hasKeyword => '$seen', - } - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); + xlog $self, "fetch emails with \$seen flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + hasKeyword => '$seen', + } + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); - xlog $self, "fetch emails without \$seen flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - notKeyword => '$seen', - } - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); + xlog $self, "fetch emails without \$seen flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + notKeyword => '$seen', + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); - xlog $self, 'set $seen flag on email'; - $res = $jmap->CallMethods([['Email/set', { - update => { - $fooid => { - keywords => { '$seen' => JSON::true }, - }, - } - }, "R1"]]); - $self->assert(exists $res->[0][1]->{updated}{$fooid}); + xlog $self, 'set $seen flag on email'; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $fooid => { + keywords => { '$seen' => JSON::true }, + }, + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]->{updated}{$fooid}); - xlog $self, "fetch emails with \$seen flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - hasKeyword => '$seen', - } - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); + xlog $self, "fetch emails with \$seen flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + hasKeyword => '$seen', + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); - xlog $self, "fetch emails without \$seen flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - notKeyword => '$seen', - } - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); + xlog $self, "fetch emails without \$seen flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + notKeyword => '$seen', + } + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); - xlog $self, "create email"; - $res = $self->make_message("bar") || die; + xlog $self, "create email"; + $res = $self->make_message("bar") || die; - xlog $self, "fetch emails without \$seen flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - notKeyword => '$seen', - } - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $barid = $res->[0][1]->{ids}[0]; - $self->assert_str_not_equals($fooid, $barid); + xlog $self, "fetch emails without \$seen flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + notKeyword => '$seen', + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $barid = $res->[0][1]->{ids}[0]; + $self->assert_str_not_equals($fooid, $barid); - xlog $self, "fetch emails sorted ascending by \$seen flag"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => 'hasKeyword', keyword => '$seen' }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($barid, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($fooid, $res->[0][1]->{ids}[1]); + xlog $self, "fetch emails sorted ascending by \$seen flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { property => 'hasKeyword', keyword => '$seen' } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($barid, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($fooid, $res->[0][1]->{ids}[1]); - xlog $self, "fetch emails sorted descending by \$seen flag"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => 'hasKeyword', keyword => '$seen', isAscending => JSON::false }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($fooid, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($barid, $res->[0][1]->{ids}[1]); + xlog $self, "fetch emails sorted descending by \$seen flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => 'hasKeyword', + keyword => '$seen', + isAscending => JSON::false + } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($fooid, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($barid, $res->[0][1]->{ids}[1]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_language b/cassandane/tiny-tests/JMAPEmail/email_query_language index 0b281c5138..47119c192c 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_language +++ b/cassandane/tiny-tests/JMAPEmail/email_query_language @@ -2,33 +2,33 @@ use Cassandane::Tiny; sub test_email_query_language - :min_version_3_3 :needs_component_jmap :JMAPExtensions - :needs_component_sieve :SearchLanguage :needs_dependency_cld2 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : JMAPExtensions + : needs_component_sieve : SearchLanguage : needs_dependency_cld2 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; -use utf8; + use utf8; - my @testEmailBodies = ({ - id => 'de', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => <<'EOF' + my @testEmailBodies = ( + { + id => 'de', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => <<'EOF' Jemand mußte Josef K. verleumdet haben, denn ohne daß er etwas Böses getan hätte, wurde er eines Morgens verhaftet. Die Köchin der Frau Grubach, seiner Zimmervermieterin, die ihm jeden Tag gegen acht Uhr früh das @@ -36,17 +36,18 @@ Frühstück brachte, kam diesmal nicht. Das war noch niemals geschehen. K. wartete noch ein Weilchen, sah von seinem Kopfkissen aus die alte Frau die ihm gegenüber wohnte und die ihn mit einer an ihr ganz ungewöhnli EOF - }, - }, - }, { - id => 'en', - bodyStructure => { - type => 'text/plain', - partId => 'part1', }, - bodyValues => { - part1 => { - value => <<'EOF' + }, + }, + { + id => 'en', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => <<'EOF' All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood. Everyone has the right to life, liberty and security @@ -54,161 +55,211 @@ of person. No one shall be held in slavery or servitude; slavery and the slave trade shall be prohibited in all their forms. No one shall be subjected to torture or to cruel, inhuman or degrading treatment or punishment. EOF - }, - }, - }, { - id => 'fr', - bodyStructure => { - type => 'text/plain', - partId => 'part1', }, - bodyValues => { - part1 => { - value => <<'EOF' + }, + }, + { + id => 'fr', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => <<'EOF' Hé quoi ! charmante Élise, vous devenez mélancolique, après les obligeantes assurances que vous avez eu la bonté de me donner de votre foi ? Je vous vois soupirer, hélas ! au milieu de ma joie ! Est-ce du regret, dites-moi, de m'avoir fait heureux ? et vous repentez-vous de cet engagement où mes feux ont pu vous contraindre ? EOF - }, - }, - }, { - id => 'fr-and-de', - bodyStructure => { - type => 'multipart/mixed', - subParts => [{ - type => 'text/plain', - partId => 'part1', - }, { - type => 'text/plain', - partId => 'part2', - }], }, - bodyValues => { - part1 => { - value => <<'EOF' + }, + }, + { + id => 'fr-and-de', + bodyStructure => { + type => 'multipart/mixed', + subParts => [ + { + type => 'text/plain', + partId => 'part1', + }, + { + type => 'text/plain', + partId => 'part2', + } + ], + }, + bodyValues => { + part1 => { + value => <<'EOF' Non, Valère, je ne puis pas me repentir de tout ce que je fais pour vous. Je m'y sens entraîner par une trop douce puissance, et je n'ai pas même la force de souhaiter que les choses ne fussent pas. Mais, a vous dire vrai, le succès me donne de l'inquiétude ; et je crains fort de vous aimer un peu plus que je ne devrais. EOF - }, - part2 => { - value => <<'EOF' + }, + part2 => { + value => <<'EOF' Pfingsten, das liebliche Fest, war gekommen! es grünten und blühten Feld und Wald; auf Hügeln und Höhn, in Büschen und Hecken Übten ein fröhliches Lied die neuermunterten Vögel; Jede Wiese sproßte von Blumen in duftenden Gründen, Festlich heiter glänzte der Himmel und farbig die Erde. EOF - }, }, - }); + }, + } + ); -no utf8; + no utf8; - my %emailIds; - foreach (@testEmailBodies) { - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - $_->{id} => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => 'foo@local' }], - to => [{ email => 'bar@local' }], - subject => $_->{id}, - bodyStructure => $_->{bodyStructure}, - bodyValues => $_->{bodyValues}, - }, + my %emailIds; + foreach (@testEmailBodies) { + my $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + $_->{id} => { + mailboxIds => { + '$inbox' => JSON::true }, - }, 'R1'], - ], $using); - $emailIds{$_->{id}} = $res->[0][1]{created}{$_->{id}}{id}; - $self->assert_not_null($emailIds{$_->{id}}); - } + from => [ { email => 'foo@local' } ], + to => [ { email => 'bar@local' } ], + subject => $_->{id}, + bodyStructure => $_->{bodyStructure}, + bodyValues => $_->{bodyValues}, + }, + }, + }, + 'R1' + ], + ], + $using + ); + $emailIds{ $_->{id} } = $res->[0][1]{created}{ $_->{id} }{id}; + $self->assert_not_null($emailIds{ $_->{id} }); + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + language => 'fr', + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + operator => 'OR', + conditions => [ + { + language => 'de', + }, + { language => 'fr', - }, - }, 'R1'], - ['Email/query', { - filter => { - operator => 'OR', - conditions => [{ - language => 'de', - }, { - language => 'fr', - }], - }, - }, 'R2'], - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - language => 'de', - }, { - language => 'fr', - }], - }, - }, 'R3'], - ['Email/query', { - filter => { - language => 'en', - }, - }, 'R4'], - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [{ - language => 'de', - }], - }, - }, 'R5'], - ['Email/query', { - filter => { - language => 'chr', - }, - }, 'R6'], - ['Email/query', { - filter => { - language => 'xxxx', - }, - }, 'R7'], - ], $using); + } + ], + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + language => 'de', + }, + { + language => 'fr', + } + ], + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + language => 'en', + }, + }, + 'R4' + ], + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { + language => 'de', + } ], + }, + }, + 'R5' + ], + [ + 'Email/query', + { + filter => { + language => 'chr', + }, + }, + 'R6' + ], + [ + 'Email/query', + { + filter => { + language => 'xxxx', + }, + }, + 'R7' + ], + ], + $using + ); - # fr - my @wantIds = sort ($emailIds{'fr'}, $emailIds{'fr-and-de'}); - my @gotIds = sort @{$res->[0][1]->{ids}}; - $self->assert_deep_equals(\@wantIds, \@gotIds); + # fr + my @wantIds = sort ($emailIds{'fr'}, $emailIds{'fr-and-de'}); + my @gotIds = sort @{ $res->[0][1]->{ids} }; + $self->assert_deep_equals(\@wantIds, \@gotIds); - # OR de,fr - @wantIds = sort ($emailIds{'fr'}, $emailIds{'de'}, $emailIds{'fr-and-de'}); - @gotIds = sort @{$res->[1][1]->{ids}}; - $self->assert_deep_equals(\@wantIds, \@gotIds); + # OR de,fr + @wantIds = sort ($emailIds{'fr'}, $emailIds{'de'}, $emailIds{'fr-and-de'}); + @gotIds = sort @{ $res->[1][1]->{ids} }; + $self->assert_deep_equals(\@wantIds, \@gotIds); - # AND de,fr - $self->assert_deep_equals([$emailIds{'fr-and-de'}], $res->[2][1]->{ids}); + # AND de,fr + $self->assert_deep_equals([ $emailIds{'fr-and-de'} ], $res->[2][1]->{ids}); - # en - $self->assert_deep_equals([$emailIds{'en'}], $res->[3][1]->{ids}); + # en + $self->assert_deep_equals([ $emailIds{'en'} ], $res->[3][1]->{ids}); - # NOT de - @wantIds = sort ($emailIds{'en'}, $emailIds{'fr'}); - @gotIds = sort @{$res->[4][1]->{ids}}; - $self->assert_deep_equals(\@wantIds, \@gotIds); + # NOT de + @wantIds = sort ($emailIds{'en'}, $emailIds{'fr'}); + @gotIds = sort @{ $res->[4][1]->{ids} }; + $self->assert_deep_equals(\@wantIds, \@gotIds); - # chr - $self->assert_deep_equals([], $res->[5][1]->{ids}); + # chr + $self->assert_deep_equals([], $res->[5][1]->{ids}); - # xxxx - $self->assert_str_equals('invalidArguments', $res->[6][1]{type}); + # xxxx + $self->assert_str_equals('invalidArguments', $res->[6][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_language_french_contractions b/cassandane/tiny-tests/JMAPEmail/email_query_language_french_contractions index c4993d4f53..01c1d102e5 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_language_french_contractions +++ b/cassandane/tiny-tests/JMAPEmail/email_query_language_french_contractions @@ -2,84 +2,101 @@ use Cassandane::Tiny; sub test_email_query_language_french_contractions - :min_version_3_3 :needs_component_jmap :JMAPExtensions - :needs_component_sieve :SearchLanguage :needs_dependency_cld2 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : JMAPExtensions + : needs_component_sieve : SearchLanguage : needs_dependency_cld2 { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; -use utf8; + use utf8; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true - }, - subject => "fr", - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => <<'EOF' + my $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true + }, + subject => "fr", + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => <<'EOF' C'est dadaïste d'Amérique j’aime je l'aime Je m’appelle n’est pas là qu’il s’escrit Je t’aime. EOF - } - }, - }, + } + }, }, - }, 'R1'], - ], $using); - my $emailId = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId); + }, + }, + 'R1' + ], + ], + $using + ); + my $emailId = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId); -no utf8; + no utf8; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my @tests = ({ - body => "c'est", - wantIds => [$emailId], - }, { - body => "est", - wantIds => [$emailId], - }, { - body => "p'est", - wantIds => [], - }, { - body => "amerique", - wantIds => [$emailId], - }, { - body => "s'appelle", - wantIds => [$emailId], - }, { - body => "il", - wantIds => [$emailId], - }); - - foreach (@tests) { - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => $_->{body}, - }, - }, 'R1'], - ]); - $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + my @tests = ( + { + body => "c'est", + wantIds => [$emailId], + }, + { + body => "est", + wantIds => [$emailId], + }, + { + body => "p'est", + wantIds => [], + }, + { + body => "amerique", + wantIds => [$emailId], + }, + { + body => "s'appelle", + wantIds => [$emailId], + }, + { + body => "il", + wantIds => [$emailId], } + ); + + foreach (@tests) { + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + body => $_->{body}, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_language_stats b/cassandane/tiny-tests/JMAPEmail/email_query_language_stats index a0ca97d37d..a52108519f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_language_stats +++ b/cassandane/tiny-tests/JMAPEmail/email_query_language_stats @@ -2,27 +2,24 @@ use Cassandane::Tiny; sub test_email_query_language_stats - :min_version_3_1 :needs_component_jmap :needs_dependency_cld2 - :needs_component_sieve :SearchLanguage :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap : needs_dependency_cld2 + : needs_component_sieve : SearchLanguage : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $body = "" + my $body + = "" . "--boundary\r\n" . "Content-Type: text/plain;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" . "Hoch oben in den L=C3=BCften =C3=BCber den reichgesegneten Landschaften des\r\n" . "s=C3=BCdlichen Frankreichs schwebte eine gewaltige dunkle Kugel.\r\n" . "\r\n" . "Ein Luftballon war es, der, in der Nacht aufgefahren, eine lange\r\n" - . "Dauerfahrt antreten wollte.\r\n" - . "\r\n" + . "Dauerfahrt antreten wollte.\r\n" . "\r\n" . "--boundary\r\n" . "Content-Type: text/plain;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" . "The Bellman, who was almost morbidly sensitive about appearances, used\r\n" . "to have the bowsprit unshipped once or twice a week to be revarnished,\r\n" . "and it more than once happened, when the time came for replacing it,\r\n" @@ -30,8 +27,7 @@ sub test_email_query_language_stats . "\r\n" . "--boundary\r\n" . "Content-Type: text/plain;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" . "Verri=C3=A8res est abrit=C3=A9e du c=C3=B4t=C3=A9 du nord par une haute mon=\r\n" . "tagne, c'est une\r\n" . "des branches du Jura. Les cimes bris=C3=A9es du Verra se couvrent de neige\r\n" @@ -42,71 +38,81 @@ sub test_email_query_language_stats . "mouvement =C3=A0 un grand nombre de scies =C3=A0 bois; c'est une industrie =\r\n" . "--boundary--\r\n"; - $self->make_message("A multi-language email", - mime_type => "multipart/mixed", - mime_boundary => "boundary", - body => $body - ); + $self->make_message( + "A multi-language email", + mime_type => "multipart/mixed", + mime_boundary => "boundary", + body => $body + ); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1' ] - ], $using); - $self->assert_deep_equals({ - iso => { - de => 1, - fr => 1, - en => 1, - }, - unknown => 0, - }, $res->[0][1]{languageStats}); + my $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ] ], $using); + $self->assert_deep_equals( + { + iso => { + de => 1, + fr => 1, + en => 1, + }, + unknown => 0, + }, + $res->[0][1]{languageStats} + ); } + sub test_email_set_received_at - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => q{foo@bar} }], - to => [{ email => q{bar@foo} }], - receivedAt => '2019-05-02T03:15:00Z', - subject => "test", - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "A text body", - }, - }, - } + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => q{foo@bar} } ], + to => [ { email => q{bar@foo} } ], + receivedAt => '2019-05-02T03:15:00Z', + subject => "test", + bodyStructure => { + partId => '1', }, - }, 'R1'], - ['Email/get', { - ids => ['#email1'], - properties => ['receivedAt'], - }, 'R2'], - ]); - my $email = $res->[1][1]{list}[0]; - $self->assert_str_equals('2019-05-02T03:15:00Z', $email->{receivedAt}); + bodyValues => { + "1" => { + value => "A text body", + }, + }, + } + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => ['receivedAt'], + }, + 'R2' + ], + ]); + my $email = $res->[1][1]{list}[0]; + $self->assert_str_equals('2019-05-02T03:15:00Z', $email->{receivedAt}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_listid b/cassandane/tiny-tests/JMAPEmail/email_query_listid index b3376ffa41..2ea2b4d459 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_listid +++ b/cassandane/tiny-tests/JMAPEmail/email_query_listid @@ -2,108 +2,138 @@ use Cassandane::Tiny; sub test_email_query_listid - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog "Append emails with list-id"; - $self->make_message("msg1", # RFC 2919 - extra_headers => [['list-id', "Foo "]], - body => "msg1" - ) || die; - $self->make_message("msg2", # as seen at Yahoo, Google, et al - extra_headers => [['list-id', 'list aaa@bbb.ccc; contact aaa-contact@bbb.ccc']], - body => "msg2" - ) || die; - $self->make_message("msg3", # as seen from sentry, just plain text - extra_headers => [['list-id', 'sub3.sub2.sub1.top']], - body => "msg3" - ) || die; - $self->make_message("msg4", # as seen in the wild - extra_headers => [['list-id', '"foo" "msg4" - ) || die; - $self->make_message("msg5", # as seen in the wild - extra_headers => [['list-id', '1234567890 list "msg5" - ) || die; + xlog "Append emails with list-id"; + $self->make_message( + "msg1", # RFC 2919 + extra_headers => [ [ 'list-id', "Foo " ] ], + body => "msg1" + ) || die; + $self->make_message( + "msg2", # as seen at Yahoo, Google, et al + extra_headers => + [ [ 'list-id', 'list aaa@bbb.ccc; contact aaa-contact@bbb.ccc' ] ], + body => "msg2" + ) || die; + $self->make_message( + "msg3", # as seen from sentry, just plain text + extra_headers => [ [ 'list-id', 'sub3.sub2.sub1.top' ] ], + body => "msg3" + ) || die; + $self->make_message( + "msg4", # as seen in the wild + extra_headers => [ [ 'list-id', '"foo" "msg4" + ) || die; + $self->make_message( + "msg5", # as seen in the wild + extra_headers => [ [ 'list-id', '1234567890 list "msg5" + ) || die; - xlog "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-Z'); + xlog "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-Z'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ - property => 'subject', - }], - }, 'R1'], - ], $using); - my @ids = @{$res->[0][1]{ids}}; - $self->assert_num_equals(5, scalar @ids); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + sort => [ { + property => 'subject', + } ], + }, + 'R1' + ], + ], + $using + ); + my @ids = @{ $res->[0][1]{ids} }; + $self->assert_num_equals(5, scalar @ids); - my @testCases = ({ - desc => 'simple list-id', - listId => 'xxx.yyy.zzz', - wantIds => [$ids[0], $ids[3], $ids[4]], - }, { - desc => 'no substring search for list-id', - listId => 'yyy', - wantIds => [], - }, { - desc => 'no wildcard search for list-id', - listId => 'xxx.yyy.*', - wantIds => [], - }, { - desc => 'no substring search for list-id #2', - listId => 'foo', - wantIds => [], - }, { - desc => 'ignore whitespace', - listId => 'xxx . yyy . zzz', - wantIds => [$ids[0], $ids[3], $ids[4]], - }, { - desc => 'Groups-style list-id', - listId => 'aaa@bbb.ccc', - wantIds => [$ids[1]], - }, { - desc => 'Ignore contact in groups-style list-id', - listId => 'aaa-contact@bbb.ccc', - wantIds => [], - }, { - desc => 'Groups-style list-id with whitespace', - listId => 'aaa @ bbb . ccc', - wantIds => [$ids[1]], - }, { - desc => 'Also no substring search in groups-style list-id', - listId => 'aaa', - wantIds => [], - }, { - desc => 'unbracketed list-id', - listId => 'sub3.sub2.sub1.top', - wantIds => [$ids[2]], - }); - - foreach (@testCases) { - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - listId => $_->{listId}, - }, - sort => [{ property => 'subject' }], - }, 'R1'], - ], $using); - $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + my @testCases = ( + { + desc => 'simple list-id', + listId => 'xxx.yyy.zzz', + wantIds => [ $ids[0], $ids[3], $ids[4] ], + }, + { + desc => 'no substring search for list-id', + listId => 'yyy', + wantIds => [], + }, + { + desc => 'no wildcard search for list-id', + listId => 'xxx.yyy.*', + wantIds => [], + }, + { + desc => 'no substring search for list-id #2', + listId => 'foo', + wantIds => [], + }, + { + desc => 'ignore whitespace', + listId => 'xxx . yyy . zzz', + wantIds => [ $ids[0], $ids[3], $ids[4] ], + }, + { + desc => 'Groups-style list-id', + listId => 'aaa@bbb.ccc', + wantIds => [ $ids[1] ], + }, + { + desc => 'Ignore contact in groups-style list-id', + listId => 'aaa-contact@bbb.ccc', + wantIds => [], + }, + { + desc => 'Groups-style list-id with whitespace', + listId => 'aaa @ bbb . ccc', + wantIds => [ $ids[1] ], + }, + { + desc => 'Also no substring search in groups-style list-id', + listId => 'aaa', + wantIds => [], + }, + { + desc => 'unbracketed list-id', + listId => 'sub3.sub2.sub1.top', + wantIds => [ $ids[2] ], } + ); + + foreach (@testCases) { + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + listId => $_->{listId}, + }, + sort => [ { property => 'subject' } ], + }, + 'R1' + ], + ], + $using + ); + $self->assert_deep_equals($_->{wantIds}, $res->[0][1]{ids}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_long b/cassandane/tiny-tests/JMAPEmail/email_query_long index 685189956d..e56bdfcfc8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_long +++ b/cassandane/tiny-tests/JMAPEmail/email_query_long @@ -2,47 +2,54 @@ use Cassandane::Tiny; sub test_email_query_long - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); - for (1..100) { - $self->make_message("Email $_"); - } + for (1 .. 100) { + $self->make_message("Email $_"); + } - xlog $self, "list first 60 emails"; - $res = $jmap->CallMethods([['Email/query', { - limit => 60, - position => 0, - collapseThreads => JSON::true, - sort => [{ property => "id" }], - calculateTotal => JSON::true, - }, "R1"]]); - $self->assert_num_equals(60, scalar @{$res->[0][1]->{ids}}); - $self->assert_num_equals(100, $res->[0][1]->{total}); - $self->assert_num_equals(0, $res->[0][1]->{position}); + xlog $self, "list first 60 emails"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + limit => 60, + position => 0, + collapseThreads => JSON::true, + sort => [ { property => "id" } ], + calculateTotal => JSON::true, + }, + "R1" + ] ]); + $self->assert_num_equals(60, scalar @{ $res->[0][1]->{ids} }); + $self->assert_num_equals(100, $res->[0][1]->{total}); + $self->assert_num_equals(0, $res->[0][1]->{position}); - xlog $self, "list 5 emails from offset 55 by anchor"; - $res = $jmap->CallMethods([['Email/query', { - limit => 5, - anchorOffset => 1, - anchor => $res->[0][1]->{ids}[55], - collapseThreads => JSON::true, - sort => [{ property => "id" }], - calculateTotal => JSON::true, - }, "R1"]]); - $self->assert_num_equals(5, scalar @{$res->[0][1]->{ids}}); - $self->assert_num_equals(100, $res->[0][1]->{total}); - $self->assert_num_equals(56, $res->[0][1]->{position}); + xlog $self, "list 5 emails from offset 55 by anchor"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + limit => 5, + anchorOffset => 1, + anchor => $res->[0][1]->{ids}[55], + collapseThreads => JSON::true, + sort => [ { property => "id" } ], + calculateTotal => JSON::true, + }, + "R1" + ] ]); + $self->assert_num_equals(5, scalar @{ $res->[0][1]->{ids} }); + $self->assert_num_equals(100, $res->[0][1]->{total}); + $self->assert_num_equals(56, $res->[0][1]->{position}); - my $ids = $res->[0][1]->{ids}; - my @subids; + my $ids = $res->[0][1]->{ids}; + my @subids; } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_mailbox_andor b/cassandane/tiny-tests/JMAPEmail/email_query_mailbox_andor index a6517dff55..45817c314f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_mailbox_andor +++ b/cassandane/tiny-tests/JMAPEmail/email_query_mailbox_andor @@ -2,84 +2,107 @@ use Cassandane::Tiny; sub test_email_query_mailbox_andor - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, - mboxC => { - name => 'C', - }, - } - }, 'R1'], - ['Email/set', { - create => { - emailAB => { - mailboxIds => { - '#mboxA' => JSON::true, - '#mboxB' => JSON::true, - }, - subject => 'emailAB', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'emailAB', - } - }, - }, + my $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', }, - }, 'R2'], - ], $using); - my $mboxA = $res->[0][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[0][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - my $mboxC = $res->[0][1]{created}{mboxC}{id}; - $self->assert_not_null($mboxC); - my $emailId = $res->[1][1]{created}{emailAB}{id}; - $self->assert_not_null($emailId); - - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - inMailbox => $mboxA, - }, { - operator => 'OR', - conditions => [{ - inMailbox => $mboxB, - }, { - inMailbox => $mboxC, - }], - }], + mboxB => { + name => 'B', + }, + mboxC => { + name => 'C', }, - }, 'R1'], - ], $using); + } + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + emailAB => { + mailboxIds => { + '#mboxA' => JSON::true, + '#mboxB' => JSON::true, + }, + subject => 'emailAB', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'emailAB', + } + }, + }, + }, + }, + 'R2' + ], + ], + $using + ); + my $mboxA = $res->[0][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[0][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + my $mboxC = $res->[0][1]{created}{mboxC}{id}; + $self->assert_not_null($mboxC); + my $emailId = $res->[1][1]{created}{emailAB}{id}; + $self->assert_not_null($emailId); + + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + inMailbox => $mboxA, + }, + { + operator => 'OR', + conditions => [ + { + inMailbox => $mboxB, + }, + { + inMailbox => $mboxC, + } + ], + } + ], + }, + }, + 'R1' + ], + ], + $using + ); - $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); - $self->assert_equals(JSON::true, - $res->[0][1]{performance}{details}{isImapFolderSearch}); + $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isImapFolderSearch}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_moved b/cassandane/tiny-tests/JMAPEmail/email_query_moved index 37265614c0..fcac1ba56d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_moved +++ b/cassandane/tiny-tests/JMAPEmail/email_query_moved @@ -2,148 +2,183 @@ use Cassandane::Tiny; sub test_email_query_moved - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; - $imap->create("INBOX.B") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $mboxByName{'A'}->{id}; - my $mboxIdB = $mboxByName{'B'}->{id}; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; + $imap->create("INBOX.B") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $mboxByName{'A'}->{id}; + my $mboxIdB = $mboxByName{'B'}->{id}; - xlog $self, "create emails in mailbox A"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'msg1' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'message 1', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog $self, "create emails in mailbox A"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'msg1' => { + mailboxIds => { + $mboxIdA => JSON::true, }, - }, 'R1'], - ['Email/set', { - create => { - 'msg2' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'message 2', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'message 1', + bodyStructure => { + type => 'text/plain', + partId => 'part1', }, - }, 'R2'], - ]); - my $emailId1 = $res->[0][1]->{created}{msg1}{id}; - $self->assert_not_null($emailId1); - my $emailId2 = $res->[1][1]->{created}{msg2}{id}; - $self->assert_not_null($emailId2); + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + 'msg2' => { + mailboxIds => { + $mboxIdA => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'message 2', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R2' + ], + ]); + my $emailId1 = $res->[0][1]->{created}{msg1}{id}; + $self->assert_not_null($emailId1); + my $emailId2 = $res->[1][1]->{created}{msg2}{id}; + $self->assert_not_null($emailId2); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "query emails"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - inMailbox => $mboxIdA, - text => 'message', - }, - sort => [{ - property => 'subject', - isAscending => JSON::true, - }], - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[1]); + xlog $self, "query emails"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + inMailbox => $mboxIdA, + text => 'message', + }, + sort => [ { + property => 'subject', + isAscending => JSON::true, + } ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($emailId2, $res->[0][1]->{ids}[1]); - xlog $self, "move msg2 to mailbox B"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId2 => { - mailboxIds => { - $mboxIdB => JSON::true, - }, - }, + xlog $self, "move msg2 to mailbox B"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId2 => { + mailboxIds => { + $mboxIdB => JSON::true, }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId2}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$emailId2}); - xlog $self, "assert move"; - $res = $jmap->CallMethods([ - ['Email/get', { - ids => [$emailId1, $emailId2], - properties => ['mailboxIds'], - }, 'R1'], - ]); - $self->assert_str_equals($emailId1, $res->[0][1]{list}[0]{id}); - my $wantMailboxIds1 = { $mboxIdA => JSON::true }; - $self->assert_deep_equals($wantMailboxIds1, $res->[0][1]{list}[0]{mailboxIds}); + xlog $self, "assert move"; + $res = $jmap->CallMethods([ + [ + 'Email/get', + { + ids => [ $emailId1, $emailId2 ], + properties => ['mailboxIds'], + }, + 'R1' + ], + ]); + $self->assert_str_equals($emailId1, $res->[0][1]{list}[0]{id}); + my $wantMailboxIds1 = { $mboxIdA => JSON::true }; + $self->assert_deep_equals($wantMailboxIds1, + $res->[0][1]{list}[0]{mailboxIds}); - $self->assert_str_equals($emailId2, $res->[0][1]{list}[1]{id}); - my $wantMailboxIds2 = { $mboxIdB => JSON::true }; - $self->assert_deep_equals($wantMailboxIds2, $res->[0][1]{list}[1]{mailboxIds}); + $self->assert_str_equals($emailId2, $res->[0][1]{list}[1]{id}); + my $wantMailboxIds2 = { $mboxIdB => JSON::true }; + $self->assert_deep_equals($wantMailboxIds2, + $res->[0][1]{list}[1]{mailboxIds}); - xlog $self, "query emails"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - inMailbox => $mboxIdA, - text => 'message', - }, - }, 'R1'], - ['Email/query', { - filter => { - inMailbox => $mboxIdB, - text => 'message', - }, - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); - $self->assert_num_equals(1, scalar @{$res->[1][1]->{ids}}); - $self->assert_str_equals($emailId2, $res->[1][1]->{ids}[0]); + xlog $self, "query emails"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + inMailbox => $mboxIdA, + text => 'message', + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxIdB, + text => 'message', + }, + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]->{ids}[0]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]->{ids} }); + $self->assert_str_equals($emailId2, $res->[1][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_multiple_to_cross_domain b/cassandane/tiny-tests/JMAPEmail/email_query_multiple_to_cross_domain index 131c6809fd..f73c8a7003 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_multiple_to_cross_domain +++ b/cassandane/tiny-tests/JMAPEmail/email_query_multiple_to_cross_domain @@ -2,54 +2,63 @@ use Cassandane::Tiny; sub test_email_query_multiple_to_cross_domain - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $account = undef; - my $store = $self->{store}; - my $mboxprefix = "INBOX"; - my $talk = $store->get_client(); - - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => $account }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; - - xlog $self, "create email1"; - my $msg1 = { - mailboxIds => { $inboxid => JSON::true }, - subject => 'msg1', - to => [ - { name => undef, email => "foo\@example.com" }, - { name => undef, email => "bar\@example.net" } - ] - }; - - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $msg1 }}, "R1"]]); - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "fetch emails without filter"; - $res = $jmap->CallMethods([ - ['Email/query', { accountId => $account }, 'R1'], - ['Email/get', { - accountId => $account, - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => [ 'subject', 'mailboxIds', 'to' ], - }, 'R2'], - ]); - - my %m = map { $_->{subject} => $_ } @{$res->[1][1]{list}}; - my $emailId1 = $m{"msg1"}->{id}; - $self->assert_not_null($emailId1); - - xlog $self, "filter to with mixed localpart and domain"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - to => 'foo@example.net' - } - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $account = undef; + my $store = $self->{store}; + my $mboxprefix = "INBOX"; + my $talk = $store->get_client(); + + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { accountId => $account }, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; + + xlog $self, "create email1"; + my $msg1 = { + mailboxIds => { $inboxid => JSON::true }, + subject => 'msg1', + to => [ + { name => undef, email => "foo\@example.com" }, + { name => undef, email => "bar\@example.net" } + ] + }; + + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $msg1 } }, "R1" ] ]); + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "fetch emails without filter"; + $res = $jmap->CallMethods([ + [ 'Email/query', { accountId => $account }, 'R1' ], + [ + 'Email/get', + { + accountId => $account, + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ 'subject', 'mailboxIds', 'to' ], + }, + 'R2' + ], + ]); + + my %m = map { $_->{subject} => $_ } @{ $res->[1][1]{list} }; + my $emailId1 = $m{"msg1"}->{id}; + $self->assert_not_null($emailId1); + + xlog $self, "filter to with mixed localpart and domain"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + to => 'foo@example.net' + } + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_negative_position b/cassandane/tiny-tests/JMAPEmail/email_query_negative_position index ced855dde6..7d2545beaa 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_negative_position +++ b/cassandane/tiny-tests/JMAPEmail/email_query_negative_position @@ -2,115 +2,158 @@ use Cassandane::Tiny; sub test_email_query_negative_position - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPQueryCacheMaxAge1s :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPQueryCacheMaxAge1s : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Creating emails"; - foreach my $i (1..9) { - $self->make_message("test") || die; - } + xlog "Creating emails"; + foreach my $i (1 .. 9) { + $self->make_message("test") || die; + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query emails"; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => 'id' }], - }, 'R1'], - ]); - my @emailIds = @{$res->[0][1]{ids}}; - $self->assert_num_equals(9, scalar @emailIds); + xlog "Query emails"; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { property => 'id' } ], + }, + 'R1' + ], + ]); + my @emailIds = @{ $res->[0][1]{ids} }; + $self->assert_num_equals(9, scalar @emailIds); - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'https://cyrusimap.org/ns/jmap/debug', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'https://cyrusimap.org/ns/jmap/debug', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; - xlog "Query with negative position (in range)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -3, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -3, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R2'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -3, - limit => 2, - disableGuidSearch => JSON::false, - }, 'R3'], - ], $using); - my @wantIds = @emailIds[6..7]; - # Check UID search - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isCached}); - $self->assert_num_equals(6, $res->[0][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); + xlog "Query with negative position (in range)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -3, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -3, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -3, + limit => 2, + disableGuidSearch => JSON::false, + }, + 'R3' + ], + ], + $using + ); + my @wantIds = @emailIds[ 6 .. 7 ]; + # Check UID search + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isCached}); + $self->assert_num_equals(6, $res->[0][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isCached}); - $self->assert_num_equals(6, $res->[1][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - # Check GUID search - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_num_equals(6, $res->[2][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isCached}); + $self->assert_num_equals(6, $res->[1][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + # Check GUID search + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_num_equals(6, $res->[2][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); - xlog "Create dummy message to invalidate query cache"; - $self->make_message("dummy") || die; + xlog "Create dummy message to invalidate query cache"; + $self->make_message("dummy") || die; - xlog "Query with negative position (out of range)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -100, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -100, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R2'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -100, - limit => 2, - disableGuidSearch => JSON::false, - }, 'R3'], - ], $using); - @wantIds = @emailIds[0..1]; - # Check UID search - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isCached}); - $self->assert_num_equals(0, $res->[0][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isCached}); - $self->assert_num_equals(0, $res->[1][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - # Check GUID search - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_num_equals(0, $res->[2][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + xlog "Query with negative position (out of range)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -100, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -100, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -100, + limit => 2, + disableGuidSearch => JSON::false, + }, + 'R3' + ], + ], + $using + ); + @wantIds = @emailIds[ 0 .. 1 ]; + # Check UID search + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isCached}); + $self->assert_num_equals(0, $res->[0][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isCached}); + $self->assert_num_equals(0, $res->[1][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + # Check GUID search + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_num_equals(0, $res->[2][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_negative_position_legacy b/cassandane/tiny-tests/JMAPEmail/email_query_negative_position_legacy index 5f09942bd9..7580f96a2a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_negative_position_legacy +++ b/cassandane/tiny-tests/JMAPEmail/email_query_negative_position_legacy @@ -2,115 +2,158 @@ use Cassandane::Tiny; sub test_email_query_negative_position_legacy - :min_version_3_1 :max_version_3_4 :needs_component_sieve - :needs_component_jmap :JMAPSearchDBLegacy :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : max_version_3_4 : needs_component_sieve + : needs_component_jmap : JMAPSearchDBLegacy : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Creating emails"; - foreach my $i (1..9) { - $self->make_message("test") || die; - } + xlog "Creating emails"; + foreach my $i (1 .. 9) { + $self->make_message("test") || die; + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query emails"; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => 'id' }], - }, 'R1'], - ]); - my @emailIds = @{$res->[0][1]{ids}}; - $self->assert_num_equals(9, scalar @emailIds); + xlog "Query emails"; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { property => 'id' } ], + }, + 'R1' + ], + ]); + my @emailIds = @{ $res->[0][1]{ids} }; + $self->assert_num_equals(9, scalar @emailIds); - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'https://cyrusimap.org/ns/jmap/debug', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'https://cyrusimap.org/ns/jmap/debug', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; - xlog "Query with negative position (in range)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -3, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -3, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R2'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -3, - limit => 2, - disableGuidSearch => JSON::false, - }, 'R3'], - ], $using); - my @wantIds = @emailIds[6..7]; - # Check UID search - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isCached}); - $self->assert_num_equals(6, $res->[0][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); + xlog "Query with negative position (in range)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -3, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -3, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -3, + limit => 2, + disableGuidSearch => JSON::false, + }, + 'R3' + ], + ], + $using + ); + my @wantIds = @emailIds[ 6 .. 7 ]; + # Check UID search + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isCached}); + $self->assert_num_equals(6, $res->[0][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isCached}); - $self->assert_num_equals(6, $res->[1][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - # Check GUID search - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_num_equals(6, $res->[2][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isCached}); + $self->assert_num_equals(6, $res->[1][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + # Check GUID search + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_num_equals(6, $res->[2][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); - xlog "Create dummy message to invalidate query cache"; - $self->make_message("dummy") || die; + xlog "Create dummy message to invalidate query cache"; + $self->make_message("dummy") || die; - xlog "Query with negative position (out of range)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -100, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -100, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R2'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => -100, - limit => 2, - disableGuidSearch => JSON::false, - }, 'R3'], - ], $using); - @wantIds = @emailIds[0..1]; - # Check UID search - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isCached}); - $self->assert_num_equals(0, $res->[0][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isCached}); - $self->assert_num_equals(0, $res->[1][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - # Check GUID search - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_num_equals(0, $res->[2][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + xlog "Query with negative position (out of range)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -100, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -100, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => -100, + limit => 2, + disableGuidSearch => JSON::false, + }, + 'R3' + ], + ], + $using + ); + @wantIds = @emailIds[ 0 .. 1 ]; + # Check UID search + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isCached}); + $self->assert_num_equals(0, $res->[0][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isCached}); + $self->assert_num_equals(0, $res->[1][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + # Check GUID search + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_num_equals(0, $res->[2][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_no_guidsearch_ignore_jmapuploads b/cassandane/tiny-tests/JMAPEmail/email_query_no_guidsearch_ignore_jmapuploads index cc0727f801..266b4db0c8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_no_guidsearch_ignore_jmapuploads +++ b/cassandane/tiny-tests/JMAPEmail/email_query_no_guidsearch_ignore_jmapuploads @@ -2,85 +2,101 @@ use Cassandane::Tiny; sub test_email_query_no_guidsearch_ignore_jmapuploads - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $store = $self->{store}; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $store = $self->{store}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "Create Trash mailbox"; - $imap->create("Trash", "(USE (\\Trash))") || die; + xlog $self, "Create Trash mailbox"; + $imap->create("Trash", "(USE (\\Trash))") || die; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sort => [{ - property => 'name', - }], - }, 'R1'], - ['Mailbox/get', { - '#ids' => { - resultOf => 'R1', - name => 'Mailbox/query', - path => '/ids', - }, - properties => ['name'], - }, 'R2'], - ], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); + $res = $jmap->CallMethods( + [ + [ + 'Mailbox/query', + { + sort => [ { + property => 'name', + } ], + }, + 'R1' + ], + [ + 'Mailbox/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Mailbox/query', + path => '/ids', + }, + properties => ['name'], + }, + 'R2' + ], + ], + $using + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); - my $inboxId = $res->[0][1]{ids}[0]; - my $trashId = $res->[0][1]{ids}[1]; + my $inboxId = $res->[0][1]{ids}[0]; + my $trashId = $res->[0][1]{ids}[1]; - xlog $self, "Create message in Inbox"; - $self->make_message('wantThisOne', body => 'blu blu'); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ], $using); - my $wantEmailId = $res->[0][1]{ids}[0]; - $self->assert_not_null($wantEmailId); + xlog $self, "Create message in Inbox"; + $self->make_message('wantThisOne', body => 'blu blu'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ], ], $using); + my $wantEmailId = $res->[0][1]{ids}[0]; + $self->assert_not_null($wantEmailId); - xlog $self, "Create message that exists both in Trash and #jmap"; - my $admin = $self->{adminstore}->get_client(); - $jmap->Upload('someblob', "text/plain"); - $store->set_folder('Trash'); - $self->make_message('dontWantThisOne', body => 'blu blu'); - $admin->select('user.cassandane.Trash'); - $admin->copy('1', 'user.cassandane.#jmap'); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Create message that exists both in Trash and #jmap"; + my $admin = $self->{adminstore}->get_client(); + $jmap->Upload('someblob', "text/plain"); + $store->set_folder('Trash'); + $self->make_message('dontWantThisOne', body => 'blu blu'); + $admin->select('user.cassandane.Trash'); + $admin->copy('1', 'user.cassandane.#jmap'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "Query emails exluding Trash"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - text => 'blu', - }, { - inMailboxOtherThan => [$trashId], - }], - }, - sort => [ - { - "isAscending" => JSON::false, - "property" => "receivedAt" - } + xlog $self, "Query emails exluding Trash"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + text => 'blu', + }, + { + inMailboxOtherThan => [$trashId], + } ], - disableGuidSearch => JSON::true, - }, 'R1'], - ], $using); + }, + sort => [ { + "isAscending" => JSON::false, + "property" => "receivedAt" + } ], + disableGuidSearch => JSON::true, + }, + 'R1' + ], + ], + $using + ); - xlog $self, "Assert that message from #jmap folder is not found"; - $self->assert_deep_equals([$wantEmailId], $res->[0][1]{ids}); + xlog $self, "Assert that message from #jmap folder is not found"; + $self->assert_deep_equals([$wantEmailId], $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_not_match b/cassandane/tiny-tests/JMAPEmail/email_query_not_match index fbbf62a952..04aab168bf 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_not_match +++ b/cassandane/tiny-tests/JMAPEmail/email_query_not_match @@ -2,149 +2,172 @@ use Cassandane::Tiny; sub test_email_query_not_match - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "mboxA" => { - name => "A", - }, - "mboxB" => { - name => "B", - }, - "mboxC" => { - name => "C", - }, - } - }, "R1"] - ]); - my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; - my $mboxIdB = $res->[0][1]{created}{mboxB}{id}; - my $mboxIdC = $res->[0][1]{created}{mboxC}{id}; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "mboxA" => { + name => "A", + }, + "mboxB" => { + name => "B", + }, + "mboxC" => { + name => "C", + }, + } + }, + "R1" + ] ]); + my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; + my $mboxIdB = $res->[0][1]{created}{mboxB}{id}; + my $mboxIdC = $res->[0][1]{created}{mboxC}{id}; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - $mboxIdA => JSON::true - }, - from => [{ email => q{foo1@bar} }], - to => [{ email => q{bar1@foo} }], - subject => "email1", - keywords => { - keyword1 => JSON::true - }, - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email1 body", - }, - }, - }, - email2 => { - mailboxIds => { - $mboxIdB => JSON::true - }, - from => [{ email => q{foo2@bar} }], - to => [{ email => q{bar2@foo} }], - subject => "email2", - bodyStructure => { - partId => '2', - }, - bodyValues => { - "2" => { - value => "email2 body", - }, - }, - }, - email3 => { - mailboxIds => { - $mboxIdC => JSON::true - }, - from => [{ email => q{foo3@bar} }], - to => [{ email => q{bar3@foo} }], - subject => "email3", - bodyStructure => { - partId => '3', - }, - bodyValues => { - "3" => { - value => "email3 body", - }, - }, - } + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + $mboxIdA => JSON::true + }, + from => [ { email => q{foo1@bar} } ], + to => [ { email => q{bar1@foo} } ], + subject => "email1", + keywords => { + keyword1 => JSON::true + }, + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email1 body", + }, + }, + }, + email2 => { + mailboxIds => { + $mboxIdB => JSON::true + }, + from => [ { email => q{foo2@bar} } ], + to => [ { email => q{bar2@foo} } ], + subject => "email2", + bodyStructure => { + partId => '2', + }, + bodyValues => { + "2" => { + value => "email2 body", + }, }, - }, 'R1'], - ]); - my $emailId1 = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId1); - my $emailId2 = $res->[0][1]{created}{email2}{id}; - $self->assert_not_null($emailId2); - my $emailId3 = $res->[0][1]{created}{email3}{id}; - $self->assert_not_null($emailId3); + }, + email3 => { + mailboxIds => { + $mboxIdC => JSON::true + }, + from => [ { email => q{foo3@bar} } ], + to => [ { email => q{bar3@foo} } ], + subject => "email3", + bodyStructure => { + partId => '3', + }, + bodyValues => { + "3" => { + value => "email3 body", + }, + }, + } + }, + }, + 'R1' + ], + ]); + my $emailId1 = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId1); + my $emailId2 = $res->[0][1]{created}{email2}{id}; + $self->assert_not_null($emailId2); + my $emailId3 = $res->[0][1]{created}{email3}{id}; + $self->assert_not_null($emailId3); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [{ - text => "email2", - }], - }, - sort => [{ property => "subject" }], - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); - $self->assert_str_equals($emailId3, $res->[0][1]{ids}[1]); + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { + text => "email2", + } ], + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); + $self->assert_str_equals($emailId3, $res->[0][1]{ids}[1]); - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - operator => 'NOT', - conditions => [{ - text => "email1" - }], - }, { - operator => 'NOT', - conditions => [{ - text => "email3" - }], - }], + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + operator => 'NOT', + conditions => [ { + text => "email1" + } ], }, - sort => [{ property => "subject" }], - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($emailId2, $res->[0][1]{ids}[0]); + { + operator => 'NOT', + conditions => [ { + text => "email3" + } ], + } + ], + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($emailId2, $res->[0][1]{ids}[0]); - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', - conditions => [{ - operator => 'NOT', - conditions => [{ - text => "email3" - }], - }, { - hasKeyword => 'keyword1', - }], + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + operator => 'NOT', + conditions => [ { + text => "email3" + } ], }, - sort => [{ property => "subject" }], - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); + { + hasKeyword => 'keyword1', + } + ], + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_str_equals($emailId1, $res->[0][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_notes b/cassandane/tiny-tests/JMAPEmail/email_query_notes index 17a9256fe4..9c476f1479 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_notes +++ b/cassandane/tiny-tests/JMAPEmail/email_query_notes @@ -3,30 +3,26 @@ use Cassandane::Tiny; use base qw(Cassandane::Cyrus::TestCase); sub test_email_query_notes - :min_version_3_1 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/notes' capability - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/notes'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/notes' capability + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/notes'; + $jmap->DefaultUsing(\@using); - # force creation of notes mailbox prior to creating notes - my $res = $jmap->CallMethods([ - ['Note/set', { - }, "R0"] - ]); + # force creation of notes mailbox prior to creating notes + my $res = $jmap->CallMethods([ [ 'Note/set', {}, "R0" ] ]); - xlog "create note"; - $res = $jmap->CallMethods([['Note/set', - { create => { "1" => {title => "foo"}, } }, - "R1"]]); - $self->assert_not_null($res); - my $note1 = $res->[0][1]{created}{"1"}{id}; + xlog "create note"; + $res = $jmap->CallMethods([ [ + 'Note/set', { create => { "1" => { title => "foo" }, } }, "R1" + ] ]); + $self->assert_not_null($res); + my $note1 = $res->[0][1]{created}{"1"}{id}; - xlog $self, "query for notes"; - $res = $jmap->CallMethods([['Email/query', { }, "R1"], ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{ids}}); + xlog $self, "query for notes"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ], ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_notinmailboxid_attached b/cassandane/tiny-tests/JMAPEmail/email_query_notinmailboxid_attached index 596afe1c5e..df6d228350 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_notinmailboxid_attached +++ b/cassandane/tiny-tests/JMAPEmail/email_query_notinmailboxid_attached @@ -2,213 +2,270 @@ use Cassandane::Tiny; sub test_email_query_notinmailboxid_attached - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; - $imap->create("INBOX.B") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $mboxByName{'A'}->{id}; - my $mboxIdB = $mboxByName{'B'}->{id}; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; + $imap->create("INBOX.B") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $mboxByName{'A'}->{id}; + my $mboxIdB = $mboxByName{'B'}->{id}; - xlog $self, "create emails"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'mA' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'covfefe@local' - }], - to => [{ - name => '', email => 'dest@local' - }], - subject => 'AB', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'this email contains xyzzy', - } - }, - }, - }, - }, 'R1'] - ]); + xlog $self, "create emails"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { + 'mA' => { + mailboxIds => { + $mboxIdA => JSON::true, + }, + from => [ { + name => '', + email => 'covfefe@local' + } ], + to => [ { + name => '', + email => 'dest@local' + } ], + subject => 'AB', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'this email contains xyzzy', + } + }, + }, + }, + }, + 'R1' + ] ]); - my $emailIdA = $res->[0][1]->{created}{mA}{id}; - my $blobA = $res->[0][1]{created}{mA}{blobId}; - $self->assert_not_null($emailIdA); - $self->assert_not_null($blobA); + my $emailIdA = $res->[0][1]->{created}{mA}{id}; + my $blobA = $res->[0][1]{created}{mA}{blobId}; + $self->assert_not_null($emailIdA); + $self->assert_not_null($blobA); - $res = $jmap->CallMethods([ - ['Email/set', { create => { mB => { - bcc => undef, + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + mB => { + bcc => undef, bodyStructure => { - subParts => [{ - partId => "text", - type => "text/plain" - },{ - blobId => $blobA, - disposition => "attachment", - type => "message/rfc822" - }], - type => "multipart/mixed", + subParts => [ + { + partId => "text", + type => "text/plain" + }, + { + blobId => $blobA, + disposition => "attachment", + type => "message/rfc822" + } + ], + type => "multipart/mixed", }, bodyValues => { - text => { - isTruncated => $JSON::false, - value => "Hello World", - }, + text => { + isTruncated => $JSON::false, + value => "Hello World", + }, }, - cc => undef, - from => [{ - email => "foo\@example.com", - name => "Captain Foo", - }], + cc => undef, + from => [ { + email => "foo\@example.com", + name => "Captain Foo", + } ], keywords => { - '$draft' => $JSON::true, - '$seen' => $JSON::true, + '$draft' => $JSON::true, + '$seen' => $JSON::true, }, mailboxIds => { - $mboxIdB => $JSON::true, + $mboxIdB => $JSON::true, }, - messageId => ["9048d4db-bd84-4ea4-9be3-ae4a136c532d\@example.com"], + messageId => ["9048d4db-bd84-4ea4-9be3-ae4a136c532d\@example.com"], receivedAt => "2019-05-09T12:48:08Z", references => undef, - replyTo => undef, - sentAt => "2019-05-09T14:48:08+02:00", - subject => "Hello again", - to => [{ - email => "bar\@example.com", - name => "Private Bar", - }], - }}}, "S1"], - ]); - my $emailIdB = $res->[0][1]->{created}{mB}{id}; + replyTo => undef, + sentAt => "2019-05-09T14:48:08+02:00", + subject => "Hello again", + to => [ { + email => "bar\@example.com", + name => "Private Bar", + } ], + } + } + }, + "S1" + ], + ]); + my $emailIdB = $res->[0][1]->{created}{mB}{id}; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Run queries"; - $res = $jmap->CallMethods([ - ['Email/query', { - }, 'R1'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - }, - }, 'R2'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - inMailbox => $mboxIdA, - }, - }, 'R3'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - inMailboxOtherThan => [$mboxIdA], - }, - }, 'R4'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - inMailbox => $mboxIdB, - }, - }, 'R5'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - inMailboxOtherThan => [$mboxIdB], - }, - }, 'R6'], - ]); + xlog "Run queries"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + inMailbox => $mboxIdA, + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + inMailboxOtherThan => [$mboxIdA], + }, + }, + 'R4' + ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + inMailbox => $mboxIdB, + }, + }, + 'R5' + ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + inMailboxOtherThan => [$mboxIdB], + }, + }, + 'R6' + ], + ]); - $self->assert_num_equals(2, scalar(@{$res->[0][1]{ids}})); - $self->assert_num_equals(1, scalar(@{$res->[1][1]{ids}})); - $self->assert_num_equals(1, scalar(@{$res->[2][1]{ids}})); - $self->assert_equals($emailIdA, $res->[2][1]{ids}[0]); - $self->assert_num_equals(0, scalar(@{$res->[3][1]{ids}})); - $self->assert_num_equals(0, scalar(@{$res->[4][1]{ids}})); - $self->assert_num_equals(1, scalar(@{$res->[5][1]{ids}})); - $self->assert_equals($emailIdA, $res->[5][1]{ids}[0]); + $self->assert_num_equals(2, scalar(@{ $res->[0][1]{ids} })); + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{ids} })); + $self->assert_num_equals(1, scalar(@{ $res->[2][1]{ids} })); + $self->assert_equals($emailIdA, $res->[2][1]{ids}[0]); + $self->assert_num_equals(0, scalar(@{ $res->[3][1]{ids} })); + $self->assert_num_equals(0, scalar(@{ $res->[4][1]{ids} })); + $self->assert_num_equals(1, scalar(@{ $res->[5][1]{ids} })); + $self->assert_equals($emailIdA, $res->[5][1]{ids}[0]); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog "Run queries with extra using"; - $res = $jmap->CallMethods([ - ['Email/query', { - }, 'R1'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - }, - }, 'R2'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - inMailbox => $mboxIdA, - }, - }, 'R3'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - inMailboxOtherThan => [$mboxIdA], - }, - }, 'R4'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - inMailbox => $mboxIdB, - }, - }, 'R5'], - ['Email/query', { - filter => { - from => 'covfefe', - text => 'xyzzy', - inMailboxOtherThan => [$mboxIdB], - }, - }, 'R6'], - ], $using); + xlog "Run queries with extra using"; + $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + inMailbox => $mboxIdA, + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + inMailboxOtherThan => [$mboxIdA], + }, + }, + 'R4' + ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + inMailbox => $mboxIdB, + }, + }, + 'R5' + ], + [ + 'Email/query', + { + filter => { + from => 'covfefe', + text => 'xyzzy', + inMailboxOtherThan => [$mboxIdB], + }, + }, + 'R6' + ], + ], + $using + ); - $self->assert_num_equals(2, scalar(@{$res->[0][1]{ids}})); - $self->assert_num_equals(1, scalar(@{$res->[1][1]{ids}})); - $self->assert_num_equals(1, scalar(@{$res->[2][1]{ids}})); - $self->assert_equals($emailIdA, $res->[2][1]{ids}[0]); - $self->assert_num_equals(0, scalar(@{$res->[3][1]{ids}})); - $self->assert_num_equals(0, scalar(@{$res->[4][1]{ids}})); - $self->assert_num_equals(1, scalar(@{$res->[5][1]{ids}})); - $self->assert_equals($emailIdA, $res->[5][1]{ids}[0]); + $self->assert_num_equals(2, scalar(@{ $res->[0][1]{ids} })); + $self->assert_num_equals(1, scalar(@{ $res->[1][1]{ids} })); + $self->assert_num_equals(1, scalar(@{ $res->[2][1]{ids} })); + $self->assert_equals($emailIdA, $res->[2][1]{ids}[0]); + $self->assert_num_equals(0, scalar(@{ $res->[3][1]{ids} })); + $self->assert_num_equals(0, scalar(@{ $res->[4][1]{ids} })); + $self->assert_num_equals(1, scalar(@{ $res->[5][1]{ids} })); + $self->assert_equals($emailIdA, $res->[5][1]{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_position b/cassandane/tiny-tests/JMAPEmail/email_query_position index e452ef765b..e292ce3ef0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_position +++ b/cassandane/tiny-tests/JMAPEmail/email_query_position @@ -2,116 +2,161 @@ use Cassandane::Tiny; sub test_email_query_position - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPQueryCacheMaxAge1s :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPQueryCacheMaxAge1s : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Creating emails"; - foreach my $i (1..9) { - $self->make_message("test") || die; - } + xlog "Creating emails"; + foreach my $i (1 .. 9) { + $self->make_message("test") || die; + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query emails"; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => 'id' }], - }, 'R1'], - ]); - my @emailIds = @{$res->[0][1]{ids}}; - $self->assert_num_equals(9, scalar @emailIds); + xlog "Query emails"; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { property => 'id' } ], + }, + 'R1' + ], + ]); + my @emailIds = @{ $res->[0][1]{ids} }; + $self->assert_num_equals(9, scalar @emailIds); - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'https://cyrusimap.org/ns/jmap/debug', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'https://cyrusimap.org/ns/jmap/debug', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; - xlog "Query with positive position (in range)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 1, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 1, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R2'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 1, - limit => 2, - disableGuidSearch => JSON::false, - }, 'R3'], - ], $using); - my @wantIds = @emailIds[1..2]; - # Check UID search - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isCached}); - $self->assert_num_equals(1, $res->[0][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isCached}); - $self->assert_num_equals(1, $res->[1][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - # Check GUID search - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[2][1]{performance}{details}{isCached}); - $self->assert_num_equals(1, $res->[2][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + xlog "Query with positive position (in range)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 1, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 1, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 1, + limit => 2, + disableGuidSearch => JSON::false, + }, + 'R3' + ], + ], + $using + ); + my @wantIds = @emailIds[ 1 .. 2 ]; + # Check UID search + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isCached}); + $self->assert_num_equals(1, $res->[0][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isCached}); + $self->assert_num_equals(1, $res->[1][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + # Check GUID search + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[2][1]{performance}{details}{isCached}); + $self->assert_num_equals(1, $res->[2][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); - xlog "Create dummy message to invalidate query cache"; - $self->make_message("dummy") || die; + xlog "Create dummy message to invalidate query cache"; + $self->make_message("dummy") || die; - xlog "Query with positive position (out of range)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 100, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 100, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R2'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 100, - limit => 2, - disableGuidSearch => JSON::false, - }, 'R3'], - ], $using); - @wantIds = (); - # Check UID search - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isCached}); - $self->assert_num_equals(9, $res->[0][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isCached}); - $self->assert_num_equals(9, $res->[1][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - # Check GUID search - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[2][1]{performance}{details}{isCached}); - $self->assert_num_equals(9, $res->[2][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + xlog "Query with positive position (out of range)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 100, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 100, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 100, + limit => 2, + disableGuidSearch => JSON::false, + }, + 'R3' + ], + ], + $using + ); + @wantIds = (); + # Check UID search + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isCached}); + $self->assert_num_equals(9, $res->[0][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isCached}); + $self->assert_num_equals(9, $res->[1][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + # Check GUID search + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[2][1]{performance}{details}{isCached}); + $self->assert_num_equals(9, $res->[2][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_position_legacy b/cassandane/tiny-tests/JMAPEmail/email_query_position_legacy index 7c2cd74789..23f572159a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_position_legacy +++ b/cassandane/tiny-tests/JMAPEmail/email_query_position_legacy @@ -2,116 +2,161 @@ use Cassandane::Tiny; sub test_email_query_position_legacy - :min_version_3_1 :max_version_3_4 :needs_component_jmap - :needs_component_sieve :JMAPSearchDBLegacy :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : max_version_3_4 : needs_component_jmap + : needs_component_sieve : JMAPSearchDBLegacy : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Creating emails"; - foreach my $i (1..9) { - $self->make_message("test") || die; - } + xlog "Creating emails"; + foreach my $i (1 .. 9) { + $self->make_message("test") || die; + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query emails"; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => 'id' }], - }, 'R1'], - ]); - my @emailIds = @{$res->[0][1]{ids}}; - $self->assert_num_equals(9, scalar @emailIds); + xlog "Query emails"; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { property => 'id' } ], + }, + 'R1' + ], + ]); + my @emailIds = @{ $res->[0][1]{ids} }; + $self->assert_num_equals(9, scalar @emailIds); - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'https://cyrusimap.org/ns/jmap/debug', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'https://cyrusimap.org/ns/jmap/debug', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; - xlog "Query with positive position (in range)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 1, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 1, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R2'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 1, - limit => 2, - disableGuidSearch => JSON::false, - }, 'R3'], - ], $using); - my @wantIds = @emailIds[1..2]; - # Check UID search - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isCached}); - $self->assert_num_equals(1, $res->[0][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isCached}); - $self->assert_num_equals(1, $res->[1][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - # Check GUID search - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[2][1]{performance}{details}{isCached}); - $self->assert_num_equals(1, $res->[2][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + xlog "Query with positive position (in range)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 1, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 1, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 1, + limit => 2, + disableGuidSearch => JSON::false, + }, + 'R3' + ], + ], + $using + ); + my @wantIds = @emailIds[ 1 .. 2 ]; + # Check UID search + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isCached}); + $self->assert_num_equals(1, $res->[0][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isCached}); + $self->assert_num_equals(1, $res->[1][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + # Check GUID search + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[2][1]{performance}{details}{isCached}); + $self->assert_num_equals(1, $res->[2][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); - xlog "Create dummy message to invalidate query cache"; - $self->make_message("dummy") || die; + xlog "Create dummy message to invalidate query cache"; + $self->make_message("dummy") || die; - xlog "Query with positive position (out of range)"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 100, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 100, - limit => 2, - disableGuidSearch => JSON::true, - }, 'R2'], - ['Email/query', { - filter => { subject => 'test' }, - sort => [{ property => 'id' }], - position => 100, - limit => 2, - disableGuidSearch => JSON::false, - }, 'R3'], - ], $using); - @wantIds = (); - # Check UID search - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[0][1]{performance}{details}{isCached}); - $self->assert_num_equals(9, $res->[0][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::true, $res->[1][1]{performance}{details}{isCached}); - $self->assert_num_equals(9, $res->[1][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); - # Check GUID search - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_equals(JSON::false, $res->[2][1]{performance}{details}{isCached}); - $self->assert_num_equals(9, $res->[2][1]{position}); - $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); + xlog "Query with positive position (out of range)"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 100, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 100, + limit => 2, + disableGuidSearch => JSON::true, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { subject => 'test' }, + sort => [ { property => 'id' } ], + position => 100, + limit => 2, + disableGuidSearch => JSON::false, + }, + 'R3' + ], + ], + $using + ); + @wantIds = (); + # Check UID search + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isCached}); + $self->assert_num_equals(9, $res->[0][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isCached}); + $self->assert_num_equals(9, $res->[1][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[1][1]{ids}); + # Check GUID search + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_equals(JSON::false, + $res->[2][1]{performance}{details}{isCached}); + $self->assert_num_equals(9, $res->[2][1]{position}); + $self->assert_deep_equals(\@wantIds, $res->[2][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_punct_no_text b/cassandane/tiny-tests/JMAPEmail/email_query_punct_no_text index f6c65b7c27..7622f3dd71 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_punct_no_text +++ b/cassandane/tiny-tests/JMAPEmail/email_query_punct_no_text @@ -2,17 +2,17 @@ use Cassandane::Tiny; sub test_email_query_punct_no_text - :needs_component_sieve :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); + : needs_component_sieve : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $imap = $self->{store}->get_client(); - $imap->create("matches") or die; + $imap->create("matches") or die; - # Assert that punctuation-only terms in non-text criteria - # match nothing. Also see email_query_utf8punct_term. + # Assert that punctuation-only terms in non-text criteria + # match nothing. Also see email_query_utf8punct_term. - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; # Search: "from:\"=\"" if allof( @@ -32,9 +32,9 @@ if allof( set "stop" "Y"; } EOF - ); + ); - my $mime = <<'EOF'; + my $mime = <<'EOF'; From: from@local To: to@local Subject: test @@ -45,12 +45,12 @@ Content-Transfer-Encoding: 7bit hello EOF - $mime =~ s/\r?\n/\r\n/gs; - my $msg = Cassandane::Message->new(); - $msg->set_lines(split /\n/, $mime); - $self->{instance}->deliver($msg); + $mime =~ s/\r?\n/\r\n/gs; + my $msg = Cassandane::Message->new(); + $msg->set_lines(split /\n/, $mime); + $self->{instance}->deliver($msg); - xlog "Assert that message did not match"; - $self->assert_num_equals(0, $imap->message_count('matches')); - $self->assert_num_equals(1, $imap->message_count('INBOX')); + xlog "Assert that message did not match"; + $self->assert_num_equals(0, $imap->message_count('matches')); + $self->assert_num_equals(1, $imap->message_count('INBOX')); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_seen_ignore_jmapupload_folder b/cassandane/tiny-tests/JMAPEmail/email_query_seen_ignore_jmapupload_folder index 05de5272c5..1902b4f4dc 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_seen_ignore_jmapupload_folder +++ b/cassandane/tiny-tests/JMAPEmail/email_query_seen_ignore_jmapupload_folder @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_email_query_seen_ignore_jmapupload_folder - :min_version_3_7 :needs_component_jmap :JMAPExtensions :MagicPlus :AllowDeleted -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : JMAPExtensions : MagicPlus : + AllowDeleted { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog 'Upload some blob to create upload folder'; - $jmap->Upload('test', 'application/octets') or die; + xlog 'Upload some blob to create upload folder'; + $jmap->Upload('test', 'application/octets') or die; - xlog 'Upload MIME message'; - my $mime = <<'EOF'; + xlog 'Upload MIME message'; + my $mime = <<'EOF'; From: 'Some Example Sender' To: baseball@vitaead.com Subject: test email @@ -22,58 +22,78 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $mime =~ s/\r?\n/\r\n/gs; - my $blobId = $jmap->Upload($mime, 'message/rfc822')->{blobId}; - $self->assert_not_null($blobId); + $mime =~ s/\r?\n/\r\n/gs; + my $blobId = $jmap->Upload($mime, 'message/rfc822')->{blobId}; + $self->assert_not_null($blobId); - xlog "Undelete blobs in #jmap folder"; - $self->{instance}->run_command({ cyrus => 1 }, - 'unexpunge', '-a', '-d', 'user.cassandane.#jmap'); + xlog "Undelete blobs in #jmap folder"; + $self->{instance}->run_command({ cyrus => 1 }, + 'unexpunge', '-a', '-d', 'user.cassandane.#jmap'); - xlog 'Import message'; - my $res = $jmap->CallMethods([ - ['Email/import', { - emails => { - email1 => { - blobId => $blobId, - mailboxIds => { - '$inbox' => JSON::true, - }, - keywords => { - '$seen' => JSON::true, - }, - } + xlog 'Import message'; + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { + emails => { + email1 => { + blobId => $blobId, + mailboxIds => { + '$inbox' => JSON::true, }, - }, 'R1'], - ]); - my $emailId = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($emailId); - - xlog 'Query email by $seen'; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - hasKeyword => '$seen', - }, - }, 'R1'], - ['Email/query', { - filter => { - allInThreadHaveKeyword => '$seen', - }, - }, 'R2'], - ['Email/query', { - filter => { - someInThreadHaveKeyword => '$seen', + keywords => { + '$seen' => JSON::true, }, - }, 'R2'], - ['Email/query', { - filter => { - noneInThreadHaveKeyword => '$seen', - }, - }, 'R4'], - ]); - $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[1][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[2][1]{ids}); - $self->assert_deep_equals([], $res->[3][1]{ids}); + } + }, + }, + 'R1' + ], + ]); + my $emailId = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($emailId); + + xlog 'Query email by $seen'; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + hasKeyword => '$seen', + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + allInThreadHaveKeyword => '$seen', + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + someInThreadHaveKeyword => '$seen', + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + noneInThreadHaveKeyword => '$seen', + }, + }, + 'R4' + ], + ]); + $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[1][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[2][1]{ids}); + $self->assert_deep_equals([], $res->[3][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_seen_multimbox b/cassandane/tiny-tests/JMAPEmail/email_query_seen_multimbox index 7d7763127e..986ef62331 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_seen_multimbox +++ b/cassandane/tiny-tests/JMAPEmail/email_query_seen_multimbox @@ -2,192 +2,271 @@ use Cassandane::Tiny; sub test_email_query_seen_multimbox - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog 'Create email in mailboxes A and B'; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, + xlog 'Create email in mailboxes A and B'; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + }, + mboxB => { + name => 'B', + }, + }, + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + email => { + mailboxIds => { + '#mboxA' => JSON::true, + '#mboxB' => JSON::true, }, - }, 'R1'], - ['Email/set', { - create => { - email => { - mailboxIds => { - '#mboxA' => JSON::true, - '#mboxB' => JSON::true, - }, - from => [{ - email => 'from@local' - }], - subject => 'test', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - }, - }, - }, + from => [ { + email => 'from@local' + } ], + subject => 'test', + bodyStructure => { + type => 'text/plain', + partId => 'part1', }, - }, 'R2'], - ]); - my $mboxA = $res->[0][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[0][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - my $emailId = $res->[1][1]{created}{email}{id}; - $self->assert_not_null($emailId); - - xlog "Assert email is unseen"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - hasKeyword => '$seen', - }, - }, 'R1'], - ['Email/query', { - filter => { - inMailbox => $mboxA, - hasKeyword => '$seen', - }, - }, 'R2'], - ['Email/query', { - filter => { - inMailbox => $mboxB, - hasKeyword => '$seen', - }, - }, 'R3'], - ['Email/query', { - filter => { - notKeyword => '$seen', - }, - }, 'R4'], - ['Email/query', { - filter => { - inMailbox => $mboxA, - notKeyword => '$seen', + bodyValues => { + part1 => { + value => 'test', + }, }, - }, 'R5'], - ['Email/query', { - filter => { - inMailbox => $mboxB, - notKeyword => '$seen', - }, - }, 'R6'], - ]); - $self->assert_deep_equals([], $res->[0][1]{ids}); - $self->assert_deep_equals([], $res->[1][1]{ids}); - $self->assert_deep_equals([], $res->[2][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[3][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[4][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[5][1]{ids}); + }, + }, + }, + 'R2' + ], + ]); + my $mboxA = $res->[0][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[0][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + my $emailId = $res->[1][1]{created}{email}{id}; + $self->assert_not_null($emailId); - xlog 'Set \Seen on message in mailbox A'; - $imap->select('A'); - $imap->store('1', '+flags', '(\Seen)'); + xlog "Assert email is unseen"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + hasKeyword => '$seen', + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxA, + hasKeyword => '$seen', + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxB, + hasKeyword => '$seen', + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + notKeyword => '$seen', + }, + }, + 'R4' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxA, + notKeyword => '$seen', + }, + }, + 'R5' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxB, + notKeyword => '$seen', + }, + }, + 'R6' + ], + ]); + $self->assert_deep_equals([], $res->[0][1]{ids}); + $self->assert_deep_equals([], $res->[1][1]{ids}); + $self->assert_deep_equals([], $res->[2][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[3][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[4][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[5][1]{ids}); - xlog "Assert email still is unseen"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - hasKeyword => '$seen', - }, - }, 'R1'], - ['Email/query', { - filter => { - inMailbox => $mboxA, - hasKeyword => '$seen', - }, - }, 'R2'], - ['Email/query', { - filter => { - inMailbox => $mboxB, - hasKeyword => '$seen', - }, - }, 'R3'], - ['Email/query', { - filter => { - notKeyword => '$seen', - }, - }, 'R4'], - ['Email/query', { - filter => { - inMailbox => $mboxA, - notKeyword => '$seen', - }, - }, 'R5'], - ['Email/query', { - filter => { - inMailbox => $mboxB, - notKeyword => '$seen', - }, - }, 'R6'], - ]); - $self->assert_deep_equals([], $res->[0][1]{ids}); - $self->assert_deep_equals([], $res->[1][1]{ids}); - $self->assert_deep_equals([], $res->[2][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[3][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[4][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[5][1]{ids}); + xlog 'Set \Seen on message in mailbox A'; + $imap->select('A'); + $imap->store('1', '+flags', '(\Seen)'); - xlog 'Set \Seen on message in mailbox B'; - $imap->select('B'); - $imap->store('1', '+flags', '(\Seen)'); + xlog "Assert email still is unseen"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + hasKeyword => '$seen', + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxA, + hasKeyword => '$seen', + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxB, + hasKeyword => '$seen', + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + notKeyword => '$seen', + }, + }, + 'R4' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxA, + notKeyword => '$seen', + }, + }, + 'R5' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxB, + notKeyword => '$seen', + }, + }, + 'R6' + ], + ]); + $self->assert_deep_equals([], $res->[0][1]{ids}); + $self->assert_deep_equals([], $res->[1][1]{ids}); + $self->assert_deep_equals([], $res->[2][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[3][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[4][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[5][1]{ids}); - xlog "Assert email seen"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - hasKeyword => '$seen', - }, - }, 'R1'], - ['Email/query', { - filter => { - inMailbox => $mboxA, - hasKeyword => '$seen', - }, - }, 'R2'], - ['Email/query', { - filter => { - inMailbox => $mboxB, - hasKeyword => '$seen', - }, - }, 'R3'], - ['Email/query', { - filter => { - notKeyword => '$seen', - }, - }, 'R4'], - ['Email/query', { - filter => { - inMailbox => $mboxA, - notKeyword => '$seen', - }, - }, 'R5'], - ['Email/query', { - filter => { - inMailbox => $mboxB, - notKeyword => '$seen', - }, - }, 'R6'], - ]); - $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[1][1]{ids}); - $self->assert_deep_equals([$emailId], $res->[2][1]{ids}); - $self->assert_deep_equals([], $res->[3][1]{ids}); - $self->assert_deep_equals([], $res->[4][1]{ids}); - $self->assert_deep_equals([], $res->[5][1]{ids}); + xlog 'Set \Seen on message in mailbox B'; + $imap->select('B'); + $imap->store('1', '+flags', '(\Seen)'); + + xlog "Assert email seen"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + hasKeyword => '$seen', + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxA, + hasKeyword => '$seen', + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxB, + hasKeyword => '$seen', + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + notKeyword => '$seen', + }, + }, + 'R4' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxA, + notKeyword => '$seen', + }, + }, + 'R5' + ], + [ + 'Email/query', + { + filter => { + inMailbox => $mboxB, + notKeyword => '$seen', + }, + }, + 'R6' + ], + ]); + $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[1][1]{ids}); + $self->assert_deep_equals([$emailId], $res->[2][1]{ids}); + $self->assert_deep_equals([], $res->[3][1]{ids}); + $self->assert_deep_equals([], $res->[4][1]{ids}); + $self->assert_deep_equals([], $res->[5][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_seen_shared b/cassandane/tiny-tests/JMAPEmail/email_query_seen_shared index d71668fde8..2976a2e323 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_seen_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_query_seen_shared @@ -2,145 +2,168 @@ use Cassandane::Tiny; sub test_email_query_seen_shared - :min_version_3_5 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); - $admin->create("user.other"); - $admin->setacl("user.other", admin => 'lrswipkxtecdan') or die; - $admin->setacl("user.other", other => 'lrswipkxtecdn') or die; - $admin->setacl("user.other", cassandane => 'lrswipkxtecdn') or die; + my $admin = $self->{adminstore}->get_client(); + $admin->create("user.other"); + $admin->setacl("user.other", admin => 'lrswipkxtecdan') or die; + $admin->setacl("user.other", other => 'lrswipkxtecdn') or die; + $admin->setacl("user.other", cassandane => 'lrswipkxtecdn') or die; - my $service = $self->{instance}->get_service("http"); - my $otherJmap = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $otherJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - ]); + my $service = $self->{instance}->get_service("http"); + my $otherJmap = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $otherJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + ]); - xlog "create two emails in shared mailbox"; - my $res = $otherJmap->CallMethods([ - ['Email/set', { - create => { - 'email1' => { - mailboxIds => { - '$inbox' => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'email1', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'email2' => { - mailboxIds => { - '$inbox' => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'email2', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog "create two emails in shared mailbox"; + my $res = $otherJmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'email1' => { + mailboxIds => { + '$inbox' => JSON::true, }, - }, 'R1'], - ]); - my $email1 = $res->[0][1]->{created}{email1}{id}; - $self->assert_not_null($email1); - my $email2 = $res->[0][1]->{created}{email2}{id}; - $self->assert_not_null($email2); - my @emailIds = sort ($email1, $email2); - - $res = $jmap->CallMethods([ - ['Email/set', { - accountId => 'other', - update => { - $email1 => { - keywords => { - '$seen' => JSON::true, - }, - }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'email1', + bodyStructure => { + type => 'text/plain', + partId => 'part1', }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$email1}); - - my $methods = [ - ['Email/get', { - accountId => 'other', - ids => [$email1], - properties => [ 'keywords' ], - }, 'R1'], - ['Email/get', { - accountId => 'other', - ids => [$email2], - properties => [ 'keywords' ], - }, 'R2'], - ['Email/query', { - accountId => 'other', - filter => { - hasKeyword => '$seen', + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'email2' => { + mailboxIds => { + '$inbox' => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'email2', + bodyStructure => { + type => 'text/plain', + partId => 'part1', }, - sort => [{ - property => 'id' - }], - }, 'R3'], - ['Email/query', { - accountId => 'other', - filter => { - notKeyword => '$seen', + bodyValues => { + part1 => { + value => 'test', + } }, - sort => [{ - property => 'id' - }], - }, 'R4'], - ]; + }, + }, + }, + 'R1' + ], + ]); + my $email1 = $res->[0][1]->{created}{email1}{id}; + $self->assert_not_null($email1); + my $email2 = $res->[0][1]->{created}{email2}{id}; + $self->assert_not_null($email2); + my @emailIds = sort ($email1, $email2); + + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + accountId => 'other', + update => { + $email1 => { + keywords => { + '$seen' => JSON::true, + }, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$email1}); + + my $methods = [ + [ + 'Email/get', + { + accountId => 'other', + ids => [$email1], + properties => ['keywords'], + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + ids => [$email2], + properties => ['keywords'], + }, + 'R2' + ], + [ + 'Email/query', + { + accountId => 'other', + filter => { + hasKeyword => '$seen', + }, + sort => [ { + property => 'id' + } ], + }, + 'R3' + ], + [ + 'Email/query', + { + accountId => 'other', + filter => { + notKeyword => '$seen', + }, + sort => [ { + property => 'id' + } ], + }, + 'R4' + ], + ]; - $res = $otherJmap->CallMethods($methods); - $self->assert_deep_equals({}, - $res->[0][1]{list}[0]{keywords}); - $self->assert_deep_equals({}, - $res->[1][1]{list}[0]{keywords}); - $self->assert_deep_equals([], $res->[2][1]{ids}); - $self->assert_deep_equals(\@emailIds, $res->[3][1]{ids}); + $res = $otherJmap->CallMethods($methods); + $self->assert_deep_equals({}, $res->[0][1]{list}[0]{keywords}); + $self->assert_deep_equals({}, $res->[1][1]{list}[0]{keywords}); + $self->assert_deep_equals([], $res->[2][1]{ids}); + $self->assert_deep_equals(\@emailIds, $res->[3][1]{ids}); - $res = $jmap->CallMethods($methods); - $self->assert_deep_equals({'$seen' => JSON::true}, - $res->[0][1]{list}[0]{keywords}); - $self->assert_deep_equals({}, - $res->[1][1]{list}[0]{keywords}); - $self->assert_deep_equals([$email1], $res->[2][1]{ids}); - $self->assert_deep_equals([$email2], $res->[3][1]{ids}); + $res = $jmap->CallMethods($methods); + $self->assert_deep_equals({ '$seen' => JSON::true }, + $res->[0][1]{list}[0]{keywords}); + $self->assert_deep_equals({}, $res->[1][1]{list}[0]{keywords}); + $self->assert_deep_equals([$email1], $res->[2][1]{ids}); + $self->assert_deep_equals([$email2], $res->[3][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_shared b/cassandane/tiny-tests/JMAPEmail/email_query_shared index 0b4275b54c..5192d255c7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_query_shared @@ -2,355 +2,446 @@ use Cassandane::Tiny; sub test_email_query_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $admintalk = $self->{adminstore}->get_client(); - $self->{instance}->create_user("test"); - $admintalk->setacl("user.test", "cassandane", "lrwkx") or die; - - # run tests for both the main and "test" account - foreach (undef, "test") { - my $account = $_; - my $store = defined $account ? $self->{adminstore} : $self->{store}; - my $mboxprefix = defined $account ? "user.$account" : "INBOX"; - my $talk = $store->get_client(); - - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => $account }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; - - xlog $self, "create mailboxes"; - $talk->create("$mboxprefix.A") || die; - $talk->create("$mboxprefix.B") || die; - $talk->create("$mboxprefix.C") || die; - - $res = $jmap->CallMethods([['Mailbox/get', { accountId => $account }, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxa = $m{"A"}->{id}; - my $mboxb = $m{"B"}->{id}; - my $mboxc = $m{"C"}->{id}; - $self->assert_not_null($mboxa); - $self->assert_not_null($mboxb); - $self->assert_not_null($mboxc); - - xlog $self, "create emails"; - my %params; - $store->set_folder("$mboxprefix.A"); - my $dtfoo = DateTime->new( - year => 2016, - month => 11, - day => 1, - hour => 7, - time_zone => 'Etc/UTC', - ); - my $bodyfoo = "A rather short email"; - %params = ( - date => $dtfoo, - body => $bodyfoo, - store => $store, - ); - $res = $self->make_message("foo", %params) || die; - $talk->copy(1, "$mboxprefix.C") || die; - - $store->set_folder("$mboxprefix.B"); - my $dtbar = DateTime->new( - year => 2016, - month => 3, - day => 1, - hour => 19, - time_zone => 'Etc/UTC', - ); - my $bodybar = "" - . "In the context of electronic mail, emails are viewed as having an\r\n" - . "envelope and contents. The envelope contains whatever information is\r\n" - . "needed to accomplish transmission and delivery. (See [RFC5321] for a\r\n" - . "discussion of the envelope.) The contents comprise the object to be\r\n" - . "delivered to the recipient. This specification applies only to the\r\n" - . "format and some of the semantics of email contents. It contains no\r\n" - . "specification of the information in the envelope.i\r\n" - . "\r\n" - . "However, some email systems may use information from the contents\r\n" - . "to create the envelope. It is intended that this specification\r\n" - . "facilitate the acquisition of such information by programs.\r\n" - . "\r\n" - . "This specification is intended as a definition of what email\r\n" - . "content format is to be passed between systems. Though some email\r\n" - . "systems locally store emails in this format (which eliminates the\r\n" - . "need for translation between formats) and others use formats that\r\n" - . "differ from the one specified in this specification, local storage is\r\n" - . "outside of the scope of this specification.\r\n"; - - %params = ( - date => $dtbar, - body => $bodybar, - extra_headers => [ - ['x-tra', "baz"], - ], - store => $store, - ); - $self->make_message("bar", %params) || die; - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "fetch emails without filter"; - $res = $jmap->CallMethods([ - ['Email/query', { accountId => $account }, 'R1'], - ['Email/get', { - accountId => $account, - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } - }, 'R2'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_num_equals(2, scalar @{$res->[1][1]->{list}}); - - %m = map { $_->{subject} => $_ } @{$res->[1][1]{list}}; - my $foo = $m{"foo"}->{id}; - my $bar = $m{"bar"}->{id}; - $self->assert_not_null($foo); - $self->assert_not_null($bar); - - xlog $self, "filter text"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - text => "foo", - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter NOT text"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - operator => "NOT", - conditions => [ {text => "foo"} ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailbox A"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - inMailbox => $mboxa, - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailboxes"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - operator => 'OR', - conditions => [ - { - inMailbox => $mboxa, - }, - { - inMailbox => $mboxc, - }, - ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailboxes with not in"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - inMailboxOtherThan => [$mboxb], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailboxes with not in"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - inMailboxOtherThan => [$mboxa], - }, - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "filter mailboxes with not in"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - inMailboxOtherThan => [$mboxa, $mboxc], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter mailboxes"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - operator => 'AND', - conditions => [ - { - inMailbox => $mboxa, - }, - { - inMailbox => $mboxb, - }, - { - inMailbox => $mboxc, - }, - ], - }, - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "filter not in mailbox A"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - operator => 'NOT', - conditions => [ - { - inMailbox => $mboxa, - }, - ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by before"; - my $dtbefore = $dtfoo->clone()->subtract(seconds => 1); - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - before => $dtbefore->strftime('%Y-%m-%dT%TZ'), - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by after", - my $dtafter = $dtbar->clone()->add(seconds => 1); - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - after => $dtafter->strftime('%Y-%m-%dT%TZ'), - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by after and before", - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - after => $dtafter->strftime('%Y-%m-%dT%TZ'), - before => $dtbefore->strftime('%Y-%m-%dT%TZ'), - }, - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "filter by minSize"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - minSize => length($bodybar), - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by maxSize"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - maxSize => length($bodybar), - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by header"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - header => [ "x-tra" ], - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - - xlog $self, "filter by header and value"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - filter => { - header => [ "x-tra", "bam" ], - }, - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "sort by ascending receivedAt"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "receivedAt" }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by descending receivedAt"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "receivedAt", isAscending => JSON::false, }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by ascending size"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "size" }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by descending size"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "size", isAscending => JSON::false }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); - - xlog $self, "sort by ascending id"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "id" }], - }, "R1"]]); - my @ids = sort ($foo, $bar); - $self->assert_deep_equals(\@ids, $res->[0][1]->{ids}); - - xlog $self, "sort by descending id"; - $res = $jmap->CallMethods([['Email/query', { - accountId => $account, - sort => [{ property => "id", isAscending => JSON::false }], - }, "R1"]]); - @ids = reverse sort ($foo, $bar); - $self->assert_deep_equals(\@ids, $res->[0][1]->{ids}); - - xlog $self, "delete mailboxes"; - $talk->delete("$mboxprefix.A") or die; - $talk->delete("$mboxprefix.B") or die; - $talk->delete("$mboxprefix.C") or die; - } + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $admintalk = $self->{adminstore}->get_client(); + $self->{instance}->create_user("test"); + $admintalk->setacl("user.test", "cassandane", "lrwkx") or die; + + # run tests for both the main and "test" account + foreach (undef, "test") { + my $account = $_; + my $store = defined $account ? $self->{adminstore} : $self->{store}; + my $mboxprefix = defined $account ? "user.$account" : "INBOX"; + my $talk = $store->get_client(); + + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { accountId => $account }, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; + + xlog $self, "create mailboxes"; + $talk->create("$mboxprefix.A") || die; + $talk->create("$mboxprefix.B") || die; + $talk->create("$mboxprefix.C") || die; + + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { accountId => $account }, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxa = $m{"A"}->{id}; + my $mboxb = $m{"B"}->{id}; + my $mboxc = $m{"C"}->{id}; + $self->assert_not_null($mboxa); + $self->assert_not_null($mboxb); + $self->assert_not_null($mboxc); + + xlog $self, "create emails"; + my %params; + $store->set_folder("$mboxprefix.A"); + my $dtfoo = DateTime->new( + year => 2016, + month => 11, + day => 1, + hour => 7, + time_zone => 'Etc/UTC', + ); + my $bodyfoo = "A rather short email"; + %params = ( + date => $dtfoo, + body => $bodyfoo, + store => $store, + ); + $res = $self->make_message("foo", %params) || die; + $talk->copy(1, "$mboxprefix.C") || die; + + $store->set_folder("$mboxprefix.B"); + my $dtbar = DateTime->new( + year => 2016, + month => 3, + day => 1, + hour => 19, + time_zone => 'Etc/UTC', + ); + my $bodybar + = "" + . "In the context of electronic mail, emails are viewed as having an\r\n" + . "envelope and contents. The envelope contains whatever information is\r\n" + . "needed to accomplish transmission and delivery. (See [RFC5321] for a\r\n" + . "discussion of the envelope.) The contents comprise the object to be\r\n" + . "delivered to the recipient. This specification applies only to the\r\n" + . "format and some of the semantics of email contents. It contains no\r\n" + . "specification of the information in the envelope.i\r\n" . "\r\n" + . "However, some email systems may use information from the contents\r\n" + . "to create the envelope. It is intended that this specification\r\n" + . "facilitate the acquisition of such information by programs.\r\n" + . "\r\n" + . "This specification is intended as a definition of what email\r\n" + . "content format is to be passed between systems. Though some email\r\n" + . "systems locally store emails in this format (which eliminates the\r\n" + . "need for translation between formats) and others use formats that\r\n" + . "differ from the one specified in this specification, local storage is\r\n" + . "outside of the scope of this specification.\r\n"; + + %params = ( + date => $dtbar, + body => $bodybar, + extra_headers => [ [ 'x-tra', "baz" ], ], + store => $store, + ); + $self->make_message("bar", %params) || die; + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "fetch emails without filter"; + $res = $jmap->CallMethods([ + [ 'Email/query', { accountId => $account }, 'R1' ], + [ + 'Email/get', + { + accountId => $account, + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } + }, + 'R2' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_num_equals(2, scalar @{ $res->[1][1]->{list} }); + + %m = map { $_->{subject} => $_ } @{ $res->[1][1]{list} }; + my $foo = $m{"foo"}->{id}; + my $bar = $m{"bar"}->{id}; + $self->assert_not_null($foo); + $self->assert_not_null($bar); + + xlog $self, "filter text"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + text => "foo", + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter NOT text"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + operator => "NOT", + conditions => [ { text => "foo" } ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailbox A"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + inMailbox => $mboxa, + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailboxes"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + operator => 'OR', + conditions => [ + { + inMailbox => $mboxa, + }, + { + inMailbox => $mboxc, + }, + ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailboxes with not in"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + inMailboxOtherThan => [$mboxb], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailboxes with not in"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + inMailboxOtherThan => [$mboxa], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "filter mailboxes with not in"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + inMailboxOtherThan => [ $mboxa, $mboxc ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter mailboxes"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + operator => 'AND', + conditions => [ + { + inMailbox => $mboxa, + }, + { + inMailbox => $mboxb, + }, + { + inMailbox => $mboxc, + }, + ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "filter not in mailbox A"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + operator => 'NOT', + conditions => [ + { + inMailbox => $mboxa, + }, + ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by before"; + my $dtbefore = $dtfoo->clone()->subtract(seconds => 1); + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + before => $dtbefore->strftime('%Y-%m-%dT%TZ'), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by after", + my $dtafter = $dtbar->clone()->add(seconds => 1); + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + after => $dtafter->strftime('%Y-%m-%dT%TZ'), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by after and before", + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + after => $dtafter->strftime('%Y-%m-%dT%TZ'), + before => $dtbefore->strftime('%Y-%m-%dT%TZ'), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "filter by minSize"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + minSize => length($bodybar), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by maxSize"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + maxSize => length($bodybar), + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by header"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + header => ["x-tra"], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + + xlog $self, "filter by header and value"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + filter => { + header => [ "x-tra", "bam" ], + }, + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "sort by ascending receivedAt"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "receivedAt" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by descending receivedAt"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "receivedAt", isAscending => JSON::false, } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by ascending size"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "size" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by descending size"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "size", isAscending => JSON::false } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($bar, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($foo, $res->[0][1]->{ids}[1]); + + xlog $self, "sort by ascending id"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "id" } ], + }, + "R1" + ] ]); + my @ids = sort ($foo, $bar); + $self->assert_deep_equals(\@ids, $res->[0][1]->{ids}); + + xlog $self, "sort by descending id"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + accountId => $account, + sort => [ { property => "id", isAscending => JSON::false } ], + }, + "R1" + ] ]); + @ids = reverse sort ($foo, $bar); + $self->assert_deep_equals(\@ids, $res->[0][1]->{ids}); + + xlog $self, "delete mailboxes"; + $talk->delete("$mboxprefix.A") or die; + $talk->delete("$mboxprefix.B") or die; + $talk->delete("$mboxprefix.C") or die; + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_shared_move b/cassandane/tiny-tests/JMAPEmail/email_query_shared_move index 9c9b2f4bde..1d2d28eb5f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_shared_move +++ b/cassandane/tiny-tests/JMAPEmail/email_query_shared_move @@ -2,47 +2,52 @@ use Cassandane::Tiny; sub test_email_query_shared_move - :min_version_3_5 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - my $admintalk = $self->{adminstore}->get_client(); - - # Share account - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lr") or die; - - # Create mailbox A - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lr") or die; - - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - $self->make_message("Email", store => $self->{adminstore}) or die; - - my $res = $jmap->Call('Email/get', { - accountId => 'other', - ids => [], - }); - $self->assert_not_null($res); - my $oldState = $res->{state}; - - # Move the message to invisible shared folder (leaving a - # removed instance in the visibile folder) - $admintalk->create("user.other.B") or die; - $admintalk->setacl("user.other.B", "cassandane", "") or die; - $admintalk->move(1, "user.other.B"); - - # Fetch Changes - $res = $jmap->Call('Email/changes', { - accountId => 'other', - sinceState => $oldState, - }); - $self->assert_not_null($res); - $self->assert_num_equals(0, scalar @{$res->{created}}); - $self->assert_num_equals(1, scalar @{$res->{destroyed}}); + : min_version_3_5 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + my $admintalk = $self->{adminstore}->get_client(); + + # Share account + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lr") or die; + + # Create mailbox A + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lr") or die; + + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + $self->make_message("Email", store => $self->{adminstore}) or die; + + my $res = $jmap->Call( + 'Email/get', + { + accountId => 'other', + ids => [], + } + ); + $self->assert_not_null($res); + my $oldState = $res->{state}; + + # Move the message to invisible shared folder (leaving a + # removed instance in the visibile folder) + $admintalk->create("user.other.B") or die; + $admintalk->setacl("user.other.B", "cassandane", "") or die; + $admintalk->move(1, "user.other.B"); + + # Fetch Changes + $res = $jmap->Call( + 'Email/changes', + { + accountId => 'other', + sinceState => $oldState, + } + ); + $self->assert_not_null($res); + $self->assert_num_equals(0, scalar @{ $res->{created} }); + $self->assert_num_equals(1, scalar @{ $res->{destroyed} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_sieve_8bit_header b/cassandane/tiny-tests/JMAPEmail/email_query_sieve_8bit_header index 3ae5e79c3f..c8696da559 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_sieve_8bit_header +++ b/cassandane/tiny-tests/JMAPEmail/email_query_sieve_8bit_header @@ -2,23 +2,24 @@ use Cassandane::Tiny; sub test_email_query_sieve_8bit_header - :min_version_3_9 :needs_component_jmap :needs_component_sieve :NoMunge8Bit :RFC2047_UTF8 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_component_jmap : needs_component_sieve : + NoMunge8Bit : RFC2047_UTF8 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $imap->create("matches") or die; + $imap->create("matches") or die; - xlog "Assert that message got moved into INBOX.matches"; - $imap->select('matches'); - $self->assert_num_equals(0, $imap->get_response_code('exists')); - $imap->unselect(); + xlog "Assert that message got moved into INBOX.matches"; + $imap->select('matches'); + $self->assert_num_equals(0, $imap->get_response_code('exists')); + $imap->unselect(); - # "subject" : "płatność" + # "subject" : "płatność" -use utf8; - $self->{instance}->install_sieve_script(<<'EOF' + use utf8; + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -32,9 +33,9 @@ if fileinto "matches"; } EOF - ); + ); - my $mime = <<'EOF'; + my $mime = <<'EOF'; From: from@local To: to@local Subject: test płatność @@ -46,15 +47,15 @@ Content-Transfer-Encoding: 7bit hello EOF - $mime =~ s/\r?\n/\r\n/gs; -no utf8; + $mime =~ s/\r?\n/\r\n/gs; + no utf8; - my $msg = Cassandane::Message->new(); - $msg->set_lines(split /\n/, $mime); - $self->{instance}->deliver($msg); + my $msg = Cassandane::Message->new(); + $msg->set_lines(split /\n/, $mime); + $self->{instance}->deliver($msg); - xlog "Assert that message got moved into INBOX.matches"; - $imap->select('matches'); - $self->assert_num_equals(1, $imap->get_response_code('exists')); - $imap->unselect(); + xlog "Assert that message got moved into INBOX.matches"; + $imap->select('matches'); + $self->assert_num_equals(1, $imap->get_response_code('exists')); + $imap->unselect(); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_sieve_some_in_thread_have_keyword b/cassandane/tiny-tests/JMAPEmail/email_query_sieve_some_in_thread_have_keyword index bca50946bf..06476fd43f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_sieve_some_in_thread_have_keyword +++ b/cassandane/tiny-tests/JMAPEmail/email_query_sieve_some_in_thread_have_keyword @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_email_query_sieve_some_in_thread_have_keyword - :needs_component_jmap :needs_component_sieve :ConversationMaxThread10 -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : needs_component_jmap : needs_component_sieve : ConversationMaxThread10 { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Set up Sieve script"; - $imap->create("matches") or die; - my $sieve = <create("matches") or die; + my $sieve = <{instance}->install_sieve_script($sieve); + $self->{instance}->install_sieve_script($sieve); - xlog $self, "Create split conversation"; - my $messageId = 'messageid1@example.com'; - $self->make_message('Email A', messageid => $messageId); - my $convMaxthread = $self->{instance}->{config}->get('conversations_max_thread'); - foreach (1 .. 2 * $convMaxthread - 1) { - $self->make_message("Re: Email A", - references => [ "<$messageId>" ], - ); - } + xlog $self, "Create split conversation"; + my $messageId = 'messageid1@example.com'; + $self->make_message('Email A', messageid => $messageId); + my $convMaxthread + = $self->{instance}->{config}->get('conversations_max_thread'); + foreach (1 .. 2 * $convMaxthread - 1) { + $self->make_message("Re: Email A", references => ["<$messageId>"],); + } - xlog $self, "Set flag on message in first conversation split"; - my $res = $imap->store($convMaxthread - 1, '+flags', '($IsMailingList)'); + xlog $self, "Set flag on message in first conversation split"; + my $res = $imap->store($convMaxthread - 1, '+flags', '($IsMailingList)'); - my $mime = <new(); - $msg->set_lines(split /\n/, $mime); + $mime =~ s/\r?\n/\r\n/gs; + my $msg = Cassandane::Message->new(); + $msg->set_lines(split /\n/, $mime); - xlog $self, "Deliver message for conversation"; - $msg->remove_headers('Message-Id'); - $msg->set_headers('Message-Id', '<4bb20c19-3a9d-483f@local>'); - $self->{instance}->deliver($msg); + xlog $self, "Deliver message for conversation"; + $msg->remove_headers('Message-Id'); + $msg->set_headers('Message-Id', '<4bb20c19-3a9d-483f@local>'); + $self->{instance}->deliver($msg); - xlog $self, "Assert that someInThreadHaveKeyword did not match"; - # XXX Might be OK to match here. But that's not how it is implemented. - $self->assert_num_equals(0, $imap->message_count('matches')); + xlog $self, "Assert that someInThreadHaveKeyword did not match"; + # XXX Might be OK to match here. But that's not how it is implemented. + $self->assert_num_equals(0, $imap->message_count('matches')); - xlog $self, "Set flag on message in second conversation split"; - my $res = $imap->store($convMaxthread + 1, '+flags', '($IsMailingList)'); + xlog $self, "Set flag on message in second conversation split"; + my $res = $imap->store($convMaxthread + 1, '+flags', '($IsMailingList)'); - xlog $self, "Deliver message for conversation"; - $msg->remove_headers('Message-Id'); - $msg->set_headers('Message-Id', ''); - $self->{instance}->deliver($msg); + xlog $self, "Deliver message for conversation"; + $msg->remove_headers('Message-Id'); + $msg->set_headers('Message-Id', ''); + $self->{instance}->deliver($msg); - xlog $self, "Assert that someInThreadHaveKeyword did match"; - $self->assert_num_equals(1, $imap->message_count('matches')); + xlog $self, "Assert that someInThreadHaveKeyword did match"; + $self->assert_num_equals(1, $imap->message_count('matches')); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_snippets b/cassandane/tiny-tests/JMAPEmail/email_query_snippets index a4a3aa837b..2f92f3566d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_snippets +++ b/cassandane/tiny-tests/JMAPEmail/email_query_snippets @@ -2,86 +2,102 @@ use Cassandane::Tiny; sub test_email_query_snippets - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); - xlog $self, "generating email A"; - $exp{A} = $self->make_message("Email A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + xlog $self, "generating email A"; + $exp{A} = $self->make_message("Email A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "fetch email and snippet"; - $res = $jmap->CallMethods([ - ['Email/query', { filter => { text => "email" }}, "R1"], - ['SearchSnippet/get', { - '#emailIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - '#filter' => { - resultOf => 'R1', - name => 'Email/query', - path => '/filter', - }, - }, 'R2'], - ]); + xlog $self, "fetch email and snippet"; + $res = $jmap->CallMethods([ + [ 'Email/query', { filter => { text => "email" } }, "R1" ], + [ + 'SearchSnippet/get', + { + '#emailIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + '#filter' => { + resultOf => 'R1', + name => 'Email/query', + path => '/filter', + }, + }, + 'R2' + ], + ]); - my $snippet = $res->[1][1]{list}[0]; - $self->assert_not_null($snippet); - $self->assert_num_not_equals(-1, index($snippet->{subject}, "Email A")); + my $snippet = $res->[1][1]{list}[0]; + $self->assert_not_null($snippet); + $self->assert_num_not_equals(-1, + index($snippet->{subject}, "Email A")); - xlog $self, "fetch email and snippet with no filter"; - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['SearchSnippet/get', { - '#emailIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - }, 'R2'], - ]); - $snippet = $res->[1][1]{list}[0]; - $self->assert_not_null($snippet); - $self->assert_null($snippet->{subject}); - $self->assert_null($snippet->{preview}); + xlog $self, "fetch email and snippet with no filter"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'SearchSnippet/get', + { + '#emailIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + }, + 'R2' + ], + ]); + $snippet = $res->[1][1]{list}[0]; + $self->assert_not_null($snippet); + $self->assert_null($snippet->{subject}); + $self->assert_null($snippet->{preview}); - xlog $self, "fetch email and snippet with no text filter"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => "OR", - conditions => [{minSize => 1}, {maxSize => 1}] - }, - }, "R1"], - ['SearchSnippet/get', { - '#emailIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - '#filter' => { - resultOf => 'R1', - name => 'Email/query', - path => '/filter', - }, - }, 'R2'], - ]); + xlog $self, "fetch email and snippet with no text filter"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + operator => "OR", + conditions => [ { minSize => 1 }, { maxSize => 1 } ] + }, + }, + "R1" + ], + [ + 'SearchSnippet/get', + { + '#emailIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + '#filter' => { + resultOf => 'R1', + name => 'Email/query', + path => '/filter', + }, + }, + 'R2' + ], + ]); - $snippet = $res->[1][1]{list}[0]; - $self->assert_not_null($snippet); - $self->assert_null($snippet->{subject}); - $self->assert_null($snippet->{preview}); + $snippet = $res->[1][1]{list}[0]; + $self->assert_not_null($snippet); + $self->assert_null($snippet->{subject}); + $self->assert_null($snippet->{preview}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_snooze b/cassandane/tiny-tests/JMAPEmail/email_query_snooze index a59e277b83..005f9b0d97 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_snooze +++ b/cassandane/tiny-tests/JMAPEmail/email_query_snooze @@ -2,143 +2,166 @@ use Cassandane::Tiny; sub test_email_query_snooze - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd - :needs_component_sieve :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # snoozed property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); - - xlog $self, "Get mailbox id of Inbox"; - my $res = $jmap->CallMethods([['Mailbox/query', - {filter => {role => 'inbox'}}, "R1"]]); - my $inbox = $res->[0][1]->{ids}[0]; - - xlog $self, "create snooze mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "snoozed", - parentId => undef, - role => "snoozed" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $snoozedmbox = $res->[0][1]{created}{"1"}{id}; - - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => 30)); - my $datestr1 = $maildate->strftime('%Y-%m-%dT%TZ'); - - my $draft1 = { - mailboxIds => { $snoozedmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo1", - snoozed => { "until" => "$datestr1" }, - }; - - $maildate->add(DateTime::Duration->new(seconds => -15)); - my $datestr2 = $maildate->strftime('%Y-%m-%dT%TZ'); - - my $draft2 = { - mailboxIds => { $snoozedmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo2", - snoozed => { "until" => "$datestr2" }, - }; - - $maildate->add(DateTime::Duration->new(seconds => 30)); - my $datestr3 = $maildate->strftime('%Y-%m-%dT%TZ'); - - my $draft3 = { - mailboxIds => { $snoozedmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo3", - snoozed => { "until" => "$datestr3" }, - }; - - $maildate->add(DateTime::Duration->new(seconds => -1)); - my $datestr4 = $maildate->strftime('%Y-%m-%dT%TZ'); - - my $draft4 = { - mailboxIds => { $snoozedmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo4", - snoozed => { "until" => "$datestr4" }, - }; - - $maildate->add(DateTime::Duration->new(seconds => 10)); - my $datestr5 = $maildate->strftime('%Y-%m-%dT%TZ'); - - my $draft5 = { - mailboxIds => { $inbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo5", - receivedAt => "$datestr5", - }; - - $maildate->add(DateTime::Duration->new(seconds => -5)); - my $datestr6 = $maildate->strftime('%Y-%m-%dT%TZ'); - - my $draft6 = { - mailboxIds => { $inbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo6", - receivedAt => "$datestr6", - }; - - xlog $self, "Create 6 drafts"; - $res = $jmap->CallMethods([['Email/set', - { create => - { "1" => $draft1, - "2" => $draft2, - "3" => $draft3, - "4" => $draft4, - "5" => $draft5, - "6" => $draft6 }}, "R1"]]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - my $id2 = $res->[0][1]{created}{"2"}{id}; - my $id3 = $res->[0][1]{created}{"3"}{id}; - my $id4 = $res->[0][1]{created}{"4"}{id}; - my $id5 = $res->[0][1]{created}{"5"}{id}; - my $id6 = $res->[0][1]{created}{"6"}{id}; - - xlog $self, "sort by ascending snoozedUntil"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => "snoozedUntil", - mailboxId => "$snoozedmbox" }], - }, "R1"]]); - $self->assert_num_equals(6, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($id2, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($id1, $res->[0][1]->{ids}[1]); - $self->assert_str_equals($id4, $res->[0][1]->{ids}[2]); - $self->assert_str_equals($id3, $res->[0][1]->{ids}[3]); - $self->assert_str_equals($id6, $res->[0][1]->{ids}[4]); - $self->assert_str_equals($id5, $res->[0][1]->{ids}[5]); - - xlog $self, "sort by descending snoozedUntil"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => "snoozedUntil", - mailboxId => "$snoozedmbox", - isAscending => JSON::false }], - }, "R1"]]); - $self->assert_num_equals(6, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($id5, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($id6, $res->[0][1]->{ids}[1]); - $self->assert_str_equals($id3, $res->[0][1]->{ids}[2]); - $self->assert_str_equals($id4, $res->[0][1]->{ids}[3]); - $self->assert_str_equals($id1, $res->[0][1]->{ids}[4]); - $self->assert_str_equals($id2, $res->[0][1]->{ids}[5]); + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd + : needs_component_sieve : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # snoozed property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); + + xlog $self, "Get mailbox id of Inbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/query', { filter => { role => 'inbox' } }, "R1" ] ]); + my $inbox = $res->[0][1]->{ids}[0]; + + xlog $self, "create snooze mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "snoozed", + parentId => undef, + role => "snoozed" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $snoozedmbox = $res->[0][1]{created}{"1"}{id}; + + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => 30)); + my $datestr1 = $maildate->strftime('%Y-%m-%dT%TZ'); + + my $draft1 = { + mailboxIds => { $snoozedmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo1", + snoozed => { "until" => "$datestr1" }, + }; + + $maildate->add(DateTime::Duration->new(seconds => -15)); + my $datestr2 = $maildate->strftime('%Y-%m-%dT%TZ'); + + my $draft2 = { + mailboxIds => { $snoozedmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo2", + snoozed => { "until" => "$datestr2" }, + }; + + $maildate->add(DateTime::Duration->new(seconds => 30)); + my $datestr3 = $maildate->strftime('%Y-%m-%dT%TZ'); + + my $draft3 = { + mailboxIds => { $snoozedmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo3", + snoozed => { "until" => "$datestr3" }, + }; + + $maildate->add(DateTime::Duration->new(seconds => -1)); + my $datestr4 = $maildate->strftime('%Y-%m-%dT%TZ'); + + my $draft4 = { + mailboxIds => { $snoozedmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo4", + snoozed => { "until" => "$datestr4" }, + }; + + $maildate->add(DateTime::Duration->new(seconds => 10)); + my $datestr5 = $maildate->strftime('%Y-%m-%dT%TZ'); + + my $draft5 = { + mailboxIds => { $inbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo5", + receivedAt => "$datestr5", + }; + + $maildate->add(DateTime::Duration->new(seconds => -5)); + my $datestr6 = $maildate->strftime('%Y-%m-%dT%TZ'); + + my $draft6 = { + mailboxIds => { $inbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo6", + receivedAt => "$datestr6", + }; + + xlog $self, "Create 6 drafts"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { + "1" => $draft1, + "2" => $draft2, + "3" => $draft3, + "4" => $draft4, + "5" => $draft5, + "6" => $draft6 + } + }, + "R1" + ] ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + my $id2 = $res->[0][1]{created}{"2"}{id}; + my $id3 = $res->[0][1]{created}{"3"}{id}; + my $id4 = $res->[0][1]{created}{"4"}{id}; + my $id5 = $res->[0][1]{created}{"5"}{id}; + my $id6 = $res->[0][1]{created}{"6"}{id}; + + xlog $self, "sort by ascending snoozedUntil"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "snoozedUntil", + mailboxId => "$snoozedmbox" + } ], + }, + "R1" + ] ]); + $self->assert_num_equals(6, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($id2, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($id1, $res->[0][1]->{ids}[1]); + $self->assert_str_equals($id4, $res->[0][1]->{ids}[2]); + $self->assert_str_equals($id3, $res->[0][1]->{ids}[3]); + $self->assert_str_equals($id6, $res->[0][1]->{ids}[4]); + $self->assert_str_equals($id5, $res->[0][1]->{ids}[5]); + + xlog $self, "sort by descending snoozedUntil"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "snoozedUntil", + mailboxId => "$snoozedmbox", + isAscending => JSON::false + } ], + }, + "R1" + ] ]); + $self->assert_num_equals(6, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($id5, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($id6, $res->[0][1]->{ids}[1]); + $self->assert_str_equals($id3, $res->[0][1]->{ids}[2]); + $self->assert_str_equals($id4, $res->[0][1]->{ids}[3]); + $self->assert_str_equals($id1, $res->[0][1]->{ids}[4]); + $self->assert_str_equals($id2, $res->[0][1]->{ids}[5]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_sort_break_tie b/cassandane/tiny-tests/JMAPEmail/email_query_sort_break_tie index ff8d265fd3..0012251321 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_sort_break_tie +++ b/cassandane/tiny-tests/JMAPEmail/email_query_sort_break_tie @@ -2,79 +2,95 @@ use Cassandane::Tiny; sub test_email_query_sort_break_tie - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $emailCount = 10; - my %createEmails; - for (my $i = 1; $i <= $emailCount; $i++) { - $createEmails{$i} = { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => "from\@local" }], - to => [{ email => "to\@local" }], - subject => "email$i", - receivedAt => sprintf('2020-03-25T10:%02d:00Z', $i), - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email$i body", - }, - }, - } - } - my $res = $jmap->CallMethods([ - ['Email/set', { - create => \%createEmails, - }, 'R1'], - ]); - $self->assert_num_equals($emailCount, scalar keys %{$res->[0][1]{created}}); - my @wantEmailIds; - # Want emails returned in descending receivedAt. - for (my $i = $emailCount; $i >= 1; $i--) { - push @wantEmailIds, $res->[0][1]{created}{$i}{id}; - } + my $emailCount = 10; + my %createEmails; + for (my $i = 1; $i <= $emailCount; $i++) { + $createEmails{$i} = { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => "from\@local" } ], + to => [ { email => "to\@local" } ], + subject => "email$i", + receivedAt => sprintf('2020-03-25T10:%02d:00Z', $i), + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email$i body", + }, + }, + }; + } + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => \%createEmails, + }, + 'R1' + ], + ]); + $self->assert_num_equals($emailCount, scalar keys %{ $res->[0][1]{created} }); + my @wantEmailIds; + # Want emails returned in descending receivedAt. + for (my $i = $emailCount; $i >= 1; $i--) { + push @wantEmailIds, $res->[0][1]{created}{$i}{id}; + } - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Run queries"; - $res = $jmap->CallMethods([ - ['Email/query', { - }, 'R1'], - ['Email/query', { - sort => [{ - property => 'from', - }], - }, 'R2'], - ['Email/query', { - filter => { - body => 'body', - }, - }, 'R3'], - ], $using); + xlog "Run queries"; + $res = $jmap->CallMethods( + [ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/query', + { + sort => [ { + property => 'from', + } ], + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + body => 'body', + }, + }, + 'R3' + ], + ], + $using + ); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals(\@wantEmailIds, $res->[0][1]{ids}); - $self->assert_equals(JSON::false, $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals(\@wantEmailIds, $res->[1][1]{ids}); - $self->assert_equals(JSON::true, $res->[2][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals(\@wantEmailIds, $res->[2][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals(\@wantEmailIds, $res->[0][1]{ids}); + $self->assert_equals(JSON::false, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals(\@wantEmailIds, $res->[1][1]{ids}); + $self->assert_equals(JSON::true, + $res->[2][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals(\@wantEmailIds, $res->[2][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_text_nomail b/cassandane/tiny-tests/JMAPEmail/email_query_text_nomail index a9cc99d6ba..7e45b2e6d3 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_text_nomail +++ b/cassandane/tiny-tests/JMAPEmail/email_query_text_nomail @@ -2,14 +2,14 @@ use Cassandane::Tiny; sub test_email_query_text_nomail - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "search for some text"; - my $res = $jmap->CallMethods([['Email/query', { filter => { text => 'foo' } }, "R1"]]); + xlog "search for some text"; + my $res = $jmap->CallMethods( + [ [ 'Email/query', { filter => { text => 'foo' } }, "R1" ] ]); - # check that the query succeeded - $self->assert_str_equals($res->[0][0], "Email/query"); + # check that the query succeeded + $self->assert_str_equals($res->[0][0], "Email/query"); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_threadkeywords b/cassandane/tiny-tests/JMAPEmail/email_query_threadkeywords index aec411965d..5abb82d17d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_threadkeywords +++ b/cassandane/tiny-tests/JMAPEmail/email_query_threadkeywords @@ -2,157 +2,197 @@ use Cassandane::Tiny; sub test_email_query_threadkeywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; - - my $imaptalk = $self->{store}->get_client(); - - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); - - my $convflags = $self->{instance}->{config}->get('conversations_counted_flags'); - if (not defined $convflags) { - xlog $self, "conversations_counted_flags not configured. Skipping test"; - return; - } - - my $store = $self->{store}; - my $talk = $store->get_client(); - - my %params = (store => $store); - $store->set_folder("INBOX"); - - xlog $self, "generating email A"; - $exp{A} = $self->make_message("Email A", %params); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B", %params); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "generating email C referencing A"; - %params = ( - references => [ $exp{A} ], - store => $store, - ); - $exp{C} = $self->make_message("Re: Email A", %params); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog $self, "fetch email ids"; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; + + my $imaptalk = $self->{store}->get_client(); + + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); + + my $convflags + = $self->{instance}->{config}->get('conversations_counted_flags'); + if (not defined $convflags) { + xlog $self, "conversations_counted_flags not configured. Skipping test"; + return; + } + + my $store = $self->{store}; + my $talk = $store->get_client(); + + my %params = (store => $store); + $store->set_folder("INBOX"); + + xlog $self, "generating email A"; + $exp{A} = $self->make_message("Email A", %params); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B", %params); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "generating email C referencing A"; + %params = ( + references => [ $exp{A} ], + store => $store, + ); + $exp{C} = $self->make_message("Re: Email A", %params); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog $self, "fetch email ids"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + my %m = map { $_->{subject} => $_ } @{ $res->[1][1]{list} }; + my $msga = $m{"Email A"}; + my $msgb = $m{"Email B"}; + my $msgc = $m{"Re: Email A"}; + $self->assert_not_null($msga); + $self->assert_not_null($msgb); + $self->assert_not_null($msgc); + + my @flags = split ' ', $convflags; + foreach (@flags) { + my $flag = $_; + next if lc $flag eq '$hasattachment'; # special case + + xlog $self, "Testing for counted conversation flag $flag"; + $flag =~ s+^\\+\$+; + + xlog $self, "fetch collapsed threads with some $flag flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + someInThreadHaveKeyword => $flag, + }, + collapseThreads => JSON::true, + }, + "R1" + ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); + + xlog $self, "set $flag flag on email email A"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $msga->{id} => { + keywords => { $flag => JSON::true }, + }, + } + }, + "R1" + ] ]); + + xlog $self, "fetch collapsed threads with some $flag flag"; $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2' ], + [ + 'Email/query', + { + filter => { + someInThreadHaveKeyword => $flag, + }, + collapseThreads => JSON::true, + }, + "R1" + ], ]); - my %m = map { $_->{subject} => $_ } @{$res->[1][1]{list}}; - my $msga = $m{"Email A"}; - my $msgb = $m{"Email B"}; - my $msgc = $m{"Re: Email A"}; - $self->assert_not_null($msga); - $self->assert_not_null($msgb); - $self->assert_not_null($msgc); - - my @flags = split ' ', $convflags; - foreach (@flags) { - my $flag = $_; - next if lc $flag eq '$hasattachment'; # special case - - xlog $self, "Testing for counted conversation flag $flag"; - $flag =~ s+^\\+\$+ ; - - xlog $self, "fetch collapsed threads with some $flag flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - someInThreadHaveKeyword => $flag, - }, - collapseThreads => JSON::true, - }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); - - xlog $self, "set $flag flag on email email A"; - $res = $jmap->CallMethods([['Email/set', { - update => { - $msga->{id} => { - keywords => { $flag => JSON::true }, - }, - } - }, "R1"]]); - - xlog $self, "fetch collapsed threads with some $flag flag"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - someInThreadHaveKeyword => $flag, - }, - collapseThreads => JSON::true, - }, "R1"], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert( - ($msga->{id} eq $res->[0][1]->{ids}[0]) or - ($msgc->{id} eq $res->[0][1]->{ids}[0]) - ); - - xlog $self, "fetch collapsed threads with no $flag flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - noneInThreadHaveKeyword => $flag, - }, - collapseThreads => JSON::true, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($msgb->{id}, $res->[0][1]->{ids}[0]); - - xlog $self, "fetch collapsed threads sorted ascending by $flag"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => "someInThreadHaveKeyword", keyword => $flag }], - collapseThreads => JSON::true, - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($msgb->{id}, $res->[0][1]->{ids}[0]); - $self->assert( - ($msga->{id} eq $res->[0][1]->{ids}[1]) or - ($msgc->{id} eq $res->[0][1]->{ids}[1]) - ); - - xlog $self, "fetch collapsed threads sorted descending by $flag"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => "someInThreadHaveKeyword", keyword => $flag, isAscending => JSON::false }], - collapseThreads => JSON::true, - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert( - ($msga->{id} eq $res->[0][1]->{ids}[0]) or - ($msgc->{id} eq $res->[0][1]->{ids}[0]) - ); - $self->assert_str_equals($msgb->{id}, $res->[0][1]->{ids}[1]); - - xlog $self, 'reset keywords on email email A'; - $res = $jmap->CallMethods([['Email/set', { - update => { - $msga->{id} => { - keywords => { }, - }, - } - }, "R1"]]); - } - - # test that 'someInThreadHaveKeyword' filter fail - # with an 'cannotDoFilter' error for flags that are not defined - # in the conversations_counted_flags config option - xlog $self, "fetch collapsed threads with unsupported flag"; - $res = $jmap->CallMethods([['Email/query', { + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert( + ($msga->{id} eq $res->[0][1]->{ids}[0]) + or ($msgc->{id} eq $res->[0][1]->{ids}[0]) + ); + + xlog $self, "fetch collapsed threads with no $flag flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { filter => { - someInThreadHaveKeyword => 'notcountedflag', + noneInThreadHaveKeyword => $flag, }, collapseThreads => JSON::true, - }, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('unsupportedFilter', $res->[0][1]->{type}); + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($msgb->{id}, $res->[0][1]->{ids}[0]); + + xlog $self, "fetch collapsed threads sorted ascending by $flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { property => "someInThreadHaveKeyword", keyword => $flag } ], + collapseThreads => JSON::true, + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($msgb->{id}, $res->[0][1]->{ids}[0]); + $self->assert( + ($msga->{id} eq $res->[0][1]->{ids}[1]) + or ($msgc->{id} eq $res->[0][1]->{ids}[1]) + ); + + xlog $self, "fetch collapsed threads sorted descending by $flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "someInThreadHaveKeyword", + keyword => $flag, + isAscending => JSON::false + } ], + collapseThreads => JSON::true, + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert( + ($msga->{id} eq $res->[0][1]->{ids}[0]) + or ($msgc->{id} eq $res->[0][1]->{ids}[0]) + ); + $self->assert_str_equals($msgb->{id}, $res->[0][1]->{ids}[1]); + + xlog $self, 'reset keywords on email email A'; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $msga->{id} => { + keywords => {}, + }, + } + }, + "R1" + ] ]); + } + + # test that 'someInThreadHaveKeyword' filter fail + # with an 'cannotDoFilter' error for flags that are not defined + # in the conversations_counted_flags config option + xlog $self, "fetch collapsed threads with unsupported flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + someInThreadHaveKeyword => 'notcountedflag', + }, + collapseThreads => JSON::true, + }, + "R1" + ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('unsupportedFilter', $res->[0][1]->{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_toaddress b/cassandane/tiny-tests/JMAPEmail/email_query_toaddress index 40db4efb4b..3faf946243 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_toaddress +++ b/cassandane/tiny-tests/JMAPEmail/email_query_toaddress @@ -2,136 +2,154 @@ use Cassandane::Tiny; sub test_email_query_toaddress - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; - $imap->create("INBOX.B") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $mboxByName{'A'}->{id}; - my $mboxIdB = $mboxByName{'B'}->{id}; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; + $imap->create("INBOX.B") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $mboxByName{'A'}->{id}; + my $mboxIdB = $mboxByName{'B'}->{id}; - xlog $self, "create emails"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'mAB' => { - mailboxIds => { - $mboxIdA => JSON::true, - $mboxIdB => JSON::true, - }, - from => [{ - name => '', email => 'bar@local' - }], - to => [{ - name => '', email => 'xyzzy@remote' - }], - subject => 'AB', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mA' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'A', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mB' => { - mailboxIds => { - $mboxIdB => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'B', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog $self, "create emails"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'mAB' => { + mailboxIds => { + $mboxIdA => JSON::true, + $mboxIdB => JSON::true, }, - }, 'R1'], - ]); - my $emailIdAB = $res->[0][1]->{created}{mAB}{id}; - $self->assert_not_null($emailIdAB); - my $emailIdA = $res->[0][1]->{created}{mA}{id}; - $self->assert_not_null($emailIdA); - my $emailIdB = $res->[0][1]->{created}{mB}{id}; - $self->assert_not_null($emailIdB); + from => [ { + name => '', + email => 'bar@local' + } ], + to => [ { + name => '', + email => 'xyzzy@remote' + } ], + subject => 'AB', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mA' => { + mailboxIds => { + $mboxIdA => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'A', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + 'mB' => { + mailboxIds => { + $mboxIdB => JSON::true, + }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'B', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailIdAB = $res->[0][1]->{created}{mAB}{id}; + $self->assert_not_null($emailIdAB); + my $emailIdA = $res->[0][1]->{created}{mA}{id}; + $self->assert_not_null($emailIdA); + my $emailIdB = $res->[0][1]->{created}{mB}{id}; + $self->assert_not_null($emailIdB); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "query emails that are to bar"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'AND', + xlog $self, "query emails that are to bar"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'AND', + conditions => [ + { + operator => 'OR', conditions => [ - { - operator => 'OR', - conditions => [ - { "to" => 'bar@local' }, - { "cc" => 'bar@local' }, - { "bcc" => 'bar@local' }, - ], - }, - { "text" => "test" }, - { "inMailboxOtherThan" => [ $mboxIdB ] }, + { "to" => 'bar@local' }, + { "cc" => 'bar@local' }, + { "bcc" => 'bar@local' }, ], - }, - disableGuidSearch => JSON::true, - }, 'R1'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($emailIdA, $res->[0][1]->{ids}[0]); + }, + { "text" => "test" }, + { "inMailboxOtherThan" => [$mboxIdB] }, + ], + }, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($emailIdA, $res->[0][1]->{ids}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_toplevel_calendar b/cassandane/tiny-tests/JMAPEmail/email_query_toplevel_calendar index f469eb0097..bc618d9df8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_toplevel_calendar +++ b/cassandane/tiny-tests/JMAPEmail/email_query_toplevel_calendar @@ -2,23 +2,22 @@ use Cassandane::Tiny; sub test_email_query_toplevel_calendar - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: from@local To: to@local Subject: test @@ -43,36 +42,55 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - xlog $self, 'run squatter'; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, 'run squatter'; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - from => 'organizer@local', - }, - }, 'R1'], - ['Email/query', { - filter => { - to => 'attendee@local', - }, - }, 'R2'], - ['Email/query', { - filter => { - from => 'from@local', - }, - }, 'R3'], - ['Email/query', { - filter => { - to => 'to@local', - }, - }, 'R4'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{ids}}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{ids}}); - $self->assert_num_equals(1, scalar @{$res->[3][1]{ids}}); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + from => 'organizer@local', + }, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => { + to => 'attendee@local', + }, + }, + 'R2' + ], + [ + 'Email/query', + { + filter => { + from => 'from@local', + }, + }, + 'R3' + ], + [ + 'Email/query', + { + filter => { + to => 'to@local', + }, + }, + 'R4' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{ids} }); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{ids} }); + $self->assert_num_equals(1, scalar @{ $res->[3][1]{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_toplevel_calendar_sieve b/cassandane/tiny-tests/JMAPEmail/email_query_toplevel_calendar_sieve index c30e2312c5..9b9e67b344 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_toplevel_calendar_sieve +++ b/cassandane/tiny-tests/JMAPEmail/email_query_toplevel_calendar_sieve @@ -2,15 +2,15 @@ use Cassandane::Tiny; sub test_email_query_toplevel_calendar_sieve - :min_version_3_5 :needs_component_jmap :JMAPExtensions - :needs_component_sieve -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_jmap : JMAPExtensions + : needs_component_sieve { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - $imap->create("INBOX.matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + $imap->create("INBOX.matches") or die; + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -24,9 +24,9 @@ if fileinto "INBOX.matches"; } EOF - ); + ); - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: from@local To: to@local Subject: test @@ -51,10 +51,10 @@ LAST-MODIFIED:20150928T132434Z END:VEVENT END:VCALENDAR EOF - $rawMessage =~ s/\r?\n/\r\n/gs; + $rawMessage =~ s/\r?\n/\r\n/gs; - my $msg = Cassandane::Message->new(); - $msg->set_lines(split /\n/, $rawMessage); - $self->{instance}->deliver($msg); - $self->assert_num_equals(1, $imap->message_count('INBOX.matches')); + my $msg = Cassandane::Message->new(); + $msg->set_lines(split /\n/, $rawMessage); + $self->{instance}->deliver($msg); + $self->assert_num_equals(1, $imap->message_count('INBOX.matches')); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_unicodefdfx b/cassandane/tiny-tests/JMAPEmail/email_query_unicodefdfx index 7c1a440c06..c7c089af19 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_unicodefdfx +++ b/cassandane/tiny-tests/JMAPEmail/email_query_unicodefdfx @@ -2,56 +2,60 @@ use Cassandane::Tiny; sub test_email_query_unicodefdfx - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :SearchLanguage -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : SearchLanguage { + my ($self) = @_; + my $jmap = $self->{jmap}; - # Unicode block FDFX for Arabic contains some code points that - # make Cyrus search form blow up the stem word length over the - # allowed limit of 200 bytes. This test asserts that Cyrus doesn't - # choke on these and still indexes the unstemmed form. + # Unicode block FDFX for Arabic contains some code points that + # make Cyrus search form blow up the stem word length over the + # allowed limit of 200 bytes. This test asserts that Cyrus doesn't + # choke on these and still indexes the unstemmed form. - my $binary = slurp_file(abs_path('data/mime/unicodefdfx.eml')); - my $data = $jmap->Upload($binary, "message/rfc822"); - my $blobId = $data->{blobId}; + my $binary = slurp_file(abs_path('data/mime/unicodefdfx.eml')); + my $data = $jmap->Upload($binary, "message/rfc822"); + my $blobId = $data->{blobId}; - my $res = $jmap->CallMethods([ - ['Email/import', { - emails => { - "1" => { - blobId => $blobId, - mailboxIds => { - '$inbox' => JSON::true}, - }, - }, - }, "R1"] - ]); - $self->assert_not_null($res->[0][1]{created}{1}); + my $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobId, + mailboxIds => { + '$inbox' => JSON::true + }, + }, + }, + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}{1}); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - # As seen in the wild: multiple U+FDFA codepoints without separating - # spaces. The unstemmed form in UTF-8 is about 30 bytes long, but - # the stemmed term in Cyrus search form is 270 bytes long. + # As seen in the wild: multiple U+FDFA codepoints without separating + # spaces. The unstemmed form in UTF-8 is about 30 bytes long, but + # the stemmed term in Cyrus search form is 270 bytes long. - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => "" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" . - "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}", - }, - }, 'R1'] - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + body => "" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}" + . "\N{ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM}", + }, + }, + 'R1' + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_unknown_mailbox b/cassandane/tiny-tests/JMAPEmail/email_query_unknown_mailbox index c1c60b523b..0c1eac5812 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_unknown_mailbox +++ b/cassandane/tiny-tests/JMAPEmail/email_query_unknown_mailbox @@ -2,24 +2,28 @@ use Cassandane::Tiny; sub test_email_query_unknown_mailbox - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "filter inMailbox with unknown mailbox"; - $res = $jmap->CallMethods([['Email/query', { filter => { inMailbox => "foo" } }, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - $self->assert_str_equals('filter/inMailbox', $res->[0][1]{arguments}[0]); + xlog $self, "filter inMailbox with unknown mailbox"; + $res = $jmap->CallMethods( + [ [ 'Email/query', { filter => { inMailbox => "foo" } }, "R1" ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + $self->assert_str_equals('filter/inMailbox', $res->[0][1]{arguments}[0]); - xlog $self, "filter inMailboxOtherThan with unknown mailbox"; - $res = $jmap->CallMethods([['Email/query', { filter => { inMailboxOtherThan => ["foo"] } }, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - $self->assert_str_equals('filter/inMailboxOtherThan[0:foo]', $res->[0][1]{arguments}[0]); + xlog $self, "filter inMailboxOtherThan with unknown mailbox"; + $res + = $jmap->CallMethods( + [ [ 'Email/query', { filter => { inMailboxOtherThan => ["foo"] } }, "R1" ] ] + ); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + $self->assert_str_equals('filter/inMailboxOtherThan[0:foo]', + $res->[0][1]{arguments}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_userkeywords b/cassandane/tiny-tests/JMAPEmail/email_query_userkeywords index 067496bcc3..0cc520c616 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_userkeywords +++ b/cassandane/tiny-tests/JMAPEmail/email_query_userkeywords @@ -2,70 +2,93 @@ use Cassandane::Tiny; sub test_email_query_userkeywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "create email foo"; - my $res = $self->make_message("foo") || die; + xlog $self, "create email foo"; + my $res = $self->make_message("foo") || die; - xlog $self, "fetch foo's id"; - $res = $jmap->CallMethods([['Email/query', { }, "R1"]]); - my $fooid = $res->[0][1]->{ids}[0]; - $self->assert_not_null($fooid); + xlog $self, "fetch foo's id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $fooid = $res->[0][1]->{ids}[0]; + $self->assert_not_null($fooid); - xlog $self, 'set foo flag on email foo'; - $res = $jmap->CallMethods([['Email/set', { - update => { - $fooid => { - keywords => { 'foo' => JSON::true }, - }, - } - }, "R1"]]); - $self->assert(exists $res->[0][1]->{updated}{$fooid}); + xlog $self, 'set foo flag on email foo'; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $fooid => { + keywords => { 'foo' => JSON::true }, + }, + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]->{updated}{$fooid}); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "fetch emails with foo flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - hasKeyword => 'foo', - } - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($fooid, $res->[0][1]->{ids}[0]); + xlog $self, "fetch emails with foo flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + hasKeyword => 'foo', + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($fooid, $res->[0][1]->{ids}[0]); - xlog $self, "create email bar"; - $res = $self->make_message("bar") || die; + xlog $self, "create email bar"; + $res = $self->make_message("bar") || die; - xlog $self, "fetch emails without foo flag"; - $res = $jmap->CallMethods([['Email/query', { - filter => { - notKeyword => 'foo', - } - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $barid = $res->[0][1]->{ids}[0]; - $self->assert_str_not_equals($barid, $fooid); + xlog $self, "fetch emails without foo flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { + notKeyword => 'foo', + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $barid = $res->[0][1]->{ids}[0]; + $self->assert_str_not_equals($barid, $fooid); - xlog $self, "fetch emails sorted ascending by foo flag"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => 'hasKeyword', keyword => 'foo' }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($barid, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($fooid, $res->[0][1]->{ids}[1]); + xlog $self, "fetch emails sorted ascending by foo flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { property => 'hasKeyword', keyword => 'foo' } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($barid, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($fooid, $res->[0][1]->{ids}[1]); - xlog $self, "fetch emails sorted descending by foo flag"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => 'hasKeyword', keyword => 'foo', isAscending => JSON::false }], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($fooid, $res->[0][1]->{ids}[0]); - $self->assert_str_equals($barid, $res->[0][1]->{ids}[1]); + xlog $self, "fetch emails sorted descending by foo flag"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => 'hasKeyword', + keyword => 'foo', + isAscending => JSON::false + } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($fooid, $res->[0][1]->{ids}[0]); + $self->assert_str_equals($barid, $res->[0][1]->{ids}[1]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_utf8punct_term b/cassandane/tiny-tests/JMAPEmail/email_query_utf8punct_term index de0b73bd02..132c7ba4f0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_utf8punct_term +++ b/cassandane/tiny-tests/JMAPEmail/email_query_utf8punct_term @@ -1,20 +1,19 @@ #!perl use Cassandane::Tiny; use Encode qw(decode encode); -use JSON qw(encode_json); +use JSON qw(encode_json); sub test_email_query_utf8punct_term - :needs_component_jmap :needs_component_sieve :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/performance'); - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/debug'); - $jmap->AddUsing('https://cyrusimap.org/ns/jmap/mail'); + : needs_component_jmap : needs_component_sieve : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/performance'); + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/debug'); + $jmap->AddUsing('https://cyrusimap.org/ns/jmap/mail'); - xlog $self, "Create MIME message containing a non-ASCII punctuation char"; - my $mime = <<"EOF"; + xlog $self, "Create MIME message containing a non-ASCII punctuation char"; + my $mime = <<"EOF"; From: To: Subject: test @@ -25,45 +24,58 @@ Content-Type: text/plain; charset=utf-8 hello \N{U+2013} world EOF - $mime =~ s/\r?\n/\r\n/gs; - $mime = encode('utf-8', $mime); - $imap->append('INBOX', $mime) || die $@; + $mime =~ s/\r?\n/\r\n/gs; + $mime = encode('utf-8', $mime); + $imap->append('INBOX', $mime) || die $@; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $filter = { - operator => 'AND', - conditions => [{ - body => 'hello' - }, { - body => "\N{U+2013}", - }, { - body => 'world', - }], - }; + my $filter = { + operator => 'AND', + conditions => [ + { + body => 'hello' + }, + { + body => "\N{U+2013}", + }, + { + body => 'world', + } + ], + }; - xlog $self, "Assert Email/query ignores punctuation character in filter"; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => $filter, - disableGuidSearch => JSON::true, - }, 'R1'], - ['Email/query', { - filter => $filter, - }, 'R2'], - ]); - $self->assert_equals(JSON::false, - $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_equals(JSON::true, - $res->[1][1]{performance}{details}{isGuidSearch}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{ids}}); + xlog $self, "Assert Email/query ignores punctuation character in filter"; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => $filter, + disableGuidSearch => JSON::true, + }, + 'R1' + ], + [ + 'Email/query', + { + filter => $filter, + }, + 'R2' + ], + ]); + $self->assert_equals(JSON::false, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_equals(JSON::true, + $res->[1][1]{performance}{details}{isGuidSearch}); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{ids} }); - xlog $self, "Assert JMAP Sieve ignores punctuation character in filter"; - $imap->create("matches") or die; - my $filterAsStr = encode_json($filter); - $self->{instance}->install_sieve_script(<<"EOF" + xlog $self, "Assert JMAP Sieve ignores punctuation character in filter"; + $imap->create("matches") or die; + my $filterAsStr = encode_json($filter); + $self->{instance}->install_sieve_script( + <<"EOF" require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -75,13 +87,13 @@ if fileinto "matches"; } EOF - ); + ); - my $msg = Cassandane::Message->new(); - $msg->set_lines(split /\n/, $mime); - $self->{instance}->deliver($msg); + my $msg = Cassandane::Message->new(); + $msg->set_lines(split /\n/, $mime); + $self->{instance}->deliver($msg); - $imap->select('matches'); - $self->assert_num_equals(1, $imap->get_response_code('exists')); - $imap->unselect(); + $imap->select('matches'); + $self->assert_num_equals(1, $imap->get_response_code('exists')); + $imap->unselect(); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_window b/cassandane/tiny-tests/JMAPEmail/email_query_window index d73bdea7cb..c1be6968ab 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_window +++ b/cassandane/tiny-tests/JMAPEmail/email_query_window @@ -2,9 +2,8 @@ use Cassandane::Tiny; sub test_email_query_window - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - $self->email_query_window_internal(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + $self->email_query_window_internal(); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_window_cached b/cassandane/tiny-tests/JMAPEmail/email_query_window_cached index 37af0af49c..7503dced3e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_window_cached +++ b/cassandane/tiny-tests/JMAPEmail/email_query_window_cached @@ -2,9 +2,8 @@ use Cassandane::Tiny; sub test_email_query_window_cached - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPQueryCacheMaxAge1s :JMAPExtensions -{ - my ($self) = @_; - $self->email_query_window_internal(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPQueryCacheMaxAge1s : JMAPExtensions { + my ($self) = @_; + $self->email_query_window_internal(); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_query_window_guidsearch b/cassandane/tiny-tests/JMAPEmail/email_query_window_guidsearch index 2115c228b8..7b3b201e45 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_query_window_guidsearch +++ b/cassandane/tiny-tests/JMAPEmail/email_query_window_guidsearch @@ -2,18 +2,18 @@ use Cassandane::Tiny; sub test_email_query_window_guidsearch - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; - # guidsearch supports calculating total if version >= 3.5 - my ($maj, $min) = Cassandane::Instance->get_version(); - my $calculateTotal = ($maj > 3 || ($maj == 3 && $min >= 5)) ? JSON::true : JSON::false; + # guidsearch supports calculating total if version >= 3.5 + my ($maj, $min) = Cassandane::Instance->get_version(); + my $calculateTotal + = ($maj > 3 || ($maj == 3 && $min >= 5)) ? JSON::true : JSON::false; - $self->email_query_window_internal( - wantGuidSearch => JSON::true, - calculateTotal => $calculateTotal, - filter => { subject => 'Email'}, - ); + $self->email_query_window_internal( + wantGuidSearch => JSON::true, + calculateTotal => $calculateTotal, + filter => { subject => 'Email' }, + ); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges b/cassandane/tiny-tests/JMAPEmail/email_querychanges index ab7311f491..911ad57f4a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges @@ -2,39 +2,44 @@ use Cassandane::Tiny; sub test_email_querychanges - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("Email A") || die; + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("Email A") || die; - xlog $self, "Get email id"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); + xlog $self, "Get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); - $state = $res->[0][1]->{queryState}; + $state = $res->[0][1]->{queryState}; - $self->make_message("Email B") || die; + $self->make_message("Email B") || die; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); - my ($idb) = grep { $_ ne $ida } @{$res->[0][1]->{ids}}; + my ($idb) = grep { $_ ne $ida } @{ $res->[0][1]->{ids} }; - xlog $self, "get email list updates"; - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state }, "R1"]]); + xlog $self, "get email list updates"; + $res = $jmap->CallMethods( + [ [ 'Email/queryChanges', { sinceQueryState => $state }, "R1" ] ]); - $self->assert_equals($idb, $res->[0][1]{added}[0]{id}); + $self->assert_equals($idb, $res->[0][1]{added}[0]{id}); - xlog $self, "get email list updates with threads collapsed"; - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state, collapseThreads => JSON::true }, "R1"]]); + xlog $self, "get email list updates with threads collapsed"; + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', + { sinceQueryState => $state, collapseThreads => JSON::true }, "R1" + ] ] + ); - $self->assert_equals($idb, $res->[0][1]{added}[0]{id}); + $self->assert_equals($idb, $res->[0][1]{added}[0]{id}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic b/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic index 69d382d68b..86aeb30b5c 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic @@ -2,52 +2,56 @@ use Cassandane::Tiny; sub test_email_querychanges_basic - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $draftsmbox; - - xlog $self, "Generate some email in INBOX via IMAP"; - $self->make_message("Email A") || die; - $self->make_message("Email B") || die; - $self->make_message("Email C") || die; - $self->make_message("Email D") || die; - - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - }, 'R1']]); - - $talk->select("INBOX"); - $talk->store("3", "+flags", "(\\Flagged)"); - - my $old = $res->[0][1]; - - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - sinceQueryState => $old->{queryState}, - }, 'R2']]); - - my $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - $self->assert_num_equals(1, scalar @{$new->{added}}); - $self->assert_num_equals(1, scalar @{$new->{removed}}); - $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); - $self->assert_str_equals($new->{removed}[0], $old->{ids}[$new->{added}[0]{index}]); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $draftsmbox; + + xlog $self, "Generate some email in INBOX via IMAP"; + $self->make_message("Email A") || die; + $self->make_message("Email B") || die; + $self->make_message("Email C") || die; + $self->make_message("Email D") || die; + + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + }, + 'R1' + ] ]); + + $talk->select("INBOX"); + $talk->store("3", "+flags", "(\\Flagged)"); + + my $old = $res->[0][1]; + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + sinceQueryState => $old->{queryState}, + }, + 'R2' + ] ]); + + my $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + $self->assert_num_equals(1, scalar @{ $new->{added} }); + $self->assert_num_equals(1, scalar @{ $new->{removed} }); + $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); + $self->assert_str_equals($new->{removed}[0], + $old->{ids}[ $new->{added}[0]{index} ]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_collapse b/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_collapse index 92a7a9feed..0fcddd0494 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_collapse +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_collapse @@ -2,54 +2,58 @@ use Cassandane::Tiny; sub test_email_querychanges_basic_collapse - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $draftsmbox; - - xlog $self, "Generate some email in INBOX via IMAP"; - $self->make_message("Email A") || die; - $self->make_message("Email B") || die; - $self->make_message("Email C") || die; - $self->make_message("Email D") || die; - - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - collapseThreads => $JSON::true, - }, 'R1']]); - - $talk->select("INBOX"); - $talk->store("3", "+flags", "(\\Flagged)"); - - my $old = $res->[0][1]; - - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - collapseThreads => $JSON::true, - sinceQueryState => $old->{queryState}, - }, 'R2']]); - - my $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - $self->assert_num_equals(1, scalar @{$new->{added}}); - $self->assert_num_equals(1, scalar @{$new->{removed}}); - $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); - $self->assert_str_equals($new->{removed}[0], $old->{ids}[$new->{added}[0]{index}]); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $draftsmbox; + + xlog $self, "Generate some email in INBOX via IMAP"; + $self->make_message("Email A") || die; + $self->make_message("Email B") || die; + $self->make_message("Email C") || die; + $self->make_message("Email D") || die; + + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + collapseThreads => $JSON::true, + }, + 'R1' + ] ]); + + $talk->select("INBOX"); + $talk->store("3", "+flags", "(\\Flagged)"); + + my $old = $res->[0][1]; + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + collapseThreads => $JSON::true, + sinceQueryState => $old->{queryState}, + }, + 'R2' + ] ]); + + my $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + $self->assert_num_equals(1, scalar @{ $new->{added} }); + $self->assert_num_equals(1, scalar @{ $new->{removed} }); + $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); + $self->assert_str_equals($new->{removed}[0], + $old->{ids}[ $new->{added}[0]{index} ]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_mb b/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_mb index 6d5418dcc6..251ef5720a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_mb +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_mb @@ -2,54 +2,58 @@ use Cassandane::Tiny; sub test_email_querychanges_basic_mb - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inboxid = $self->getinbox()->{id}; - - xlog $self, "Generate some email in INBOX via IMAP"; - $self->make_message("Email A") || die; - $self->make_message("Email B") || die; - $self->make_message("Email C") || die; - $self->make_message("Email D") || die; - - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - }, 'R1']]); - - $talk->select("INBOX"); - $talk->store("3", "+flags", "(\\Flagged)"); - - my $old = $res->[0][1]; - - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - sinceQueryState => $old->{queryState}, - }, 'R2']]); - - my $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - $self->assert_num_equals(1, scalar @{$new->{added}}); - $self->assert_num_equals(1, scalar @{$new->{removed}}); - $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); - $self->assert_str_equals($new->{removed}[0], $old->{ids}[$new->{added}[0]{index}]); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inboxid = $self->getinbox()->{id}; + + xlog $self, "Generate some email in INBOX via IMAP"; + $self->make_message("Email A") || die; + $self->make_message("Email B") || die; + $self->make_message("Email C") || die; + $self->make_message("Email D") || die; + + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + }, + 'R1' + ] ]); + + $talk->select("INBOX"); + $talk->store("3", "+flags", "(\\Flagged)"); + + my $old = $res->[0][1]; + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + sinceQueryState => $old->{queryState}, + }, + 'R2' + ] ]); + + my $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + $self->assert_num_equals(1, scalar @{ $new->{added} }); + $self->assert_num_equals(1, scalar @{ $new->{removed} }); + $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); + $self->assert_str_equals($new->{removed}[0], + $old->{ids}[ $new->{added}[0]{index} ]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_mb_collapse b/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_mb_collapse index 5ab8761616..fd9aaaec23 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_mb_collapse +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_basic_mb_collapse @@ -2,123 +2,135 @@ use Cassandane::Tiny; sub test_email_querychanges_basic_mb_collapse - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inboxid = $self->getinbox()->{id}; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inboxid = $self->getinbox()->{id}; - xlog $self, "Generate some email in INBOX via IMAP"; - $self->make_message("Email A") || die; - $self->make_message("Email B") || die; - $self->make_message("Email C") || die; - $self->make_message("Email D") || die; + xlog $self, "Generate some email in INBOX via IMAP"; + $self->make_message("Email A") || die; + $self->make_message("Email B") || die; + $self->make_message("Email C") || die; + $self->make_message("Email D") || die; - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - }, 'R1']]); + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + }, + 'R1' + ] ]); - $talk->select("INBOX"); - $talk->store("3", "+flags", "(\\Flagged)"); - $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); + $talk->select("INBOX"); + $talk->store("3", "+flags", "(\\Flagged)"); + $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); - my $old = $res->[0][1]; + my $old = $res->[0][1]; - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - sinceQueryState => $old->{queryState}, - ##upToId => $old->{ids}[3], - }, 'R2']]); + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + sinceQueryState => $old->{queryState}, + ##upToId => $old->{ids}[3], + }, + 'R2' + ] ]); - my $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - # with collased threads we have to check - $self->assert_num_equals(1, scalar @{$new->{added}}); - $self->assert_num_equals(1, scalar @{$new->{removed}}); - $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); - $self->assert_str_equals($new->{removed}[0], $old->{ids}[$new->{added}[0]{index}]); + my $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + # with collased threads we have to check + $self->assert_num_equals(1, scalar @{ $new->{added} }); + $self->assert_num_equals(1, scalar @{ $new->{removed} }); + $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); + $self->assert_str_equals($new->{removed}[0], + $old->{ids}[ $new->{added}[0]{index} ]); - xlog $self, "now with upto past"; - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - sinceQueryState => $old->{queryState}, - upToId => $old->{ids}[3], - }, 'R2']]); + xlog $self, "now with upto past"; + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + sinceQueryState => $old->{queryState}, + upToId => $old->{ids}[3], + }, + 'R2' + ] ]); - $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - $self->assert_num_equals(1, scalar @{$new->{added}}); - $self->assert_num_equals(1, scalar @{$new->{removed}}); - $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); - $self->assert_str_equals($new->{removed}[0], $old->{ids}[$new->{added}[0]{index}]); + $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + $self->assert_num_equals(1, scalar @{ $new->{added} }); + $self->assert_num_equals(1, scalar @{ $new->{removed} }); + $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); + $self->assert_str_equals($new->{removed}[0], + $old->{ids}[ $new->{added}[0]{index} ]); - xlog $self, "now with upto equal"; - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - sinceQueryState => $old->{queryState}, - upToId => $old->{ids}[2], - }, 'R2']]); + xlog $self, "now with upto equal"; + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + sinceQueryState => $old->{queryState}, + upToId => $old->{ids}[2], + }, + 'R2' + ] ]); - $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - $self->assert_num_equals(1, scalar @{$new->{added}}); - $self->assert_num_equals(1, scalar @{$new->{removed}}); - $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); - $self->assert_str_equals($new->{removed}[0], $old->{ids}[$new->{added}[0]{index}]); + $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + $self->assert_num_equals(1, scalar @{ $new->{added} }); + $self->assert_num_equals(1, scalar @{ $new->{removed} }); + $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); + $self->assert_str_equals($new->{removed}[0], + $old->{ids}[ $new->{added}[0]{index} ]); - xlog $self, "now with upto early"; - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - sinceQueryState => $old->{queryState}, - upToId => $old->{ids}[1], - }, 'R2']]); + xlog $self, "now with upto early"; + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + sinceQueryState => $old->{queryState}, + upToId => $old->{ids}[1], + }, + 'R2' + ] ]); - $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - $self->assert_num_equals(0, scalar @{$new->{added}}); - $self->assert_num_equals(0, scalar @{$new->{removed}}); + $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + $self->assert_num_equals(0, scalar @{ $new->{added} }); + $self->assert_num_equals(0, scalar @{ $new->{removed} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_deletedcopy b/cassandane/tiny-tests/JMAPEmail/email_querychanges_deletedcopy index efaa1bb7c2..83f9552fa2 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_deletedcopy +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_deletedcopy @@ -2,64 +2,69 @@ use Cassandane::Tiny; sub test_email_querychanges_deletedcopy - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inboxid = $self->getinbox()->{id}; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inboxid = $self->getinbox()->{id}; - xlog $self, "Generate some email in INBOX via IMAP"; - $self->make_message("Email A") || die; - $self->make_message("Email B") || die; - $self->make_message("Email C") || die; - $self->make_message("Email D") || die; + xlog $self, "Generate some email in INBOX via IMAP"; + $self->make_message("Email A") || die; + $self->make_message("Email B") || die; + $self->make_message("Email C") || die; + $self->make_message("Email D") || die; - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - }, 'R1']]); + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + }, + 'R1' + ] ]); - $talk->create("INBOX.foo"); - $talk->select("INBOX"); - $talk->move("2", "INBOX.foo"); - $talk->select("INBOX.foo"); - $talk->move("1", "INBOX"); - $talk->select("INBOX"); - $talk->store("2", "+flags", "(\\Flagged)"); + $talk->create("INBOX.foo"); + $talk->select("INBOX"); + $talk->move("2", "INBOX.foo"); + $talk->select("INBOX.foo"); + $talk->move("1", "INBOX"); + $talk->select("INBOX"); + $talk->store("2", "+flags", "(\\Flagged)"); - # order is now A (B) C D B, and (B), C and B are "changed" + # order is now A (B) C D B, and (B), C and B are "changed" - my $old = $res->[0][1]; + my $old = $res->[0][1]; - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - sinceQueryState => $old->{queryState}, - }, 'R2']]); + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + sinceQueryState => $old->{queryState}, + }, + 'R2' + ] ]); - my $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - # with collased threads we have to check - $self->assert_num_equals(2, scalar @{$new->{added}}); - $self->assert_num_equals(2, scalar @{$new->{removed}}); - $self->assert_str_equals($new->{added}[0]{id}, $old->{ids}[$new->{added}[0]{index}]); - $self->assert_str_equals($new->{added}[1]{id}, $old->{ids}[$new->{added}[1]{index}]); + my $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + # with collased threads we have to check + $self->assert_num_equals(2, scalar @{ $new->{added} }); + $self->assert_num_equals(2, scalar @{ $new->{removed} }); + $self->assert_str_equals($new->{added}[0]{id}, + $old->{ids}[ $new->{added}[0]{index} ]); + $self->assert_str_equals($new->{added}[1]{id}, + $old->{ids}[ $new->{added}[1]{index} ]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_fromcontactgroupid b/cassandane/tiny-tests/JMAPEmail/email_querychanges_fromcontactgroupid index 3066ff7829..62305ae6c2 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_fromcontactgroupid +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_fromcontactgroupid @@ -2,121 +2,163 @@ use Cassandane::Tiny; sub test_email_querychanges_fromcontactgroupid - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/contacts', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/contacts', + ]; - my $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact1 => { - emails => [{ - type => 'personal', - value => 'contact1@local', - }] - }, - } - }, 'R1'], - ['ContactGroup/set', { - create => { - contactGroup1 => { - name => 'contactGroup1', - contactIds => ['#contact1'], - } + my $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + create => { + contact1 => { + emails => [ { + type => 'personal', + value => 'contact1@local', + } ] + }, + } + }, + 'R1' + ], + [ + 'ContactGroup/set', + { + create => { + contactGroup1 => { + name => 'contactGroup1', + contactIds => ['#contact1'], } - }, 'R2'], - ], $using); - my $contactId1 = $res->[0][1]{created}{contact1}{id}; - $self->assert_not_null($contactId1); - my $contactGroupId1 = $res->[1][1]{created}{contactGroup1}{id}; - $self->assert_not_null($contactGroupId1); + } + }, + 'R2' + ], + ], + $using + ); + my $contactId1 = $res->[0][1]{created}{contact1}{id}; + $self->assert_not_null($contactId1); + my $contactGroupId1 = $res->[1][1]{created}{contactGroup1}{id}; + $self->assert_not_null($contactGroupId1); - # Make emails. - $self->make_message("msg1", from => Cassandane::Address->new( - localpart => 'contact1', domain => 'local' - )) or die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => "subject" }], - }, 'R1'] - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - my $emailId1 = $res->[0][1]{ids}[0]; + # Make emails. + $self->make_message( + "msg1", + from => Cassandane::Address->new( + localpart => 'contact1', + domain => 'local' + ) + ) or die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + my $emailId1 = $res->[0][1]{ids}[0]; - # Query changes. - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - fromContactGroupId => $contactGroupId1 - }, - sort => [ - { property => "subject" } - ], - }, 'R1'] - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); - my $queryState = $res->[0][1]{queryState}; - $self->assert_not_null($queryState); + # Query changes. + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + filter => { + fromContactGroupId => $contactGroupId1 + }, + sort => [ { property => "subject" } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); + my $queryState = $res->[0][1]{queryState}; + $self->assert_not_null($queryState); - # Add new matching email. - $self->make_message("msg2", from => Cassandane::Address->new( - localpart => 'contact1', domain => 'local' - )) or die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - $res = $jmap->CallMethods([ - ['Email/queryChanges', { - filter => { - fromContactGroupId => $contactGroupId1 - }, - sort => [ - { property => "subject" } - ], - sinceQueryState => $queryState, - }, 'R1'] - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{added}}); + # Add new matching email. + $self->make_message( + "msg2", + from => Cassandane::Address->new( + localpart => 'contact1', + domain => 'local' + ) + ) or die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', + { + filter => { + fromContactGroupId => $contactGroupId1 + }, + sort => [ { property => "subject" } ], + sinceQueryState => $queryState, + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{added} }); - # Invalidate query state for ContactGroup state changes. - $res = $jmap->CallMethods([ - ['Contact/set', { - create => { - contact2 => { - emails => [{ - type => 'personal', - value => 'contact2@local', - }] - }, - } - }, 'R1'], - ['ContactGroup/set', { - update => { - $contactGroupId1 => { - contactIds => [$contactId1, '#contact2'], - } - } - }, 'R2'], - ['Email/queryChanges', { - filter => { - fromContactGroupId => $contactGroupId1 + # Invalidate query state for ContactGroup state changes. + $res = $jmap->CallMethods( + [ + [ + 'Contact/set', + { + create => { + contact2 => { + emails => [ { + type => 'personal', + value => 'contact2@local', + } ] }, - sort => [ - { property => "subject" } - ], - sinceQueryState => $queryState, - }, 'R3'] - ], $using); - my $contactId2 = $res->[0][1]{created}{contact2}{id}; - $self->assert_not_null($contactId2); - $self->assert(exists $res->[1][1]{updated}{$contactGroupId1}); - $self->assert_str_equals('cannotCalculateChanges', $res->[2][1]{type}); + } + }, + 'R1' + ], + [ + 'ContactGroup/set', + { + update => { + $contactGroupId1 => { + contactIds => [ $contactId1, '#contact2' ], + } + } + }, + 'R2' + ], + [ + 'Email/queryChanges', + { + filter => { + fromContactGroupId => $contactGroupId1 + }, + sort => [ { property => "subject" } ], + sinceQueryState => $queryState, + }, + 'R3' + ] + ], + $using + ); + my $contactId2 = $res->[0][1]{created}{contact2}{id}; + $self->assert_not_null($contactId2); + $self->assert(exists $res->[1][1]{updated}{$contactGroupId1}); + $self->assert_str_equals('cannotCalculateChanges', $res->[2][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_implementation b/cassandane/tiny-tests/JMAPEmail/email_querychanges_implementation index 5ffd9d26ac..ff923ca5d5 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_implementation +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_implementation @@ -2,148 +2,170 @@ use Cassandane::Tiny; sub test_email_querychanges_implementation - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - # Also see https://github.com/cyrusimap/cyrus-imapd/issues/2294 - - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "Generate two emails via IMAP"; - $self->make_message("EmailA") || die; - $self->make_message("EmailB") || die; - - # The JMAP implementation in Cyrus uses two strategies - # for processing an Email/queryChanges request, depending - # on the query arguments: - # - # (1) 'trivial': if collapseThreads is false - # - # (2) 'collapse': if collapseThreads is true - # - # The results should be the same for (1) and (2), where - # updated message are reported as both 'added' and 'removed'. - - my $inboxid = $self->getinbox()->{id}; - - xlog $self, "Get email ids and state"; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [ - { isAscending => JSON::true, property => 'subject' } - ], - collapseThreads => JSON::false, - }, "R1"], - ['Email/query', { - sort => [ - { isAscending => JSON::true, property => 'subject' } - ], - collapseThreads => JSON::true, - }, "R2"], - ]); - my $msgidA = $res->[0][1]->{ids}[0]; - $self->assert_not_null($msgidA); - my $msgidB = $res->[0][1]->{ids}[1]; - $self->assert_not_null($msgidB); - - my $state_trivial = $res->[0][1]->{queryState}; - $self->assert_not_null($state_trivial); - my $state_collapsed = $res->[1][1]->{queryState}; - $self->assert_not_null($state_collapsed); - - xlog $self, "update email B"; - $res = $jmap->CallMethods([['Email/set', { - update => { $msgidB => { - 'keywords/$seen' => JSON::true } - }, - }, "R1"]]); - $self->assert(exists $res->[0][1]->{updated}{$msgidB}); - - xlog $self, "Create two new emails via IMAP"; - $self->make_message("EmailC") || die; - $self->make_message("EmailD") || die; - - xlog $self, "Get email ids"; - $res = $jmap->CallMethods([['Email/query', { - sort => [{ isAscending => JSON::true, property => 'subject' }], - }, "R1"]]); - my $msgidC = $res->[0][1]->{ids}[2]; - $self->assert_not_null($msgidC); - my $msgidD = $res->[0][1]->{ids}[3]; - $self->assert_not_null($msgidD); - - xlog $self, "Query changes up to first newly created message"; - $res = $jmap->CallMethods([ - ['Email/queryChanges', { - sort => [ - { isAscending => JSON::true, property => 'subject' } - ], - sinceQueryState => $state_trivial, - collapseThreads => JSON::false, - upToId => $msgidC, - }, "R1"], - ['Email/queryChanges', { - sort => [ - { isAscending => JSON::true, property => 'subject' } - ], - sinceQueryState => $state_collapsed, - collapseThreads => JSON::true, - upToId => $msgidC, - }, "R2"], - ]); - - # 'trivial' case - $self->assert_num_equals(2, scalar @{$res->[0][1]{added}}); - $self->assert_str_equals($msgidB, $res->[0][1]{added}[0]{id}); - $self->assert_num_equals(1, $res->[0][1]{added}[0]{index}); - $self->assert_str_equals($msgidC, $res->[0][1]{added}[1]{id}); - $self->assert_num_equals(2, $res->[0][1]{added}[1]{index}); - $self->assert_deep_equals([$msgidB, $msgidC], $res->[0][1]{removed}); - $self->assert_num_equals(4, $res->[0][1]{total}); - $state_trivial = $res->[0][1]{newQueryState}; - - # 'collapsed' case - $self->assert_num_equals(2, scalar @{$res->[1][1]{added}}); - $self->assert_str_equals($msgidB, $res->[1][1]{added}[0]{id}); - $self->assert_num_equals(1, $res->[1][1]{added}[0]{index}); - $self->assert_str_equals($msgidC, $res->[1][1]{added}[1]{id}); - $self->assert_num_equals(2, $res->[1][1]{added}[1]{index}); - $self->assert_deep_equals([$msgidB, $msgidC], $res->[1][1]{removed}); - $self->assert_num_equals(4, $res->[0][1]{total}); - $state_collapsed = $res->[1][1]{newQueryState}; - - xlog $self, "delete email C ($msgidC)"; - $res = $jmap->CallMethods([['Email/set', { destroy => [ $msgidC ] }, "R1"]]); - $self->assert_str_equals($msgidC, $res->[0][1]->{destroyed}[0]); - - xlog $self, "Query changes"; - $res = $jmap->CallMethods([ - ['Email/queryChanges', { - sort => [ - { isAscending => JSON::true, property => 'subject' } - ], - sinceQueryState => $state_trivial, - collapseThreads => JSON::false, - }, "R1"], - ['Email/queryChanges', { - sort => [ - { isAscending => JSON::true, property => 'subject' } - ], - sinceQueryState => $state_collapsed, - collapseThreads => JSON::true, - }, "R2"], - ]); - - # 'trivial' case - $self->assert_num_equals(0, scalar @{$res->[0][1]{added}}); - $self->assert_deep_equals([$msgidC], $res->[0][1]{removed}); - $self->assert_num_equals(3, $res->[0][1]{total}); - - # 'collapsed' case - $self->assert_num_equals(0, scalar @{$res->[1][1]{added}}); - $self->assert_deep_equals([$msgidC], $res->[1][1]{removed}); - $self->assert_num_equals(3, $res->[0][1]{total}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + # Also see https://github.com/cyrusimap/cyrus-imapd/issues/2294 + + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "Generate two emails via IMAP"; + $self->make_message("EmailA") || die; + $self->make_message("EmailB") || die; + + # The JMAP implementation in Cyrus uses two strategies + # for processing an Email/queryChanges request, depending + # on the query arguments: + # + # (1) 'trivial': if collapseThreads is false + # + # (2) 'collapse': if collapseThreads is true + # + # The results should be the same for (1) and (2), where + # updated message are reported as both 'added' and 'removed'. + + my $inboxid = $self->getinbox()->{id}; + + xlog $self, "Get email ids and state"; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { isAscending => JSON::true, property => 'subject' } ], + collapseThreads => JSON::false, + }, + "R1" + ], + [ + 'Email/query', + { + sort => [ { isAscending => JSON::true, property => 'subject' } ], + collapseThreads => JSON::true, + }, + "R2" + ], + ]); + my $msgidA = $res->[0][1]->{ids}[0]; + $self->assert_not_null($msgidA); + my $msgidB = $res->[0][1]->{ids}[1]; + $self->assert_not_null($msgidB); + + my $state_trivial = $res->[0][1]->{queryState}; + $self->assert_not_null($state_trivial); + my $state_collapsed = $res->[1][1]->{queryState}; + $self->assert_not_null($state_collapsed); + + xlog $self, "update email B"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $msgidB => { + 'keywords/$seen' => JSON::true + } + }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]->{updated}{$msgidB}); + + xlog $self, "Create two new emails via IMAP"; + $self->make_message("EmailC") || die; + $self->make_message("EmailD") || die; + + xlog $self, "Get email ids"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { isAscending => JSON::true, property => 'subject' } ], + }, + "R1" + ] ]); + my $msgidC = $res->[0][1]->{ids}[2]; + $self->assert_not_null($msgidC); + my $msgidD = $res->[0][1]->{ids}[3]; + $self->assert_not_null($msgidD); + + xlog $self, "Query changes up to first newly created message"; + $res = $jmap->CallMethods([ + [ + 'Email/queryChanges', + { + sort => [ { isAscending => JSON::true, property => 'subject' } ], + sinceQueryState => $state_trivial, + collapseThreads => JSON::false, + upToId => $msgidC, + }, + "R1" + ], + [ + 'Email/queryChanges', + { + sort => [ { isAscending => JSON::true, property => 'subject' } ], + sinceQueryState => $state_collapsed, + collapseThreads => JSON::true, + upToId => $msgidC, + }, + "R2" + ], + ]); + + # 'trivial' case + $self->assert_num_equals(2, scalar @{ $res->[0][1]{added} }); + $self->assert_str_equals($msgidB, $res->[0][1]{added}[0]{id}); + $self->assert_num_equals(1, $res->[0][1]{added}[0]{index}); + $self->assert_str_equals($msgidC, $res->[0][1]{added}[1]{id}); + $self->assert_num_equals(2, $res->[0][1]{added}[1]{index}); + $self->assert_deep_equals([ $msgidB, $msgidC ], $res->[0][1]{removed}); + $self->assert_num_equals(4, $res->[0][1]{total}); + $state_trivial = $res->[0][1]{newQueryState}; + + # 'collapsed' case + $self->assert_num_equals(2, scalar @{ $res->[1][1]{added} }); + $self->assert_str_equals($msgidB, $res->[1][1]{added}[0]{id}); + $self->assert_num_equals(1, $res->[1][1]{added}[0]{index}); + $self->assert_str_equals($msgidC, $res->[1][1]{added}[1]{id}); + $self->assert_num_equals(2, $res->[1][1]{added}[1]{index}); + $self->assert_deep_equals([ $msgidB, $msgidC ], $res->[1][1]{removed}); + $self->assert_num_equals(4, $res->[0][1]{total}); + $state_collapsed = $res->[1][1]{newQueryState}; + + xlog $self, "delete email C ($msgidC)"; + $res + = $jmap->CallMethods([ [ 'Email/set', { destroy => [$msgidC] }, "R1" ] ]); + $self->assert_str_equals($msgidC, $res->[0][1]->{destroyed}[0]); + + xlog $self, "Query changes"; + $res = $jmap->CallMethods([ + [ + 'Email/queryChanges', + { + sort => [ { isAscending => JSON::true, property => 'subject' } ], + sinceQueryState => $state_trivial, + collapseThreads => JSON::false, + }, + "R1" + ], + [ + 'Email/queryChanges', + { + sort => [ { isAscending => JSON::true, property => 'subject' } ], + sinceQueryState => $state_collapsed, + collapseThreads => JSON::true, + }, + "R2" + ], + ]); + + # 'trivial' case + $self->assert_num_equals(0, scalar @{ $res->[0][1]{added} }); + $self->assert_deep_equals([$msgidC], $res->[0][1]{removed}); + $self->assert_num_equals(3, $res->[0][1]{total}); + + # 'collapsed' case + $self->assert_num_equals(0, scalar @{ $res->[1][1]{added} }); + $self->assert_deep_equals([$msgidC], $res->[1][1]{removed}); + $self->assert_num_equals(3, $res->[0][1]{total}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_mailbox_or b/cassandane/tiny-tests/JMAPEmail/email_querychanges_mailbox_or index fbc9f51bff..3d1fb65005 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_mailbox_or +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_mailbox_or @@ -2,75 +2,94 @@ use Cassandane::Tiny; sub test_email_querychanges_mailbox_or - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email => { - mailboxIds => { - '$inbox' => JSON::true, - }, - subject => 'email', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'email', - } - }, - }, + my $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + email => { + mailboxIds => { + '$inbox' => JSON::true, + }, + subject => 'email', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'email', + } + }, }, - }, 'R1'], - ['Mailbox/query', { - }, 'R2'], - ], $using); - my $emailId = $res->[0][1]{created}{email}{id}; - $self->assert_not_null($emailId); - my $inboxId = $res->[1][1]{ids}[0]; - $self->assert_not_null($inboxId); + }, + }, + 'R1' + ], + [ 'Mailbox/query', {}, 'R2' ], + ], + $using + ); + my $emailId = $res->[0][1]{created}{email}{id}; + $self->assert_not_null($emailId); + my $inboxId = $res->[1][1]{ids}[0]; + $self->assert_not_null($inboxId); - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'OR', - conditions => [{ - inMailbox => $inboxId, - }], - }, - }, 'R1'], - ], $using); + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + operator => 'OR', + conditions => [ { + inMailbox => $inboxId, + } ], + }, + }, + 'R1' + ], + ], + $using + ); - $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); - $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); - my $queryState = $res->[0][1]{queryState}; + $self->assert_deep_equals([$emailId], $res->[0][1]{ids}); + $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); + my $queryState = $res->[0][1]{queryState}; - $res = $jmap->CallMethods([ - ['Email/queryChanges', { - filter => { - operator => 'OR', - conditions => [{ - inMailbox => $inboxId, - }], - }, - sinceQueryState => $queryState, - }, 'R1'], - ], $using); - $self->assert_deep_equals([], $res->[0][1]{added}); - $self->assert_deep_equals([], $res->[0][1]{removed}); + $res = $jmap->CallMethods( + [ + [ + 'Email/queryChanges', + { + filter => { + operator => 'OR', + conditions => [ { + inMailbox => $inboxId, + } ], + }, + sinceQueryState => $queryState, + }, + 'R1' + ], + ], + $using + ); + $self->assert_deep_equals([], $res->[0][1]{added}); + $self->assert_deep_equals([], $res->[0][1]{removed}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_order b/cassandane/tiny-tests/JMAPEmail/email_querychanges_order index 18f4254193..d910a4dbd9 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_order +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_order @@ -2,60 +2,69 @@ use Cassandane::Tiny; sub test_email_querychanges_order - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("A") || die; - - # First order descending by subject. We expect Email/queryChanges - # to return any items added after 'state' to show up at the start of - # the result list. - my $sort = [{ property => "subject", isAscending => JSON::false }]; - - xlog $self, "Get email id and state"; - $res = $jmap->CallMethods([['Email/query', { sort => $sort }, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); - $state = $res->[0][1]->{queryState}; - - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("B") || die; - - xlog $self, "Fetch updated list"; - $res = $jmap->CallMethods([['Email/query', { sort => $sort }, "R1"]]); - my $idb = $res->[0][1]->{ids}[0]; - $self->assert_str_not_equals($ida, $idb); - - xlog $self, "get email list updates"; - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state, sort => $sort }, "R1"]]); - $self->assert_equals($idb, $res->[0][1]{added}[0]{id}); - $self->assert_num_equals(0, $res->[0][1]{added}[0]{index}); - - # Now restart with sorting by ascending subject. We refetch the state - # just to be sure. Then we expect an additional item to show up at the - # end of the result list. - xlog $self, "Fetch reverse sorted list and state"; - $sort = [{ property => "subject" }]; - $res = $jmap->CallMethods([['Email/query', { sort => $sort }, "R1"]]); - $ida = $res->[0][1]->{ids}[0]; - $self->assert_str_not_equals($ida, $idb); - $idb = $res->[0][1]->{ids}[1]; - $state = $res->[0][1]->{queryState}; - - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("C") || die; - - xlog $self, "get email list updates"; - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state, sort => $sort }, "R1"]]); - $self->assert_str_not_equals($ida, $res->[0][1]{added}[0]{id}); - $self->assert_str_not_equals($idb, $res->[0][1]{added}[0]{id}); - $self->assert_num_equals(2, $res->[0][1]{added}[0]{index}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("A") || die; + + # First order descending by subject. We expect Email/queryChanges + # to return any items added after 'state' to show up at the start of + # the result list. + my $sort = [ { property => "subject", isAscending => JSON::false } ]; + + xlog $self, "Get email id and state"; + $res = $jmap->CallMethods([ [ 'Email/query', { sort => $sort }, "R1" ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); + $state = $res->[0][1]->{queryState}; + + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("B") || die; + + xlog $self, "Fetch updated list"; + $res = $jmap->CallMethods([ [ 'Email/query', { sort => $sort }, "R1" ] ]); + my $idb = $res->[0][1]->{ids}[0]; + $self->assert_str_not_equals($ida, $idb); + + xlog $self, "get email list updates"; + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', { sinceQueryState => $state, sort => $sort }, + "R1" + ] ] + ); + $self->assert_equals($idb, $res->[0][1]{added}[0]{id}); + $self->assert_num_equals(0, $res->[0][1]{added}[0]{index}); + + # Now restart with sorting by ascending subject. We refetch the state + # just to be sure. Then we expect an additional item to show up at the + # end of the result list. + xlog $self, "Fetch reverse sorted list and state"; + $sort = [ { property => "subject" } ]; + $res = $jmap->CallMethods([ [ 'Email/query', { sort => $sort }, "R1" ] ]); + $ida = $res->[0][1]->{ids}[0]; + $self->assert_str_not_equals($ida, $idb); + $idb = $res->[0][1]->{ids}[1]; + $state = $res->[0][1]->{queryState}; + + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("C") || die; + + xlog $self, "get email list updates"; + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', { sinceQueryState => $state, sort => $sort }, + "R1" + ] ] + ); + $self->assert_str_not_equals($ida, $res->[0][1]{added}[0]{id}); + $self->assert_str_not_equals($idb, $res->[0][1]{added}[0]{id}); + $self->assert_num_equals(2, $res->[0][1]{added}[0]{index}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_skipdeleted b/cassandane/tiny-tests/JMAPEmail/email_querychanges_skipdeleted index f01087614f..b4716dffe5 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_skipdeleted +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_skipdeleted @@ -2,63 +2,67 @@ use Cassandane::Tiny; sub test_email_querychanges_skipdeleted - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inboxid = $self->getinbox()->{id}; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inboxid = $self->getinbox()->{id}; - xlog $self, "Generate some email in INBOX via IMAP"; - $self->make_message("Email A") || die; - $self->make_message("Email B") || die; - $self->make_message("Email C") || die; - $self->make_message("Email D") || die; + xlog $self, "Generate some email in INBOX via IMAP"; + $self->make_message("Email A") || die; + $self->make_message("Email B") || die; + $self->make_message("Email C") || die; + $self->make_message("Email D") || die; - $talk->create("INBOX.foo"); - $talk->select("INBOX"); - $talk->move("1:2", "INBOX.foo"); - $talk->select("INBOX.foo"); - $talk->move("1:2", "INBOX"); + $talk->create("INBOX.foo"); + $talk->select("INBOX"); + $talk->move("1:2", "INBOX.foo"); + $talk->select("INBOX.foo"); + $talk->move("1:2", "INBOX"); - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - }, 'R1']]); + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + }, + 'R1' + ] ]); - my $old = $res->[0][1]; + my $old = $res->[0][1]; - $talk->select("INBOX"); - $talk->store("1", "+flags", "(\\Flagged)"); + $talk->select("INBOX"); + $talk->store("1", "+flags", "(\\Flagged)"); - $res = $jmap->CallMethods([['Email/queryChanges', { - sort => [ - { - property => "subject", - isAscending => $JSON::true, - } - ], - filter => { inMailbox => $inboxid }, - collapseThreads => $JSON::true, - sinceQueryState => $old->{queryState}, - }, 'R2']]); + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sort => [ { + property => "subject", + isAscending => $JSON::true, + } ], + filter => { inMailbox => $inboxid }, + collapseThreads => $JSON::true, + sinceQueryState => $old->{queryState}, + }, + 'R2' + ] ]); - my $new = $res->[0][1]; - $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); - $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); - # with collased threads we have to check - $self->assert_num_equals(1, scalar @{$new->{added}}); - $self->assert_num_equals(1, scalar @{$new->{removed}}); - $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); - $self->assert_str_equals($new->{removed}[0], $old->{ids}[$new->{added}[0]{index}]); + my $new = $res->[0][1]; + $self->assert_str_equals($old->{queryState}, $new->{oldQueryState}); + $self->assert_str_not_equals($old->{queryState}, $new->{newQueryState}); + # with collased threads we have to check + $self->assert_num_equals(1, scalar @{ $new->{added} }); + $self->assert_num_equals(1, scalar @{ $new->{removed} }); + $self->assert_str_equals($new->{removed}[0], $new->{added}[0]{id}); + $self->assert_str_equals($new->{removed}[0], + $old->{ids}[ $new->{added}[0]{index} ]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged b/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged index 8493bc8cf1..cf3cdc3a31 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged @@ -2,138 +2,201 @@ use Cassandane::Tiny; sub test_email_querychanges_sortflagged - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - my %exp; - my $dt; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "generating email A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -3)); - $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - - xlog $self, "Get email id"; - $res = $jmap->CallMethods([['Email/query', { - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); - - $state = $res->[0][1]->{queryState}; - - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B", body => "b"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "generating email C referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -2)); - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "c"); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "generating email D referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -1)); - $exp{D} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "d"); - $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); - - # EXPECTED ORDER OF MESSAGES NOW BY DATE IS: - # A C D B - # fetch them all by ID now to get an ID map - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { property => "receivedAt", - "isAscending" => $JSON::true }, - ], - }, "R1"]]); - my @ids = @{$res->[0][1]->{ids}}; - $self->assert_num_equals(4, scalar @ids); - $self->assert_str_equals($ida, $ids[0]); - my $idc = $ids[1]; - my $idd = $ids[2]; - my $idb = $ids[3]; - - # raw fetch - check order now - $res = $jmap->CallMethods([['Email/query', { - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $self->assert_deep_equals([$idb, $idd], $res->[0][1]->{ids}); - - $res = $jmap->CallMethods([['Email/queryChanges', { - sinceQueryState => $state, collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $state = $res->[0][1]{newQueryState}; - - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{removed}}); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{added}}); - # check that the order is B D - $self->assert_deep_equals([{id => $idb, index => 0}, {id => $idd, index => 1}], $res->[0][1]{added}); - - $talk->select("INBOX"); - $talk->store('1', "+flags", '\\Flagged'); - - # this will sort D to the top because of the flag on A - - # raw fetch - check order now - $res = $jmap->CallMethods([['Email/query', { - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $self->assert_deep_equals([$idd, $idb], $res->[0][1]->{ids}); - - $res = $jmap->CallMethods([['Email/queryChanges', { - sinceQueryState => $state, collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $state = $res->[0][1]{newQueryState}; - - $self->assert_num_equals(2, $res->[0][1]{total}); - # will have removed 'D' (old exemplar) and 'A' (touched) - $self->assert_num_equals(3, scalar @{$res->[0][1]->{removed}}); - $self->assert_not_null(grep { $_ eq $idd } map { $_ } @{$res->[0][1]->{removed}}); - $self->assert_not_null(grep { $_ eq $ida } map { $_ } @{$res->[0][1]->{removed}}); - $self->assert_not_null(grep { $_ eq $idc } map { $_ } @{$res->[0][1]->{removed}}); - $self->assert_deep_equals([{id => $idd, index => 0}], $res->[0][1]{added}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + my %exp; + my $dt; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "generating email A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -3)); + $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + + xlog $self, "Get email id"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); + + $state = $res->[0][1]->{queryState}; + + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B", body => "b"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "generating email C referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -2)); + $exp{C} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "c" + ); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "generating email D referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -1)); + $exp{D} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "d" + ); + $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); + + # EXPECTED ORDER OF MESSAGES NOW BY DATE IS: + # A C D B + # fetch them all by ID now to get an ID map + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ + { + property => "receivedAt", + "isAscending" => $JSON::true + }, + ], + }, + "R1" + ] ]); + my @ids = @{ $res->[0][1]->{ids} }; + $self->assert_num_equals(4, scalar @ids); + $self->assert_str_equals($ida, $ids[0]); + my $idc = $ids[1]; + my $idd = $ids[2]; + my $idb = $ids[3]; + + # raw fetch - check order now + $res = $jmap->CallMethods([ [ + 'Email/query', + { + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $self->assert_deep_equals([ $idb, $idd ], $res->[0][1]->{ids}); + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sinceQueryState => $state, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $state = $res->[0][1]{newQueryState}; + + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{removed} }); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{added} }); + # check that the order is B D + $self->assert_deep_equals( + [ { id => $idb, index => 0 }, { id => $idd, index => 1 } ], + $res->[0][1]{added}); + + $talk->select("INBOX"); + $talk->store('1', "+flags", '\\Flagged'); + + # this will sort D to the top because of the flag on A + + # raw fetch - check order now + $res = $jmap->CallMethods([ [ + 'Email/query', + { + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $self->assert_deep_equals([ $idd, $idb ], $res->[0][1]->{ids}); + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sinceQueryState => $state, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $state = $res->[0][1]{newQueryState}; + + $self->assert_num_equals(2, $res->[0][1]{total}); + # will have removed 'D' (old exemplar) and 'A' (touched) + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{removed} }); + $self->assert_not_null(grep { $_ eq $idd } + map { $_ } @{ $res->[0][1]->{removed} }); + $self->assert_not_null(grep { $_ eq $ida } + map { $_ } @{ $res->[0][1]->{removed} }); + $self->assert_not_null(grep { $_ eq $idc } + map { $_ } @{ $res->[0][1]->{removed} }); + $self->assert_deep_equals([ { id => $idd, index => 0 } ], + $res->[0][1]{added}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged_otherfolder b/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged_otherfolder index 5881cd9974..d8a9fbaeb0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged_otherfolder +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged_otherfolder @@ -2,150 +2,213 @@ use Cassandane::Tiny; sub test_email_querychanges_sortflagged_otherfolder - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - my %exp; - my $dt; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "generating email A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -3)); - $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - - xlog $self, "Get mailbox id"; - $res = $jmap->CallMethods([['Mailbox/query', {}, "R1"]]); - my $mbid = $res->[0][1]->{ids}[0]; - $self->assert_not_null($mbid); - - xlog $self, "Get email id"; - $res = $jmap->CallMethods([['Email/query', { - filter => { inMailbox => $mbid }, - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); - - $state = $res->[0][1]->{queryState}; - - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B", body => "b"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "generating email C referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -2)); - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "c"); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "Create new mailbox"; - $res = $jmap->CallMethods([['Mailbox/set', { create => { 1 => { name => "foo" } } }, "R1"]]); - - $self->{store}->set_folder("INBOX.foo"); - xlog $self, "generating email D referencing A (in foo)"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -1)); - $exp{D} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "d"); - $exp{D}->set_attributes(uid => 1, cid => $exp{A}->get_attribute('cid')); - - # EXPECTED ORDER OF MESSAGES NOW BY DATE IS: - # A C B (with D in the other mailbox) - # fetch them all by ID now to get an ID map - $res = $jmap->CallMethods([['Email/query', { - filter => { inMailbox => $mbid }, - sort => [ - { property => "receivedAt", - "isAscending" => $JSON::true }, - ], - }, "R1"]]); - my @ids = @{$res->[0][1]->{ids}}; - $self->assert_num_equals(3, scalar @ids); - $self->assert_str_equals($ida, $ids[0]); - my $idc = $ids[1]; - my $idb = $ids[2]; - - # raw fetch - check order now - $res = $jmap->CallMethods([['Email/query', { - filter => { inMailbox => $mbid }, - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $self->assert_deep_equals([$idb, $idc], $res->[0][1]->{ids}); - - $res = $jmap->CallMethods([['Email/queryChanges', { - filter => { inMailbox => $mbid }, - sinceQueryState => $state, collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $state = $res->[0][1]{newQueryState}; - - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{removed}}); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{added}}); - # check that the order is B C - $self->assert_deep_equals([{id => $idb, index => 0}, {id => $idc, index => 1}], $res->[0][1]{added}); - - $talk->select("INBOX.foo"); - $talk->store('1', "+flags", '\\Flagged'); - - # this has put the flag on D, which should sort C to the top! - - # raw fetch - check order now - $res = $jmap->CallMethods([['Email/query', { - filter => { inMailbox => $mbid }, - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $self->assert_deep_equals([$idc, $idb], $res->[0][1]->{ids}); - - $res = $jmap->CallMethods([['Email/queryChanges', { - filter => { inMailbox => $mbid }, - sinceQueryState => $state, collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $state = $res->[0][1]{newQueryState}; - - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{removed}}); - $self->assert_not_null(grep { $_ eq $ida } map { $_ } @{$res->[0][1]->{removed}}); - $self->assert_not_null(grep { $_ eq $idc } map { $_ } @{$res->[0][1]->{removed}}); - $self->assert_deep_equals([{id => $idc, index => 0}], $res->[0][1]{added}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + my %exp; + my $dt; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "generating email A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -3)); + $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + + xlog $self, "Get mailbox id"; + $res = $jmap->CallMethods([ [ 'Mailbox/query', {}, "R1" ] ]); + my $mbid = $res->[0][1]->{ids}[0]; + $self->assert_not_null($mbid); + + xlog $self, "Get email id"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { inMailbox => $mbid }, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); + + $state = $res->[0][1]->{queryState}; + + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B", body => "b"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "generating email C referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -2)); + $exp{C} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "c" + ); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "Create new mailbox"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/set', { create => { 1 => { name => "foo" } } }, "R1" ] ]); + + $self->{store}->set_folder("INBOX.foo"); + xlog $self, "generating email D referencing A (in foo)"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -1)); + $exp{D} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "d" + ); + $exp{D}->set_attributes(uid => 1, cid => $exp{A}->get_attribute('cid')); + + # EXPECTED ORDER OF MESSAGES NOW BY DATE IS: + # A C B (with D in the other mailbox) + # fetch them all by ID now to get an ID map + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { inMailbox => $mbid }, + sort => [ + { + property => "receivedAt", + "isAscending" => $JSON::true + }, + ], + }, + "R1" + ] ]); + my @ids = @{ $res->[0][1]->{ids} }; + $self->assert_num_equals(3, scalar @ids); + $self->assert_str_equals($ida, $ids[0]); + my $idc = $ids[1]; + my $idb = $ids[2]; + + # raw fetch - check order now + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { inMailbox => $mbid }, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $self->assert_deep_equals([ $idb, $idc ], $res->[0][1]->{ids}); + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + filter => { inMailbox => $mbid }, + sinceQueryState => $state, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $state = $res->[0][1]{newQueryState}; + + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{removed} }); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{added} }); + # check that the order is B C + $self->assert_deep_equals( + [ { id => $idb, index => 0 }, { id => $idc, index => 1 } ], + $res->[0][1]{added}); + + $talk->select("INBOX.foo"); + $talk->store('1', "+flags", '\\Flagged'); + + # this has put the flag on D, which should sort C to the top! + + # raw fetch - check order now + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { inMailbox => $mbid }, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $self->assert_deep_equals([ $idc, $idb ], $res->[0][1]->{ids}); + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + filter => { inMailbox => $mbid }, + sinceQueryState => $state, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $state = $res->[0][1]{newQueryState}; + + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{removed} }); + $self->assert_not_null(grep { $_ eq $ida } + map { $_ } @{ $res->[0][1]->{removed} }); + $self->assert_not_null(grep { $_ eq $idc } + map { $_ } @{ $res->[0][1]->{removed} }); + $self->assert_deep_equals([ { id => $idc, index => 0 } ], + $res->[0][1]{added}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged_topmessage b/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged_topmessage index cc95d52231..2a7180671c 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged_topmessage +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_sortflagged_topmessage @@ -2,139 +2,202 @@ use Cassandane::Tiny; sub test_email_querychanges_sortflagged_topmessage - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - my %exp; - my $dt; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "generating email A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -3)); - $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - - xlog $self, "Get email id"; - $res = $jmap->CallMethods([['Email/query', { - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); - - $state = $res->[0][1]->{queryState}; - - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B", body => "b"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "generating email C referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -2)); - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "c"); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "generating email D referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -1)); - $exp{D} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "d"); - $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); - - # EXPECTED ORDER OF MESSAGES NOW BY DATE IS: - # A C D B - # fetch them all by ID now to get an ID map - $res = $jmap->CallMethods([['Email/query', { - sort => [ - { property => "receivedAt", - "isAscending" => $JSON::true }, - ], - }, "R1"]]); - my @ids = @{$res->[0][1]->{ids}}; - $self->assert_num_equals(4, scalar @ids); - $self->assert_str_equals($ida, $ids[0]); - my $idc = $ids[1]; - my $idd = $ids[2]; - my $idb = $ids[3]; - - # raw fetch - check order now - $res = $jmap->CallMethods([['Email/query', { - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $self->assert_deep_equals([$idb, $idd], $res->[0][1]->{ids}); - - $res = $jmap->CallMethods([['Email/queryChanges', { - sinceQueryState => $state, collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $state = $res->[0][1]{newQueryState}; - - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{removed}}); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{added}}); - # check that the order is B D - $self->assert_deep_equals([{id => $idb, index => 0}, {id => $idd, index => 1}], $res->[0][1]{added}); - - $talk->select("INBOX"); - $talk->store('4', "+flags", '\\Flagged'); - - # this will sort D to the top because of the flag on D - - # raw fetch - check order now - $res = $jmap->CallMethods([['Email/query', { - collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $self->assert_deep_equals([$idd, $idb], $res->[0][1]->{ids}); - - $res = $jmap->CallMethods([['Email/queryChanges', { - sinceQueryState => $state, collapseThreads => $JSON::true, - sort => [ - { property => "someInThreadHaveKeyword", - keyword => "\$flagged", - isAscending => $JSON::false }, - { property => "receivedAt", - isAscending => $JSON::false }, - ], - }, "R1"]]); - $state = $res->[0][1]{newQueryState}; - - $self->assert_num_equals(2, $res->[0][1]{total}); - # will have removed 'D' (touched) as well as - # XXX: C and A because it can't know what the old order was, oh well - $self->assert_num_equals(3, scalar @{$res->[0][1]->{removed}}); - $self->assert_not_null(grep { $_ eq $idd } map { $_ } @{$res->[0][1]->{removed}}); - $self->assert_not_null(grep { $_ eq $ida } map { $_ } @{$res->[0][1]->{removed}}); - $self->assert_not_null(grep { $_ eq $idc } map { $_ } @{$res->[0][1]->{removed}}); - $self->assert_deep_equals([{id => $idd, index => 0}], $res->[0][1]{added}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + my %exp; + my $dt; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "generating email A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -3)); + $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + + xlog $self, "Get email id"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); + + $state = $res->[0][1]->{queryState}; + + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B", body => "b"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "generating email C referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -2)); + $exp{C} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "c" + ); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "generating email D referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -1)); + $exp{D} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "d" + ); + $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); + + # EXPECTED ORDER OF MESSAGES NOW BY DATE IS: + # A C D B + # fetch them all by ID now to get an ID map + $res = $jmap->CallMethods([ [ + 'Email/query', + { + sort => [ + { + property => "receivedAt", + "isAscending" => $JSON::true + }, + ], + }, + "R1" + ] ]); + my @ids = @{ $res->[0][1]->{ids} }; + $self->assert_num_equals(4, scalar @ids); + $self->assert_str_equals($ida, $ids[0]); + my $idc = $ids[1]; + my $idd = $ids[2]; + my $idb = $ids[3]; + + # raw fetch - check order now + $res = $jmap->CallMethods([ [ + 'Email/query', + { + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $self->assert_deep_equals([ $idb, $idd ], $res->[0][1]->{ids}); + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sinceQueryState => $state, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $state = $res->[0][1]{newQueryState}; + + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{removed} }); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{added} }); + # check that the order is B D + $self->assert_deep_equals( + [ { id => $idb, index => 0 }, { id => $idd, index => 1 } ], + $res->[0][1]{added}); + + $talk->select("INBOX"); + $talk->store('4', "+flags", '\\Flagged'); + + # this will sort D to the top because of the flag on D + + # raw fetch - check order now + $res = $jmap->CallMethods([ [ + 'Email/query', + { + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $self->assert_deep_equals([ $idd, $idb ], $res->[0][1]->{ids}); + + $res = $jmap->CallMethods([ [ + 'Email/queryChanges', + { + sinceQueryState => $state, + collapseThreads => $JSON::true, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$flagged", + isAscending => $JSON::false + }, + { + property => "receivedAt", + isAscending => $JSON::false + }, + ], + }, + "R1" + ] ]); + $state = $res->[0][1]{newQueryState}; + + $self->assert_num_equals(2, $res->[0][1]{total}); + # will have removed 'D' (touched) as well as + # XXX: C and A because it can't know what the old order was, oh well + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{removed} }); + $self->assert_not_null(grep { $_ eq $idd } + map { $_ } @{ $res->[0][1]->{removed} }); + $self->assert_not_null(grep { $_ eq $ida } + map { $_ } @{ $res->[0][1]->{removed} }); + $self->assert_not_null(grep { $_ eq $idc } + map { $_ } @{ $res->[0][1]->{removed} }); + $self->assert_deep_equals([ { id => $idd, index => 0 } ], + $res->[0][1]{added}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_thread b/cassandane/tiny-tests/JMAPEmail/email_querychanges_thread index 02f59ae401..03ee38c4ff 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_thread +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_thread @@ -2,79 +2,105 @@ use Cassandane::Tiny; sub test_email_querychanges_thread - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - my %exp; - my $dt; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "generating email A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -3)); - $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - - xlog $self, "Get email id"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); - - $state = $res->[0][1]->{queryState}; - - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B", body => "b"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "generating email C referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -2)); - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "c"); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "generating email D referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -1)); - $exp{D} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "d"); - $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); - - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state, collapseThreads => JSON::true }, "R1"]]); - $state = $res->[0][1]{newQueryState}; - - $self->assert_num_equals(2, $res->[0][1]{total}); - # assert that IDA got destroyed - $self->assert_not_null(grep { $_ eq $ida } map { $_ } @{$res->[0][1]->{removed}}); - # and not recreated - $self->assert_null(grep { $_ eq $ida } map { $_->{id} } @{$res->[0][1]->{added}}); - - $talk->select("INBOX"); - $talk->store('3', "+flags", '\\Deleted'); - $talk->expunge(); - - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state, collapseThreads => JSON::true }, "R1"]]); - $state = $res->[0][1]{newQueryState}; - - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert(ref($res->[0][1]{added}) eq 'ARRAY'); - $self->assert_num_equals(0, scalar @{$res->[0][1]{added}}); - $self->assert(ref($res->[0][1]{removed}) eq 'ARRAY'); - $self->assert_num_equals(0, scalar @{$res->[0][1]{removed}}); - - $talk->store('3', "+flags", '\\Deleted'); - $talk->expunge(); - - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state, collapseThreads => JSON::true }, "R1"]]); - - $self->assert_num_equals(2, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar(@{$res->[0][1]{added}})); - $self->assert_num_equals(2, scalar(@{$res->[0][1]{removed}})); - - # same thread, back to ida - $self->assert_str_equals($ida, $res->[0][1]{added}[0]{id}); - #$self->assert_str_equals($res->[0][1]{added}[0]{threadId}, $res->[0][1]{destroyed}[0]{threadId}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + my %exp; + my $dt; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "generating email A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -3)); + $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + + xlog $self, "Get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); + + $state = $res->[0][1]->{queryState}; + + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B", body => "b"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "generating email C referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -2)); + $exp{C} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "c" + ); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "generating email D referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -1)); + $exp{D} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "d" + ); + $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); + + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', + { sinceQueryState => $state, collapseThreads => JSON::true }, "R1" + ] ] + ); + $state = $res->[0][1]{newQueryState}; + + $self->assert_num_equals(2, $res->[0][1]{total}); + # assert that IDA got destroyed + $self->assert_not_null(grep { $_ eq $ida } + map { $_ } @{ $res->[0][1]->{removed} }); + # and not recreated + $self->assert_null(grep { $_ eq $ida } + map { $_->{id} } @{ $res->[0][1]->{added} }); + + $talk->select("INBOX"); + $talk->store('3', "+flags", '\\Deleted'); + $talk->expunge(); + + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', + { sinceQueryState => $state, collapseThreads => JSON::true }, "R1" + ] ] + ); + $state = $res->[0][1]{newQueryState}; + + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert(ref($res->[0][1]{added}) eq 'ARRAY'); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{added} }); + $self->assert(ref($res->[0][1]{removed}) eq 'ARRAY'); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{removed} }); + + $talk->store('3', "+flags", '\\Deleted'); + $talk->expunge(); + + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', + { sinceQueryState => $state, collapseThreads => JSON::true }, "R1" + ] ] + ); + + $self->assert_num_equals(2, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar(@{ $res->[0][1]{added} })); + $self->assert_num_equals(2, scalar(@{ $res->[0][1]{removed} })); + + # same thread, back to ida + $self->assert_str_equals($ida, $res->[0][1]{added}[0]{id}); +#$self->assert_str_equals($res->[0][1]{added}[0]{threadId}, $res->[0][1]{destroyed}[0]{threadId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_toomany b/cassandane/tiny-tests/JMAPEmail/email_querychanges_toomany index 9332d0ee1c..01501966fe 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_toomany +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_toomany @@ -2,43 +2,57 @@ use Cassandane::Tiny; sub test_email_querychanges_toomany - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("Email A") || die; - - xlog $self, "Get email id"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); - - $state = $res->[0][1]->{queryState}; - - $self->make_message("Email B") || die; - - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - - my ($idb) = grep { $_ ne $ida } @{$res->[0][1]->{ids}}; - - xlog $self, "get email list updates"; - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state, maxChanges => 1 }, "R1"]]); - - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("tooManyChanges", $res->[0][1]{type}); - $self->assert_str_equals("R1", $res->[0][2]); - - xlog $self, "get email list updates with threads collapsed"; - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state, collapseThreads => JSON::true, maxChanges => 1 }, "R1"]]); - - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("tooManyChanges", $res->[0][1]{type}); - $self->assert_str_equals("R1", $res->[0][2]); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("Email A") || die; + + xlog $self, "Get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); + + $state = $res->[0][1]->{queryState}; + + $self->make_message("Email B") || die; + + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + + my ($idb) = grep { $_ ne $ida } @{ $res->[0][1]->{ids} }; + + xlog $self, "get email list updates"; + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', { sinceQueryState => $state, maxChanges => 1 }, + "R1" + ] ] + ); + + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("tooManyChanges", $res->[0][1]{type}); + $self->assert_str_equals("R1", $res->[0][2]); + + xlog $self, "get email list updates with threads collapsed"; + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', + { + sinceQueryState => $state, + collapseThreads => JSON::true, + maxChanges => 1 + }, + "R1" + ] ] + ); + + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("tooManyChanges", $res->[0][1]{type}); + $self->assert_str_equals("R1", $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_querychanges_zerosince b/cassandane/tiny-tests/JMAPEmail/email_querychanges_zerosince index 9c4ab417a3..5237f46e59 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_querychanges_zerosince +++ b/cassandane/tiny-tests/JMAPEmail/email_querychanges_zerosince @@ -2,38 +2,43 @@ use Cassandane::Tiny; sub test_email_querychanges_zerosince - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $res; - my $state; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $res; + my $state; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("Email A") || die; + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("Email A") || die; - xlog $self, "Get email id"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ida = $res->[0][1]->{ids}[0]; - $self->assert_not_null($ida); + xlog $self, "Get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ida = $res->[0][1]->{ids}[0]; + $self->assert_not_null($ida); - $state = $res->[0][1]->{queryState}; + $state = $res->[0][1]->{queryState}; - $self->make_message("Email B") || die; + $self->make_message("Email B") || die; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); - my ($idb) = grep { $_ ne $ida } @{$res->[0][1]->{ids}}; + my ($idb) = grep { $_ ne $ida } @{ $res->[0][1]->{ids} }; - xlog $self, "get email list updates"; - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => $state }, "R1"]]); + xlog $self, "get email list updates"; + $res = $jmap->CallMethods( + [ [ 'Email/queryChanges', { sinceQueryState => $state }, "R1" ] ]); - $self->assert_equals($idb, $res->[0][1]{added}[0]{id}); + $self->assert_equals($idb, $res->[0][1]{added}[0]{id}); - xlog $self, "get email list updates with threads collapsed"; - $res = $jmap->CallMethods([['Email/queryChanges', { sinceQueryState => "0", collapseThreads => JSON::true }, "R1"]]); - $self->assert_equals('error', $res->[0][0]); + xlog $self, "get email list updates with threads collapsed"; + $res = $jmap->CallMethods( + [ [ + 'Email/queryChanges', + { sinceQueryState => "0", collapseThreads => JSON::true }, "R1" + ] ] + ); + $self->assert_equals('error', $res->[0][0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_seen_shared b/cassandane/tiny-tests/JMAPEmail/email_seen_shared index 3574e5bd84..34b2acf01f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_seen_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_seen_shared @@ -2,71 +2,84 @@ use Cassandane::Tiny; sub test_email_seen_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Share account - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lr") or die; + # Share account + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lr") or die; - # Create mailbox A - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lrs") or die; + # Create mailbox A + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lrs") or die; - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - $self->make_message("Email", store => $self->{adminstore}) or die; + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + $self->make_message("Email", store => $self->{adminstore}) or die; - # Set \Seen on message A as user cassandane - $self->{store}->set_folder('user.other.A'); - $talk->select('user.other.A'); - $talk->store('1', '+flags', '(\\Seen)'); + # Set \Seen on message A as user cassandane + $self->{store}->set_folder('user.other.A'); + $talk->select('user.other.A'); + $talk->store('1', '+flags', '(\\Seen)'); - # Get email and assert $seen - my $res = $jmap->CallMethods([ - ['Email/query', { - accountId => 'other', - }, 'R1'], - ['Email/get', { - accountId => 'other', - properties => ['keywords'], - '#ids' => { - resultOf => 'R1', name => 'Email/query', path => '/ids' - } - }, 'R2' ] - ]); - my $emailId = $res->[1][1]{list}[0]{id}; - my $wantKeywords = { '$seen' => JSON::true }; - $self->assert_deep_equals($wantKeywords, $res->[1][1]{list}[0]{keywords}); + # Get email and assert $seen + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + accountId => 'other', + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + properties => ['keywords'], + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + } + }, + 'R2' + ] + ]); + my $emailId = $res->[1][1]{list}[0]{id}; + my $wantKeywords = { '$seen' => JSON::true }; + $self->assert_deep_equals($wantKeywords, $res->[1][1]{list}[0]{keywords}); - # Set $seen via JMAP on the shared mailbox - $res = $jmap->CallMethods([ - ['Email/set', { - accountId => 'other', - update => { - $emailId => { - keywords => { }, - }, - }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); + # Set $seen via JMAP on the shared mailbox + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'other', + update => { + $emailId => { + keywords => {}, + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); - # Assert $seen got updated - $res = $jmap->CallMethods([ - ['Email/get', { - accountId => 'other', - properties => ['keywords'], - ids => [$emailId], - }, 'R1' ] - ]); - $wantKeywords = { }; - $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); + # Assert $seen got updated + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + properties => ['keywords'], + ids => [$emailId], + }, + 'R1' + ] ]); + $wantKeywords = {}; + $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_seen_shared_twofolder b/cassandane/tiny-tests/JMAPEmail/email_seen_shared_twofolder index 21c6940af1..5a7de6f54b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_seen_shared_twofolder +++ b/cassandane/tiny-tests/JMAPEmail/email_seen_shared_twofolder @@ -2,77 +2,90 @@ use Cassandane::Tiny; sub test_email_seen_shared_twofolder - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Share account - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lr") or die; + # Share account + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lr") or die; - # Create mailbox A - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lrs") or die; - $admintalk->create("user.other.A.sub") or die; - $admintalk->setacl("user.other.A.sub", "cassandane", "lrs") or die; + # Create mailbox A + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lrs") or die; + $admintalk->create("user.other.A.sub") or die; + $admintalk->setacl("user.other.A.sub", "cassandane", "lrs") or die; - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - $self->make_message("Email", store => $self->{adminstore}) or die; + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + $self->make_message("Email", store => $self->{adminstore}) or die; - # Set \Seen on message A as user cassandane - $self->{store}->set_folder('user.other.A'); - $admintalk->select('user.other.A'); - $admintalk->copy('1', 'user.other.A.sub'); - $talk->select('user.other.A'); - $talk->store('1', '+flags', '(\\Seen)'); - $talk->select('user.other.A.sub'); - $talk->store('1', '+flags', '(\\Seen)'); + # Set \Seen on message A as user cassandane + $self->{store}->set_folder('user.other.A'); + $admintalk->select('user.other.A'); + $admintalk->copy('1', 'user.other.A.sub'); + $talk->select('user.other.A'); + $talk->store('1', '+flags', '(\\Seen)'); + $talk->select('user.other.A.sub'); + $talk->store('1', '+flags', '(\\Seen)'); - # Get email and assert $seen - my $res = $jmap->CallMethods([ - ['Email/query', { - accountId => 'other', - }, 'R1'], - ['Email/get', { - accountId => 'other', - properties => ['keywords'], - '#ids' => { - resultOf => 'R1', name => 'Email/query', path => '/ids' - } - }, 'R2' ] - ]); - my $emailId = $res->[1][1]{list}[0]{id}; - my $wantKeywords = { '$seen' => JSON::true }; - $self->assert_deep_equals($wantKeywords, $res->[1][1]{list}[0]{keywords}); + # Get email and assert $seen + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + accountId => 'other', + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + properties => ['keywords'], + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + } + }, + 'R2' + ] + ]); + my $emailId = $res->[1][1]{list}[0]{id}; + my $wantKeywords = { '$seen' => JSON::true }; + $self->assert_deep_equals($wantKeywords, $res->[1][1]{list}[0]{keywords}); - # Set $seen via JMAP on the shared mailbox - $res = $jmap->CallMethods([ - ['Email/set', { - accountId => 'other', - update => { - $emailId => { - keywords => { }, - }, - }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); + # Set $seen via JMAP on the shared mailbox + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'other', + update => { + $emailId => { + keywords => {}, + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); - # Assert $seen got updated - $res = $jmap->CallMethods([ - ['Email/get', { - accountId => 'other', - properties => ['keywords'], - ids => [$emailId], - }, 'R1' ] - ]); - $wantKeywords = { }; - $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); + # Assert $seen got updated + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + properties => ['keywords'], + ids => [$emailId], + }, + 'R1' + ] ]); + $wantKeywords = {}; + $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_seen_shared_twofolder_hidden b/cassandane/tiny-tests/JMAPEmail/email_seen_shared_twofolder_hidden index eae7df4489..575cf74bd5 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_seen_shared_twofolder_hidden +++ b/cassandane/tiny-tests/JMAPEmail/email_seen_shared_twofolder_hidden @@ -2,76 +2,89 @@ use Cassandane::Tiny; sub test_email_seen_shared_twofolder_hidden - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Share account - $self->{instance}->create_user("other"); - $admintalk->setacl("user.other", "cassandane", "lr") or die; + # Share account + $self->{instance}->create_user("other"); + $admintalk->setacl("user.other", "cassandane", "lr") or die; - # Create mailbox A - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lrs") or die; - # NOTE: user cassandane does NOT get permission to see this one - $admintalk->create("user.other.A.sub") or die; - $admintalk->setacl("user.other.A.sub", "cassandane", "") or die; + # Create mailbox A + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lrs") or die; + # NOTE: user cassandane does NOT get permission to see this one + $admintalk->create("user.other.A.sub") or die; + $admintalk->setacl("user.other.A.sub", "cassandane", "") or die; - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - $self->make_message("Email", store => $self->{adminstore}) or die; + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + $self->make_message("Email", store => $self->{adminstore}) or die; - # Set \Seen on message A as user cassandane - $self->{store}->set_folder('user.other.A'); - $admintalk->select('user.other.A'); - $admintalk->copy('1', 'user.other.A.sub'); - $talk->select('user.other.A'); - $talk->store('1', '+flags', '(\\Seen)'); + # Set \Seen on message A as user cassandane + $self->{store}->set_folder('user.other.A'); + $admintalk->select('user.other.A'); + $admintalk->copy('1', 'user.other.A.sub'); + $talk->select('user.other.A'); + $talk->store('1', '+flags', '(\\Seen)'); - # Get email and assert $seen - my $res = $jmap->CallMethods([ - ['Email/query', { - accountId => 'other', - }, 'R1'], - ['Email/get', { - accountId => 'other', - properties => ['keywords'], - '#ids' => { - resultOf => 'R1', name => 'Email/query', path => '/ids' - } - }, 'R2' ] - ]); - my $emailId = $res->[1][1]{list}[0]{id}; - my $wantKeywords = { '$seen' => JSON::true }; - $self->assert_deep_equals($wantKeywords, $res->[1][1]{list}[0]{keywords}); + # Get email and assert $seen + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + accountId => 'other', + }, + 'R1' + ], + [ + 'Email/get', + { + accountId => 'other', + properties => ['keywords'], + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + } + }, + 'R2' + ] + ]); + my $emailId = $res->[1][1]{list}[0]{id}; + my $wantKeywords = { '$seen' => JSON::true }; + $self->assert_deep_equals($wantKeywords, $res->[1][1]{list}[0]{keywords}); - # Set $seen via JMAP on the shared mailbox - $res = $jmap->CallMethods([ - ['Email/set', { - accountId => 'other', - update => { - $emailId => { - keywords => { }, - }, - }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); + # Set $seen via JMAP on the shared mailbox + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'other', + update => { + $emailId => { + keywords => {}, + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); - # Assert $seen got updated - $res = $jmap->CallMethods([ - ['Email/get', { - accountId => 'other', - properties => ['keywords'], - ids => [$emailId], - }, 'R1' ] - ]); - $wantKeywords = { }; - $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); + # Assert $seen got updated + $res = $jmap->CallMethods([ [ + 'Email/get', + { + accountId => 'other', + properties => ['keywords'], + ids => [$emailId], + }, + 'R1' + ] ]); + $wantKeywords = {}; + $self->assert_deep_equals($wantKeywords, $res->[0][1]{list}[0]{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_attachments b/cassandane/tiny-tests/JMAPEmail/email_set_attachments index 6b78ad17d7..c7edcdf919 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_attachments +++ b/cassandane/tiny-tests/JMAPEmail/email_set_attachments @@ -2,156 +2,180 @@ use Cassandane::Tiny; sub test_email_set_attachments - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; - # Generate an email to have some blob ids - xlog $self, "Generate an email in $inbox via IMAP"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "Content-Disposition: inline\r\n" . "\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: image/jpeg;foo=bar\r\n" - . "Content-Disposition: attachment\r\n" - . "Content-Transfer-Encoding: base64\r\n" . "\r\n" - . "beefc0de" - . "\r\n--sub\r\n" - . "Content-Type: image/png\r\n" - . "Content-Disposition: attachment\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . "f00bae==" - . "\r\n--sub--\r\n", - ); + # Generate an email to have some blob ids + xlog $self, "Generate an email in $inbox via IMAP"; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: image/jpeg;foo=bar\r\n" + . "Content-Disposition: attachment\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "beefc0de" + . "\r\n--sub\r\n" + . "Content-Type: image/png\r\n" + . "Content-Disposition: attachment\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "f00bae==" + . "\r\n--sub--\r\n", + ); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { ids => $ids }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => $ids }, "R1" ] ]); + my $msg = $res->[0][1]{list}[0]; - my %m = map { $_->{type} => $_ } @{$res->[0][1]{list}[0]->{attachments}}; - my $blobJpeg = $m{"image/jpeg"}->{blobId}; - my $blobPng = $m{"image/png"}->{blobId}; - $self->assert_not_null($blobJpeg); - $self->assert_not_null($blobPng); + my %m = map { $_->{type} => $_ } @{ $res->[0][1]{list}[0]->{attachments} }; + my $blobJpeg = $m{"image/jpeg"}->{blobId}; + my $blobPng = $m{"image/png"}->{blobId}; + $self->assert_not_null($blobJpeg); + $self->assert_not_null($blobPng); - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $shortfname = "test\N{GRINNING FACE}.jpg"; - my $longfname = "a_very_long_filename_thats_looking_quite_bogus_but_in_fact_is_absolutely_valid\N{GRINNING FACE}!.bin"; + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + my $shortfname = "test\N{GRINNING FACE}.jpg"; + my $longfname + = "a_very_long_filename_thats_looking_quite_bogus_but_in_fact_is_absolutely_valid\N{GRINNING FACE}!.bin"; - my $draft = { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo", - htmlBody => [{ partId => '1' }], - bodyValues => { - '1' => { - value => "I'm givin' ya one last chance ta surrenda! ". - "", - }, - }, - attachments => [{ - blobId => $blobJpeg, - name => $shortfname, - type => 'image/jpeg', - }, { - blobId => $blobPng, - cid => "foo\@local", - type => 'image/png', - disposition => 'inline', - }, { - blobId => $blobJpeg, - type => "application/test", - name => $longfname, - }, { - blobId => $blobPng, - type => "application/test2", - name => "simple", - }], - keywords => { '$draft' => JSON::true }, - }; + my $draft = { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + htmlBody => [ { partId => '1' } ], + bodyValues => { + '1' => { + value => "I'm givin' ya one last chance ta surrenda! " + . "", + }, + }, + attachments => [ + { + blobId => $blobJpeg, + name => $shortfname, + type => 'image/jpeg', + }, + { + blobId => $blobPng, + cid => "foo\@local", + type => 'image/png', + disposition => 'inline', + }, + { + blobId => $blobJpeg, + type => "application/test", + name => $longfname, + }, + { + blobId => $blobPng, + type => "application/test2", + name => "simple", + } + ], + keywords => { '$draft' => JSON::true }, + }; - my $wantBodyStructure = { - type => 'multipart/mixed', - name => undef, - cid => undef, + my $wantBodyStructure = { + type => 'multipart/mixed', + name => undef, + cid => undef, + disposition => undef, + subParts => [ + { + type => 'multipart/related', + name => undef, + cid => undef, disposition => undef, - subParts => [{ - type => 'multipart/related', - name => undef, - cid => undef, + subParts => [ + { + type => 'text/html', + name => undef, + cid => undef, disposition => undef, - subParts => [{ - type => 'text/html', - name => undef, - cid => undef, - disposition => undef, - subParts => [], - },{ - type => 'image/png', - cid => "foo\@local", - disposition => 'inline', - name => undef, - subParts => [], - }], - },{ - type => 'image/jpeg', - name => $shortfname, - cid => undef, - disposition => 'attachment', - subParts => [], - },{ - type => 'application/test', - name => $longfname, - cid => undef, - disposition => 'attachment', - subParts => [], - },{ - type => 'application/test2', - name => 'simple', - cid => undef, - disposition => 'attachment', - subParts => [], - }] - }; + subParts => [], + }, + { + type => 'image/png', + cid => "foo\@local", + disposition => 'inline', + name => undef, + subParts => [], + } + ], + }, + { + type => 'image/jpeg', + name => $shortfname, + cid => undef, + disposition => 'attachment', + subParts => [], + }, + { + type => 'application/test', + name => $longfname, + cid => undef, + disposition => 'attachment', + subParts => [], + }, + { + type => 'application/test2', + name => 'simple', + cid => undef, + disposition => 'attachment', + subParts => [], + } + ] + }; - xlog $self, "Create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "Create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { - ids => [$id], - properties => ['bodyStructure'], - bodyProperties => ['type', 'name', 'cid','disposition', 'subParts'], - }, "R1"]]); - $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$id], + properties => ['bodyStructure'], + bodyProperties => [ 'type', 'name', 'cid', 'disposition', 'subParts' ], + }, + "R1" + ] ]); + $msg = $res->[0][1]->{list}[0]; - $self->assert_deep_equals($wantBodyStructure, $msg->{bodyStructure}); + $self->assert_deep_equals($wantBodyStructure, $msg->{bodyStructure}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_blobencoding b/cassandane/tiny-tests/JMAPEmail/email_set_blobencoding index 49818ebfab..9478b5a873 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_blobencoding +++ b/cassandane/tiny-tests/JMAPEmail/email_set_blobencoding @@ -2,20 +2,19 @@ use Cassandane::Tiny; sub test_email_set_blobencoding - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Upload a data blob"; - my $binary = slurp_file(abs_path('data/logo.gif')); - my $data = $jmap->Upload($binary, "image/gif"); - my $dataBlobId = $data->{blobId}; + xlog $self, "Upload a data blob"; + my $binary = slurp_file(abs_path('data/logo.gif')); + my $data = $jmap->Upload($binary, "image/gif"); + my $dataBlobId = $data->{blobId}; - my $emailBlob = <<'EOF'; + my $emailBlob = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -26,47 +25,56 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $emailBlob =~ s/\r?\n/\r\n/gs; - $data = $jmap->Upload($emailBlob, "application/octet"); - my $rfc822Blobid = $data->{blobId}; + $emailBlob =~ s/\r?\n/\r\n/gs; + $data = $jmap->Upload($emailBlob, "application/octet"); + my $rfc822Blobid = $data->{blobId}; - xlog $self, "Create email with body structure"; - my $inboxid = $self->getinbox()->{id}; - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [{ name => "Test", email => q{foo@bar} }], - subject => "test", - textBody => [{ - type => 'text/plain', - partId => '1', - }], - bodyValues => { - '1' => { - value => "A text body", - }, - }, - attachments => [{ - type => 'image/gif', - blobId => $dataBlobId, - }, { - type => 'message/rfc822', - blobId => $rfc822Blobid, - }], - }; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { '1' => $email } }, 'R1'], - ['Email/get', { - ids => [ '#1' ], - properties => [ 'bodyStructure' ], - bodyProperties => [ 'type', 'header:Content-Transfer-Encoding' ], - }, 'R2' ], - ]); + xlog $self, "Create email with body structure"; + my $inboxid = $self->getinbox()->{id}; + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Test", email => q{foo@bar} } ], + subject => "test", + textBody => [ { + type => 'text/plain', + partId => '1', + } ], + bodyValues => { + '1' => { + value => "A text body", + }, + }, + attachments => [ + { + type => 'image/gif', + blobId => $dataBlobId, + }, + { + type => 'message/rfc822', + blobId => $rfc822Blobid, + } + ], + }; + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { '1' => $email } }, 'R1' ], + [ + 'Email/get', + { + ids => ['#1'], + properties => ['bodyStructure'], + bodyProperties => [ 'type', 'header:Content-Transfer-Encoding' ], + }, + 'R2' + ], + ]); - my $gotPart; - $gotPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]; - $self->assert_str_equals('message/rfc822', $gotPart->{type}); - $self->assert_str_equals(' 7BIT', $gotPart->{'header:Content-Transfer-Encoding'}); - $gotPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[2]; - $self->assert_str_equals('image/gif', $gotPart->{type}); - $self->assert_str_equals(' BASE64', uc($gotPart->{'header:Content-Transfer-Encoding'})); + my $gotPart; + $gotPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]; + $self->assert_str_equals('message/rfc822', $gotPart->{type}); + $self->assert_str_equals(' 7BIT', + $gotPart->{'header:Content-Transfer-Encoding'}); + $gotPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[2]; + $self->assert_str_equals('image/gif', $gotPart->{type}); + $self->assert_str_equals(' BASE64', + uc($gotPart->{'header:Content-Transfer-Encoding'})); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_bodystructure b/cassandane/tiny-tests/JMAPEmail/email_set_bodystructure index 176f0fc22c..e6c4c50308 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_bodystructure +++ b/cassandane/tiny-tests/JMAPEmail/email_set_bodystructure @@ -2,114 +2,134 @@ use Cassandane::Tiny; sub test_email_set_bodystructure - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "Content-Disposition: inline\r\n" . "\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . "Return-Path: \r\n" - . "Mime-Version: 1.0\r\n" - . "Content-Type: text/plain\r\n" - . "Content-Transfer-Encoding: 7bit\r\n" - . "Subject: bar\r\n" - . "From: Ava T. Nguyen \r\n" - . "Message-ID: \r\n" - . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "An embedded email" - . "\r\n--sub--\r\n", - ) || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['attachments', 'blobId'], - }, 'R2' ], - ]); - my $emailBlobId = $res->[1][1]->{list}[0]->{blobId}; - my $embeddedEmailBlobId = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "Content-Disposition: inline\r\n" . "\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "Return-Path: \r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain\r\n" + . "Content-Transfer-Encoding: 7bit\r\n" + . "Subject: bar\r\n" + . "From: Ava T. Nguyen \r\n" + . "Message-ID: \r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: Test User \r\n" . "\r\n" + . "An embedded email" + . "\r\n--sub--\r\n", + ) || die; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ 'attachments', 'blobId' ], + }, + 'R2' + ], + ]); + my $emailBlobId = $res->[1][1]->{list}[0]->{blobId}; + my $embeddedEmailBlobId = $res->[1][1]->{list}[0]->{attachments}[0]{blobId}; - xlog $self, "Upload a data blob"; - my $binary = pack "H*", "beefcode"; - my $data = $jmap->Upload($binary, "image/gif"); - my $dataBlobId = $data->{blobId}; + xlog $self, "Upload a data blob"; + my $binary = pack "H*", "beefcode"; + my $data = $jmap->Upload($binary, "image/gif"); + my $dataBlobId = $data->{blobId}; - $self->assert_not_null($emailBlobId); - $self->assert_not_null($embeddedEmailBlobId); - $self->assert_not_null($dataBlobId); + $self->assert_not_null($emailBlobId); + $self->assert_not_null($embeddedEmailBlobId); + $self->assert_not_null($dataBlobId); - my $bodyStructure = { - type => "multipart/alternative", - subParts => [{ - type => 'text/plain', - partId => '1', - }, { - type => 'message/rfc822', - blobId => $embeddedEmailBlobId, - }, { - type => 'image/gif', - blobId => $dataBlobId, - }, { - # No type set - blobId => $dataBlobId, - }, { - type => 'message/rfc822', - blobId => $emailBlobId, - }], - }; + my $bodyStructure = { + type => "multipart/alternative", + subParts => [ + { + type => 'text/plain', + partId => '1', + }, + { + type => 'message/rfc822', + blobId => $embeddedEmailBlobId, + }, + { + type => 'image/gif', + blobId => $dataBlobId, + }, + { + # No type set + blobId => $dataBlobId, + }, + { + type => 'message/rfc822', + blobId => $emailBlobId, + } + ], + }; - xlog $self, "Create email with body structure"; - my $inboxid = $self->getinbox()->{id}; - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [{ name => "Test", email => q{foo@bar} }], - subject => "test", - bodyStructure => $bodyStructure, - bodyValues => { - "1" => { - value => "A text body", - }, - }, - }; - $res = $jmap->CallMethods([ - ['Email/set', { create => { '1' => $email } }, 'R1'], - ['Email/get', { - ids => [ '#1' ], - properties => [ 'bodyStructure' ], - bodyProperties => [ 'partId', 'blobId', 'type' ], - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]); + xlog $self, "Create email with body structure"; + my $inboxid = $self->getinbox()->{id}; + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Test", email => q{foo@bar} } ], + subject => "test", + bodyStructure => $bodyStructure, + bodyValues => { + "1" => { + value => "A text body", + }, + }, + }; + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { '1' => $email } }, 'R1' ], + [ + 'Email/get', + { + ids => ['#1'], + properties => ['bodyStructure'], + bodyProperties => [ 'partId', 'blobId', 'type' ], + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); - # Normalize server-set properties - my $gotBodyStructure = $res->[1][1]{list}[0]{bodyStructure}; - $self->assert_str_equals('multipart/alternative', $gotBodyStructure->{type}); - $self->assert_null($gotBodyStructure->{blobId}); - $self->assert_str_equals('text/plain', $gotBodyStructure->{subParts}[0]{type}); - $self->assert_not_null($gotBodyStructure->{subParts}[0]{blobId}); - $self->assert_str_equals('message/rfc822', $gotBodyStructure->{subParts}[1]{type}); - $self->assert_str_equals($embeddedEmailBlobId, $gotBodyStructure->{subParts}[1]{blobId}); - $self->assert_str_equals('image/gif', $gotBodyStructure->{subParts}[2]{type}); - $self->assert_str_equals($dataBlobId, $gotBodyStructure->{subParts}[2]{blobId}); - # Default type is text/plain if no Content-Type header is set - $self->assert_str_equals('text/plain', $gotBodyStructure->{subParts}[3]{type}); - $self->assert_str_equals($dataBlobId, $gotBodyStructure->{subParts}[3]{blobId}); - $self->assert_str_equals('message/rfc822', $gotBodyStructure->{subParts}[4]{type}); - $self->assert_str_equals($emailBlobId, $gotBodyStructure->{subParts}[4]{blobId}); + # Normalize server-set properties + my $gotBodyStructure = $res->[1][1]{list}[0]{bodyStructure}; + $self->assert_str_equals('multipart/alternative', $gotBodyStructure->{type}); + $self->assert_null($gotBodyStructure->{blobId}); + $self->assert_str_equals('text/plain', + $gotBodyStructure->{subParts}[0]{type}); + $self->assert_not_null($gotBodyStructure->{subParts}[0]{blobId}); + $self->assert_str_equals('message/rfc822', + $gotBodyStructure->{subParts}[1]{type}); + $self->assert_str_equals($embeddedEmailBlobId, + $gotBodyStructure->{subParts}[1]{blobId}); + $self->assert_str_equals('image/gif', $gotBodyStructure->{subParts}[2]{type}); + $self->assert_str_equals($dataBlobId, + $gotBodyStructure->{subParts}[2]{blobId}); + # Default type is text/plain if no Content-Type header is set + $self->assert_str_equals('text/plain', + $gotBodyStructure->{subParts}[3]{type}); + $self->assert_str_equals($dataBlobId, + $gotBodyStructure->{subParts}[3]{blobId}); + $self->assert_str_equals('message/rfc822', + $gotBodyStructure->{subParts}[4]{type}); + $self->assert_str_equals($emailBlobId, + $gotBodyStructure->{subParts}[4]{blobId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_copymove_no_permission_shared b/cassandane/tiny-tests/JMAPEmail/email_set_copymove_no_permission_shared index c071860f16..db214114b0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_copymove_no_permission_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_set_copymove_no_permission_shared @@ -2,88 +2,100 @@ use Cassandane::Tiny; sub test_email_set_copymove_no_permission_shared - :min_version_3_5 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); - $admin->create("user.other"); - $admin->setacl("user.other", admin => 'lrswipkxtecdan') or die; - $admin->setacl("user.other", other => 'lrswipkxtecdn') or die; + my $admin = $self->{adminstore}->get_client(); + $admin->create("user.other"); + $admin->setacl("user.other", admin => 'lrswipkxtecdan') or die; + $admin->setacl("user.other", other => 'lrswipkxtecdn') or die; - my $service = $self->{instance}->get_service("http"); - my $otherJmap = Mail::JMAPTalk->new( - user => 'other', - password => 'pass', - host => $service->host(), - port => $service->port(), - scheme => 'http', - url => '/jmap/', - ); - $otherJmap->DefaultUsing([ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - ]); + my $service = $self->{instance}->get_service("http"); + my $otherJmap = Mail::JMAPTalk->new( + user => 'other', + password => 'pass', + host => $service->host(), + port => $service->port(), + scheme => 'http', + url => '/jmap/', + ); + $otherJmap->DefaultUsing([ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + ]); - my $res = $otherJmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, + my $res = $otherJmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + }, + mboxB => { + name => 'B', + }, + }, + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + 'email' => { + mailboxIds => { + '#mboxA' => JSON::true, }, - }, 'R1'], - ['Email/set', { - create => { - 'email' => { - mailboxIds => { - '#mboxA' => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'email', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'email', + bodyStructure => { + type => 'text/plain', + partId => 'part1', }, - }, 'R1'], - ]); - my $mboxA = $res->[0][1]->{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[0][1]->{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - my $email = $res->[1][1]->{created}{email}{id}; - $self->assert_not_null($email); + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $mboxA = $res->[0][1]->{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[0][1]->{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + my $email = $res->[1][1]->{created}{email}{id}; + $self->assert_not_null($email); - $admin->setacl("user.other.A", cassandane => 'lrs') or die; - $admin->setacl("user.other.B", cassandane => 'lrswitedn') or die; + $admin->setacl("user.other.A", cassandane => 'lrs') or die; + $admin->setacl("user.other.B", cassandane => 'lrswitedn') or die; - $res = $jmap->CallMethods([ - ['Email/set', { - accountId => 'other', - update => { - $email => { - 'mailboxIds/'.$mboxA => undef, - 'mailboxIds/'.$mboxB => JSON::true, - } - } - }, 'R1'], - ]); - $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$email}{type}); + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + accountId => 'other', + update => { + $email => { + 'mailboxIds/' . $mboxA => undef, + 'mailboxIds/' . $mboxB => JSON::true, + } + } + }, + 'R1' + ], + ]); + $self->assert_str_equals('forbidden', $res->[0][1]{notUpdated}{$email}{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_create_snooze b/cassandane/tiny-tests/JMAPEmail/email_set_create_snooze index fcca2a9505..77e1b407a3 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_create_snooze +++ b/cassandane/tiny-tests/JMAPEmail/email_set_create_snooze @@ -2,115 +2,136 @@ use Cassandane::Tiny; sub test_email_set_create_snooze - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd - :needs_component_sieve :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd + : needs_component_sieve : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # snoozed property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # snoozed property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - xlog $self, "create snooze mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "snoozed", - parentId => undef, - role => "snoozed" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $snoozedmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create snooze mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "snoozed", + parentId => undef, + role => "snoozed" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $snoozedmbox = $res->[0][1]{created}{"1"}{id}; - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R4"] - ]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsId = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R4" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsId = $res->[0][1]{created}{"1"}{id}; - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => 30)); - my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => 30)); + my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - my $draft = { - mailboxIds => { $snoozedmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - sender => [{ name => "Marvin the Martian", email => "marvin\@acme.local" }], - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - { name => "Rainer M\N{LATIN SMALL LETTER U WITH DIAERESIS}ller", email => "rainer\@de.local" }, - ], - cc => [ - { name => "Elmer Fudd", email => "elmer\@acme.local" }, - { name => "Porky Pig", email => "porky\@acme.local" }, - ], - bcc => [ - { name => "Wile E. Coyote", email => "coyote\@acme.local" }, - ], - replyTo => [ { name => undef, email => "the.other.sam\@acme.local" } ], - subject => "Memo", - textBody => [{ partId => '1' }], - htmlBody => [{ partId => '2' }], - bodyValues => { - '1' => { value => "I'm givin' ya one last chance ta surrenda!" }, - '2' => { value => "Oh!!! I hate that Rabbit." }, - }, - keywords => { '$draft' => JSON::true }, - snoozed => { "until" => "$datestr", "moveToMailboxId" => "$draftsId" }, - }; + my $draft = { + mailboxIds => { $snoozedmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + sender => + [ { name => "Marvin the Martian", email => "marvin\@acme.local" } ], + to => [ + { name => "Bugs Bunny", email => "bugs\@acme.local" }, + { + name => "Rainer M\N{LATIN SMALL LETTER U WITH DIAERESIS}ller", + email => "rainer\@de.local" + }, + ], + cc => [ + { name => "Elmer Fudd", email => "elmer\@acme.local" }, + { name => "Porky Pig", email => "porky\@acme.local" }, + ], + bcc => [ { name => "Wile E. Coyote", email => "coyote\@acme.local" }, ], + replyTo => [ { name => undef, email => "the.other.sam\@acme.local" } ], + subject => "Memo", + textBody => [ { partId => '1' } ], + htmlBody => [ { partId => '2' } ], + bodyValues => { + '1' => { value => "I'm givin' ya one last chance ta surrenda!" }, + '2' => { value => "Oh!!! I hate that Rabbit." }, + }, + keywords => { '$draft' => JSON::true }, + snoozed => { "until" => "$datestr", "moveToMailboxId" => "$draftsId" }, + }; - xlog $self, "Create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "Create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; - $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); - $self->assert_deep_equals($msg->{from}, $draft->{from}); - $self->assert_deep_equals($msg->{sender}, $draft->{sender}); - $self->assert_deep_equals($msg->{to}, $draft->{to}); - $self->assert_deep_equals($msg->{cc}, $draft->{cc}); - $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); - $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); - $self->assert_str_equals($msg->{subject}, $draft->{subject}); - $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); - $self->assert_num_equals(1, scalar keys %{$msg->{keywords}}); - $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); - $self->assert_str_equals($datestr, $msg->{addedDates}{"$snoozedmbox"}); + $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); + $self->assert_deep_equals($msg->{from}, $draft->{from}); + $self->assert_deep_equals($msg->{sender}, $draft->{sender}); + $self->assert_deep_equals($msg->{to}, $draft->{to}); + $self->assert_deep_equals($msg->{cc}, $draft->{cc}); + $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); + $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); + $self->assert_str_equals($msg->{subject}, $draft->{subject}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); + $self->assert_num_equals(1, scalar keys %{ $msg->{keywords} }); + $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); + $self->assert_str_equals($datestr, $msg->{addedDates}{"$snoozedmbox"}); - # Now change the draft keyword, which is allowed since approx ~Q1/2018. - xlog $self, "Update a draft"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $id => { 'keywords/$draft' => undef } }, - }, "R1"] - ]); - $self->assert(exists $res->[0][1]{updated}{$id}); + # Now change the draft keyword, which is allowed since approx ~Q1/2018. + xlog $self, "Update a draft"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { $id => { 'keywords/$draft' => undef } }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); - xlog $self, "trigger re-delivery of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 30 ); + xlog $self, "trigger re-delivery of snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 30); - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $id ], - properties => [ 'mailboxIds', 'keywords', 'snoozed', 'addedDates' ]}, "R7" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_equals(JSON::true, $msg->{mailboxIds}{"$draftsId"}); - $self->assert_num_equals(0, scalar keys %{$msg->{keywords}}); - $self->assert_not_null($msg->{snoozed}); - $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); - $self->assert_str_equals($datestr, $msg->{addedDates}{"$draftsId"}); + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$id], + properties => [ 'mailboxIds', 'keywords', 'snoozed', 'addedDates' ] + }, + "R7" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_equals(JSON::true, $msg->{mailboxIds}{"$draftsId"}); + $self->assert_num_equals(0, scalar keys %{ $msg->{keywords} }); + $self->assert_not_null($msg->{snoozed}); + $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); + $self->assert_str_equals($datestr, $msg->{addedDates}{"$draftsId"}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_date b/cassandane/tiny-tests/JMAPEmail/email_set_date index 27cd175644..625bb45984 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_date +++ b/cassandane/tiny-tests/JMAPEmail/email_set_date @@ -2,42 +2,50 @@ use Cassandane::Tiny; sub test_email_set_date - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => q{foo@bar} }], - to => [{ email => q{bar@foo} }], - sentAt => '2019-05-02T03:15:00+07:00', - subject => "test", - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "A text body", - }, - }, - } + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true }, - }, 'R1'], - ['Email/get', { - ids => ['#email1'], - properties => ['sentAt', 'header:Date'], - }, 'R2'], - ]); - my $email = $res->[1][1]{list}[0]; - $self->assert_str_equals('2019-05-02T03:15:00+07:00', $email->{sentAt}); - $self->assert_str_equals(' Thu, 02 May 2019 03:15:00 +0700', $email->{'header:Date'}); + from => [ { email => q{foo@bar} } ], + to => [ { email => q{bar@foo} } ], + sentAt => '2019-05-02T03:15:00+07:00', + subject => "test", + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "A text body", + }, + }, + } + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => [ 'sentAt', 'header:Date' ], + }, + 'R2' + ], + ]); + my $email = $res->[1][1]{list}[0]; + $self->assert_str_equals('2019-05-02T03:15:00+07:00', $email->{sentAt}); + $self->assert_str_equals(' Thu, 02 May 2019 03:15:00 +0700', + $email->{'header:Date'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_delayed_deleted_mailbox b/cassandane/tiny-tests/JMAPEmail/email_set_delayed_deleted_mailbox index c5514cdea7..2d64cd0543 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_delayed_deleted_mailbox +++ b/cassandane/tiny-tests/JMAPEmail/email_set_delayed_deleted_mailbox @@ -2,102 +2,121 @@ use Cassandane::Tiny; sub test_email_set_delayed_deleted_mailbox - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Create mailbox A"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - } - }, 'R1'], - ]); - my $mboxA = $res->[0][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); + xlog "Create mailbox A"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + }, + } + }, + 'R1' + ], + ]); + my $mboxA = $res->[0][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); - xlog "Create an email in Inbox"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true, - }, - messageId => ['email1@local'], - subject => 'test', - keywords => { - '$seen' => JSON::true, - }, - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog "Create an email in Inbox"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true, }, - }, 'R1'], - ]); - my $email1 = $res->[0][1]{created}{email1}{id}; - $self->assert_not_null($email1); + messageId => ['email1@local'], + subject => 'test', + keywords => { + '$seen' => JSON::true, + }, + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $email1 = $res->[0][1]{created}{email1}{id}; + $self->assert_not_null($email1); - xlog "Destroy mailbox A"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [ $mboxA ], - }, 'R2'], - ]); - $self->assert_deep_equals([$mboxA], $res->[0][1]{destroyed}); + xlog "Destroy mailbox A"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + destroy => [$mboxA], + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$mboxA], $res->[0][1]{destroyed}); - xlog "Can't move email to destroyed mailbox A"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $email1 => { - mailboxIds => { - $mboxA => JSON::true, - }, - }, + xlog "Can't move email to destroyed mailbox A"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $email1 => { + mailboxIds => { + $mboxA => JSON::true, }, - }, 'R1'], - ]); - $self->assert_deep_equals(['mailboxIds'], - $res->[0][1]{notUpdated}{$email1}{properties}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals(['mailboxIds'], + $res->[0][1]{notUpdated}{$email1}{properties}); - xlog "Can't create an email in destroyed mailbox A";; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email2 => { - mailboxIds => { - $mboxA => JSON::true, - }, - messageId => ['email2@local'], - subject => 'test', - keywords => { - '$seen' => JSON::true, - }, - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog "Can't create an email in destroyed mailbox A"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email2 => { + mailboxIds => { + $mboxA => JSON::true, + }, + messageId => ['email2@local'], + subject => 'test', + keywords => { + '$seen' => JSON::true, + }, + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } }, - }, 'R1'], - ]); - $self->assert_deep_equals(['mailboxIds'], - $res->[0][1]{notCreated}{email2}{properties}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_deep_equals(['mailboxIds'], + $res->[0][1]{notCreated}{email2}{properties}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_destroy b/cassandane/tiny-tests/JMAPEmail/email_set_destroy index e5d7927e0c..6e9fd22dad 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_destroy +++ b/cassandane/tiny-tests/JMAPEmail/email_set_destroy @@ -2,86 +2,77 @@ use Cassandane::Tiny; sub test_email_set_destroy - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create mailboxes"; - my $res = $jmap->CallMethods( - [ - [ - 'Mailbox/set', - { - create => { - "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }, - "2" => { - name => "foo", - parentId => undef, - }, - "3" => { - name => "bar", - parentId => undef, - }, - } - }, - "R1" - ] - ] - ); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null( $res->[0][1]{created} ); - my $mailboxids = { - $res->[0][1]{created}{"1"}{id} => JSON::true, - $res->[0][1]{created}{"2"}{id} => JSON::true, - $res->[0][1]{created}{"3"}{id} => JSON::true, - }; + xlog $self, "create mailboxes"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + }, + "2" => { + name => "foo", + parentId => undef, + }, + "3" => { + name => "bar", + parentId => undef, + }, + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $mailboxids = { + $res->[0][1]{created}{"1"}{id} => JSON::true, + $res->[0][1]{created}{"2"}{id} => JSON::true, + $res->[0][1]{created}{"3"}{id} => JSON::true, + }; - xlog $self, "Create a draft"; - my $draft = { - mailboxIds => $mailboxids, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" } ], - subject => "created", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "Oh!!! I *hate* that Rabbit." }}, - keywords => { '$draft' => JSON::true }, - }; - $res = $jmap->CallMethods( - [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ], - ); - my $id = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($id); + xlog $self, "Create a draft"; + my $draft = { + mailboxIds => $mailboxids, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" } ], + subject => "created", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "Oh!!! I *hate* that Rabbit." } }, + keywords => { '$draft' => JSON::true }, + }; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ],); + my $id = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($id); - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods( [ [ 'Email/get', { ids => [$id] }, "R1" ] ]); - $self->assert_num_equals(3, scalar keys %{$res->[0][1]->{list}[0]{mailboxIds}}); + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + $self->assert_num_equals(3, + scalar keys %{ $res->[0][1]->{list}[0]{mailboxIds} }); - xlog $self, "Destroy draft $id"; - $res = $jmap->CallMethods( - [ [ 'Email/set', { destroy => [ $id ] }, "R1" ] ], - ); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "Destroy draft $id"; + $res = $jmap->CallMethods([ [ 'Email/set', { destroy => [$id] }, "R1" ] ],); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods( [ [ 'Email/get', { ids => [$id] }, "R1" ] ]); - $self->assert_str_equals($id, $res->[0][1]->{notFound}[0]); + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]->{notFound}[0]); - xlog $self, "Get emails"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); + xlog $self, "Get emails"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min >= 9)) { - xlog $self, "Attempt to destroy draft $id again"; - $res = $jmap->CallMethods( - [ [ 'Email/set', { destroy => [ $id ] }, "R1" ] ], - ); - $self->assert_not_null($id, $res->[0][1]{notDestroyed}{$id}); - } + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min >= 9)) { + xlog $self, "Attempt to destroy draft $id again"; + $res = $jmap->CallMethods([ [ 'Email/set', { destroy => [$id] }, "R1" ] ],); + $self->assert_not_null($id, $res->[0][1]{notDestroyed}{$id}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_destroy_bulk b/cassandane/tiny-tests/JMAPEmail/email_set_destroy_bulk index 0e27d515a1..ed20cb09b7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_destroy_bulk +++ b/cassandane/tiny-tests/JMAPEmail/email_set_destroy_bulk @@ -2,35 +2,34 @@ use Cassandane::Tiny; sub test_email_set_destroy_bulk - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $store = $self->{store}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $store = $self->{store}; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create('INBOX.A') or die; - $talk->create('INBOX.B') or die; + $talk->create('INBOX.A') or die; + $talk->create('INBOX.B') or die; - # Email 1 is in both A and B mailboxes. - $store->set_folder('INBOX.A'); - $self->make_message('Email 1') || die; - $talk->copy(1, 'INBOX.B'); + # Email 1 is in both A and B mailboxes. + $store->set_folder('INBOX.A'); + $self->make_message('Email 1') || die; + $talk->copy(1, 'INBOX.B'); - # Email 2 is in mailbox A. - $store->set_folder('INBOX.A'); - $self->make_message('Email 2') || die; + # Email 2 is in mailbox A. + $store->set_folder('INBOX.A'); + $self->make_message('Email 2') || die; - # Email 3 is in mailbox B. - $store->set_folder('INBOX.B'); - $self->make_message('Email 3') || die; + # Email 3 is in mailbox B. + $store->set_folder('INBOX.B'); + $self->make_message('Email 3') || die; - my $res = $jmap->CallMethods([['Email/query', { }, 'R1']]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - my $ids = $res->[0][1]->{ids}; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + my $ids = $res->[0][1]->{ids}; - $res = $jmap->CallMethods([['Email/set', { destroy => $ids }, 'R1']]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{destroyed}}); + $res = $jmap->CallMethods([ [ 'Email/set', { destroy => $ids }, 'R1' ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{destroyed} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_draft b/cassandane/tiny-tests/JMAPEmail/email_set_draft index a5ccbafe8a..332ac470ff 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_draft +++ b/cassandane/tiny-tests/JMAPEmail/email_set_draft @@ -2,75 +2,85 @@ use Cassandane::Tiny; sub test_email_set_draft - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $draft = { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - sender => [{ name => "Marvin the Martian", email => "marvin\@acme.local" }], - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - { name => "Rainer M\N{LATIN SMALL LETTER U WITH DIAERESIS}ller", email => "rainer\@de.local" }, - ], - cc => [ - { name => "Elmer Fudd", email => "elmer\@acme.local" }, - { name => "Porky Pig", email => "porky\@acme.local" }, - ], - bcc => [ - { name => "Wile E. Coyote", email => "coyote\@acme.local" }, - ], - replyTo => [ { name => undef, email => "the.other.sam\@acme.local" } ], - subject => "Memo", - textBody => [{ partId => '1' }], - htmlBody => [{ partId => '2' }], - bodyValues => { - '1' => { value => "I'm givin' ya one last chance ta surrenda!" }, - '2' => { value => "Oh!!! I hate that Rabbit." }, - }, - keywords => { '$draft' => JSON::true }, - }; + my $draft = { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + sender => + [ { name => "Marvin the Martian", email => "marvin\@acme.local" } ], + to => [ + { name => "Bugs Bunny", email => "bugs\@acme.local" }, + { + name => "Rainer M\N{LATIN SMALL LETTER U WITH DIAERESIS}ller", + email => "rainer\@de.local" + }, + ], + cc => [ + { name => "Elmer Fudd", email => "elmer\@acme.local" }, + { name => "Porky Pig", email => "porky\@acme.local" }, + ], + bcc => [ { name => "Wile E. Coyote", email => "coyote\@acme.local" }, ], + replyTo => [ { name => undef, email => "the.other.sam\@acme.local" } ], + subject => "Memo", + textBody => [ { partId => '1' } ], + htmlBody => [ { partId => '2' } ], + bodyValues => { + '1' => { value => "I'm givin' ya one last chance ta surrenda!" }, + '2' => { value => "Oh!!! I hate that Rabbit." }, + }, + keywords => { '$draft' => JSON::true }, + }; - xlog $self, "Create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "Create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; - $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); - $self->assert_deep_equals($msg->{from}, $draft->{from}); - $self->assert_deep_equals($msg->{sender}, $draft->{sender}); - $self->assert_deep_equals($msg->{to}, $draft->{to}); - $self->assert_deep_equals($msg->{cc}, $draft->{cc}); - $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); - $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); - $self->assert_str_equals($msg->{subject}, $draft->{subject}); - $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); - $self->assert_num_equals(1, scalar keys %{$msg->{keywords}}); + $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); + $self->assert_deep_equals($msg->{from}, $draft->{from}); + $self->assert_deep_equals($msg->{sender}, $draft->{sender}); + $self->assert_deep_equals($msg->{to}, $draft->{to}); + $self->assert_deep_equals($msg->{cc}, $draft->{cc}); + $self->assert_deep_equals($msg->{bcc}, $draft->{bcc}); + $self->assert_deep_equals($msg->{replyTo}, $draft->{replyTo}); + $self->assert_str_equals($msg->{subject}, $draft->{subject}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); + $self->assert_num_equals(1, scalar keys %{ $msg->{keywords} }); - # Now change the draft keyword, which is allowed since approx ~Q1/2018. - xlog $self, "Update a draft"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $id => { 'keywords/$draft' => undef } }, - }, "R1"] - ]); - $self->assert(exists $res->[0][1]{updated}{$id}); + # Now change the draft keyword, which is allowed since approx ~Q1/2018. + xlog $self, "Update a draft"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { $id => { 'keywords/$draft' => undef } }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_email_duplicates_mailbox_counts b/cassandane/tiny-tests/JMAPEmail/email_set_email_duplicates_mailbox_counts index 3fc3e5d1d6..5478c5e412 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_email_duplicates_mailbox_counts +++ b/cassandane/tiny-tests/JMAPEmail/email_set_email_duplicates_mailbox_counts @@ -2,58 +2,67 @@ use Cassandane::Tiny; sub test_email_set_email_duplicates_mailbox_counts - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - - my $inboxid = $self->getinbox()->{id}; - - # This is the opposite of a tooManyMailboxes error. It makes - # sure that duplicate emails within a mailbox do not count - # as multiple mailbox instances. - - my $accountCapabilities = $self->get_account_capabilities(); - my $maxMailboxesPerEmail = $accountCapabilities->{'urn:ietf:params:jmap:mail'}{maxMailboxesPerEmail}; - - $self->assert($maxMailboxesPerEmail > 0); - - my $todo = $maxMailboxesPerEmail - 2; - - open(my $F, 'data/mime/simple.eml') || die $!; - for (1..$todo) { - $imap->create("INBOX.M$_") || die; - - # two copies in each folder - $imap->append("INBOX.M$_", $F) || die $@; - } - close($F); - - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['mailboxIds'] - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals($todo, scalar keys %{$res->[1][1]{list}[0]{mailboxIds}}); - - my $emailId = $res->[0][1]{ids}[0]; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId => { - 'keywords/foo' => JSON::true, - "mailboxIds/$inboxid" => JSON::true, - }, - } - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + + my $inboxid = $self->getinbox()->{id}; + + # This is the opposite of a tooManyMailboxes error. It makes + # sure that duplicate emails within a mailbox do not count + # as multiple mailbox instances. + + my $accountCapabilities = $self->get_account_capabilities(); + my $maxMailboxesPerEmail + = $accountCapabilities->{'urn:ietf:params:jmap:mail'}{maxMailboxesPerEmail}; + + $self->assert($maxMailboxesPerEmail > 0); + + my $todo = $maxMailboxesPerEmail - 2; + + open(my $F, 'data/mime/simple.eml') || die $!; + for (1 .. $todo) { + $imap->create("INBOX.M$_") || die; + + # two copies in each folder + $imap->append("INBOX.M$_", $F) || die $@; + } + close($F); + + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['mailboxIds'] + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals($todo, + scalar keys %{ $res->[1][1]{list}[0]{mailboxIds} }); + + my $emailId = $res->[0][1]{ids}[0]; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + 'keywords/foo' => JSON::true, + "mailboxIds/$inboxid" => JSON::true, + }, + } + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_encode_plain_text_attachment b/cassandane/tiny-tests/JMAPEmail/email_set_encode_plain_text_attachment index 5417b2309f..f790921921 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_encode_plain_text_attachment +++ b/cassandane/tiny-tests/JMAPEmail/email_set_encode_plain_text_attachment @@ -2,55 +2,65 @@ use Cassandane::Tiny; sub test_email_set_encode_plain_text_attachment - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :needs_dependency_chardet -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : needs_dependency_chardet { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $text = "This line ends with a LF\nThis line does as well\n"; + my $text = "This line ends with a LF\nThis line does as well\n"; - my $data = $jmap->Upload($text, "text/plain"); - my $blobId = $data->{blobId}; + my $data = $jmap->Upload($text, "text/plain"); + my $blobId = $data->{blobId}; - my $email = { - mailboxIds => { '$inbox' => JSON::true }, - from => [{ name => "Test", email => q{test@local} }], - subject => "test", - bodyStructure => { - type => "multipart/mixed", - subParts => [{ - type => 'text/plain', - partId => '1', - }, { - type => 'text/plain', - blobId => $blobId, - }, { - type => 'text/plain', - disposition => 'attachment', - blobId => $blobId, - }] + my $email = { + mailboxIds => { '$inbox' => JSON::true }, + from => [ { name => "Test", email => q{test@local} } ], + subject => "test", + bodyStructure => { + type => "multipart/mixed", + subParts => [ + { + type => 'text/plain', + partId => '1', }, - bodyValues => { - 1 => { - value => "A plain text body", - } + { + type => 'text/plain', + blobId => $blobId, + }, + { + type => 'text/plain', + disposition => 'attachment', + blobId => $blobId, } - }; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { '1' => $email } }, 'R1'], - ['Email/get', { - ids => [ '#1' ], - properties => [ 'bodyStructure', 'bodyValues' ], - bodyProperties => [ - 'partId', 'blobId', 'type', 'header:Content-Transfer-Encoding', 'size' - ], - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]); - my $subPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]; - $self->assert_str_equals(' QUOTED-PRINTABLE', $subPart->{'header:Content-Transfer-Encoding'}); - $subPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[2]; - $self->assert_str_equals(' BASE64', $subPart->{'header:Content-Transfer-Encoding'}); + ] + }, + bodyValues => { + 1 => { + value => "A plain text body", + } + } + }; + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { '1' => $email } }, 'R1' ], + [ + 'Email/get', + { + ids => ['#1'], + properties => [ 'bodyStructure', 'bodyValues' ], + bodyProperties => [ + 'partId', 'blobId', 'type', 'header:Content-Transfer-Encoding', + 'size' + ], + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); + my $subPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]; + $self->assert_str_equals(' QUOTED-PRINTABLE', + $subPart->{'header:Content-Transfer-Encoding'}); + $subPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[2]; + $self->assert_str_equals(' BASE64', + $subPart->{'header:Content-Transfer-Encoding'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_filename b/cassandane/tiny-tests/JMAPEmail/email_set_filename index 90c2b79049..96af6a6f3b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_filename +++ b/cassandane/tiny-tests/JMAPEmail/email_set_filename @@ -2,89 +2,109 @@ use Cassandane::Tiny; sub test_email_set_filename - :min_version_3_4 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_4 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Upload a data blob"; - my $binary = pack "H*", "beefcode"; - my $data = $jmap->Upload($binary, "image/gif"); - my $dataBlobId = $data->{blobId}; + xlog $self, "Upload a data blob"; + my $binary = pack "H*", "beefcode"; + my $data = $jmap->Upload($binary, "image/gif"); + my $dataBlobId = $data->{blobId}; - my @testcases = ({ - name => 'foo', - wantCt => " image/gif; name=\"foo\"", - wantCd => " attachment; filename=\"foo\"", - }, { - name => "I feel \N{WHITE SMILING FACE}", - wantCt => " image/gif; name=\"=?UTF-8?Q?I_feel_=E2=98=BA?=\"", - wantCd => " attachment; filename*=utf-8''I%20feel%20%E2%98%BA", - }, { - name => "foo" . ("_foo" x 20), - wantCt => " image/gif;\r\n\tname=\"=?UTF-8?Q?foo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffo?=\r\n =?UTF-8?Q?o=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo?=\"", - wantCd => " attachment;\r\n\tfilename*0=\"foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_f\";\r\n\tfilename*1=\"oo_foo_foo_foo_foo_foo\"", - }, { - name => "foo" . ("_foo" x 20) . "\N{WHITE SMILING FACE}", - wantCt => " image/gif;\r\n\tname=\"=?UTF-8?Q?foo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffo?=\r\n =?UTF-8?Q?o=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo?=\r\n =?UTF-8?Q?=E2=98=BA?=\"", - wantCd => " attachment;\r\n\tfilename*0*=utf-8\'\'foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_fo;\r\n\tfilename*1*=o_foo_foo_foo_foo_foo_foo_foo%E2%98%BA", - }, { - name => 'Incoming Email Flow.xml', - wantCt => " image/gif; name=\"Incoming Email Flow.xml\"", - wantCd => " attachment; filename=\"Incoming Email Flow.xml\"", - }, { - name => 'a"b\c.txt', - wantCt => " image/gif; name=\"a\\\"b\\\\c.txt\"", - wantCd => " attachment; filename=\"a\\\"b\\\\c.txt\"", - }); + my @testcases = ( + { + name => 'foo', + wantCt => " image/gif; name=\"foo\"", + wantCd => " attachment; filename=\"foo\"", + }, + { + name => "I feel \N{WHITE SMILING FACE}", + wantCt => " image/gif; name=\"=?UTF-8?Q?I_feel_=E2=98=BA?=\"", + wantCd => " attachment; filename*=utf-8''I%20feel%20%E2%98%BA", + }, + { + name => "foo" . ("_foo" x 20), + wantCt => + " image/gif;\r\n\tname=\"=?UTF-8?Q?foo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffo?=\r\n =?UTF-8?Q?o=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo?=\"", + wantCd => + " attachment;\r\n\tfilename*0=\"foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_f\";\r\n\tfilename*1=\"oo_foo_foo_foo_foo_foo\"", + }, + { + name => "foo" . ("_foo" x 20) . "\N{WHITE SMILING FACE}", + wantCt => + " image/gif;\r\n\tname=\"=?UTF-8?Q?foo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffo?=\r\n =?UTF-8?Q?o=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo=5Ffoo?=\r\n =?UTF-8?Q?=E2=98=BA?=\"", + wantCd => + " attachment;\r\n\tfilename*0*=utf-8\'\'foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_foo_fo;\r\n\tfilename*1*=o_foo_foo_foo_foo_foo_foo_foo%E2%98%BA", + }, + { + name => 'Incoming Email Flow.xml', + wantCt => " image/gif; name=\"Incoming Email Flow.xml\"", + wantCd => " attachment; filename=\"Incoming Email Flow.xml\"", + }, + { + name => 'a"b\c.txt', + wantCt => " image/gif; name=\"a\\\"b\\\\c.txt\"", + wantCd => " attachment; filename=\"a\\\"b\\\\c.txt\"", + } + ); - foreach my $tc (@testcases) { - xlog $self, "Checking name $tc->{name}"; - my $bodyStructure = { - type => "multipart/alternative", - subParts => [{ - type => 'text/plain', - partId => '1', - }, { - type => 'image/gif', - disposition => 'attachment', - name => $tc->{name}, - blobId => $dataBlobId, - }], - }; + foreach my $tc (@testcases) { + xlog $self, "Checking name $tc->{name}"; + my $bodyStructure = { + type => "multipart/alternative", + subParts => [ + { + type => 'text/plain', + partId => '1', + }, + { + type => 'image/gif', + disposition => 'attachment', + name => $tc->{name}, + blobId => $dataBlobId, + } + ], + }; - xlog $self, "Create email with body structure"; - my $inboxid = $self->getinbox()->{id}; - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [{ name => "Test", email => q{foo@bar} }], - subject => "test", - bodyStructure => $bodyStructure, - bodyValues => { - "1" => { - value => "A text body", - }, - }, - }; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { '1' => $email } }, 'R1'], - ['Email/get', { - ids => [ '#1' ], - properties => [ 'bodyStructure' ], - bodyProperties => [ 'partId', 'blobId', 'type', 'name', 'disposition', 'header:Content-Type', 'header:Content-Disposition' ], - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]); + xlog $self, "Create email with body structure"; + my $inboxid = $self->getinbox()->{id}; + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Test", email => q{foo@bar} } ], + subject => "test", + bodyStructure => $bodyStructure, + bodyValues => { + "1" => { + value => "A text body", + }, + }, + }; + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { '1' => $email } }, 'R1' ], + [ + 'Email/get', + { + ids => ['#1'], + properties => ['bodyStructure'], + bodyProperties => [ + 'partId', 'blobId', 'type', 'name', 'disposition', + 'header:Content-Type', 'header:Content-Disposition' + ], + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); - my $gotBodyStructure = $res->[1][1]{list}[0]{bodyStructure}; - my $gotName = $gotBodyStructure->{subParts}[1]{name}; - $self->assert_str_equals($tc->{name}, $gotName); - my $gotCt = $gotBodyStructure->{subParts}[1]{'header:Content-Type'}; - $self->assert_str_equals($tc->{wantCt}, $gotCt); - my $gotCd = $gotBodyStructure->{subParts}[1]{'header:Content-Disposition'}; - $self->assert_str_equals($tc->{wantCd}, $gotCd); - } + my $gotBodyStructure = $res->[1][1]{list}[0]{bodyStructure}; + my $gotName = $gotBodyStructure->{subParts}[1]{name}; + $self->assert_str_equals($tc->{name}, $gotName); + my $gotCt = $gotBodyStructure->{subParts}[1]{'header:Content-Type'}; + $self->assert_str_equals($tc->{wantCt}, $gotCt); + my $gotCd = $gotBodyStructure->{subParts}[1]{'header:Content-Disposition'}; + $self->assert_str_equals($tc->{wantCd}, $gotCd); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_flagged b/cassandane/tiny-tests/JMAPEmail/email_set_flagged index 224580ec40..b2aad84df0 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_flagged +++ b/cassandane/tiny-tests/JMAPEmail/email_set_flagged @@ -2,39 +2,45 @@ use Cassandane::Tiny; sub test_email_set_flagged - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $drafts = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $drafts = $res->[0][1]{created}{"1"}{id}; - my $draft = { - mailboxIds => { $drafts => JSON::true }, - keywords => { '$draft' => JSON::true, '$Flagged' => JSON::true }, - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "a flagged draft" }}, - }; + my $draft = { + mailboxIds => { $drafts => JSON::true }, + keywords => { '$draft' => JSON::true, '$Flagged' => JSON::true }, + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "a flagged draft" } }, + }; - xlog $self, "Create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "Create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; - $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); - $self->assert_equals(JSON::true, $msg->{keywords}->{'$flagged'}); + $self->assert_deep_equals($msg->{mailboxIds}, $draft->{mailboxIds}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'$flagged'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_getquota b/cassandane/tiny-tests/JMAPEmail/email_set_getquota index 3b588b8868..59711aeb76 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_getquota +++ b/cassandane/tiny-tests/JMAPEmail/email_set_getquota @@ -2,71 +2,78 @@ use Cassandane::Tiny; sub test_email_set_getquota - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_quotalimits(storage => 1000); # that's 1000 * 1024 bytes + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_quotalimits(storage => 1000); # that's 1000 * 1024 bytes - my $jmap = $self->{jmap}; - my $service = $self->{instance}->get_service("http"); - my $inboxId = $self->getinbox()->{id}; + my $jmap = $self->{jmap}; + my $service = $self->{instance}->get_service("http"); + my $inboxId = $self->getinbox()->{id}; - # we need 'https://cyrusimap.org/ns/jmap/quota' capability for - # Quota/get method - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/quota'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/quota' capability for + # Quota/get method + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/quota'; + $jmap->DefaultUsing(\@using); - my $res; + my $res; - $res = $jmap->CallMethods([ - ['Quota/get', { - accountId => 'cassandane', - ids => undef, - }, 'R1'], - ]); + $res = $jmap->CallMethods([ + [ + 'Quota/get', + { + accountId => 'cassandane', + ids => undef, + }, + 'R1' + ], + ]); - my $mailQuota = $res->[0][1]{list}[0]; - $self->assert_str_equals('mail', $mailQuota->{id}); - $self->assert_num_equals(0, $mailQuota->{used}); - $self->assert_num_equals(1000 * 1024, $mailQuota->{total}); - my $quotaState = $res->[0][1]{state}; - $self->assert_not_null($quotaState); + my $mailQuota = $res->[0][1]{list}[0]; + $self->assert_str_equals('mail', $mailQuota->{id}); + $self->assert_num_equals(0, $mailQuota->{used}); + $self->assert_num_equals(1000 * 1024, $mailQuota->{total}); + my $quotaState = $res->[0][1]{state}; + $self->assert_not_null($quotaState); - xlog $self, "Create email"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - msgA1 => { - mailboxIds => { - $inboxId => JSON::true, - }, - from => [{ - email => q{test1@local}, - name => q{} - }], - to => [{ - email => q{test2@local}, - name => '', - }], - subject => 'foo', - keywords => { - '$seen' => JSON::true, - }, - }, - } - }, "R1"], - ['Quota/get', {}, 'R2'], - ]); + xlog $self, "Create email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + msgA1 => { + mailboxIds => { + $inboxId => JSON::true, + }, + from => [ { + email => q{test1@local}, + name => q{} + } ], + to => [ { + email => q{test2@local}, + name => '', + } ], + subject => 'foo', + keywords => { + '$seen' => JSON::true, + }, + }, + } + }, + "R1" + ], + [ 'Quota/get', {}, 'R2' ], + ]); - $self->assert_str_equals('Quota/get', $res->[1][0]); - $mailQuota = $res->[1][1]{list}[0]; - $self->assert_str_equals('mail', $mailQuota->{id}); - $self->assert_num_not_equals(0, $mailQuota->{used}); - $self->assert_num_equals(1000 * 1024, $mailQuota->{total}); - $self->assert_str_not_equals($quotaState, $res->[1][1]{state}); + $self->assert_str_equals('Quota/get', $res->[1][0]); + $mailQuota = $res->[1][1]{list}[0]; + $self->assert_str_equals('mail', $mailQuota->{id}); + $self->assert_num_not_equals(0, $mailQuota->{used}); + $self->assert_num_equals(1000 * 1024, $mailQuota->{total}); + $self->assert_str_not_equals($quotaState, $res->[1][1]{state}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_groupaddr b/cassandane/tiny-tests/JMAPEmail/email_set_groupaddr index 4a7a8a8fb4..52a078293e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_groupaddr +++ b/cassandane/tiny-tests/JMAPEmail/email_set_groupaddr @@ -2,204 +2,258 @@ use Cassandane::Tiny; sub test_email_set_groupaddr - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my @testCases = ({ - # Example from from Appendix A.1.3 of RFC 5322 - rawHeader => 'A Group:Ed Jones ,joe@where.test,John ', - wantAddresses => [{ - name => 'Ed Jones', + my @testCases = ( + { + # Example from from Appendix A.1.3 of RFC 5322 + rawHeader => + 'A Group:Ed Jones ,joe@where.test,John ', + wantAddresses => [ + { + name => 'Ed Jones', + email => 'c@a.test', + }, + { + name => undef, + email => 'joe@where.test' + }, + { + name => 'John', + email => 'jdoe@one.test', + } + ], + wantGroupedAddresses => [ { + name => 'A Group', + addresses => [ + { + name => 'Ed Jones', email => 'c@a.test', - }, { - name => undef, + }, + { + name => undef, email => 'joe@where.test' - }, { - name => 'John', + }, + { + name => 'John', email => 'jdoe@one.test', - }], - wantGroupedAddresses => [{ - name => 'A Group', - addresses => [{ - name => 'Ed Jones', - email => 'c@a.test', - }, { - name => undef, - email => 'joe@where.test' - }, { - name => 'John', - email => 'jdoe@one.test', - }], - }], - }, { - # Example from JMAP mail spec, RFC 8621, Section 4.1.2.3 - rawHeader => '"James Smythe" , Friends:' - . 'jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= ' - . ';', - wantAddresses => [{ - name => 'James Smythe', - email => 'james@example.com' - }, { - name => undef, - email => 'jane@example.com' - }, { - name => "John Sm\N{U+00EE}th", - email => 'john@example.com' - }], - wantGroupedAddresses => [{ - name => undef, - addresses => [{ - name => 'James Smythe', - email => 'james@example.com' - }], - }, { - name => 'Friends', - addresses => [{ - name => undef, - email => 'jane@example.com' - }, { - name => "John Sm\N{U+00EE}th", - email => 'john@example.com' - }], - }] - }, { - # Issue https://github.com/cyrusimap/cyrus-imapd/issues/2959 - rawHeader => 'undisclosed-recipients:', - wantAddresses => [], - wantGroupedAddresses => [{ - name => 'undisclosed-recipients', - addresses => [], - }], - }, { - # Sanity check - rawHeader => 'addr1@local, addr2@local, GroupA:; addr3@local, ' - . 'GroupB:addr4@local,addr5@local;addr6@local', - wantAddresses => [{ - name => undef, - email => 'addr1@local', - }, { - name => undef, - email => 'addr2@local', - }, { - name => undef, + } + ], + } ], + }, + { + # Example from JMAP mail spec, RFC 8621, Section 4.1.2.3 + rawHeader => '"James Smythe" , Friends:' + . 'jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= ' + . ';', + wantAddresses => [ + { + name => 'James Smythe', + email => 'james@example.com' + }, + { + name => undef, + email => 'jane@example.com' + }, + { + name => "John Sm\N{U+00EE}th", + email => 'john@example.com' + } + ], + wantGroupedAddresses => [ + { + name => undef, + addresses => [ { + name => 'James Smythe', + email => 'james@example.com' + } ], + }, + { + name => 'Friends', + addresses => [ + { + name => undef, + email => 'jane@example.com' + }, + { + name => "John Sm\N{U+00EE}th", + email => 'john@example.com' + } + ], + } + ] + }, + { + # Issue https://github.com/cyrusimap/cyrus-imapd/issues/2959 + rawHeader => 'undisclosed-recipients:', + wantAddresses => [], + wantGroupedAddresses => [ { + name => 'undisclosed-recipients', + addresses => [], + } ], + }, + { + # Sanity check + rawHeader => 'addr1@local, addr2@local, GroupA:; addr3@local, ' + . 'GroupB:addr4@local,addr5@local;addr6@local', + wantAddresses => [ + { + name => undef, + email => 'addr1@local', + }, + { + name => undef, + email => 'addr2@local', + }, + { + name => undef, + email => 'addr3@local', + }, + { + name => undef, + email => 'addr4@local', + }, + { + name => undef, + email => 'addr5@local', + }, + { + name => undef, + email => 'addr6@local', + } + ], + wantGroupedAddresses => [ + { + name => undef, + addresses => [ + { + name => undef, + email => 'addr1@local', + }, + { + name => undef, + email => 'addr2@local', + } + ], + }, + { + name => 'GroupA', + addresses => [], + }, + { + name => undef, + addresses => [ { + name => undef, email => 'addr3@local', - }, { - name => undef, - email => 'addr4@local', - }, { - name => undef, - email => 'addr5@local', - }, { - name => undef, + } ], + }, + { + name => 'GroupB', + addresses => [ + { + name => undef, + email => 'addr4@local', + }, + { + name => undef, + email => 'addr5@local', + } + ], + }, + { + name => undef, + addresses => [ { + name => undef, email => 'addr6@local', - }], - wantGroupedAddresses => [{ - name => undef, - addresses => [{ - name => undef, - email => 'addr1@local', - }, { - name => undef, - email => 'addr2@local', - }], - }, { - name => 'GroupA', - addresses => [], - }, { - name => undef, - addresses => [{ - name => undef, - email => 'addr3@local', - }], - }, { - name => 'GroupB', - addresses => [{ - name => undef, - email => 'addr4@local', - }, { - name => undef, - email => 'addr5@local', - }], - }, { - name => undef, - addresses => [{ - name => undef, - email => 'addr6@local', - }], - }], - }); + } ], + } + ], + } + ); - foreach my $tc (@testCases) { - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true, - }, - from => [{ email => q{foo1@bar} }], - 'header:to' => $tc->{rawHeader}, - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email1 body", - }, - }, - }, + foreach my $tc (@testCases) { + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true, + }, + from => [ { email => q{foo1@bar} } ], + 'header:to' => $tc->{rawHeader}, + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email1 body", }, - }, 'R1'], - ['Email/get', { - ids => ['#email1'], - properties => [ - 'header:to:asAddresses', - 'header:to:asGroupedAddresses', - ], - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}{email1}{id}); - $self->assert_deep_equals($tc->{wantAddresses}, - $res->[1][1]{list}[0]->{'header:to:asAddresses'}); - $self->assert_deep_equals($tc->{wantGroupedAddresses}, - $res->[1][1]{list}[0]->{'header:to:asGroupedAddresses'}); + }, + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => + [ 'header:to:asAddresses', 'header:to:asGroupedAddresses', ], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{email1}{id}); + $self->assert_deep_equals($tc->{wantAddresses}, + $res->[1][1]{list}[0]->{'header:to:asAddresses'}); + $self->assert_deep_equals($tc->{wantGroupedAddresses}, + $res->[1][1]{list}[0]->{'header:to:asGroupedAddresses'}); - # Now assert that group addresses loop back if set in Email/set. + # Now assert that group addresses loop back if set in Email/set. - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email2 => { - mailboxIds => { - '$inbox' => JSON::true, - }, - from => [{ email => q{foo2@bar} }], - 'header:to:asGroupedAddresses' => $tc->{wantGroupedAddresses}, - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "email2 body", - }, - }, - }, + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email2 => { + mailboxIds => { + '$inbox' => JSON::true, + }, + from => [ { email => q{foo2@bar} } ], + 'header:to:asGroupedAddresses' => $tc->{wantGroupedAddresses}, + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "email2 body", }, - }, 'R1'], - ['Email/get', { - ids => ['#email2'], - properties => [ - 'header:to:asAddresses', - 'header:to:asGroupedAddresses', - ], - }, 'R2'], - ]); - $self->assert_not_null($res->[0][1]{created}{email2}{id}); - $self->assert_deep_equals($tc->{wantAddresses}, - $res->[1][1]{list}[0]->{'header:to:asAddresses'}); - $self->assert_deep_equals($tc->{wantGroupedAddresses}, - $res->[1][1]{list}[0]->{'header:to:asGroupedAddresses'}); - } + }, + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#email2'], + properties => + [ 'header:to:asAddresses', 'header:to:asGroupedAddresses', ], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{email2}{id}); + $self->assert_deep_equals($tc->{wantAddresses}, + $res->[1][1]{list}[0]->{'header:to:asAddresses'}); + $self->assert_deep_equals($tc->{wantGroupedAddresses}, + $res->[1][1]{list}[0]->{'header:to:asGroupedAddresses'}); + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_guidsearch_updated_internaldate b/cassandane/tiny-tests/JMAPEmail/email_set_guidsearch_updated_internaldate index fc1c571177..5c036884b7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_guidsearch_updated_internaldate +++ b/cassandane/tiny-tests/JMAPEmail/email_set_guidsearch_updated_internaldate @@ -2,135 +2,166 @@ use Cassandane::Tiny; sub test_email_set_guidsearch_updated_internaldate - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/quota', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/quota', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - xlog $self, "create emails"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'mA' => { - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - mailboxIds => { - '$inbox' => JSON::true, - }, - receivedAt => '2020-02-01T00:00:00Z', - subject => 'test', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, - 'mB' => { - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - mailboxIds => { - '$inbox' => JSON::true, - }, - receivedAt => '2020-02-02T00:00:00Z', - subject => 'test', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + xlog $self, "create emails"; + my $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + 'mA' => { + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + mailboxIds => { + '$inbox' => JSON::true, + }, + receivedAt => '2020-02-01T00:00:00Z', + subject => 'test', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, }, - }, 'R1'], - ], $using); - my $emailIdA = $res->[0][1]->{created}{mA}{id}; - $self->assert_not_null($emailIdA); - my $emailBlobIdA = $res->[0][1]->{created}{mA}{blobId}; - $self->assert_not_null($emailBlobIdA); - my $emailIdB = $res->[0][1]->{created}{mB}{id}; - $self->assert_not_null($emailIdB); + 'mB' => { + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + mailboxIds => { + '$inbox' => JSON::true, + }, + receivedAt => '2020-02-02T00:00:00Z', + subject => 'test', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ], + $using + ); + my $emailIdA = $res->[0][1]->{created}{mA}{id}; + $self->assert_not_null($emailIdA); + my $emailBlobIdA = $res->[0][1]->{created}{mA}{blobId}; + $self->assert_not_null($emailBlobIdA); + my $emailIdB = $res->[0][1]->{created}{mB}{id}; + $self->assert_not_null($emailIdB); - xlog "Download blob of message A"; - $res = $jmap->Download('cassandane', $emailBlobIdA); - my $emailBlobA = $res->{content}; - $self->assert_not_null($emailBlobA); + xlog "Download blob of message A"; + $res = $jmap->Download('cassandane', $emailBlobIdA); + my $emailBlobA = $res->{content}; + $self->assert_not_null($emailBlobA); - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Query sorted by internaldate, then destroy message A"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - subject => 'test', - }, - sort => [{ - property => 'receivedAt', - isAscending => JSON::true, - }] - }, 'R1'], - ['Email/set', { - destroy => [$emailIdA], - }, 'R2'], - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals([$emailIdA, $emailIdB], $res->[0][1]{ids}); - $self->assert_str_equals($emailIdA, $res->[1][1]{destroyed}[0]); + xlog "Query sorted by internaldate, then destroy message A"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + subject => 'test', + }, + sort => [ { + property => 'receivedAt', + isAscending => JSON::true, + } ] + }, + 'R1' + ], + [ + 'Email/set', + { + destroy => [$emailIdA], + }, + 'R2' + ], + ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals([ $emailIdA, $emailIdB ], $res->[0][1]{ids}); + $self->assert_str_equals($emailIdA, $res->[1][1]{destroyed}[0]); - xlog $self, "Compact search tier t1 to t2"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-z', 't2', '-t', 't1'); + xlog $self, "Compact search tier t1 to t2"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'squatter', '-z', 't2', '-t', 't1'); - xlog "Sleep one second"; - sleep(1); + xlog "Sleep one second"; + sleep(1); - xlog "Create dummy message"; - $self->make_message("dummy") || die; + xlog "Create dummy message"; + $self->make_message("dummy") || die; - xlog "Append blob of message A via IMAP"; - $imap->append('INBOX', $emailBlobA) || die $@; + xlog "Append blob of message A via IMAP"; + $imap->append('INBOX', $emailBlobA) || die $@; - xlog $self, "run incremental squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-i'); + xlog $self, "run incremental squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-i'); - xlog "Query sorted by internaldate"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - subject => 'test', - }, - sort => [{ - property => 'receivedAt', - isAscending => JSON::true, - }] - }, 'R1'], - ], $using); - $self->assert_equals(JSON::true, $res->[0][1]{performance}{details}{isGuidSearch}); - $self->assert_deep_equals([$emailIdB, $emailIdA], $res->[0][1]{ids}); + xlog "Query sorted by internaldate"; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + subject => 'test', + }, + sort => [ { + property => 'receivedAt', + isAscending => JSON::true, + } ] + }, + 'R1' + ], + ], + $using + ); + $self->assert_equals(JSON::true, + $res->[0][1]{performance}{details}{isGuidSearch}); + $self->assert_deep_equals([ $emailIdB, $emailIdA ], $res->[0][1]{ids}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_header_control b/cassandane/tiny-tests/JMAPEmail/email_set_header_control index 97f10f7a3f..4863f65555 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_header_control +++ b/cassandane/tiny-tests/JMAPEmail/email_set_header_control @@ -3,57 +3,64 @@ use Cassandane::Tiny; sub test_email_set_header_control - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my @testCases = ({ - name => 'header:x-header-crlf', - val => " a\r\nb", - valid => 0, - }, { - name => 'header:x-header-tab', - val => " a\tb", - valid => 0, - }, { - name => 'header:x-header-fold', - val => " a\r\n b", - valid => 1, - }); - - while (my ($i, $tc) = each @testCases) { - my $creationId = "email$i"; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - $creationId => { - $tc->{name} => $tc->{val}, - mailboxIds => { - '$inbox' => JSON::true, - }, - subject => 'email', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'email', - } - }, - }, - }, - }, 'R1'], - ]); + my @testCases = ( + { + name => 'header:x-header-crlf', + val => " a\r\nb", + valid => 0, + }, + { + name => 'header:x-header-tab', + val => " a\tb", + valid => 0, + }, + { + name => 'header:x-header-fold', + val => " a\r\n b", + valid => 1, + } + ); - if ($tc->{valid}) { - $self->assert_not_null($res->[0][1]{created}{$creationId}); - } else { - $self->assert_deep_equals([$tc->{name}], - $res->[0][1]{notCreated}{$creationId}{properties}); - } + while (my ($i, $tc) = each @testCases) { + my $creationId = "email$i"; + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + $creationId => { + $tc->{name} => $tc->{val}, + mailboxIds => { + '$inbox' => JSON::true, + }, + subject => 'email', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'email', + } + }, + }, + }, + }, + 'R1' + ], + ]); + if ($tc->{valid}) { + $self->assert_not_null($res->[0][1]{created}{$creationId}); + } else { + $self->assert_deep_equals([ $tc->{name} ], + $res->[0][1]{notCreated}{$creationId}{properties}); } + } + } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_headers b/cassandane/tiny-tests/JMAPEmail/email_set_headers index 353cb52618..63c2e85063 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_headers +++ b/cassandane/tiny-tests/JMAPEmail/email_set_headers @@ -2,165 +2,183 @@ use Cassandane::Tiny; sub test_email_set_headers - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - my $text = "x"; + my $text = "x"; - # Prepare test headers - my $headers = { - 'header:X-TextHeader8bit' => { - format => 'asText', - value => "I feel \N{WHITE SMILING FACE}", - wantRaw => " =?UTF-8?Q?I_feel_=E2=98=BA?=" + # Prepare test headers + my $headers = { + 'header:X-TextHeader8bit' => { + format => 'asText', + value => "I feel \N{WHITE SMILING FACE}", + wantRaw => " =?UTF-8?Q?I_feel_=E2=98=BA?=" + }, + 'header:X-TextHeaderFold' => { + format => 'asText', + value => + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus dictum facilisis feugiat.", + wantRaw => + " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus dictum\r\n facilisis feugiat.", + }, + 'header:X-TextHeaderEncodeNoWSP' => { + format => 'asText', + value => "x" x 80, + wantRaw => " =?UTF-8?Q?" + . ("x" x 62) + . "?=\r\n =?UTF-8?Q?" + . ("x" x 18) . "?=" + }, + 'header:X-TextHeaderEncodeLongLine' => { + format => 'asText', + value => "xxx " . ("x" x 80) . " xxx", + wantRaw => " =?UTF-8?Q?xxx_" + . ("x" x 58) + . "?=\r\n =?UTF-8?Q?" + . ("x" x 22) + . "_xxx?=", + }, + 'header:X-TextHeaderShort' => { + format => 'asText', + value => "x", + wantRaw => " x" + }, + 'header:X-MsgIdsShort' => { + format => 'asMessageIds', + value => ['foobar@ba'], + wantRaw => " ", + }, + 'header:X-MsgIdsLong' => { + format => 'asMessageIds', + value => [ + 'foobar@ba', 'foobar@ba', 'foobar@ba', 'foobar@ba', + 'foobar@ba', 'foobar@ba', 'foobar@ba', 'foobar@ba', + ], + wantRaw => (" " x 5) . "\r\n" . (" " x 3), + }, + 'header:X-AddrsShort' => { + format => 'asAddresses', + value => [ { 'name' => 'foo', email => 'bar@local' } ], + wantRaw => ' foo ', + }, + 'header:X-AddrsQuoted' => { + format => 'asAddresses', + value => [ { 'name' => 'Foo Bar', email => 'quotbar@local' } ], + wantRaw => ' "Foo Bar" ', + }, + 'header:X-Addrs8bit' => { + format => 'asAddresses', + value => [ { + 'name' => "Rudi R\N{LATIN SMALL LETTER U WITH DIAERESIS}be", + email => 'bar@local' + } ], + wantRaw => ' =?UTF-8?Q?Rudi_R=C3=BCbe?= ', + }, + 'header:X-AddrsLong' => { + format => 'asAddresses', + value => [ + { + 'name' => 'foo', + email => 'bar@local' }, - 'header:X-TextHeaderFold' => { - format => 'asText', - value => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus dictum facilisis feugiat.", - wantRaw => " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus dictum\r\n facilisis feugiat.", + { + 'name' => 'foo', + email => 'bar@local' }, - 'header:X-TextHeaderEncodeNoWSP' => { - format => 'asText', - value => "x" x 80, - wantRaw => " =?UTF-8?Q?" . ("x" x 62) . "?=\r\n =?UTF-8?Q?" . ("x" x 18) . "?=" + { + 'name' => 'foo', + email => 'bar@local' }, - 'header:X-TextHeaderEncodeLongLine' => { - format => 'asText', - value => "xxx " . ("x" x 80) . " xxx", - wantRaw => " =?UTF-8?Q?xxx_" . ("x" x 58) . "?=\r\n =?UTF-8?Q?" . ("x" x 22) . "_xxx?=", + { + 'name' => 'foo', + email => 'bar@local' }, - 'header:X-TextHeaderShort' => { - format => 'asText', - value => "x", - wantRaw => " x" + { + 'name' => 'foo', + email => 'bar@local' }, - 'header:X-MsgIdsShort' => { - format => 'asMessageIds', - value => [ 'foobar@ba' ], - wantRaw => " ", - }, - 'header:X-MsgIdsLong' => { - format => 'asMessageIds', - value => [ - 'foobar@ba', - 'foobar@ba', - 'foobar@ba', - 'foobar@ba', - 'foobar@ba', - 'foobar@ba', - 'foobar@ba', - 'foobar@ba', - ], - wantRaw => (" " x 5)."\r\n".(" " x 3), - }, - 'header:X-AddrsShort' => { - format => 'asAddresses', - value => [{ 'name' => 'foo', email => 'bar@local' }], - wantRaw => ' foo ', - }, - 'header:X-AddrsQuoted' => { - format => 'asAddresses', - value => [{ 'name' => 'Foo Bar', email => 'quotbar@local' }], - wantRaw => ' "Foo Bar" ', - }, - 'header:X-Addrs8bit' => { - format => 'asAddresses', - value => [{ 'name' => "Rudi R\N{LATIN SMALL LETTER U WITH DIAERESIS}be", email => 'bar@local' }], - wantRaw => ' =?UTF-8?Q?Rudi_R=C3=BCbe?= ', - }, - 'header:X-AddrsLong' => { - format => 'asAddresses', - value => [{ - 'name' => 'foo', email => 'bar@local' - }, { - 'name' => 'foo', email => 'bar@local' - }, { - 'name' => 'foo', email => 'bar@local' - }, { - 'name' => 'foo', email => 'bar@local' - }, { - 'name' => 'foo', email => 'bar@local' - }, { - 'name' => 'foo', email => 'bar@local' - }, { - 'name' => 'foo', email => 'bar@local' - }, { - 'name' => 'foo', email => 'bar@local' - }], - wantRaw => (' foo ,' x 3)."\r\n".(' foo ,' x 4)."\r\n".' foo ', - }, - 'header:X-URLsShort' => { - format => 'asURLs', - value => [ 'foourl' ], - wantRaw => ' ', - }, - 'header:X-URLsLong' => { - format => 'asURLs', - value => [ - 'foourl', - 'foourl', - 'foourl', - 'foourl', - 'foourl', - 'foourl', - 'foourl', - 'foourl', - 'foourl', - 'foourl', - 'foourl', - ], - wantRaw => (' ,' x 6)."\r\n".(' ,' x 4).' ', - }, - }; - - # header fold/encode behaviour has changed -- discard some tests for - # older cyruses - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj < 3 || ($maj == 3 && $min < 5)) { - delete $headers->{'header:X-TextHeaderFold'}; - } + { + 'name' => 'foo', + email => 'bar@local' + }, + { + 'name' => 'foo', + email => 'bar@local' + }, + { + 'name' => 'foo', + email => 'bar@local' + } + ], + wantRaw => (' foo ,' x 3) . "\r\n" + . (' foo ,' x 4) . "\r\n" + . ' foo ', + }, + 'header:X-URLsShort' => { + format => 'asURLs', + value => ['foourl'], + wantRaw => ' ', + }, + 'header:X-URLsLong' => { + format => 'asURLs', + value => [ + 'foourl', 'foourl', 'foourl', 'foourl', 'foourl', 'foourl', + 'foourl', 'foourl', 'foourl', 'foourl', 'foourl', + ], + wantRaw => (' ,' x 6) . "\r\n" . (' ,' x 4) . ' ', + }, + }; - # Prepare test email - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [ { email => q{test1@robmtest.vm}, name => q{} } ], - }; - while( my ($k, $v) = each %$headers ) { - $email->{$k.':'.$v->{format}} = $v->{value}, - } + # header fold/encode behaviour has changed -- discard some tests for + # older cyruses + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj < 3 || ($maj == 3 && $min < 5)) { + delete $headers->{'header:X-TextHeaderFold'}; + } - my @properties = keys %$headers; - while( my ($k, $v) = each %$headers ) { - push @properties, $k.':'.$v->{format}; - } + # Prepare test email + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { email => q{test1@robmtest.vm}, name => q{} } ], + }; + while (my ($k, $v) = each %$headers) { + $email->{ $k . ':' . $v->{format} } = $v->{value},; + } + my @properties = keys %$headers; + while (my ($k, $v) = each %$headers) { + push @properties, $k . ':' . $v->{format}; + } - # Create and get mail - my $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ['Email/get', { - ids => [ "#1" ], - properties => \@properties, - }, "R2" ], - ]); - my $msg = $res->[1][1]{list}[0]; + # Create and get mail + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], + [ + 'Email/get', + { + ids => ["#1"], + properties => \@properties, + }, + "R2" + ], + ]); + my $msg = $res->[1][1]{list}[0]; - # Validate header values - while( my ($k, $v) = each %$headers ) { - xlog $self, "Validating $k"; - my $raw = $msg->{$k}; - my $val = $msg->{$k.':'.$v->{format}}; - # Check raw header - $self->assert_str_equals($v->{wantRaw}, $raw); - # Check formatted header - if (ref $v->{value} eq 'ARRAY') { - $self->assert_deep_equals($v->{value}, $val); - } else { - $self->assert_str_equals($v->{value}, $val); - } + # Validate header values + while (my ($k, $v) = each %$headers) { + xlog $self, "Validating $k"; + my $raw = $msg->{$k}; + my $val = $msg->{ $k . ':' . $v->{format} }; + # Check raw header + $self->assert_str_equals($v->{wantRaw}, $raw); + # Check formatted header + if (ref $v->{value} eq 'ARRAY') { + $self->assert_deep_equals($v->{value}, $val); + } else { + $self->assert_str_equals($v->{value}, $val); } + } } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_intermediary_create b/cassandane/tiny-tests/JMAPEmail/email_set_intermediary_create index 6e3f5d700d..d5ef0bc325 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_intermediary_create +++ b/cassandane/tiny-tests/JMAPEmail/email_set_intermediary_create @@ -2,44 +2,45 @@ use Cassandane::Tiny; sub test_email_set_intermediary_create - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.foo") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxId1 = $mboxByName{'i1'}->{id}; + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.foo") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxId1 = $mboxByName{'i1'}->{id}; - xlog $self, "Create email in intermediary mailbox"; - my $email = { - mailboxIds => { - $mboxId1 => JSON::true - }, - from => [{ - email => q{test1@local}, - name => q{} - }], - to => [{ - email => q{test2@local}, - name => '', - }], - subject => 'foo', - }; + xlog $self, "Create email in intermediary mailbox"; + my $email = { + mailboxIds => { + $mboxId1 => JSON::true + }, + from => [ { + email => q{test1@local}, + name => q{} + } ], + to => [ { + email => q{test2@local}, + name => '', + } ], + subject => 'foo', + }; - xlog $self, "create and get email"; - $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ['Email/get', { ids => [ "#1" ] }, "R2" ], - ]); - $self->assert_not_null($res->[0][1]{created}{1}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$mboxId1}); + xlog $self, "create and get email"; + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], + [ 'Email/get', { ids => ["#1"] }, "R2" ], + ]); + $self->assert_not_null($res->[0][1]{created}{1}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$mboxId1}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_intermediary_move b/cassandane/tiny-tests/JMAPEmail/email_set_intermediary_move index b315dd7f4d..c51a35e974 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_intermediary_move +++ b/cassandane/tiny-tests/JMAPEmail/email_set_intermediary_move @@ -2,59 +2,63 @@ use Cassandane::Tiny; sub test_email_set_intermediary_move - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.foo") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxId1 = $mboxByName{'i1'}->{id}; - my $mboxIdFoo = $mboxByName{'foo'}->{id}; + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.foo") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxId1 = $mboxByName{'i1'}->{id}; + my $mboxIdFoo = $mboxByName{'foo'}->{id}; - xlog $self, "Create email"; - my $email = { - mailboxIds => { - $mboxIdFoo => JSON::true - }, - from => [{ - email => q{test1@local}, - name => q{} - }], - to => [{ - email => q{test2@local}, - name => '', - }], - subject => 'foo', - }; - xlog $self, "create and get email"; - $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ]); - my $emailId = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($emailId); + xlog $self, "Create email"; + my $email = { + mailboxIds => { + $mboxIdFoo => JSON::true + }, + from => [ { + email => q{test1@local}, + name => q{} + } ], + to => [ { + email => q{test2@local}, + name => '', + } ], + subject => 'foo', + }; + xlog $self, "create and get email"; + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], ]); + my $emailId = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($emailId); - xlog $self, "Move email to intermediary mailbox"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId => { - mailboxIds => { - $mboxId1 => JSON::true, - }, - }, + xlog $self, "Move email to intermediary mailbox"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + mailboxIds => { + $mboxId1 => JSON::true, }, - }, 'R1'], - ['Email/get', { ids => [ $emailId ] }, "R2" ], - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); - $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$mboxId1}); + }, + }, + }, + 'R1' + ], + [ 'Email/get', { ids => [$emailId] }, "R2" ], + ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); + $self->assert_equals(JSON::true, $res->[1][1]{list}[0]{mailboxIds}{$mboxId1}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_issue2293 b/cassandane/tiny-tests/JMAPEmail/email_set_issue2293 index 9158cfbe3e..7bfe94b06a 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_issue2293 +++ b/cassandane/tiny-tests/JMAPEmail/email_set_issue2293 @@ -2,78 +2,87 @@ use Cassandane::Tiny; sub test_email_set_issue2293 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [ { email => q{test1@robmtest.vm}, name => q{} } ], - to => [ { - email => q{foo@bar.com}, - name => "asd \x{529b}\x{9928}\x{5fc5} asd \x{30ec}\x{30f1}\x{30b9}" - } ], - }; + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { email => q{test1@robmtest.vm}, name => q{} } ], + to => [ { + email => q{foo@bar.com}, + name => "asd \x{529b}\x{9928}\x{5fc5} asd \x{30ec}\x{30f1}\x{30b9}" + } ], + }; - xlog $self, "create and get email"; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ['Email/get', { ids => [ "#1" ] }, "R2" ], - ]); - my $ret = $res->[1][1]->{list}[0]; - $self->assert_str_equals($email->{to}[0]{email}, $ret->{to}[0]{email}); - $self->assert_str_equals($email->{to}[0]{name}, $ret->{to}[0]{name}); + xlog $self, "create and get email"; + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], + [ 'Email/get', { ids => ["#1"] }, "R2" ], + ]); + my $ret = $res->[1][1]->{list}[0]; + $self->assert_str_equals($email->{to}[0]{email}, $ret->{to}[0]{email}); + $self->assert_str_equals($email->{to}[0]{name}, $ret->{to}[0]{name}); + xlog $self, "create and get email"; + $email->{to}[0]{name} + = "asd \x{529b}\x{9928}\x{5fc5} asd \x{30ec}\x{30f1}\x{30b9} asd \x{3b1}\x{3bc}\x{3b5}\x{3c4}"; - xlog $self, "create and get email"; - $email->{to}[0]{name} = "asd \x{529b}\x{9928}\x{5fc5} asd \x{30ec}\x{30f1}\x{30b9} asd \x{3b1}\x{3bc}\x{3b5}\x{3c4}"; + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], + [ 'Email/get', { ids => ["#1"] }, "R2" ], + ]); + $ret = $res->[1][1]->{list}[0]; + $self->assert_str_equals($email->{to}[0]{email}, $ret->{to}[0]{email}); + $self->assert_str_equals($email->{to}[0]{name}, $ret->{to}[0]{name}); - $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ['Email/get', { ids => [ "#1" ] }, "R2" ], - ]); - $ret = $res->[1][1]->{list}[0]; - $self->assert_str_equals($email->{to}[0]{email}, $ret->{to}[0]{email}); - $self->assert_str_equals($email->{to}[0]{name}, $ret->{to}[0]{name}); + xlog $self, "create and get email"; + my $to = [ + { + name => "abcdefghijklmnopqrstuvwxyz1", + email => q{abcdefghijklmnopqrstuvwxyz1@local}, + }, + { + name => "abcdefghijklmnopqrstuvwxyz2", + email => q{abcdefghijklmnopqrstuvwxyz2@local}, + }, + { + name => "abcdefghijklmnopqrstuvwxyz3", + email => q{abcdefghijklmnopqrstuvwxyz3@local}, + }, + { + name => "abcdefghijklmnopqrstuvwxyz4", + email => q{abcdefghijklmnopqrstuvwxyz4@local}, + }, + { + name => "abcdefghijklmnopqrstuvwxyz5", + email => q{abcdefghijklmnopqrstuvwxyz5@local}, + }, + { + name => "abcdefghijklmnopqrstuvwxyz6", + email => q{abcdefghijklmnopqrstuvwxyz6@local}, + }, + { + name => "abcdefghijklmnopqrstuvwxyz7", + email => q{abcdefghijklmnopqrstuvwxyz7@local}, + }, + { + name => "abcdefghijklmnopqrstuvwxyz8", + email => q{abcdefghijklmnopqrstuvwxyz8@local}, + }, + { + name => "abcdefghijklmnopqrstuvwxyz9", + email => q{abcdefghijklmnopqrstuvwxyz9@local}, + } + ]; + $email->{to} = $to; - xlog $self, "create and get email"; - my $to = [{ - name => "abcdefghijklmnopqrstuvwxyz1", - email => q{abcdefghijklmnopqrstuvwxyz1@local}, - }, { - name => "abcdefghijklmnopqrstuvwxyz2", - email => q{abcdefghijklmnopqrstuvwxyz2@local}, - }, { - name => "abcdefghijklmnopqrstuvwxyz3", - email => q{abcdefghijklmnopqrstuvwxyz3@local}, - }, { - name => "abcdefghijklmnopqrstuvwxyz4", - email => q{abcdefghijklmnopqrstuvwxyz4@local}, - }, { - name => "abcdefghijklmnopqrstuvwxyz5", - email => q{abcdefghijklmnopqrstuvwxyz5@local}, - }, { - name => "abcdefghijklmnopqrstuvwxyz6", - email => q{abcdefghijklmnopqrstuvwxyz6@local}, - }, { - name => "abcdefghijklmnopqrstuvwxyz7", - email => q{abcdefghijklmnopqrstuvwxyz7@local}, - }, { - name => "abcdefghijklmnopqrstuvwxyz8", - email => q{abcdefghijklmnopqrstuvwxyz8@local}, - }, { - name => "abcdefghijklmnopqrstuvwxyz9", - email => q{abcdefghijklmnopqrstuvwxyz9@local}, - }]; - $email->{to} = $to; - - $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ['Email/get', { ids => [ "#1" ] }, "R2" ], - ]); - $ret = $res->[1][1]->{list}[0]; - $self->assert_deep_equals($email->{to}, $ret->{to}); + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], + [ 'Email/get', { ids => ["#1"] }, "R2" ], + ]); + $ret = $res->[1][1]->{list}[0]; + $self->assert_deep_equals($email->{to}, $ret->{to}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_issue2500 b/cassandane/tiny-tests/JMAPEmail/email_set_issue2500 index 3532e0dab1..4ae89fe5de 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_issue2500 +++ b/cassandane/tiny-tests/JMAPEmail/email_set_issue2500 @@ -2,41 +2,45 @@ use Cassandane::Tiny; sub test_email_set_issue2500 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inboxid = $self->getinbox()->{id}; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inboxid = $self->getinbox()->{id}; - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [{ name => "Test", email => q{foo@bar} }], - subject => "test", - bodyStructure => { - partId => '1', - charset => 'us/ascii', - }, - bodyValues => { - "1" => { - value => "A text body", - }, - }, - }; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { '1' => $email } }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notCreated}{1}{type}); - $self->assert_str_equals('bodyStructure/charset', $res->[0][1]{notCreated}{1}{properties}[0]); + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Test", email => q{foo@bar} } ], + subject => "test", + bodyStructure => { + partId => '1', + charset => 'us/ascii', + }, + bodyValues => { + "1" => { + value => "A text body", + }, + }, + }; + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { '1' => $email } }, 'R1' ], ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{1}{type}); + $self->assert_str_equals('bodyStructure/charset', + $res->[0][1]{notCreated}{1}{properties}[0]); - delete $email->{bodyStructure}{charset}; - $email->{bodyStructure}{'header:Content-Type'} = 'text/plain;charset=us-ascii'; - $res = $jmap->CallMethods([ - ['Email/set', { create => { '1' => $email } }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notCreated}{1}{type}); - $self->assert_str_equals('bodyStructure/header:Content-Type', $res->[0][1]{notCreated}{1}{properties}[0]); + delete $email->{bodyStructure}{charset}; + $email->{bodyStructure}{'header:Content-Type'} + = 'text/plain;charset=us-ascii'; + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { '1' => $email } }, 'R1' ], ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{1}{type}); + $self->assert_str_equals( + 'bodyStructure/header:Content-Type', + $res->[0][1]{notCreated}{1}{properties}[0] + ); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_keywords b/cassandane/tiny-tests/JMAPEmail/email_set_keywords index 157425a01f..5516c005bc 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_keywords +++ b/cassandane/tiny-tests/JMAPEmail/email_set_keywords @@ -2,137 +2,163 @@ use Cassandane::Tiny; sub test_email_set_keywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $store = $self->{store}; + my $talk = $store->get_client(); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create IMAP mailboxes"; - $talk->create('INBOX.A') || die; - $talk->create('INBOX.B') || die; - $talk->create('INBOX.C') || die; + xlog $self, "Create IMAP mailboxes"; + $talk->create('INBOX.A') || die; + $talk->create('INBOX.B') || die; + $talk->create('INBOX.C') || die; - xlog $self, "Get JMAP mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', { properties => [ 'name' ]}, "R1"]]); - my %jmailboxes = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(4, scalar keys %jmailboxes); - my $jmailboxA = $jmailboxes{A}; - my $jmailboxB = $jmailboxes{B}; - my $jmailboxC = $jmailboxes{C}; + xlog $self, "Get JMAP mailboxes"; + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => ['name'] }, "R1" ] ]); + my %jmailboxes = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(4, scalar keys %jmailboxes); + my $jmailboxA = $jmailboxes{A}; + my $jmailboxB = $jmailboxes{B}; + my $jmailboxC = $jmailboxes{C}; - my %mailboxA; - my %mailboxB; - my %mailboxC; + my %mailboxA; + my %mailboxB; + my %mailboxC; - xlog $self, "Create message in mailbox A"; - $store->set_folder('INBOX.A'); - $mailboxA{1} = $self->make_message('Message'); - $mailboxA{1}->set_attributes(id => 1, uid => 1, flags => []); + xlog $self, "Create message in mailbox A"; + $store->set_folder('INBOX.A'); + $mailboxA{1} = $self->make_message('Message'); + $mailboxA{1}->set_attributes(id => 1, uid => 1, flags => []); - xlog $self, "Copy message from A to B"; - $talk->copy('1:*', 'INBOX.B'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "Copy message from A to B"; + $talk->copy('1:*', 'INBOX.B'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "Set IMAP flag foo on message A"; - $store->set_folder('INBOX.A'); - $store->_select(); - $talk->store('1', '+flags', '(foo)'); + xlog $self, "Set IMAP flag foo on message A"; + $store->set_folder('INBOX.A'); + $store->_select(); + $talk->store('1', '+flags', '(foo)'); - xlog $self, "Get JMAP keywords"; - $res = $jmap->CallMethods([ - ['Email/query', { }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'keywords'] - }, 'R2' ] - ]); - my $jmapmsg = $res->[1][1]{list}[0]; - my $keywords = { - foo => JSON::true - }; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + xlog $self, "Get JMAP keywords"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['keywords'] + }, + 'R2' + ] + ]); + my $jmapmsg = $res->[1][1]{list}[0]; + my $keywords = { foo => JSON::true }; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); - xlog $self, "Update JMAP email keywords"; - $keywords = { - bar => JSON::true, - baz => JSON::true, - }; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $jmapmsg->{id} => { - keywords => $keywords - } - } - }, 'R1'], - ['Email/get', { - ids => [ $jmapmsg->{id} ], - properties => ['keywords'] - }, 'R2' ] - ]); - $jmapmsg = $res->[1][1]{list}[0]; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + xlog $self, "Update JMAP email keywords"; + $keywords = { + bar => JSON::true, + baz => JSON::true, + }; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $jmapmsg->{id} => { + keywords => $keywords + } + } + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [ $jmapmsg->{id} ], + properties => ['keywords'] + }, + 'R2' + ] + ]); + $jmapmsg = $res->[1][1]{list}[0]; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); - xlog $self, "Set \\Seen on message in mailbox B"; - $store->set_folder('INBOX.B'); - $store->_select(); - $talk->store('1', '+flags', '(\\Seen)'); + xlog $self, "Set \\Seen on message in mailbox B"; + $store->set_folder('INBOX.B'); + $store->_select(); + $talk->store('1', '+flags', '(\\Seen)'); - xlog $self, "Patch JMAP email keywords and update mailboxIds"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $jmapmsg->{id} => { - 'keywords/bar' => undef, - 'keywords/qux' => JSON::true, - mailboxIds => { - $jmailboxB->{id} => JSON::true, - $jmailboxC->{id} => JSON::true, - } - } + xlog $self, "Patch JMAP email keywords and update mailboxIds"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $jmapmsg->{id} => { + 'keywords/bar' => undef, + 'keywords/qux' => JSON::true, + mailboxIds => { + $jmailboxB->{id} => JSON::true, + $jmailboxC->{id} => JSON::true, } - }, 'R1'], - ['Email/get', { - ids => [ $jmapmsg->{id} ], - properties => ['keywords', 'mailboxIds'] - }, 'R2' ] - ]); - $jmapmsg = $res->[1][1]{list}[0]; - $keywords = { - baz => JSON::true, - qux => JSON::true, - }; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + } + } + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [ $jmapmsg->{id} ], + properties => [ 'keywords', 'mailboxIds' ] + }, + 'R2' + ] + ]); + $jmapmsg = $res->[1][1]{list}[0]; + $keywords = { + baz => JSON::true, + qux => JSON::true, + }; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); - $self->assert_str_not_equals($res->[0][1]{oldState}, $res->[0][1]{newState}); + $self->assert_str_not_equals($res->[0][1]{oldState}, $res->[0][1]{newState}); - xlog $self, 'Patch $seen on email'; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $jmapmsg->{id} => { - 'keywords/$seen' => JSON::true - } - } - }, 'R1'], - ['Email/get', { - ids => [ $jmapmsg->{id} ], - properties => ['keywords', 'mailboxIds'] - }, 'R2' ] - ]); - $jmapmsg = $res->[1][1]{list}[0]; - $keywords = { - baz => JSON::true, - qux => JSON::true, - '$seen' => JSON::true, - }; - $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); + xlog $self, 'Patch $seen on email'; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $jmapmsg->{id} => { + 'keywords/$seen' => JSON::true + } + } + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [ $jmapmsg->{id} ], + properties => [ 'keywords', 'mailboxIds' ] + }, + 'R2' + ] + ]); + $jmapmsg = $res->[1][1]{list}[0]; + $keywords = { + baz => JSON::true, + qux => JSON::true, + '$seen' => JSON::true, + }; + $self->assert_deep_equals($keywords, $jmapmsg->{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_keywords_bogus_values b/cassandane/tiny-tests/JMAPEmail/email_set_keywords_bogus_values index a0e991533f..44d95df60b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_keywords_bogus_values +++ b/cassandane/tiny-tests/JMAPEmail/email_set_keywords_bogus_values @@ -2,75 +2,113 @@ use Cassandane::Tiny; sub test_email_set_keywords_bogus_values - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # See https://github.com/cyrusimap/cyrus-imapd/issues/2439 + # See https://github.com/cyrusimap/cyrus-imapd/issues/2439 - $self->make_message("foo") || die; - my $res = $jmap->CallMethods([['Email/query', { }, "R1"]]); - my $emailId = $res->[0][1]{ids}[0]; - $self->assert_not_null($res); + $self->make_message("foo") || die; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailId = $res->[0][1]{ids}[0]; + $self->assert_not_null($res); - $res = $jmap->CallMethods([['Email/set', { - 'update' => { $emailId => { - keywords => { - 'foo' => JSON::false, - }, - }}, - }, 'R1' ]]); - $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); + $res = $jmap->CallMethods([ [ + 'Email/set', + { + 'update' => { + $emailId => { + keywords => { + 'foo' => JSON::false, + }, + } + }, + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); - $res = $jmap->CallMethods([['Email/set', { - 'update' => { $emailId => { - 'keywords/foo' => JSON::false, - }, + $res = $jmap->CallMethods([ [ + 'Email/set', + { + 'update' => { + $emailId => { + 'keywords/foo' => JSON::false, }, - }, 'R1' ]]); - $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); + }, + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); - $res = $jmap->CallMethods([['Email/set', { - 'update' => { $emailId => { - keywords => { - 'foo' => 1, - }, - }}, - }, 'R1' ]]); - $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); + $res = $jmap->CallMethods([ [ + 'Email/set', + { + 'update' => { + $emailId => { + keywords => { + 'foo' => 1, + }, + } + }, + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); - $res = $jmap->CallMethods([['Email/set', { - 'update' => { $emailId => { - 'keywords/foo' => 1, - }, + $res = $jmap->CallMethods([ [ + 'Email/set', + { + 'update' => { + $emailId => { + 'keywords/foo' => 1, }, - }, 'R1' ]]); - $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); + }, + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); - $res = $jmap->CallMethods([['Email/set', { - 'update' => { $emailId => { - keywords => { - 'foo' => 'true', - }, - }}, - }, 'R1' ]]); - $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); + $res = $jmap->CallMethods([ [ + 'Email/set', + { + 'update' => { + $emailId => { + keywords => { + 'foo' => 'true', + }, + } + }, + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); - $res = $jmap->CallMethods([['Email/set', { - 'update' => { $emailId => { - 'keywords/foo' => 'true', - }, + $res = $jmap->CallMethods([ [ + 'Email/set', + { + 'update' => { + $emailId => { + 'keywords/foo' => 'true', }, - }, 'R1' ]]); - $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); + }, + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$emailId}); - $res = $jmap->CallMethods([['Email/set', { - 'update' => { $emailId => { - keywords => { - 'foo' => JSON::true, - }, - }}, - }, 'R1' ]]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); + $res = $jmap->CallMethods([ [ + 'Email/set', + { + 'update' => { + $emailId => { + keywords => { + 'foo' => JSON::true, + }, + } + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_language_header b/cassandane/tiny-tests/JMAPEmail/email_set_language_header index a4b1dfddc8..3ce29b7c9f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_language_header +++ b/cassandane/tiny-tests/JMAPEmail/email_set_language_header @@ -2,44 +2,46 @@ use Cassandane::Tiny; sub test_email_set_language_header - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true, - }, - from => [{ email => q{foo1@bar} }], - bodyStructure => { - language => ['de-DE', 'en-CA'], - partId => '1', - }, - bodyValues => { - "1" => { - value => "Das ist eine Email. This is an email.", - }, - }, - }, - }, - }, 'R1'], - ['Email/get', { - ids => ['#email1'], - properties => [ - 'bodyStructure', - ], - bodyProperties => [ - 'language', - 'header:Content-Language', - ], - }, 'R2'], - ]); - $self->assert_str_equals(' de-DE, en-CA', - $res->[1][1]{list}[0]{bodyStructure}{'header:Content-Language'}); - $self->assert_deep_equals(['de-DE', 'en-CA'], - $res->[1][1]{list}[0]{bodyStructure}{language}); + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true, + }, + from => [ { email => q{foo1@bar} } ], + bodyStructure => { + language => [ 'de-DE', 'en-CA' ], + partId => '1', + }, + bodyValues => { + "1" => { + value => "Das ist eine Email. This is an email.", + }, + }, + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => [ 'bodyStructure', ], + bodyProperties => [ 'language', 'header:Content-Language', ], + }, + 'R2' + ], + ]); + $self->assert_str_equals(' de-DE, en-CA', + $res->[1][1]{list}[0]{bodyStructure}{'header:Content-Language'}); + $self->assert_deep_equals([ 'de-DE', 'en-CA' ], + $res->[1][1]{list}[0]{bodyStructure}{language}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_mailbox_alias b/cassandane/tiny-tests/JMAPEmail/email_set_mailbox_alias index a236081afb..8991a21e29 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_mailbox_alias +++ b/cassandane/tiny-tests/JMAPEmail/email_set_mailbox_alias @@ -2,72 +2,91 @@ use Cassandane::Tiny; sub test_email_set_mailbox_alias - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - # Create mailboxes - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "drafts" => { - name => "Drafts", - parentId => undef, - role => "drafts" - }, - "trash" => { - name => "Trash", - parentId => undef, - role => "trash" - } - } - }, "R1"] - ]); - my $draftsMboxId = $res->[0][1]{created}{drafts}{id}; - $self->assert_not_null($draftsMboxId); - my $trashMboxId = $res->[0][1]{created}{trash}{id}; - $self->assert_not_null($trashMboxId); + # Create mailboxes + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "drafts" => { + name => "Drafts", + parentId => undef, + role => "drafts" + }, + "trash" => { + name => "Trash", + parentId => undef, + role => "trash" + } + } + }, + "R1" + ] ]); + my $draftsMboxId = $res->[0][1]{created}{drafts}{id}; + $self->assert_not_null($draftsMboxId); + my $trashMboxId = $res->[0][1]{created}{trash}{id}; + $self->assert_not_null($trashMboxId); - # Create email in mailbox using role as id - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - "1" => { - mailboxIds => { - '$drafts' => JSON::true - }, - from => [{ email => q{from@local}, name => q{} } ], - to => [{ email => q{to@local}, name => q{} } ], - } + # Create email in mailbox using role as id + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + "1" => { + mailboxIds => { + '$drafts' => JSON::true }, - }, 'R1'], - ['Email/get', { - ids => [ "#1" ], - properties => ['mailboxIds'], - }, "R2" ], - ]); - $self->assert_num_equals(1, scalar keys %{$res->[1][1]{list}[0]{mailboxIds}}); - $self->assert_not_null($res->[1][1]{list}[0]{mailboxIds}{$draftsMboxId}); - my $emailId = $res->[0][1]{created}{1}{id}; + from => [ { email => q{from@local}, name => q{} } ], + to => [ { email => q{to@local}, name => q{} } ], + } + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ["#1"], + properties => ['mailboxIds'], + }, + "R2" + ], + ]); + $self->assert_num_equals(1, + scalar keys %{ $res->[1][1]{list}[0]{mailboxIds} }); + $self->assert_not_null($res->[1][1]{list}[0]{mailboxIds}{$draftsMboxId}); + my $emailId = $res->[0][1]{created}{1}{id}; - # Move email to mailbox using role as id - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId => { - 'mailboxIds/$drafts' => undef, - 'mailboxIds/$trash' => JSON::true - } - }, - }, 'R1'], - ['Email/get', { - ids => [ $emailId ], - properties => ['mailboxIds'], - }, "R2" ], - ]); - $self->assert_num_equals(1, scalar keys %{$res->[1][1]{list}[0]{mailboxIds}}); - $self->assert_not_null($res->[1][1]{list}[0]{mailboxIds}{$trashMboxId}); + # Move email to mailbox using role as id + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + 'mailboxIds/$drafts' => undef, + 'mailboxIds/$trash' => JSON::true + } + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [$emailId], + properties => ['mailboxIds'], + }, + "R2" + ], + ]); + $self->assert_num_equals(1, + scalar keys %{ $res->[1][1]{list}[0]{mailboxIds} }); + $self->assert_not_null($res->[1][1]{list}[0]{mailboxIds}{$trashMboxId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_mailboxids b/cassandane/tiny-tests/JMAPEmail/email_set_mailboxids index 46cae1436e..9684926e75 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_mailboxids +++ b/cassandane/tiny-tests/JMAPEmail/email_set_mailboxids @@ -2,55 +2,66 @@ use Cassandane::Tiny; sub test_email_set_mailboxids - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $inboxid = $self->getinbox()->{id}; - $self->assert_not_null($inboxid); - - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { - "1" => { name => "drafts", parentId => undef, role => "drafts" }, - }}, "R1"] - ]); - my $draftsid = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsid); - - my $msg = { - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "I'm givin' ya one last chance ta surrenda!" }}, - keywords => { '$draft' => JSON::true }, - }; - - # Not OK: at least one mailbox must be specified - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $msg }}, "R1"]]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notCreated}{"1"}{type}); - $self->assert_str_equals('mailboxIds', $res->[0][1]{notCreated}{"1"}{properties}[0]); - $msg->{mailboxIds} = {}; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $msg }}, "R1"]]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notCreated}{"1"}{type}); - $self->assert_str_equals('mailboxIds', $res->[0][1]{notCreated}{"1"}{properties}[0]); - - # OK: drafts mailbox isn't required (anymore) - $msg->{mailboxIds} = { $inboxid => JSON::true }, - $msg->{subject} = "Email 1"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $msg }}, "R1"]]); - $self->assert(exists $res->[0][1]{created}{"1"}); - - # OK: drafts mailbox is OK to create in - $msg->{mailboxIds} = { $draftsid => JSON::true }, - $msg->{subject} = "Email 2"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $msg }}, "R1"]]); - $self->assert(exists $res->[0][1]{created}{"1"}); - - # OK: drafts mailbox is OK to create in, as is for multiple mailboxes - $msg->{mailboxIds} = { $draftsid => JSON::true, $inboxid => JSON::true }, + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $inboxid = $self->getinbox()->{id}; + $self->assert_not_null($inboxid); + + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { name => "drafts", parentId => undef, role => "drafts" }, + } + }, + "R1" + ] ]); + my $draftsid = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftsid); + + my $msg = { + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => + { '1' => { value => "I'm givin' ya one last chance ta surrenda!" } }, + keywords => { '$draft' => JSON::true }, + }; + + # Not OK: at least one mailbox must be specified + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $msg } }, "R1" ] ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{"1"}{type}); + $self->assert_str_equals('mailboxIds', + $res->[0][1]{notCreated}{"1"}{properties}[0]); + $msg->{mailboxIds} = {}; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $msg } }, "R1" ] ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{"1"}{type}); + $self->assert_str_equals('mailboxIds', + $res->[0][1]{notCreated}{"1"}{properties}[0]); + + # OK: drafts mailbox isn't required (anymore) + $msg->{mailboxIds} = { $inboxid => JSON::true }, $msg->{subject} = "Email 1"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $msg } }, "R1" ] ]); + $self->assert(exists $res->[0][1]{created}{"1"}); + + # OK: drafts mailbox is OK to create in + $msg->{mailboxIds} = { $draftsid => JSON::true }, $msg->{subject} = "Email 2"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $msg } }, "R1" ] ]); + $self->assert(exists $res->[0][1]{created}{"1"}); + + # OK: drafts mailbox is OK to create in, as is for multiple mailboxes + $msg->{mailboxIds} = { $draftsid => JSON::true, $inboxid => JSON::true }, $msg->{subject} = "Email 3"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $msg }}, "R1"]]); - $self->assert(exists $res->[0][1]{created}{"1"}); + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $msg } }, "R1" ] ]); + $self->assert(exists $res->[0][1]{created}{"1"}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_mimeversion b/cassandane/tiny-tests/JMAPEmail/email_set_mimeversion index d805dd35fe..d185583780 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_mimeversion +++ b/cassandane/tiny-tests/JMAPEmail/email_set_mimeversion @@ -2,46 +2,50 @@ use Cassandane::Tiny; sub test_email_set_mimeversion - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inboxid = $self->getinbox()->{id}; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inboxid = $self->getinbox()->{id}; - my $email1 = { - mailboxIds => { $inboxid => JSON::true }, - from => [{ name => "Test", email => q{foo@bar} }], - subject => "test", - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "A text body", - }, - }, - }; - my $email2 = { - mailboxIds => { $inboxid => JSON::true }, - from => [{ name => "Test", email => q{foo@bar} }], - subject => "test", - 'header:Mime-Version' => '1.1', - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "A text body", - }, - }, - }; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { '1' => $email1 , 2 => $email2 } }, 'R1'], - ['Email/get', { ids => ['#1', '#2'], properties => ['header:mime-version'] }, 'R2'], - ]); - $self->assert_str_equals(' 1.0', $res->[1][1]{list}[0]{'header:mime-version'}); - $self->assert_str_equals(' 1.1', $res->[1][1]{list}[1]{'header:mime-version'}); + my $email1 = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Test", email => q{foo@bar} } ], + subject => "test", + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "A text body", + }, + }, + }; + my $email2 = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Test", email => q{foo@bar} } ], + subject => "test", + 'header:Mime-Version' => '1.1', + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "A text body", + }, + }, + }; + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { '1' => $email1, 2 => $email2 } }, 'R1' ], + [ + 'Email/get', + { ids => [ '#1', '#2' ], properties => ['header:mime-version'] }, 'R2' + ], + ]); + $self->assert_str_equals(' 1.0', + $res->[1][1]{list}[0]{'header:mime-version'}); + $self->assert_str_equals(' 1.1', + $res->[1][1]{list}[1]{'header:mime-version'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_move b/cassandane/tiny-tests/JMAPEmail/email_set_move index 57f4bedb88..3a0c813b5e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_move +++ b/cassandane/tiny-tests/JMAPEmail/email_set_move @@ -2,62 +2,69 @@ use Cassandane::Tiny; sub test_email_set_move - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - - xlog $self, "Create test mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { - "a" => { name => "a", parentId => undef }, - "b" => { name => "b", parentId => undef }, - "c" => { name => "c", parentId => undef }, - "d" => { name => "d", parentId => undef }, - }}, "R1"] - ]); - $self->assert_num_equals( 4, scalar keys %{$res->[0][1]{created}} ); - my $a = $res->[0][1]{created}{"a"}{id}; - my $b = $res->[0][1]{created}{"b"}{id}; - my $c = $res->[0][1]{created}{"c"}{id}; - my $d = $res->[0][1]{created}{"d"}{id}; - - xlog $self, "Generate an email via IMAP"; - my %exp_sub; - $exp_sub{A} = $self->make_message( - "foo", body => "an email", - ); - - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $id = $res->[0][1]->{ids}[0]; - - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - - local *assert_move = sub { - my ($moveto) = (@_); - - xlog $self, "move email to " . Dumper($moveto); - $res = $jmap->CallMethods( - [ [ 'Email/set', { - update => { $id => { 'mailboxIds' => $moveto } }, - }, "R1" ] ] ); - $self->assert(exists $res->[0][1]{updated}{$id}); - - $res = $jmap->CallMethods( [ [ 'Email/get', { ids => [$id], properties => ['mailboxIds'] }, "R1" ] ] ); - $msg = $res->[0][1]->{list}[0]; - - $self->assert_deep_equals($moveto, $msg->{mailboxIds}); - }; - - assert_move({$a => JSON::true, $b => JSON::true}); - assert_move({$a => JSON::true, $b => JSON::true, $c => JSON::true}); - assert_move({$d => JSON::true}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + + xlog $self, "Create test mailboxes"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "a" => { name => "a", parentId => undef }, + "b" => { name => "b", parentId => undef }, + "c" => { name => "c", parentId => undef }, + "d" => { name => "d", parentId => undef }, + } + }, + "R1" + ] ]); + $self->assert_num_equals(4, scalar keys %{ $res->[0][1]{created} }); + my $a = $res->[0][1]{created}{"a"}{id}; + my $b = $res->[0][1]{created}{"b"}{id}; + my $c = $res->[0][1]{created}{"c"}{id}; + my $d = $res->[0][1]{created}{"d"}{id}; + + xlog $self, "Generate an email via IMAP"; + my %exp_sub; + $exp_sub{A} = $self->make_message("foo", body => "an email",); + + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $id = $res->[0][1]->{ids}[0]; + + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + + local *assert_move = sub { + my ($moveto) = (@_); + + xlog $self, "move email to " . Dumper($moveto); + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { $id => { 'mailboxIds' => $moveto } }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + $res + = $jmap->CallMethods( + [ [ 'Email/get', { ids => [$id], properties => ['mailboxIds'] }, "R1" ] ] + ); + $msg = $res->[0][1]->{list}[0]; + + $self->assert_deep_equals($moveto, $msg->{mailboxIds}); + }; + + assert_move({ $a => JSON::true, $b => JSON::true }); + assert_move({ $a => JSON::true, $b => JSON::true, $c => JSON::true }); + assert_move({ $d => JSON::true }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_move_keywords b/cassandane/tiny-tests/JMAPEmail/email_set_move_keywords index df5b25124a..d9dfacd86e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_move_keywords +++ b/cassandane/tiny-tests/JMAPEmail/email_set_move_keywords @@ -2,62 +2,68 @@ use Cassandane::Tiny; sub test_email_set_move_keywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; - xlog $self, "Generate an email via IMAP"; - my %exp_sub; - $exp_sub{A} = $self->make_message( - "foo", body => "an email", - ); - xlog $self, "Set flags on message"; - $store->set_folder('INBOX'); - $talk->store('1', '+flags', '($foo \\Flagged)'); + xlog $self, "Generate an email via IMAP"; + my %exp_sub; + $exp_sub{A} = $self->make_message("foo", body => "an email",); + xlog $self, "Set flags on message"; + $store->set_folder('INBOX'); + $talk->store('1', '+flags', '($foo \\Flagged)'); - xlog $self, "get email"; - my $res = $jmap->CallMethods([ - ['Email/query', {}, 'R1'], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids'}, - properties => [ 'keywords', 'mailboxIds' ], - }, 'R2' ] - ]); - my $msg = $res->[1][1]->{list}[0]; - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - my $msgId = $msg->{id}; - my $inboxId = (keys %{$msg->{mailboxIds}})[0]; - $self->assert_not_null($inboxId); - my $keywords = $msg->{keywords}; + xlog $self, "get email"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => [ 'keywords', 'mailboxIds' ], + }, + 'R2' + ] + ]); + my $msg = $res->[1][1]->{list}[0]; + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + my $msgId = $msg->{id}; + my $inboxId = (keys %{ $msg->{mailboxIds} })[0]; + $self->assert_not_null($inboxId); + my $keywords = $msg->{keywords}; - xlog $self, "create Archive mailbox"; - $res = $jmap->CallMethods([ ['Mailbox/get', {}, 'R1'], ]); - my $mboxState = $res->[0][1]{state}; - $talk->create("INBOX.Archive", "(USE (\\Archive))") || die; - $res = $jmap->CallMethods([ - ['Mailbox/changes', {sinceState => $mboxState }, 'R1'], - ]); - my $archiveId = $res->[0][1]{created}[0]; - $self->assert_not_null($archiveId); - $self->assert_deep_equals([], $res->[0][1]->{updated}); - $self->assert_deep_equals([], $res->[0][1]->{destroyed}); + xlog $self, "create Archive mailbox"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, 'R1' ], ]); + my $mboxState = $res->[0][1]{state}; + $talk->create("INBOX.Archive", "(USE (\\Archive))") || die; + $res = $jmap->CallMethods([ + [ 'Mailbox/changes', { sinceState => $mboxState }, 'R1' ], ]); + my $archiveId = $res->[0][1]{created}[0]; + $self->assert_not_null($archiveId); + $self->assert_deep_equals([], $res->[0][1]->{updated}); + $self->assert_deep_equals([], $res->[0][1]->{destroyed}); - xlog $self, "move email to Archive"; - xlog $self, "update email"; - $res = $jmap->CallMethods([ - ['Email/set', { update => { - $msgId => { - mailboxIds => { $archiveId => JSON::true } - }, - }}, "R1"], - ['Email/get', { ids => [ $msgId ], properties => ['keywords'] }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$msgId}); - $self->assert_deep_equals($keywords, $res->[1][1]{list}[0]{keywords}); + xlog $self, "move email to Archive"; + xlog $self, "update email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $msgId => { + mailboxIds => { $archiveId => JSON::true } + }, + } + }, + "R1" + ], + [ 'Email/get', { ids => [$msgId], properties => ['keywords'] }, 'R2' ], + ]); + $self->assert(exists $res->[0][1]{updated}{$msgId}); + $self->assert_deep_equals($keywords, $res->[1][1]{list}[0]{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_move_multiuid_patch b/cassandane/tiny-tests/JMAPEmail/email_set_move_multiuid_patch index c9b85d5096..22f1feb929 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_move_multiuid_patch +++ b/cassandane/tiny-tests/JMAPEmail/email_set_move_multiuid_patch @@ -2,30 +2,31 @@ use Cassandane::Tiny; sub test_email_set_move_multiuid_patch - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog "Set up mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { - }, 'R1'], - ['Mailbox/set', { - create => { - "a" => { name => "a", parentId => undef }, - }, - }, 'R2'], - ]); - my $srcMboxId = $res->[0][1]{ids}[0]; - $self->assert_not_null($srcMboxId); - my $dstMboxId = $res->[1][1]{created}{a}{id}; - $self->assert_not_null($dstMboxId); + xlog "Set up mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], + [ + 'Mailbox/set', + { + create => { + "a" => { name => "a", parentId => undef }, + }, + }, + 'R2' + ], + ]); + my $srcMboxId = $res->[0][1]{ids}[0]; + $self->assert_not_null($srcMboxId); + my $dstMboxId = $res->[1][1]{created}{a}{id}; + $self->assert_not_null($dstMboxId); - - xlog "Append same message twice to inbox"; - my $rawMessage = <<"EOF"; + xlog "Append same message twice to inbox"; + my $rawMessage = <<"EOF"; From: \r To: to\@local\r Subject: test\r @@ -35,47 +36,54 @@ Content-Type: text/plain\r \r test\r EOF - $imap->append('INBOX', $rawMessage) || die $@; - $imap->append('INBOX', $rawMessage) || die $@; - my $msgCount = $imap->message_count("INBOX"); - $self->assert_num_equals(2, $msgCount); - $res = $jmap->CallMethods([ - ['Email/query', { - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'mailboxIds' ], - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - my $emailId = $res->[0][1]{ids}[0]; - $self->assert_deep_equals( - { $srcMboxId => JSON::true }, - $res->[1][1]{list}[0]{mailboxIds} - ); + $imap->append('INBOX', $rawMessage) || die $@; + $imap->append('INBOX', $rawMessage) || die $@; + my $msgCount = $imap->message_count("INBOX"); + $self->assert_num_equals(2, $msgCount); + $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['mailboxIds'], + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + my $emailId = $res->[0][1]{ids}[0]; + $self->assert_deep_equals({ $srcMboxId => JSON::true }, + $res->[1][1]{list}[0]{mailboxIds}); - xlog "Move email to destination mailbox with mailboxIds patch"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId => { - 'mailboxIds/' . $srcMboxId => undef, - 'mailboxIds/' . $dstMboxId => JSON::true, - }, - }, - }, 'R1'], - ['Email/get', { - ids => [$emailId], - properties => [ 'mailboxIds' ], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); - $self->assert_deep_equals( - { $dstMboxId => JSON::true }, - $res->[1][1]{list}[0]{mailboxIds} - ); + xlog "Move email to destination mailbox with mailboxIds patch"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + 'mailboxIds/' . $srcMboxId => undef, + 'mailboxIds/' . $dstMboxId => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [$emailId], + properties => ['mailboxIds'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); + $self->assert_deep_equals({ $dstMboxId => JSON::true }, + $res->[1][1]{list}[0]{mailboxIds}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_move_multiuid_set b/cassandane/tiny-tests/JMAPEmail/email_set_move_multiuid_set index f89e63adef..89eda663c5 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_move_multiuid_set +++ b/cassandane/tiny-tests/JMAPEmail/email_set_move_multiuid_set @@ -2,30 +2,31 @@ use Cassandane::Tiny; sub test_email_set_move_multiuid_set - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog "Set up mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { - }, 'R1'], - ['Mailbox/set', { - create => { - "a" => { name => "a", parentId => undef }, - }, - }, 'R2'], - ]); - my $srcMboxId = $res->[0][1]{ids}[0]; - $self->assert_not_null($srcMboxId); - my $dstMboxId = $res->[1][1]{created}{a}{id}; - $self->assert_not_null($dstMboxId); + xlog "Set up mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], + [ + 'Mailbox/set', + { + create => { + "a" => { name => "a", parentId => undef }, + }, + }, + 'R2' + ], + ]); + my $srcMboxId = $res->[0][1]{ids}[0]; + $self->assert_not_null($srcMboxId); + my $dstMboxId = $res->[1][1]{created}{a}{id}; + $self->assert_not_null($dstMboxId); - - xlog "Append same message twice to inbox"; - my $rawMessage = <<"EOF"; + xlog "Append same message twice to inbox"; + my $rawMessage = <<"EOF"; From: \r To: to\@local\r Subject: test\r @@ -35,48 +36,55 @@ Content-Type: text/plain\r \r test\r EOF - $imap->append('INBOX', $rawMessage) || die $@; - $imap->append('INBOX', $rawMessage) || die $@; - my $msgCount = $imap->message_count("INBOX"); - $self->assert_num_equals(2, $msgCount); - $res = $jmap->CallMethods([ - ['Email/query', { - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => [ 'mailboxIds' ], - }, 'R2'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - my $emailId = $res->[0][1]{ids}[0]; - $self->assert_deep_equals( - { $srcMboxId => JSON::true }, - $res->[1][1]{list}[0]{mailboxIds} - ); + $imap->append('INBOX', $rawMessage) || die $@; + $imap->append('INBOX', $rawMessage) || die $@; + my $msgCount = $imap->message_count("INBOX"); + $self->assert_num_equals(2, $msgCount); + $res = $jmap->CallMethods([ + [ 'Email/query', {}, 'R1' ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['mailboxIds'], + }, + 'R2' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + my $emailId = $res->[0][1]{ids}[0]; + $self->assert_deep_equals({ $srcMboxId => JSON::true }, + $res->[1][1]{list}[0]{mailboxIds}); - xlog "Move email to destination mailbox with mailboxIds set"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId => { - mailboxIds => { - $dstMboxId => JSON::true - } - }, - }, - }, 'R1'], - ['Email/get', { - ids => [$emailId], - properties => [ 'mailboxIds' ], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$emailId}); - $self->assert_deep_equals( - { $dstMboxId => JSON::true }, - $res->[1][1]{list}[0]{mailboxIds} - ); + xlog "Move email to destination mailbox with mailboxIds set"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + mailboxIds => { + $dstMboxId => JSON::true + } + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [$emailId], + properties => ['mailboxIds'], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$emailId}); + $self->assert_deep_equals({ $dstMboxId => JSON::true }, + $res->[1][1]{list}[0]{mailboxIds}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_multipart_related b/cassandane/tiny-tests/JMAPEmail/email_set_multipart_related index 9e8a1ede26..187418fc24 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_multipart_related +++ b/cassandane/tiny-tests/JMAPEmail/email_set_multipart_related @@ -2,55 +2,65 @@ use Cassandane::Tiny; sub test_email_set_multipart_related - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $data = $jmap->Upload((pack "H*", "beefcode"), "image/gif"); - my $blobId = $data->{blobId}; - $self->assert_not_null($blobId); + my $data = $jmap->Upload((pack "H*", "beefcode"), "image/gif"); + my $blobId = $data->{blobId}; + $self->assert_not_null($blobId); - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ - email => 'from@local' - }], - subject => "test", - bodyStructure => { - type => "multipart/related", - subParts => [{ - type => 'text/html', - partId => '1', - }, { - type => 'image/gif', - blobId => $blobId, - }], - }, - bodyValues => { - "1" => { - value => "test", - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { + email => 'from@local' + } ], + subject => "test", + bodyStructure => { + type => "multipart/related", + subParts => [ + { + type => 'text/html', + partId => '1', }, + { + type => 'image/gif', + blobId => $blobId, + } + ], + }, + bodyValues => { + "1" => { + value => "test", + }, }, - }, 'R1'], - ['Email/get', { - ids => [ '#email1' ], - properties => [ 'bodyStructure' ], - bodyProperties => [ 'type', 'header:Content-Type' ], - }, 'R2' ], - ]); - $self->assert_not_null($res->[0][1]{created}{email1}); - $self->assert_str_equals('multipart/related', - $res->[1][1]{list}[0]{bodyStructure}{type}); + }, + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#email1'], + properties => ['bodyStructure'], + bodyProperties => [ 'type', 'header:Content-Type' ], + }, + 'R2' + ], + ]); + $self->assert_not_null($res->[0][1]{created}{email1}); + $self->assert_str_equals('multipart/related', + $res->[1][1]{list}[0]{bodyStructure}{type}); - my $ct = $res->[1][1]{list}[0]{bodyStructure}{'header:Content-Type'}; - $ct =~ tr/ \t\r\n//ds; - $self->assert($ct =~ /;type=\"text\/html\"$/); + my $ct = $res->[1][1]{list}[0]{bodyStructure}{'header:Content-Type'}; + $ct =~ tr/ \t\r\n//ds; + $self->assert($ct =~ /;type=\"text\/html\"$/); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_multipartdigest b/cassandane/tiny-tests/JMAPEmail/email_set_multipartdigest index 5eafd34ce1..40582552c7 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_multipartdigest +++ b/cassandane/tiny-tests/JMAPEmail/email_set_multipartdigest @@ -2,59 +2,69 @@ use Cassandane::Tiny; sub test_email_set_multipartdigest - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Generate emails via IMAP"; - $self->make_message() || die; - $self->make_message() || die; - my $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, - properties => ['blobId'], - }, 'R2' ], - ]); - my $emailBlobId1 = $res->[1][1]->{list}[0]->{blobId}; - $self->assert_not_null($emailBlobId1); - my $emailBlobId2 = $res->[1][1]->{list}[1]->{blobId}; - $self->assert_not_null($emailBlobId2); - $self->assert_str_not_equals($emailBlobId1, $emailBlobId2); + xlog $self, "Generate emails via IMAP"; + $self->make_message() || die; + $self->make_message() || die; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['blobId'], + }, + 'R2' + ], + ]); + my $emailBlobId1 = $res->[1][1]->{list}[0]->{blobId}; + $self->assert_not_null($emailBlobId1); + my $emailBlobId2 = $res->[1][1]->{list}[1]->{blobId}; + $self->assert_not_null($emailBlobId2); + $self->assert_str_not_equals($emailBlobId1, $emailBlobId2); - xlog $self, "Create email with multipart/digest body"; - my $inboxid = $self->getinbox()->{id}; - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [{ name => "Test", email => q{test@local} }], - subject => "test", - bodyStructure => { - type => "multipart/digest", - subParts => [{ - blobId => $emailBlobId1, - }, { - blobId => $emailBlobId2, - }], + xlog $self, "Create email with multipart/digest body"; + my $inboxid = $self->getinbox()->{id}; + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Test", email => q{test@local} } ], + subject => "test", + bodyStructure => { + type => "multipart/digest", + subParts => [ + { + blobId => $emailBlobId1, }, - }; - $res = $jmap->CallMethods([ - ['Email/set', { create => { '1' => $email } }, 'R1'], - ['Email/get', { - ids => [ '#1' ], - properties => [ 'bodyStructure' ], - bodyProperties => [ 'partId', 'blobId', 'type', 'header:Content-Type' ], - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]); + { + blobId => $emailBlobId2, + } + ], + }, + }; + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { '1' => $email } }, 'R1' ], + [ + 'Email/get', + { + ids => ['#1'], + properties => ['bodyStructure'], + bodyProperties => [ 'partId', 'blobId', 'type', 'header:Content-Type' ], + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); - my $subPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[0]; - $self->assert_str_equals("message/rfc822", $subPart->{type}); - $self->assert_null($subPart->{'header:Content-Type'}); - $subPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]; - $self->assert_str_equals("message/rfc822", $subPart->{type}); - $self->assert_null($subPart->{'header:Content-Type'}); + my $subPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[0]; + $self->assert_str_equals("message/rfc822", $subPart->{type}); + $self->assert_null($subPart->{'header:Content-Type'}); + $subPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1]; + $self->assert_str_equals("message/rfc822", $subPart->{type}); + $self->assert_null($subPart->{'header:Content-Type'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_nullheader b/cassandane/tiny-tests/JMAPEmail/email_set_nullheader index 10761e542d..765a5d9ba4 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_nullheader +++ b/cassandane/tiny-tests/JMAPEmail/email_set_nullheader @@ -2,36 +2,39 @@ use Cassandane::Tiny; sub test_email_set_nullheader - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - my $text = "x"; + my $text = "x"; - # Prepare test email - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [ { email => q{test1@robmtest.vm}, name => q{} } ], - 'header:foo' => undef, - 'header:foo:asMessageIds' => undef, - }; + # Prepare test email + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { email => q{test1@robmtest.vm}, name => q{} } ], + 'header:foo' => undef, + 'header:foo:asMessageIds' => undef, + }; - # Create and get mail - my $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ['Email/get', { - ids => [ "#1" ], - properties => [ 'headers', 'header:foo' ], - }, "R2" ], - ]); - my $msg = $res->[1][1]{list}[0]; + # Create and get mail + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], + [ + 'Email/get', + { + ids => ["#1"], + properties => [ 'headers', 'header:foo' ], + }, + "R2" + ], + ]); + my $msg = $res->[1][1]{list}[0]; - foreach (@{$msg->{headers}}) { - xlog $self, "Checking header $_->{name}"; - $self->assert_str_not_equals('foo', $_->{name}); - } - $self->assert_null($msg->{'header:foo'}); + foreach (@{ $msg->{headers} }) { + xlog $self, "Checking header $_->{name}"; + $self->assert_str_not_equals('foo', $_->{name}); + } + $self->assert_null($msg->{'header:foo'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_patch b/cassandane/tiny-tests/JMAPEmail/email_set_patch index 2d5bd03aab..eeba5b2e3b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_patch +++ b/cassandane/tiny-tests/JMAPEmail/email_set_patch @@ -2,79 +2,84 @@ use Cassandane::Tiny; sub test_email_set_patch - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - my $draft = { - mailboxIds => { $inboxid => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], - subject => "Memo", - textBody => [{ partId => '1' }], - bodyValues => { '1' => { value => "Whoa!" }}, - keywords => { '$draft' => JSON::true, foo => JSON::true }, - }; + my $draft = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => "Whoa!" } }, + keywords => { '$draft' => JSON::true, foo => JSON::true }, + }; - xlog $self, "Create draft email"; - $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $draft }}, "R1"], - ]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "Create draft email"; + $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $draft } }, "R1" ], ]); + my $id = $res->[0][1]{created}{"1"}{id}; - $res = $jmap->CallMethods([ - ['Email/get', { 'ids' => [$id] }, 'R2' ] - ]); - my $msg = $res->[0][1]->{list}[0]; - $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); - $self->assert_equals(JSON::true, $msg->{keywords}->{'foo'}); - $self->assert_num_equals(2, scalar keys %{$msg->{keywords}}); - $self->assert_equals(JSON::true, $msg->{mailboxIds}->{$inboxid}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); + $res = $jmap->CallMethods([ [ 'Email/get', { 'ids' => [$id] }, 'R2' ] ]); + my $msg = $res->[0][1]->{list}[0]; + $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'foo'}); + $self->assert_num_equals(2, scalar keys %{ $msg->{keywords} }); + $self->assert_equals(JSON::true, $msg->{mailboxIds}->{$inboxid}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); - xlog $self, "Patch email keywords"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $id => { - "keywords/foo" => undef, - "keywords/bar" => JSON::true, - } - }, - }, "R1"], - ['Email/get', { ids => [$id], properties => ['keywords'] }, 'R2'], - ]); + xlog $self, "Patch email keywords"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $id => { + "keywords/foo" => undef, + "keywords/bar" => JSON::true, + } + }, + }, + "R1" + ], + [ 'Email/get', { ids => [$id], properties => ['keywords'] }, 'R2' ], + ]); - $msg = $res->[1][1]->{list}[0]; - $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); - $self->assert_equals(JSON::true, $msg->{keywords}->{'bar'}); - $self->assert_num_equals(2, scalar keys %{$msg->{keywords}}); + $msg = $res->[1][1]->{list}[0]; + $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'bar'}); + $self->assert_num_equals(2, scalar keys %{ $msg->{keywords} }); - xlog $self, "create mailbox"; - $res = $jmap->CallMethods([['Mailbox/set', {create => { "1" => { name => "baz", }}}, "R1"]]); - my $mboxid = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($mboxid); + xlog $self, "create mailbox"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/set', { create => { "1" => { name => "baz", } } }, "R1" ] ]); + my $mboxid = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($mboxid); - xlog $self, "Patch email mailboxes"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $id => { - "mailboxIds/$inboxid" => undef, - "mailboxIds/$mboxid" => JSON::true, - } - }, - }, "R1"], - ['Email/get', { ids => [$id], properties => ['mailboxIds'] }, 'R2'], - ]); - $msg = $res->[1][1]->{list}[0]; - $self->assert_equals(JSON::true, $msg->{mailboxIds}->{$mboxid}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); + xlog $self, "Patch email mailboxes"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $id => { + "mailboxIds/$inboxid" => undef, + "mailboxIds/$mboxid" => JSON::true, + } + }, + }, + "R1" + ], + [ 'Email/get', { ids => [$id], properties => ['mailboxIds'] }, 'R2' ], + ]); + $msg = $res->[1][1]->{list}[0]; + $self->assert_equals(JSON::true, $msg->{mailboxIds}->{$mboxid}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_seen b/cassandane/tiny-tests/JMAPEmail/email_set_seen index 2eac8a57ef..3f7c933eed 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_seen +++ b/cassandane/tiny-tests/JMAPEmail/email_set_seen @@ -2,40 +2,50 @@ use Cassandane::Tiny; sub test_email_set_seen - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # See https://github.com/cyrusimap/cyrus-imapd/issues/2270 + # See https://github.com/cyrusimap/cyrus-imapd/issues/2270 - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Add message"; - $self->make_message('Message A'); + xlog $self, "Add message"; + $self->make_message('Message A'); - xlog $self, "Query email"; - my $inbox = $self->getinbox(); - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { inMailbox => $inbox->{id} } - }, 'R1'], - ['Email/get', { - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids'} - }, 'R2' ] - ]); + xlog $self, "Query email"; + my $inbox = $self->getinbox(); + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { inMailbox => $inbox->{id} } + }, + 'R1' + ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } + }, + 'R2' + ] + ]); - my $keywords = { }; - my $msg = $res->[1][1]->{list}[0]; - $self->assert_deep_equals($keywords, $msg->{keywords}); + my $keywords = {}; + my $msg = $res->[1][1]->{list}[0]; + $self->assert_deep_equals($keywords, $msg->{keywords}); - $keywords->{'$seen'} = JSON::true; - $res = $jmap->CallMethods([ - ['Email/set', { update => { $msg->{id} => { 'keywords/$seen' => JSON::true } } }, 'R1'], - ['Email/get', { ids => [ $msg->{id} ] }, 'R2'], - ]); - $msg = $res->[1][1]->{list}[0]; - $self->assert_deep_equals($keywords, $msg->{keywords}); + $keywords->{'$seen'} = JSON::true; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { update => { $msg->{id} => { 'keywords/$seen' => JSON::true } } }, 'R1' + ], + [ 'Email/get', { ids => [ $msg->{id} ] }, 'R2' ], + ]); + $msg = $res->[1][1]->{list}[0]; + $self->assert_deep_equals($keywords, $msg->{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_setflags_mboxevent b/cassandane/tiny-tests/JMAPEmail/email_set_setflags_mboxevent index 674c305896..ba37cb5d4f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_setflags_mboxevent +++ b/cassandane/tiny-tests/JMAPEmail/email_set_setflags_mboxevent @@ -2,158 +2,176 @@ use Cassandane::Tiny; sub test_email_set_setflags_mboxevent - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ + : min_version_3_1 : needs_component_sieve : needs_component_jmap { - my ($self) = @_; + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "create mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "A" => { - name => "A", - }, - "B" => { - name => "B", - }, - }, - }, "R1"] - ]); - my $mboxIdA = $res->[0][1]{created}{A}{id}; - $self->assert_not_null($mboxIdA); - my $mboxIdB = $res->[0][1]{created}{B}{id}; - $self->assert_not_null($mboxIdB); + xlog $self, "create mailboxes"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "A" => { + name => "A", + }, + "B" => { + name => "B", + }, + }, + }, + "R1" + ] ]); + my $mboxIdA = $res->[0][1]{created}{A}{id}; + $self->assert_not_null($mboxIdA); + my $mboxIdB = $res->[0][1]{created}{B}{id}; + $self->assert_not_null($mboxIdB); - xlog $self, "Create emails"; - # Use separate requests for deterministic order of UIDs. - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - msgA1 => { - mailboxIds => { - $mboxIdA => JSON::true - }, - from => [{ - email => q{test1@local}, - name => q{} - }], - to => [{ - email => q{test2@local}, - name => '', - }], - subject => 'msgA1', - keywords => { - '$seen' => JSON::true, - }, - }, - } - }, "R1"], - ['Email/set', { - create => { - msgA2 => { - mailboxIds => { - $mboxIdA => JSON::true - }, - from => [{ - email => q{test1@local}, - name => q{} - }], - to => [{ - email => q{test2@local}, - name => '', - }], - subject => 'msgA2', - }, - } - }, "R2"], - ['Email/set', { - create => { - msgB1 => { - mailboxIds => { - $mboxIdB => JSON::true - }, - from => [{ - email => q{test1@local}, - name => q{} - }], - to => [{ - email => q{test2@local}, - name => '', - }], - keywords => { - baz => JSON::true, - }, - subject => 'msgB1', - }, - } - }, "R3"], - ]); - my $emailIdA1 = $res->[0][1]{created}{msgA1}{id}; - $self->assert_not_null($emailIdA1); - my $emailIdA2 = $res->[1][1]{created}{msgA2}{id}; - $self->assert_not_null($emailIdA2); - my $emailIdB1 = $res->[2][1]{created}{msgB1}{id}; - $self->assert_not_null($emailIdB1); + xlog $self, "Create emails"; + # Use separate requests for deterministic order of UIDs. + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + msgA1 => { + mailboxIds => { + $mboxIdA => JSON::true + }, + from => [ { + email => q{test1@local}, + name => q{} + } ], + to => [ { + email => q{test2@local}, + name => '', + } ], + subject => 'msgA1', + keywords => { + '$seen' => JSON::true, + }, + }, + } + }, + "R1" + ], + [ + 'Email/set', + { + create => { + msgA2 => { + mailboxIds => { + $mboxIdA => JSON::true + }, + from => [ { + email => q{test1@local}, + name => q{} + } ], + to => [ { + email => q{test2@local}, + name => '', + } ], + subject => 'msgA2', + }, + } + }, + "R2" + ], + [ + 'Email/set', + { + create => { + msgB1 => { + mailboxIds => { + $mboxIdB => JSON::true + }, + from => [ { + email => q{test1@local}, + name => q{} + } ], + to => [ { + email => q{test2@local}, + name => '', + } ], + keywords => { + baz => JSON::true, + }, + subject => 'msgB1', + }, + } + }, + "R3" + ], + ]); + my $emailIdA1 = $res->[0][1]{created}{msgA1}{id}; + $self->assert_not_null($emailIdA1); + my $emailIdA2 = $res->[1][1]{created}{msgA2}{id}; + $self->assert_not_null($emailIdA2); + my $emailIdB1 = $res->[2][1]{created}{msgB1}{id}; + $self->assert_not_null($emailIdB1); - # Clear notification cache - $self->{instance}->getnotify(); + # Clear notification cache + $self->{instance}->getnotify(); - # Update emails - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailIdA1 => { - 'keywords/$seen' => undef, - 'keywords/foo' => JSON::true, - }, - $emailIdA2 => { - keywords => { - 'bar' => JSON::true, - }, - }, - $emailIdB1 => { - 'keywords/baz' => undef, - }, - } - }, "R1"], - ]); - $self->assert(exists $res->[0][1]{updated}{$emailIdA1}); - $self->assert(exists $res->[0][1]{updated}{$emailIdA2}); - $self->assert(exists $res->[0][1]{updated}{$emailIdB1}); + # Update emails + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailIdA1 => { + 'keywords/$seen' => undef, + 'keywords/foo' => JSON::true, + }, + $emailIdA2 => { + keywords => { + 'bar' => JSON::true, + }, + }, + $emailIdB1 => { + 'keywords/baz' => undef, + }, + } + }, + "R1" + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$emailIdA1}); + $self->assert(exists $res->[0][1]{updated}{$emailIdA2}); + $self->assert(exists $res->[0][1]{updated}{$emailIdB1}); - # Gather notifications - my $data = $self->{instance}->getnotify(); - if ($self->{replica}) { - my $more = $self->{replica}->getnotify(); - push @$data, @$more; - } + # Gather notifications + my $data = $self->{instance}->getnotify(); + if ($self->{replica}) { + my $more = $self->{replica}->getnotify(); + push @$data, @$more; + } - # Assert notifications - my %flagsClearEvents; - my %flagsSetEvents; - foreach (@$data) { - my $event = decode_json($_->{MESSAGE}); - if ($event->{event} eq "FlagsClear") { - $flagsClearEvents{$event->{mailboxID}} = $event; - } - elsif ($event->{event} eq "FlagsSet") { - $flagsSetEvents{$event->{mailboxID}} = $event; - } + # Assert notifications + my %flagsClearEvents; + my %flagsSetEvents; + foreach (@$data) { + my $event = decode_json($_->{MESSAGE}); + if ($event->{event} eq "FlagsClear") { + $flagsClearEvents{ $event->{mailboxID} } = $event; + } elsif ($event->{event} eq "FlagsSet") { + $flagsSetEvents{ $event->{mailboxID} } = $event; } + } - # Assert mailbox A events. - $self->assert_str_equals('1:2', $flagsSetEvents{$mboxIdA}{uidset}); - $self->assert_num_not_equals(-1, index($flagsSetEvents{$mboxIdA}{flagNames}, 'foo')); - $self->assert_num_not_equals(-1, index($flagsSetEvents{$mboxIdA}{flagNames}, 'bar')); - $self->assert_str_equals('1', $flagsClearEvents{$mboxIdA}{uidset}); - $self->assert_str_equals('\Seen', $flagsClearEvents{$mboxIdA}{flagNames}); + # Assert mailbox A events. + $self->assert_str_equals('1:2', $flagsSetEvents{$mboxIdA}{uidset}); + $self->assert_num_not_equals(-1, + index($flagsSetEvents{$mboxIdA}{flagNames}, 'foo')); + $self->assert_num_not_equals(-1, + index($flagsSetEvents{$mboxIdA}{flagNames}, 'bar')); + $self->assert_str_equals('1', $flagsClearEvents{$mboxIdA}{uidset}); + $self->assert_str_equals('\Seen', $flagsClearEvents{$mboxIdA}{flagNames}); - # Assert mailbox B events. - $self->assert(not exists $flagsSetEvents{$mboxIdB}); - $self->assert_str_equals('1', $flagsClearEvents{$mboxIdB}{uidset}); - $self->assert_str_equals('baz', $flagsClearEvents{$mboxIdB}{flagNames}); + # Assert mailbox B events. + $self->assert(not exists $flagsSetEvents{$mboxIdB}); + $self->assert_str_equals('1', $flagsClearEvents{$mboxIdB}{uidset}); + $self->assert_str_equals('baz', $flagsClearEvents{$mboxIdB}{flagNames}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_shared b/cassandane/tiny-tests/JMAPEmail/email_set_shared index 222e663689..fe1a852903 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_shared +++ b/cassandane/tiny-tests/JMAPEmail/email_set_shared @@ -2,62 +2,76 @@ use Cassandane::Tiny; sub test_email_set_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - xlog $self, "Create user and share mailbox"; - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lrswntex") or die; - - xlog $self, "Create email in shared account via IMAP"; - $self->{adminstore}->set_folder('user.foo'); - $self->make_message("Email foo", store => $self->{adminstore}) or die; - - xlog $self, "get email"; - my $res = $jmap->CallMethods([ - ['Email/query', { accountId => 'foo' }, "R1"], - ]); - my $id = $res->[0][1]->{ids}[0]; - - xlog $self, "toggle Seen flag on email"; - $res = $jmap->CallMethods([['Email/set', { - accountId => 'foo', - update => { $id => { keywords => { '$seen' => JSON::true } } }, - }, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$id}); - - xlog $self, "Remove right to write annotations"; - $admintalk->setacl("user.foo", "cassandane", "lrtex") or die; - - xlog $self, 'Toggle \\Seen flag on email (should fail)'; - $res = $jmap->CallMethods([['Email/set', { - accountId => 'foo', - update => { $id => { keywords => { } } }, - }, "R1"]]); - $self->assert(exists $res->[0][1]{notUpdated}{$id}); - - xlog $self, "Remove right to delete email"; - $admintalk->setacl("user.foo", "cassandane", "lr") or die; - - xlog $self, 'Delete email (should fail)'; - $res = $jmap->CallMethods([['Email/set', { - accountId => 'foo', - destroy => [ $id ], - }, "R1"]]); - $self->assert(exists $res->[0][1]{notDestroyed}{$id}); - - xlog $self, "Add right to delete email"; - $admintalk->setacl("user.foo", "cassandane", "lrtex") or die; - - xlog $self, 'Delete email'; - $res = $jmap->CallMethods([['Email/set', { - accountId => 'foo', - destroy => [ $id ], - }, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + xlog $self, "Create user and share mailbox"; + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lrswntex") or die; + + xlog $self, "Create email in shared account via IMAP"; + $self->{adminstore}->set_folder('user.foo'); + $self->make_message("Email foo", store => $self->{adminstore}) or die; + + xlog $self, "get email"; + my $res + = $jmap->CallMethods([ [ 'Email/query', { accountId => 'foo' }, "R1" ], ]); + my $id = $res->[0][1]->{ids}[0]; + + xlog $self, "toggle Seen flag on email"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'foo', + update => { $id => { keywords => { '$seen' => JSON::true } } }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$id}); + + xlog $self, "Remove right to write annotations"; + $admintalk->setacl("user.foo", "cassandane", "lrtex") or die; + + xlog $self, 'Toggle \\Seen flag on email (should fail)'; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'foo', + update => { $id => { keywords => {} } }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{notUpdated}{$id}); + + xlog $self, "Remove right to delete email"; + $admintalk->setacl("user.foo", "cassandane", "lr") or die; + + xlog $self, 'Delete email (should fail)'; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'foo', + destroy => [$id], + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{notDestroyed}{$id}); + + xlog $self, "Add right to delete email"; + $admintalk->setacl("user.foo", "cassandane", "lrtex") or die; + + xlog $self, 'Delete email'; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'foo', + destroy => [$id], + }, + "R1" + ] ]); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_text_crlf b/cassandane/tiny-tests/JMAPEmail/email_set_text_crlf index d59c80d663..faa0235e11 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_text_crlf +++ b/cassandane/tiny-tests/JMAPEmail/email_set_text_crlf @@ -2,33 +2,32 @@ use Cassandane::Tiny; sub test_email_set_text_crlf - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - my $text = "ab\r\ncde\rfgh\nij"; - my $want = "ab\ncdefgh\nij"; + my $text = "ab\r\ncde\rfgh\nij"; + my $want = "ab\ncdefgh\nij"; - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [ { email => q{test1@robmtest.vm}, name => q{} } ], - to => [ { - email => q{foo@bar.com}, - name => "foo", - } ], - textBody => [{partId => '1'}], - bodyValues => {1 => { value => $text }}, - }; + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { email => q{test1@robmtest.vm}, name => q{} } ], + to => [ { + email => q{foo@bar.com}, + name => "foo", + } ], + textBody => [ { partId => '1' } ], + bodyValues => { 1 => { value => $text } }, + }; - xlog $self, "create and get email"; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ['Email/get', { ids => [ "#1" ], fetchAllBodyValues => JSON::true }, "R2" ], - ]); - my $ret = $res->[1][1]->{list}[0]; - my $got = $ret->{bodyValues}{$ret->{textBody}[0]{partId}}{value}; - $self->assert_str_equals($want, $got); + xlog $self, "create and get email"; + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], + [ 'Email/get', { ids => ["#1"], fetchAllBodyValues => JSON::true }, "R2" ], + ]); + my $ret = $res->[1][1]->{list}[0]; + my $got = $ret->{bodyValues}{ $ret->{textBody}[0]{partId} }{value}; + $self->assert_str_equals($want, $got); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_text_split b/cassandane/tiny-tests/JMAPEmail/email_set_text_split index ca79ef197c..21844a0020 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_text_split +++ b/cassandane/tiny-tests/JMAPEmail/email_set_text_split @@ -2,31 +2,30 @@ use Cassandane::Tiny; sub test_email_set_text_split - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $inboxid = $self->getinbox()->{id}; + my $inboxid = $self->getinbox()->{id}; - my $text = "x" x 2000; + my $text = "x" x 2000; - my $email = { - mailboxIds => { $inboxid => JSON::true }, - from => [ { email => q{test1@robmtest.vm}, name => q{} } ], - to => [ { - email => q{foo@bar.com}, - name => "foo", - } ], - textBody => [{partId => '1'}], - bodyValues => {1 => { value => $text }}, - }; + my $email = { + mailboxIds => { $inboxid => JSON::true }, + from => [ { email => q{test1@robmtest.vm}, name => q{} } ], + to => [ { + email => q{foo@bar.com}, + name => "foo", + } ], + textBody => [ { partId => '1' } ], + bodyValues => { 1 => { value => $text } }, + }; - xlog $self, "create and get email"; - my $res = $jmap->CallMethods([ - ['Email/set', { create => { "1" => $email }}, "R1"], - ['Email/get', { ids => [ "#1" ], fetchAllBodyValues => JSON::true }, "R2" ], - ]); - my $ret = $res->[1][1]->{list}[0]; - my $got = $ret->{bodyValues}{$ret->{textBody}[0]{partId}}{value}; + xlog $self, "create and get email"; + my $res = $jmap->CallMethods([ + [ 'Email/set', { create => { "1" => $email } }, "R1" ], + [ 'Email/get', { ids => ["#1"], fetchAllBodyValues => JSON::true }, "R2" ], + ]); + my $ret = $res->[1][1]->{list}[0]; + my $got = $ret->{bodyValues}{ $ret->{textBody}[0]{partId} }{value}; } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update b/cassandane/tiny-tests/JMAPEmail/email_set_update index c3bcea8d9a..b1e1c8794d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update @@ -2,56 +2,63 @@ use Cassandane::Tiny; sub test_email_set_update - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $drafts = $res->[0][1]{created}{"1"}{id}; - - my $draft = { - mailboxIds => {$drafts => JSON::true}, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], - to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" } ], - cc => [ { name => "Elmer Fudd", email => "elmer\@acme.local" } ], - subject => "created", - htmlBody => [ {partId => '1'} ], - bodyValues => { 1 => { value => "Oh!!! I hate that Rabbit." }}, - keywords => { - '$draft' => JSON::true, + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" } - }; + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $drafts = $res->[0][1]{created}{"1"}{id}; + + my $draft = { + mailboxIds => { $drafts => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" } ], + cc => [ { name => "Elmer Fudd", email => "elmer\@acme.local" } ], + subject => "created", + htmlBody => [ { partId => '1' } ], + bodyValues => { 1 => { value => "Oh!!! I hate that Rabbit." } }, + keywords => { + '$draft' => JSON::true, + } + }; - xlog $self, "Create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "Create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; - xlog $self, "Update draft $id"; - $draft->{keywords} = { - '$draft' => JSON::true, - '$flagged' => JSON::true, - '$seen' => JSON::true, - '$answered' => JSON::true, - }; - $res = $jmap->CallMethods([['Email/set', { update => { $id => $draft }}, "R1"]]); + xlog $self, "Update draft $id"; + $draft->{keywords} = { + '$draft' => JSON::true, + '$flagged' => JSON::true, + '$seen' => JSON::true, + '$answered' => JSON::true, + }; + $res = $jmap->CallMethods( + [ [ 'Email/set', { update => { $id => $draft } }, "R1" ] ]); - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - $msg = $res->[0][1]->{list}[0]; - $self->assert_deep_equals($draft->{keywords}, $msg->{keywords}); + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_deep_equals($draft->{keywords}, $msg->{keywords}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_after_attach b/cassandane/tiny-tests/JMAPEmail/email_set_update_after_attach index 600567b818..e4638ee9c6 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_after_attach +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_after_attach @@ -2,98 +2,121 @@ use Cassandane::Tiny; sub test_email_set_update_after_attach - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $store = $self->{store}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $store = $self->{store}; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $using = [ - 'https://cyrusimap.org/ns/jmap/debug', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/debug', 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; - $talk->create('INBOX.A') or die; - $talk->create('INBOX.B') or die; - $talk->create('INBOX.C') or die; + $talk->create('INBOX.A') or die; + $talk->create('INBOX.B') or die; + $talk->create('INBOX.C') or die; - # Get mailboxes - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]], $using); - $self->assert_not_null($res); - my %mboxIdByName = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; + # Get mailboxes + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ], $using); + $self->assert_not_null($res); + my %mboxIdByName = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; - # Create email in mailbox A - $store->set_folder('INBOX.A'); - $self->make_message('Email1') || die; + # Create email in mailbox A + $store->set_folder('INBOX.A'); + $self->make_message('Email1') || die; - $res = $jmap->CallMethods([['Email/query', { - }, 'R1']], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $emailId = $res->[0][1]->{ids}[0]; - $self->assert_not_null($emailId); + $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ] ], $using); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $emailId = $res->[0][1]->{ids}[0]; + $self->assert_not_null($emailId); - $res = $jmap->CallMethods([['Email/get', { ids => [ $emailId ], - }, 'R1']], $using); - my $blobId = $res->[0][1]->{list}[0]{blobId}; - $self->assert_not_null($blobId); + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$emailId], }, 'R1' ] ], + $using); + my $blobId = $res->[0][1]->{list}[0]{blobId}; + $self->assert_not_null($blobId); - $res = $jmap->CallMethods([['Email/set', { + $res = $jmap->CallMethods( + [ [ + 'Email/set', + { create => { - 'k1' => { - mailboxIds => { - $mboxIdByName{'B'} => JSON::true, - }, - from => [{ name => "Test", email => q{test@local} }], - subject => "test", - bodyStructure => { - type => "multipart/mixed", - subParts => [{ - type => 'text/plain', - partId => 'part1', - },{ - type => 'message/rfc822', - blobId => $blobId, - }], - }, - bodyValues => { - part1 => { - value => 'world', - } + 'k1' => { + mailboxIds => { + $mboxIdByName{'B'} => JSON::true, + }, + from => [ { name => "Test", email => q{test@local} } ], + subject => "test", + bodyStructure => { + type => "multipart/mixed", + subParts => [ + { + type => 'text/plain', + partId => 'part1', }, + { + type => 'message/rfc822', + blobId => $blobId, + } + ], }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, }, - }, 'R1']], $using); - my $newEmailId = $res->[0][1]{created}{k1}{id}; - $self->assert_not_null($newEmailId); + }, + 'R1' + ] ], + $using + ); + my $newEmailId = $res->[0][1]{created}{k1}{id}; + $self->assert_not_null($newEmailId); - # now move the new email into folder C - $res = $jmap->CallMethods([['Email/set', { + # now move the new email into folder C + $res = $jmap->CallMethods( + [ [ + 'Email/set', + { update => { - $emailId => { - # set to exact so it picks up the copy in B if we're being buggy - mailboxIds => { $mboxIdByName{'C'} => JSON::true }, - }, + $emailId => { + # set to exact so it picks up the copy in B if we're being buggy + mailboxIds => { $mboxIdByName{'C'} => JSON::true }, + }, }, - }, 'R1']], $using); - $self->assert_not_null($res); - $self->assert(exists $res->[0][1]{updated}{$emailId}); - $self->assert_null($res->[0][1]{notUpdated}); + }, + 'R1' + ] ], + $using + ); + $self->assert_not_null($res); + $self->assert(exists $res->[0][1]{updated}{$emailId}); + $self->assert_null($res->[0][1]{notUpdated}); - $res = $jmap->CallMethods([['Email/get', { - ids => [$emailId, $newEmailId], + $res = $jmap->CallMethods( + [ [ + 'Email/get', + { + ids => [ $emailId, $newEmailId ], properties => ['mailboxIds'], - }, "R1"]], $using); - $self->assert_num_equals(0, scalar @{$res->[0][1]{notFound}}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); - my %emailById = map { $_->{id} => $_ } @{$res->[0][1]{list}}; + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{notFound} }); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); + my %emailById = map { $_->{id} => $_ } @{ $res->[0][1]{list} }; - # now we need to test for actual location - $self->assert_deep_equals({$mboxIdByName{'C'} => JSON::true}, - $emailById{$emailId}->{mailboxIds}); - $self->assert_deep_equals({$mboxIdByName{'B'} => JSON::true}, - $emailById{$newEmailId}->{mailboxIds}); + # now we need to test for actual location + $self->assert_deep_equals({ $mboxIdByName{'C'} => JSON::true }, + $emailById{$emailId}->{mailboxIds}); + $self->assert_deep_equals( + { $mboxIdByName{'B'} => JSON::true }, + $emailById{$newEmailId}->{mailboxIds} + ); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_bulk b/cassandane/tiny-tests/JMAPEmail/email_set_update_bulk index 9eb57e03d9..ef5f0e9296 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_bulk +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_bulk @@ -2,123 +2,158 @@ use Cassandane::Tiny; sub test_email_set_update_bulk - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $store = $self->{store}; - - my $talk = $self->{store}->get_client(); - - my $using = [ - 'https://cyrusimap.org/ns/jmap/debug', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; - - - $talk->create('INBOX.A') or die; - $talk->create('INBOX.B') or die; - $talk->create('INBOX.C') or die; - $talk->create('INBOX.D') or die; - - # Get mailboxes - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]], $using); - $self->assert_not_null($res); - my %mboxIdByName = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; - - # Create email in mailbox A and B - $store->set_folder('INBOX.A'); - $self->make_message('Email1') || die; - $talk->copy(1, 'INBOX.B'); - $talk->store(1, "+flags", "(\\Seen hello)"); - - # check that the flags aren't on B - $talk->select("INBOX.B"); - $res = $talk->fetch("1", "(flags)"); - my @flags = @{$res->{1}{flags}}; - $self->assert_null(grep { $_ eq 'hello' } @flags); - $self->assert_null(grep { $_ eq '\\Seen' } @flags); - - # Create email in mailboox A - $talk->select("INBOX.A"); - $self->make_message('Email2') || die; - - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => 'subject' }], - }, 'R1']], $using); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - my $emailId1 = $res->[0][1]->{ids}[0]; - my $emailId2 = $res->[0][1]->{ids}[1]; - - $res = $jmap->CallMethods([['Email/set', { + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $store = $self->{store}; + + my $talk = $self->{store}->get_client(); + + my $using = [ + 'https://cyrusimap.org/ns/jmap/debug', 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; + + $talk->create('INBOX.A') or die; + $talk->create('INBOX.B') or die; + $talk->create('INBOX.C') or die; + $talk->create('INBOX.D') or die; + + # Get mailboxes + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ], $using); + $self->assert_not_null($res); + my %mboxIdByName = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; + + # Create email in mailbox A and B + $store->set_folder('INBOX.A'); + $self->make_message('Email1') || die; + $talk->copy(1, 'INBOX.B'); + $talk->store(1, "+flags", "(\\Seen hello)"); + + # check that the flags aren't on B + $talk->select("INBOX.B"); + $res = $talk->fetch("1", "(flags)"); + my @flags = @{ $res->{1}{flags} }; + $self->assert_null(grep { $_ eq 'hello' } @flags); + $self->assert_null(grep { $_ eq '\\Seen' } @flags); + + # Create email in mailboox A + $talk->select("INBOX.A"); + $self->make_message('Email2') || die; + + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + sort => [ { property => 'subject' } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + my $emailId1 = $res->[0][1]->{ids}[0]; + my $emailId2 = $res->[0][1]->{ids}[1]; + + $res = $jmap->CallMethods( + [ [ + 'Email/set', + { update => { - $emailId1 => { - mailboxIds => { - $mboxIdByName{'C'} => JSON::true, - }, + $emailId1 => { + mailboxIds => { + $mboxIdByName{'C'} => JSON::true, }, - $emailId2 => { - mailboxIds => { - $mboxIdByName{'C'} => JSON::true, - }, - } + }, + $emailId2 => { + mailboxIds => { + $mboxIdByName{'C'} => JSON::true, + }, + } }, - }, 'R1']], $using); - $self->make_message('Email3') || die; - - # check that the flags made it - $talk->select("INBOX.C"); - $res = $talk->fetch("1", "(flags)"); - @flags = @{$res->{1}{flags}}; - $self->assert_not_null(grep { $_ eq 'hello' } @flags); - # but \Seen shouldn't - $self->assert_null(grep { $_ eq '\\Seen' } @flags); - - $res = $jmap->CallMethods([['Email/query', { - sort => [{ property => 'subject' }], - }, 'R1']], $using); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - my @ids = @{$res->[0][1]->{ids}}; - my $emailId3 = $ids[2]; - - # now move all the ids to folder 'D' but two are not in the - # source folder any more - $res = $jmap->CallMethods([['Email/set', { + }, + 'R1' + ] ], + $using + ); + $self->make_message('Email3') || die; + + # check that the flags made it + $talk->select("INBOX.C"); + $res = $talk->fetch("1", "(flags)"); + @flags = @{ $res->{1}{flags} }; + $self->assert_not_null(grep { $_ eq 'hello' } @flags); + # but \Seen shouldn't + $self->assert_null(grep { $_ eq '\\Seen' } @flags); + + $res = $jmap->CallMethods( + [ [ + 'Email/query', + { + sort => [ { property => 'subject' } ], + }, + 'R1' + ] ], + $using + ); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + my @ids = @{ $res->[0][1]->{ids} }; + my $emailId3 = $ids[2]; + + # now move all the ids to folder 'D' but two are not in the + # source folder any more + $res = $jmap->CallMethods( + [ [ + 'Email/set', + { update => { - map { $_ => { - "mailboxIds/$mboxIdByName{'A'}" => undef, - "mailboxIds/$mboxIdByName{'D'}" => JSON::true, - } } @ids, + map { + $_ => { + "mailboxIds/$mboxIdByName{'A'}" => undef, + "mailboxIds/$mboxIdByName{'D'}" => JSON::true, + } + } @ids, }, - }, 'R1']], $using); - - $self->assert_not_null($res); - $self->assert(exists $res->[0][1]{updated}{$emailId1}); - $self->assert(exists $res->[0][1]{updated}{$emailId2}); - $self->assert(exists $res->[0][1]{updated}{$emailId3}); - $self->assert_null($res->[0][1]{notUpdated}); - - $res = $jmap->CallMethods([['Email/get', { - ids => [$emailId1, $emailId2, $emailId3], + }, + 'R1' + ] ], + $using + ); + + $self->assert_not_null($res); + $self->assert(exists $res->[0][1]{updated}{$emailId1}); + $self->assert(exists $res->[0][1]{updated}{$emailId2}); + $self->assert(exists $res->[0][1]{updated}{$emailId3}); + $self->assert_null($res->[0][1]{notUpdated}); + + $res = $jmap->CallMethods( + [ [ + 'Email/get', + { + ids => [ $emailId1, $emailId2, $emailId3 ], properties => ['mailboxIds'], - }, "R1"]], $using); - my %emailById = map { $_->{id} => $_ } @{$res->[0][1]{list}}; - - # now we need to test for actual location - my $wantMailboxesEmail1 = { - $mboxIdByName{'C'} => JSON::true, - $mboxIdByName{'D'} => JSON::true, - }; - my $wantMailboxesEmail2 = { - $mboxIdByName{'C'} => JSON::true, - $mboxIdByName{'D'} => JSON::true, - }; - my $wantMailboxesEmail3 = { - $mboxIdByName{'D'} => JSON::true, - }; - $self->assert_deep_equals($wantMailboxesEmail1, $emailById{$emailId1}->{mailboxIds}); - $self->assert_deep_equals($wantMailboxesEmail2, $emailById{$emailId2}->{mailboxIds}); - $self->assert_deep_equals($wantMailboxesEmail3, $emailById{$emailId3}->{mailboxIds}); + }, + "R1" + ] ], + $using + ); + my %emailById = map { $_->{id} => $_ } @{ $res->[0][1]{list} }; + + # now we need to test for actual location + my $wantMailboxesEmail1 = { + $mboxIdByName{'C'} => JSON::true, + $mboxIdByName{'D'} => JSON::true, + }; + my $wantMailboxesEmail2 = { + $mboxIdByName{'C'} => JSON::true, + $mboxIdByName{'D'} => JSON::true, + }; + my $wantMailboxesEmail3 = { $mboxIdByName{'D'} => JSON::true, }; + $self->assert_deep_equals($wantMailboxesEmail1, + $emailById{$emailId1}->{mailboxIds}); + $self->assert_deep_equals($wantMailboxesEmail2, + $emailById{$emailId2}->{mailboxIds}); + $self->assert_deep_equals($wantMailboxesEmail3, + $emailById{$emailId3}->{mailboxIds}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_mailbox_creationid b/cassandane/tiny-tests/JMAPEmail/email_set_update_mailbox_creationid index 7f126b46fb..08f5e7385c 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_mailbox_creationid +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_mailbox_creationid @@ -2,84 +2,103 @@ use Cassandane::Tiny; sub test_email_set_update_mailbox_creationid - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - # Create emails - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - "msg1" => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => q{from1@local}, name => q{} } ], - to => [{ email => q{to1@local}, name => q{} } ], - }, - "msg2" => { - mailboxIds => { - '$inbox' => JSON::true - }, - from => [{ email => q{from2@local}, name => q{} } ], - to => [{ email => q{to2@local}, name => q{} } ], - } + # Create emails + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + "msg1" => { + mailboxIds => { + '$inbox' => JSON::true }, - }, 'R1'], - ['Email/get', { - ids => [ '#msg1', '#msg2' ], - properties => ['mailboxIds'], - }, "R2" ], - ]); - my $msg1Id = $res->[0][1]{created}{msg1}{id}; - $self->assert_not_null($msg1Id); - my $msg2Id = $res->[0][1]{created}{msg2}{id}; - $self->assert_not_null($msg2Id); - my $inboxId = (keys %{$res->[1][1]{list}[0]{mailboxIds}})[0]; - $self->assert_not_null($inboxId); + from => [ { email => q{from1@local}, name => q{} } ], + to => [ { email => q{to1@local}, name => q{} } ], + }, + "msg2" => { + mailboxIds => { + '$inbox' => JSON::true + }, + from => [ { email => q{from2@local}, name => q{} } ], + to => [ { email => q{to2@local}, name => q{} } ], + } + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [ '#msg1', '#msg2' ], + properties => ['mailboxIds'], + }, + "R2" + ], + ]); + my $msg1Id = $res->[0][1]{created}{msg1}{id}; + $self->assert_not_null($msg1Id); + my $msg2Id = $res->[0][1]{created}{msg2}{id}; + $self->assert_not_null($msg2Id); + my $inboxId = (keys %{ $res->[1][1]{list}[0]{mailboxIds} })[0]; + $self->assert_not_null($inboxId); - # Move emails using mailbox creation id - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "mboxX" => { - name => "X", - parentId => undef, - }, + # Move emails using mailbox creation id + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "mboxX" => { + name => "X", + parentId => undef, + }, + } + }, + "R1" + ], + [ + 'Email/set', + { + update => { + $msg1Id => { + mailboxIds => { + '#mboxX' => JSON::true } - }, "R1"], - ['Email/set', { - update => { - $msg1Id => { - mailboxIds => { - '#mboxX' => JSON::true - } - }, - $msg2Id => { - 'mailboxIds/#mboxX' => JSON::true, - 'mailboxIds/' . $inboxId => undef, - } - }, - }, 'R2'], - ['Email/get', { - ids => [ $msg1Id, $msg2Id ], - properties => ['mailboxIds'], - }, "R3" ], - ]); - my $mboxId = $res->[0][1]{created}{mboxX}{id}; - $self->assert_not_null($mboxId); + }, + $msg2Id => { + 'mailboxIds/#mboxX' => JSON::true, + 'mailboxIds/' . $inboxId => undef, + } + }, + }, + 'R2' + ], + [ + 'Email/get', + { + ids => [ $msg1Id, $msg2Id ], + properties => ['mailboxIds'], + }, + "R3" + ], + ]); + my $mboxId = $res->[0][1]{created}{mboxX}{id}; + $self->assert_not_null($mboxId); - $self->assert(exists $res->[1][1]{updated}{$msg1Id}); - $self->assert(exists $res->[1][1]{updated}{$msg2Id}); + $self->assert(exists $res->[1][1]{updated}{$msg1Id}); + $self->assert(exists $res->[1][1]{updated}{$msg2Id}); - my @mailboxIds = keys %{$res->[2][1]{list}[0]{mailboxIds}}; - $self->assert_num_equals(1, scalar @mailboxIds); - $self->assert_str_equals($mboxId, $mailboxIds[0]); + my @mailboxIds = keys %{ $res->[2][1]{list}[0]{mailboxIds} }; + $self->assert_num_equals(1, scalar @mailboxIds); + $self->assert_str_equals($mboxId, $mailboxIds[0]); - @mailboxIds = keys %{$res->[2][1]{list}[1]{mailboxIds}}; - $self->assert_num_equals(1, scalar @mailboxIds); - $self->assert_str_equals($mboxId, $mailboxIds[0]); + @mailboxIds = keys %{ $res->[2][1]{list}[1]{mailboxIds} }; + $self->assert_num_equals(1, scalar @mailboxIds); + $self->assert_str_equals($mboxId, $mailboxIds[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_mailboxids_nonempty b/cassandane/tiny-tests/JMAPEmail/email_set_update_mailboxids_nonempty index 0955b99a60..faea8c9688 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_mailboxids_nonempty +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_mailboxids_nonempty @@ -2,118 +2,165 @@ use Cassandane::Tiny; sub test_email_set_update_mailboxids_nonempty - :min_version_3_4 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_4 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['id'], - }, 'R1'], - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, - } - }, 'R2'], - ], $using); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); - my $mboxA = $res->[1][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[1][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); + my $res = $jmap->CallMethods( + [ + [ + 'Mailbox/get', + { + properties => ['id'], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + }, + mboxB => { + name => 'B', + }, + } + }, + 'R2' + ], + ], + $using + ); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); + my $mboxA = $res->[1][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[1][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email => { - mailboxIds => { - $mboxA => JSON::true, - $mboxB => JSON::true, - }, - subject => 'test', - from => [{ - email => 'from@local' - }] , - to => [{ - email => 'to@local' - }] , - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'email', - } - }, - }, + $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + create => { + email => { + mailboxIds => { + $mboxA => JSON::true, + $mboxB => JSON::true, + }, + subject => 'test', + from => [ { + email => 'from@local' + } ], + to => [ { + email => 'to@local' + } ], + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'email', + } + }, }, - }, 'R1'], - ['Email/get', { - ids => ['#email'], - properties => ['mailboxIds'], - }, 'R2'], - ], $using); - my $emailId = $res->[0][1]{created}{email}{id}; - $self->assert_not_null($emailId); + }, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => ['#email'], + properties => ['mailboxIds'], + }, + 'R2' + ], + ], + $using + ); + my $emailId = $res->[0][1]{created}{email}{id}; + $self->assert_not_null($emailId); - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId => { - mailboxIds => {}, - }, + $res = $jmap->CallMethods( + [ + [ + 'Email/set', + { + update => { + $emailId => { + mailboxIds => {}, }, - }, 'R1'], - ['Email/set', { - update => { - $emailId => { - mailboxIds => undef, - }, + }, + }, + 'R1' + ], + [ + 'Email/set', + { + update => { + $emailId => { + mailboxIds => undef, }, - }, 'R2'], - ['Email/set', { - update => { - $emailId => { - 'mailboxIds'.$mboxA => undef, - 'mailboxIds'.$mboxB => undef, - }, + }, + }, + 'R2' + ], + [ + 'Email/set', + { + update => { + $emailId => { + 'mailboxIds' . $mboxA => undef, + 'mailboxIds' . $mboxB => undef, }, - }, 'R3'], - ['Email/set', { - update => { - $emailId => { - mailboxIds => [], - }, + }, + }, + 'R3' + ], + [ + 'Email/set', + { + update => { + $emailId => { + mailboxIds => [], }, - }, 'R4'], - ['Email/get', { - ids => [$emailId], - properties => ['mailboxIds'], - }, 'R5'], - ], $using); + }, + }, + 'R4' + ], + [ + 'Email/get', + { + ids => [$emailId], + properties => ['mailboxIds'], + }, + 'R5' + ], + ], + $using + ); - $self->assert_deep_equals({ - type => 'invalidProperties', - properties => ['mailboxIds'], - }, $res->[0][1]{notUpdated}{$emailId}); + $self->assert_deep_equals( + { + type => 'invalidProperties', + properties => ['mailboxIds'], + }, + $res->[0][1]{notUpdated}{$emailId} + ); - $self->assert_str_equals($emailId, $res->[4][1]{list}[0]{id}); + $self->assert_str_equals($emailId, $res->[4][1]{list}[0]{id}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_mailboxids_validate_nopatch b/cassandane/tiny-tests/JMAPEmail/email_set_update_mailboxids_validate_nopatch index a5c490cfcb..92abb0d233 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_mailboxids_validate_nopatch +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_mailboxids_validate_nopatch @@ -2,68 +2,81 @@ use Cassandane::Tiny; sub test_email_set_update_mailboxids_validate_nopatch - :min_version_3_5 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_5 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + }, + mboxB => { + name => 'B', + }, + }, + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + 'emailA' => { + mailboxIds => { + '#mboxA' => JSON::true, }, - }, 'R1'], - ['Email/set', { - create => { - 'emailA' => { - mailboxIds => { - '#mboxA' => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'emailA', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'test', - } - }, - }, + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'emailA', + bodyStructure => { + type => 'text/plain', + partId => 'part1', }, - }, 'R1'], - ]); - my $mboxA = $res->[0][1]->{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[0][1]->{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - my $emailA = $res->[1][1]->{created}{emailA}{id}; - $self->assert_not_null($emailA); + bodyValues => { + part1 => { + value => 'test', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $mboxA = $res->[0][1]->{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[0][1]->{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + my $emailA = $res->[1][1]->{created}{emailA}{id}; + $self->assert_not_null($emailA); - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailA => { - mailboxIds => { - $mboxA => undef, - $mboxB => JSON::true, - }, - } - } - }, 'R1'], - ]); - $self->assert_not_null($res->[0][1]{notUpdated}{$emailA}); - $self->assert_deep_equals(['mailboxIds/'.$mboxA], - $res->[0][1]{notUpdated}{$emailA}{properties}); + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailA => { + mailboxIds => { + $mboxA => undef, + $mboxB => JSON::true, + }, + } + } + }, + 'R1' + ], + ]); + $self->assert_not_null($res->[0][1]{notUpdated}{$emailA}); + $self->assert_deep_equals([ 'mailboxIds/' . $mboxA ], + $res->[0][1]{notUpdated}{$emailA}{properties}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_no_id b/cassandane/tiny-tests/JMAPEmail/email_set_update_no_id index 3f85b0be2b..3dbe4eca98 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_no_id +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_no_id @@ -2,47 +2,54 @@ use Cassandane::Tiny; sub test_email_set_update_no_id - :min_version_3_4 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_4 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Email/set', { - create => { - email => { - mailboxIds => { - '$inbox' => JSON::true, - }, - subject => 'email', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'email', - } - }, - }, + my $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + email => { + mailboxIds => { + '$inbox' => JSON::true, }, - }, 'R1'], - ]); - my $emailId = $res->[0][1]{created}{email}{id}; - $self->assert_not_null($emailId); + subject => 'email', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'email', + } + }, + }, + }, + }, + 'R1' + ], + ]); + my $emailId = $res->[0][1]{created}{email}{id}; + $self->assert_not_null($emailId); - $res = $jmap->CallMethods([ - ['Email/set', { - update => { - $emailId => { - keywords => { - 'foo' => JSON::true, - }, - }, + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + keywords => { + 'foo' => JSON::true, }, - }, 'R1'], - ]); - $self->assert_equals(undef, $res->[0][1]{updated}{$emailId}); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_equals(undef, $res->[0][1]{updated}{$emailId}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_snooze b/cassandane/tiny-tests/JMAPEmail/email_set_update_snooze index 13bf2da8cf..3c5d121d7d 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_snooze +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_snooze @@ -2,232 +2,306 @@ use Cassandane::Tiny; sub test_email_set_update_snooze - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd - :needs_component_sieve :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # snoozed property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); - - xlog $self, "Get mailbox id of Inbox"; - my $inboxId = $self->getinbox()->{id}; - - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; - - xlog $self, "get email id"; - my $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R2" ] ] ); - my $emailId = $res->[0][1]->{ids}[0]; - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R3" ] ] ); - my $msg = $res->[0][1]->{list}[0]; - $self->assert_not_null($msg->{mailboxIds}{$inboxId}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - - xlog $self, "create snooze mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "snoozed", - parentId => undef, - role => "snoozed" - }}}, "R4"] - ]); - $self->assert_not_null($res->[0][1]{created}); - my $snoozedId = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R4"] - ]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsId = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "Move message to drafts and snoozed mailbox"; - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => 30)); - my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "mailboxIds/$inboxId" => undef, - "mailboxIds/$snoozedId" => $JSON::true, - "snoozed" => { "until" => "$datestr", - "setKeywords" => { '$seen' => $JSON::true } }, - keywords => { '$flagged' => JSON::true, '$seen' => JSON::true }, - }} - }, 'R5'] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'addedDates', 'snoozed' ]}, "R6" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_null($msg->{mailboxIds}{$inboxId}); - $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); - $self->assert_null($msg->{mailboxIds}{$draftsId}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); - $self->assert_str_equals($datestr, $msg->{addedDates}{$snoozedId}); - - xlog $self, "Adjust snooze#until"; - $maildate->add(DateTime::Duration->new(seconds => 15)); - $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "mailboxIds/$draftsId" => $JSON::true, - "snoozed/until" => "$datestr", - 'snoozed/setKeywords/$awakened' => $JSON::true, - 'snoozed/setKeywords/$seen' => $JSON::false, - }} - }, 'R5'] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'addedDates', 'snoozed' ]}, "R6" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_null($msg->{mailboxIds}{$inboxId}); - $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); - $self->assert_not_null($msg->{mailboxIds}{$draftsId}); - $self->assert_num_equals(2, scalar keys %{$msg->{mailboxIds}}); - $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); - $self->assert_str_equals($datestr, $msg->{addedDates}{$snoozedId}); - # but it shouldn't be changed on the drafts folder. This is a little raceful, in that - # the snooze#until date could just happen to be now... - $self->assert_str_not_equals($datestr, $msg->{addedDates}{$draftsId}); - - xlog $self, "trigger re-delivery of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 30 ); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'addedDates', 'snoozed' ]}, "R7" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(2, scalar keys %{$msg->{mailboxIds}}); - $self->assert_not_null($msg->{snoozed}); - $self->assert_num_equals(2, scalar keys %{$msg->{keywords}}); - $self->assert_equals(JSON::true, $msg->{keywords}{'$awakened'}); - $self->assert_null($msg->{keywords}{'$seen'}); - $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); - $self->assert_str_equals($datestr, $msg->{addedDates}{$inboxId}); - # but it shouldn't be changed on the drafts folder. This is a little raceful, in that - # the snooze#until date could just happen to be now... - $self->assert_str_not_equals($datestr, $msg->{addedDates}{$draftsId}); - - xlog $self, "Re-snooze"; - $maildate->add(DateTime::Duration->new(seconds => 15)); - $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "mailboxIds/$inboxId" => undef, - "mailboxIds/$snoozedId" => $JSON::true, - 'keywords/$awakened' => undef, - "snoozed/until" => "$datestr", - }} - }, 'R8'] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R9" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(2, scalar keys %{$msg->{mailboxIds}}); - $self->assert_not_null($msg->{snoozed}); - $self->assert_num_equals(1, scalar keys %{$msg->{keywords}}); - $self->assert_null($msg->{keywords}{'$seen'}); - $self->assert_null($msg->{keywords}{'$awakened'}); - $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); - - xlog $self, "trigger re-delivery of re-snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 30 ); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'addedDates', 'snoozed' ]}, "R7" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(2, scalar keys %{$msg->{mailboxIds}}); - $self->assert_not_null($msg->{snoozed}); - $self->assert_num_equals(2, scalar keys %{$msg->{keywords}}); - $self->assert_equals(JSON::true, $msg->{keywords}{'$awakened'}); - $self->assert_null($msg->{keywords}{'$seen'}); - $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); - $self->assert_str_equals($datestr, $msg->{addedDates}{$inboxId}); - # but it shouldn't be changed on the drafts folder. This is a little raceful, in that - # the snooze#until date could just happen to be now... - $self->assert_str_not_equals($datestr, $msg->{addedDates}{$draftsId}); - - xlog $self, "Remove snoozed"; - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "mailboxIds/$inboxId" => undef, - "snoozed" => undef - }} - }, 'R8'] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R9" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_null($msg->{snoozed}); - $self->assert_num_equals(2, scalar keys %{$msg->{keywords}}); - $self->assert_equals(JSON::true, $msg->{keywords}{'$seen'}); - $self->assert_null($msg->{keywords}{'$awakened'}); - - xlog $self, "Restore snoozed"; - $maildate->add(DateTime::Duration->new(seconds => 15)); - $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "mailboxIds" => { "$inboxId" => $JSON::true }, - "snoozed" => { - "until" => "$datestr", - "setKeywords" => { '$awakened' => $JSON::true, '$seen' => $JSON::false } - }, - }} - }, 'R8'] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R9" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_not_null($msg->{snoozed}); - $self->assert_num_equals(2, scalar keys %{$msg->{keywords}}); - $self->assert_equals(JSON::true, $msg->{keywords}{'$seen'}); - $self->assert_null($msg->{keywords}{'$awakened'}); - $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd + : needs_component_sieve : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # snoozed property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); + + xlog $self, "Get mailbox id of Inbox"; + my $inboxId = $self->getinbox()->{id}; + + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; + + xlog $self, "get email id"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R2" ] ]); + my $emailId = $res->[0][1]->{ids}[0]; + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R3" + ] ]); + my $msg = $res->[0][1]->{list}[0]; + $self->assert_not_null($msg->{mailboxIds}{$inboxId}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + + xlog $self, "create snooze mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "snoozed", + parentId => undef, + role => "snoozed" + } + } + }, + "R4" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $snoozedId = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R4" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsId = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "Move message to drafts and snoozed mailbox"; + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => 30)); + my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$inboxId" => undef, + "mailboxIds/$snoozedId" => $JSON::true, + "snoozed" => { + "until" => "$datestr", + "setKeywords" => { '$seen' => $JSON::true } + }, + keywords => { '$flagged' => JSON::true, '$seen' => JSON::true }, + } + } + }, + 'R5' + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'addedDates', 'snoozed' ] + }, + "R6" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_null($msg->{mailboxIds}{$inboxId}); + $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); + $self->assert_null($msg->{mailboxIds}{$draftsId}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); + $self->assert_str_equals($datestr, $msg->{addedDates}{$snoozedId}); + + xlog $self, "Adjust snooze#until"; + $maildate->add(DateTime::Duration->new(seconds => 15)); + $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$draftsId" => $JSON::true, + "snoozed/until" => "$datestr", + 'snoozed/setKeywords/$awakened' => $JSON::true, + 'snoozed/setKeywords/$seen' => $JSON::false, + } + } + }, + 'R5' + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'addedDates', 'snoozed' ] + }, + "R6" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_null($msg->{mailboxIds}{$inboxId}); + $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); + $self->assert_not_null($msg->{mailboxIds}{$draftsId}); + $self->assert_num_equals(2, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); + $self->assert_str_equals($datestr, $msg->{addedDates}{$snoozedId}); +# but it shouldn't be changed on the drafts folder. This is a little raceful, in that +# the snooze#until date could just happen to be now... + $self->assert_str_not_equals($datestr, $msg->{addedDates}{$draftsId}); + + xlog $self, "trigger re-delivery of snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 30); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'addedDates', 'snoozed' ] + }, + "R7" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(2, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_not_null($msg->{snoozed}); + $self->assert_num_equals(2, scalar keys %{ $msg->{keywords} }); + $self->assert_equals(JSON::true, $msg->{keywords}{'$awakened'}); + $self->assert_null($msg->{keywords}{'$seen'}); + $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); + $self->assert_str_equals($datestr, $msg->{addedDates}{$inboxId}); +# but it shouldn't be changed on the drafts folder. This is a little raceful, in that +# the snooze#until date could just happen to be now... + $self->assert_str_not_equals($datestr, $msg->{addedDates}{$draftsId}); + + xlog $self, "Re-snooze"; + $maildate->add(DateTime::Duration->new(seconds => 15)); + $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$inboxId" => undef, + "mailboxIds/$snoozedId" => $JSON::true, + 'keywords/$awakened' => undef, + "snoozed/until" => "$datestr", + } + } + }, + 'R8' + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R9" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(2, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_not_null($msg->{snoozed}); + $self->assert_num_equals(1, scalar keys %{ $msg->{keywords} }); + $self->assert_null($msg->{keywords}{'$seen'}); + $self->assert_null($msg->{keywords}{'$awakened'}); + $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); + + xlog $self, "trigger re-delivery of re-snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 30); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'addedDates', 'snoozed' ] + }, + "R7" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(2, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_not_null($msg->{snoozed}); + $self->assert_num_equals(2, scalar keys %{ $msg->{keywords} }); + $self->assert_equals(JSON::true, $msg->{keywords}{'$awakened'}); + $self->assert_null($msg->{keywords}{'$seen'}); + $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); + $self->assert_str_equals($datestr, $msg->{addedDates}{$inboxId}); +# but it shouldn't be changed on the drafts folder. This is a little raceful, in that +# the snooze#until date could just happen to be now... + $self->assert_str_not_equals($datestr, $msg->{addedDates}{$draftsId}); + + xlog $self, "Remove snoozed"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$inboxId" => undef, + "snoozed" => undef + } + } + }, + 'R8' + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R9" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_null($msg->{snoozed}); + $self->assert_num_equals(2, scalar keys %{ $msg->{keywords} }); + $self->assert_equals(JSON::true, $msg->{keywords}{'$seen'}); + $self->assert_null($msg->{keywords}{'$awakened'}); + + xlog $self, "Restore snoozed"; + $maildate->add(DateTime::Duration->new(seconds => 15)); + $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds" => { "$inboxId" => $JSON::true }, + "snoozed" => { + "until" => "$datestr", + "setKeywords" => + { '$awakened' => $JSON::true, '$seen' => $JSON::false } + }, + } + } + }, + 'R8' + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R9" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_not_null($msg->{snoozed}); + $self->assert_num_equals(2, scalar keys %{ $msg->{keywords} }); + $self->assert_equals(JSON::true, $msg->{keywords}{'$seen'}); + $self->assert_null($msg->{keywords}{'$awakened'}); + $self->assert_str_equals($datestr, $msg->{snoozed}{'until'}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_keywords b/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_keywords index f040884e2a..6b8d22daa8 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_keywords +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_keywords @@ -2,38 +2,42 @@ use Cassandane::Tiny; sub test_email_set_update_too_many_keywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $self->{store}->get_client(); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $store = $self->{store}; + my $talk = $self->{store}->get_client(); - my $inboxId = $self->getinbox()->{id}; + my $inboxId = $self->getinbox()->{id}; - # Create email in INBOX - $self->make_message('Email') || die; + # Create email in INBOX + $self->make_message('Email') || die; - my $res = $jmap->CallMethods([['Email/query', { }, 'R1']]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $emailId = $res->[0][1]->{ids}[0]; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $emailId = $res->[0][1]->{ids}[0]; - my $accountCapabilities = $self->get_account_capabilities(); - my $mailCapabilities = $accountCapabilities->{'urn:ietf:params:jmap:mail'}; - my $maxKeywordsPerEmail = $mailCapabilities->{maxKeywordsPerEmail}; - $self->assert($maxKeywordsPerEmail > 0); + my $accountCapabilities = $self->get_account_capabilities(); + my $mailCapabilities = $accountCapabilities->{'urn:ietf:params:jmap:mail'}; + my $maxKeywordsPerEmail = $mailCapabilities->{maxKeywordsPerEmail}; + $self->assert($maxKeywordsPerEmail > 0); - # Set lots of keywords on this email - my %keywords; - for (my $i = 1; $i < $maxKeywordsPerEmail + 2; $i++) { - $keywords{"keyword$i"} = JSON::true; - } - $res = $jmap->CallMethods([['Email/set', { - update => { - $emailId => { - keywords => \%keywords, - }, + # Set lots of keywords on this email + my %keywords; + for (my $i = 1; $i < $maxKeywordsPerEmail + 2; $i++) { + $keywords{"keyword$i"} = JSON::true; + } + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + keywords => \%keywords, }, - }, 'R1']]); - $self->assert_str_equals('tooManyKeywords', $res->[0][1]{notUpdated}{$emailId}{type}); + }, + }, + 'R1' + ] ]); + $self->assert_str_equals('tooManyKeywords', + $res->[0][1]{notUpdated}{$emailId}{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_mailboxes b/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_mailboxes index 6c0edd5024..7fa9401e2b 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_mailboxes +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_mailboxes @@ -2,45 +2,49 @@ use Cassandane::Tiny; sub test_email_set_update_too_many_mailboxes - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $self->{store}->get_client(); - - my $inboxId = $self->getinbox()->{id}; - - # Create email in INBOX - $self->make_message('Email') || die; - - my $res = $jmap->CallMethods([['Email/query', { }, 'R1']]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $emailId = $res->[0][1]->{ids}[0]; - - my $accountCapabilities = $self->get_account_capabilities(); - my $mailCapabilities = $accountCapabilities->{'urn:ietf:params:jmap:mail'}; - my $maxMailboxesPerEmail = $mailCapabilities->{maxMailboxesPerEmail}; - $self->assert($maxMailboxesPerEmail > 0); - - # Create and get mailboxes - for (my $i = 1; $i < $maxMailboxesPerEmail + 2; $i++) { - $talk->create("INBOX.mbox$i") or die; - } - $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $self->assert_not_null($res); - my %mboxIds = map { $_->{id} => JSON::true } @{$res->[0][1]{list}}; - - # remove from INBOX - delete $mboxIds{$inboxId}; - - # Move mailbox to too many mailboxes - $res = $jmap->CallMethods([['Email/set', { - update => { - $emailId => { - mailboxIds => \%mboxIds, - }, + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $store = $self->{store}; + my $talk = $self->{store}->get_client(); + + my $inboxId = $self->getinbox()->{id}; + + # Create email in INBOX + $self->make_message('Email') || die; + + my $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $emailId = $res->[0][1]->{ids}[0]; + + my $accountCapabilities = $self->get_account_capabilities(); + my $mailCapabilities = $accountCapabilities->{'urn:ietf:params:jmap:mail'}; + my $maxMailboxesPerEmail = $mailCapabilities->{maxMailboxesPerEmail}; + $self->assert($maxMailboxesPerEmail > 0); + + # Create and get mailboxes + for (my $i = 1; $i < $maxMailboxesPerEmail + 2; $i++) { + $talk->create("INBOX.mbox$i") or die; + } + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $self->assert_not_null($res); + my %mboxIds = map { $_->{id} => JSON::true } @{ $res->[0][1]{list} }; + + # remove from INBOX + delete $mboxIds{$inboxId}; + + # Move mailbox to too many mailboxes + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + mailboxIds => \%mboxIds, }, - }, 'R1']]); - $self->assert_str_equals('tooManyMailboxes', $res->[0][1]{notUpdated}{$emailId}{type}); + }, + }, + 'R1' + ] ]); + $self->assert_str_equals('tooManyMailboxes', + $res->[0][1]{notUpdated}{$emailId}{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_mailboxes_lowlimit b/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_mailboxes_lowlimit index e4e3cd6ef7..051d86c777 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_mailboxes_lowlimit +++ b/cassandane/tiny-tests/JMAPEmail/email_set_update_too_many_mailboxes_lowlimit @@ -2,49 +2,53 @@ use Cassandane::Tiny; sub test_email_set_update_too_many_mailboxes_lowlimit - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :LowEmailLimits -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $self->{store}->get_client(); - - my $inboxId = $self->getinbox()->{id}; - - # Create email in INBOX - $self->make_message('Email') || die; - - my $res = $jmap->CallMethods([['Email/query', { }, 'R1']]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $emailId = $res->[0][1]->{ids}[0]; - - my $accountCapabilities = $self->get_account_capabilities(); - my $mailCapabilities = $accountCapabilities->{'urn:ietf:params:jmap:mail'}; - my $maxMailboxesPerEmail = 5; # from the magic - $self->assert($maxMailboxesPerEmail > 0); - - # Create and get mailboxes - for (my $i = 1; $i < $maxMailboxesPerEmail + 2; $i++) { - $talk->create("INBOX.mbox$i") or die; - } - $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $self->assert_not_null($res); - my %mboxIds = map { $_->{id} => JSON::true } @{$res->[0][1]{list}}; - - # remove from INBOX - delete $mboxIds{$inboxId}; - - # Move mailbox to too many mailboxes - $res = $jmap->CallMethods([['Email/set', { - update => { - $emailId => { - mailboxIds => \%mboxIds, - }, + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : LowEmailLimits { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $store = $self->{store}; + my $talk = $self->{store}->get_client(); + + my $inboxId = $self->getinbox()->{id}; + + # Create email in INBOX + $self->make_message('Email') || die; + + my $res = $jmap->CallMethods([ [ 'Email/query', {}, 'R1' ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $emailId = $res->[0][1]->{ids}[0]; + + my $accountCapabilities = $self->get_account_capabilities(); + my $mailCapabilities = $accountCapabilities->{'urn:ietf:params:jmap:mail'}; + my $maxMailboxesPerEmail = 5; # from the magic + $self->assert($maxMailboxesPerEmail > 0); + + # Create and get mailboxes + for (my $i = 1; $i < $maxMailboxesPerEmail + 2; $i++) { + $talk->create("INBOX.mbox$i") or die; + } + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $self->assert_not_null($res); + my %mboxIds = map { $_->{id} => JSON::true } @{ $res->[0][1]{list} }; + + # remove from INBOX + delete $mboxIds{$inboxId}; + + # Move mailbox to too many mailboxes + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + mailboxIds => \%mboxIds, }, - }, 'R1']]); - $self->assert_str_equals('tooManyMailboxes', $res->[0][1]{notUpdated}{$emailId}{type}); - - $self->assert_syslog_matches($self->{instance}, - qr{IOERROR: conversations GUID limit}); + }, + }, + 'R1' + ] ]); + $self->assert_str_equals('tooManyMailboxes', + $res->[0][1]{notUpdated}{$emailId}{type}); + + $self->assert_syslog_matches($self->{instance}, + qr{IOERROR: conversations GUID limit}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_set_userkeywords b/cassandane/tiny-tests/JMAPEmail/email_set_userkeywords index 581ef354e7..607e83a3fe 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_set_userkeywords +++ b/cassandane/tiny-tests/JMAPEmail/email_set_userkeywords @@ -2,73 +2,82 @@ use Cassandane::Tiny; sub test_email_set_userkeywords - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $draft = { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo", - textBody => [{ partId => '1' }], - bodyValues => { - '1' => { - value => "I'm givin' ya one last chance ta surrenda!" - } - }, - keywords => { - '$draft' => JSON::true, - 'foo' => JSON::true - }, - }; + my $draft = { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { + '1' => { + value => "I'm givin' ya one last chance ta surrenda!" + } + }, + keywords => { + '$draft' => JSON::true, + 'foo' => JSON::true + }, + }; - xlog $self, "Create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "Create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - my $msg = $res->[0][1]->{list}[0]; + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + my $msg = $res->[0][1]->{list}[0]; - $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); - $self->assert_equals(JSON::true, $msg->{keywords}->{'foo'}); - $self->assert_num_equals(2, scalar keys %{$msg->{keywords}}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'$draft'}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'foo'}); + $self->assert_num_equals(2, scalar keys %{ $msg->{keywords} }); - xlog $self, "Update draft"; - $res = $jmap->CallMethods([['Email/set', { - update => { - $id => { - "keywords" => { - '$draft' => JSON::true, - 'foo' => JSON::true, - 'bar' => JSON::true - } - } + xlog $self, "Update draft"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $id => { + "keywords" => { + '$draft' => JSON::true, + 'foo' => JSON::true, + 'bar' => JSON::true + } } - }, "R1"]]); + } + }, + "R1" + ] ]); - xlog $self, "Get draft $id"; - $res = $jmap->CallMethods([['Email/get', { ids => [$id] }, "R1"]]); - $msg = $res->[0][1]->{list}[0]; - $self->assert_equals(JSON::true, JSON::true, $msg->{keywords}->{'$draft'}); # case-insensitive! - $self->assert_equals(JSON::true, $msg->{keywords}->{'foo'}); - $self->assert_equals(JSON::true, $msg->{keywords}->{'bar'}); - $self->assert_num_equals(3, scalar keys %{$msg->{keywords}}); + xlog $self, "Get draft $id"; + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [$id] }, "R1" ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_equals(JSON::true, JSON::true, $msg->{keywords}->{'$draft'}) + ; # case-insensitive! + $self->assert_equals(JSON::true, $msg->{keywords}->{'foo'}); + $self->assert_equals(JSON::true, $msg->{keywords}->{'bar'}); + $self->assert_num_equals(3, scalar keys %{ $msg->{keywords} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_snooze_awaken_bad_mailbox b/cassandane/tiny-tests/JMAPEmail/email_snooze_awaken_bad_mailbox index 8db8b7eea2..49c01abf2f 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_snooze_awaken_bad_mailbox +++ b/cassandane/tiny-tests/JMAPEmail/email_snooze_awaken_bad_mailbox @@ -2,70 +2,81 @@ use Cassandane::Tiny; sub test_email_snooze_awaken_bad_mailbox - :min_version_3_7 :needs_component_jmap :needs_component_calalarmd - :needs_component_sieve :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : needs_component_calalarmd + : needs_component_sieve : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # snoozed property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # snoozed property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; - xlog $self, "Get email id, Inbox id, and create snoozed & awaken mailboxes"; - my $res = $jmap->CallMethods([ - [ 'Email/query', {}, "R0" ], - [ 'Mailbox/query', {filter => {role => 'inbox'}}, "R1"], - [ 'Mailbox/set', { - create => { - "1" => { - name => "snoozed", - parentId => undef, - role => "snoozed" - }, - "2" => { - name => "awaken", - parentId => undef - } - }}, "R2" ] - ]); - my $emailId = $res->[0][1]->{ids}[0]; - my $inbox = $res->[1][1]->{ids}[0]; - my $snoozedmbox = $res->[2][1]{created}{"1"}{id}; - my $awakenmbox = $res->[2][1]{created}{"2"}{id}; + xlog $self, "Get email id, Inbox id, and create snoozed & awaken mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R0" ], + [ 'Mailbox/query', { filter => { role => 'inbox' } }, "R1" ], + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "snoozed", + parentId => undef, + role => "snoozed" + }, + "2" => { + name => "awaken", + parentId => undef + } + } + }, + "R2" + ] + ]); + my $emailId = $res->[0][1]->{ids}[0]; + my $inbox = $res->[1][1]->{ids}[0]; + my $snoozedmbox = $res->[2][1]{created}{"1"}{id}; + my $awakenmbox = $res->[2][1]{created}{"2"}{id}; - xlog $self, "Snooze email and destroy awaken mailbox"; - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => 30)); - my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + xlog $self, "Snooze email and destroy awaken mailbox"; + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => 30)); + my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - $res = $jmap->CallMethods([ - [ 'Email/set', { - update => { $emailId => { - "mailboxIds/$inbox" => undef, - "mailboxIds/$snoozedmbox" => $JSON::true, - "snoozed" => { "until" => "$datestr", - "moveToMailboxId" => "$awakenmbox" } - }} - }, 'R3' ], - [ 'Mailbox/set', { destroy => [ $awakenmbox ] }, "R4"] - ]); + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$inbox" => undef, + "mailboxIds/$snoozedmbox" => $JSON::true, + "snoozed" => { + "until" => "$datestr", + "moveToMailboxId" => "$awakenmbox" + } + } + } + }, + 'R3' + ], + [ 'Mailbox/set', { destroy => [$awakenmbox] }, "R4" ] + ]); - xlog $self, "Trigger awakening of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 30 ); + xlog $self, "Trigger awakening of snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 30); - xlog $self, "Verify email was awakened to Inbox"; - $res = $jmap->CallMethods([ - [ 'Email/get', - { ids => [ $emailId ], properties => [ 'mailboxIds' ] }, "R7" ] - ]); - my $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_equals(JSON::true, $msg->{mailboxIds}{"$inbox"}); + xlog $self, "Verify email was awakened to Inbox"; + $res = $jmap->CallMethods([ [ + 'Email/get', { ids => [$emailId], properties => ['mailboxIds'] }, "R7" + ] ]); + my $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_equals(JSON::true, $msg->{mailboxIds}{"$inbox"}); } diff --git a/cassandane/tiny-tests/JMAPEmail/email_zero_length_text b/cassandane/tiny-tests/JMAPEmail/email_zero_length_text index d4f9cb35e8..0cfdab7cf3 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_zero_length_text +++ b/cassandane/tiny-tests/JMAPEmail/email_zero_length_text @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_email_zero_length_text - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; MIME-Version: 1.0 From: "Example.com" To: "Me" @@ -33,30 +32,41 @@ foo ----boundary_34056-- EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; - my $inboxid = $self->getinbox()->{id}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; + my $inboxid = $self->getinbox()->{id}; - xlog $self, "import and get email from blob $blobid"; - my $res = $jmap->CallMethods([['Email/import', { + xlog $self, "import and get email from blob $blobid"; + my $res = $jmap->CallMethods([ + [ + 'Email/import', + { emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$inboxid => JSON::true}, - }, + "1" => { + blobId => $blobid, + mailboxIds => { $inboxid => JSON::true }, + }, }, - }, "R1"], ["Email/get", { - ids => ["#1"], - properties => ['bodyStructure', 'bodyValues'], + }, + "R1" + ], + [ + "Email/get", + { + ids => ["#1"], + properties => [ 'bodyStructure', 'bodyValues' ], fetchAllBodyValues => JSON::true, - }, "R2" ]]); + }, + "R2" + ] + ]); - $self->assert_str_equals("Email/import", $res->[0][0]); - $self->assert_str_equals("Email/get", $res->[1][0]); + $self->assert_str_equals("Email/import", $res->[0][0]); + $self->assert_str_equals("Email/get", $res->[1][0]); - my $msg = $res->[1][1]{list}[0]; - my $bodyValue = $msg->{bodyValues}{1}; - $self->assert_str_equals("", $bodyValue->{value}); - $self->assert_equals(JSON::false, $bodyValue->{isEncodingProblem}); + my $msg = $res->[1][1]{list}[0]; + my $bodyValue = $msg->{bodyValues}{1}; + $self->assert_str_equals("", $bodyValue->{value}); + $self->assert_equals(JSON::false, $bodyValue->{isEncodingProblem}); } diff --git a/cassandane/tiny-tests/JMAPEmail/identity_get b/cassandane/tiny-tests/JMAPEmail/identity_get index 8fc95008e9..6116a31886 100644 --- a/cassandane/tiny-tests/JMAPEmail/identity_get +++ b/cassandane/tiny-tests/JMAPEmail/identity_get @@ -2,32 +2,30 @@ use Cassandane::Tiny; sub test_identity_get - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $id; - my $res; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $id; + my $res; - # Make sure it's in the correct JMAP capability, as reported in - # https://github.com/cyrusimap/cyrus-imapd/issues/2912 - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:submission', - ]; + # Make sure it's in the correct JMAP capability, as reported in + # https://github.com/cyrusimap/cyrus-imapd/issues/2912 + my $using + = [ 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:submission', ]; - xlog $self, "get identities"; - $res = $jmap->CallMethods([['Identity/get', { }, "R1"]], $using); + xlog $self, "get identities"; + $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{notFound}}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{notFound} }); - $id = $res->[0][1]->{list}[0]; - $self->assert_not_null($id->{id}); - $self->assert_not_null($id->{email}); + $id = $res->[0][1]->{list}[0]; + $self->assert_not_null($id->{id}); + $self->assert_not_null($id->{email}); - xlog $self, "get unknown identities"; - $res = $jmap->CallMethods([['Identity/get', { ids => ["foo"] }, "R1"]], $using); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{list}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{notFound}}); + xlog $self, "get unknown identities"; + $res = $jmap->CallMethods([ [ 'Identity/get', { ids => ["foo"] }, "R1" ] ], + $using); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{list} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{notFound} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/implementation_email_query b/cassandane/tiny-tests/JMAPEmail/implementation_email_query index f983dd3503..2a9487179c 100644 --- a/cassandane/tiny-tests/JMAPEmail/implementation_email_query +++ b/cassandane/tiny-tests/JMAPEmail/implementation_email_query @@ -2,101 +2,120 @@ use Cassandane::Tiny; sub test_implementation_email_query - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - # These assertions are implementation-specific. Breaking them - # isn't necessarly a regression, but change them with caution. + # These assertions are implementation-specific. Breaking them + # isn't necessarly a regression, but change them with caution. - my $now = DateTime->now(); + my $now = DateTime->now(); - xlog $self, "Generate an email in INBOX via IMAP"; - my $res = $self->make_message("foo") || die; - my $uid = $res->{attrs}->{uid}; - my $msg; + xlog $self, "Generate an email in INBOX via IMAP"; + my $res = $self->make_message("foo") || die; + my $uid = $res->{attrs}->{uid}; + my $msg; - my $inbox = $self->getinbox(); + my $inbox = $self->getinbox(); - xlog $self, "non-filtered query can calculate changes"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - $self->assert($res->[0][1]{canCalculateChanges}); + xlog $self, "non-filtered query can calculate changes"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + $self->assert($res->[0][1]{canCalculateChanges}); - xlog $self, "inMailbox query can calculate changes"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { inMailbox => $inbox->{id} }, - sort => [ { - isAscending => $JSON::false, - property => 'receivedAt', - } ], - }, "R1"], - ]); - $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); + xlog $self, "inMailbox query can calculate changes"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { inMailbox => $inbox->{id} }, + sort => [ { + isAscending => $JSON::false, + property => 'receivedAt', + } ], + }, + "R1" + ], + ]); + $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); - xlog $self, "inMailbox query can calculate changes with mutable sort"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { inMailbox => $inbox->{id} }, - sort => [ { - property => "someInThreadHaveKeyword", - keyword => "\$seen", + xlog $self, "inMailbox query can calculate changes with mutable sort"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { inMailbox => $inbox->{id} }, + sort => [ + { + property => "someInThreadHaveKeyword", + keyword => "\$seen", isAscending => $JSON::false, - }, { - property => 'receivedAt', + }, + { + property => 'receivedAt', isAscending => $JSON::false, - } ], - }, "R1"], - ]); - $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); + } + ], + }, + "R1" + ], + ]); + $self->assert_equals(JSON::true, $res->[0][1]{canCalculateChanges}); - xlog $self, "inMailbox query with keyword can not calculate changes"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - conditions => [ - { inMailbox => $inbox->{id} }, - { conditions => [ { allInThreadHaveKeyword => "\$seen" } ], - operator => 'NOT', - }, - ], - operator => 'AND', - }, - sort => [ { - isAscending => $JSON::false, - property => 'receivedAt', - } ], - }, "R1"], - ]); - $self->assert_equals(JSON::false, $res->[0][1]{canCalculateChanges}); + xlog $self, "inMailbox query with keyword can not calculate changes"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + conditions => [ + { inMailbox => $inbox->{id} }, + { + conditions => [ { allInThreadHaveKeyword => "\$seen" } ], + operator => 'NOT', + }, + ], + operator => 'AND', + }, + sort => [ { + isAscending => $JSON::false, + property => 'receivedAt', + } ], + }, + "R1" + ], + ]); + $self->assert_equals(JSON::false, $res->[0][1]{canCalculateChanges}); - xlog $self, "negated inMailbox query can not calculate changes"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [ - { inMailbox => $inbox->{id} }, - ], - }, - }, "R1"], - ]); - $self->assert_equals(JSON::false, $res->[0][1]{canCalculateChanges}); + xlog $self, "negated inMailbox query can not calculate changes"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { inMailbox => $inbox->{id} }, ], + }, + }, + "R1" + ], + ]); + $self->assert_equals(JSON::false, $res->[0][1]{canCalculateChanges}); - xlog $self, "inMailboxOtherThan query can not calculate changes"; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - operator => 'NOT', - conditions => [ - { inMailboxOtherThan => [$inbox->{id}] }, - ], - }, - }, "R1"], - ]); - $self->assert_equals(JSON::false, $res->[0][1]{canCalculateChanges}); + xlog $self, "inMailboxOtherThan query can not calculate changes"; + $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + operator => 'NOT', + conditions => [ { inMailboxOtherThan => [ $inbox->{id} ] }, ], + }, + }, + "R1" + ], + ]); + $self->assert_equals(JSON::false, $res->[0][1]{canCalculateChanges}); } diff --git a/cassandane/tiny-tests/JMAPEmail/issue_2664 b/cassandane/tiny-tests/JMAPEmail/issue_2664 index 90e2e8ad69..ada87b1bae 100644 --- a/cassandane/tiny-tests/JMAPEmail/issue_2664 +++ b/cassandane/tiny-tests/JMAPEmail/issue_2664 @@ -2,56 +2,67 @@ use Cassandane::Tiny; sub test_issue_2664 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityId = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityId); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityId = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityId); - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - 'mbox1' => { - name => 'foo', - } - } - }, 'R1'], - ['Email/set', { - create => { - email1 => { - mailboxIds => { - '#mbox1' => JSON::true - }, - from => [{ email => q{foo@bar} }], - to => [{ email => q{bar@foo} }], - subject => "test", - bodyStructure => { - partId => '1', - }, - bodyValues => { - "1" => { - value => "A text body", - }, - }, - } + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + 'mbox1' => { + name => 'foo', + } + } + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + email1 => { + mailboxIds => { + '#mbox1' => JSON::true }, - }, 'R2'], - ['EmailSubmission/set', { - create => { - 'emailSubmission1' => { - identityId => $identityId, - emailId => '#email1' - } - } - }, 'R3'], - ]); - $self->assert(exists $res->[0][1]{created}{mbox1}); - $self->assert(exists $res->[1][1]{created}{email1}); - $self->assert(exists $res->[2][1]{created}{emailSubmission1}); + from => [ { email => q{foo@bar} } ], + to => [ { email => q{bar@foo} } ], + subject => "test", + bodyStructure => { + partId => '1', + }, + bodyValues => { + "1" => { + value => "A text body", + }, + }, + } + }, + }, + 'R2' + ], + [ + 'EmailSubmission/set', + { + create => { + 'emailSubmission1' => { + identityId => $identityId, + emailId => '#email1' + } + } + }, + 'R3' + ], + ]); + $self->assert(exists $res->[0][1]{created}{mbox1}); + $self->assert(exists $res->[1][1]{created}{email1}); + $self->assert(exists $res->[2][1]{created}{emailSubmission1}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_brokenrfc822_badendline b/cassandane/tiny-tests/JMAPEmail/misc_brokenrfc822_badendline index c762384286..bcf72c9619 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_brokenrfc822_badendline +++ b/cassandane/tiny-tests/JMAPEmail/misc_brokenrfc822_badendline @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_misc_brokenrfc822_badendline - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -18,33 +17,43 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email =~ s/\r//gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; + $email =~ s/\r//gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsmbox); + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftsmbox); - xlog $self, "import email from blob $blobid"; - $res = $jmap->CallMethods([['Email/import', { - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$draftsmbox => JSON::true}, - keywords => { - '$draft' => JSON::true, - }, - }, - }, - }, "R1"]]); - my $error = $@; - $self->assert_str_equals("invalidEmail", $res->[0][1]{notCreated}{1}{type}); + xlog $self, "import email from blob $blobid"; + $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $draftsmbox => JSON::true }, + keywords => { + '$draft' => JSON::true, + }, + }, + }, + }, + "R1" + ] ]); + my $error = $@; + $self->assert_str_equals("invalidEmail", $res->[0][1]{notCreated}{1}{type}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_collapsethreads_issue2024 b/cassandane/tiny-tests/JMAPEmail/misc_collapsethreads_issue2024 index 5872402e5a..c2b0bea938 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_collapsethreads_issue2024 +++ b/cassandane/tiny-tests/JMAPEmail/misc_collapsethreads_issue2024 @@ -2,42 +2,44 @@ use Cassandane::Tiny; sub test_misc_collapsethreads_issue2024 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # test that the collapseThreads property is echoed back verbatim - # see https://github.com/cyrusimap/cyrus-imapd/issues/2024 + # test that the collapseThreads property is echoed back verbatim + # see https://github.com/cyrusimap/cyrus-imapd/issues/2024 - # check IMAP server has the XCONVERSATIONS capability - $self->assert($self->{store}->get_client()->capability()->{xconversations}); + # check IMAP server has the XCONVERSATIONS capability + $self->assert($self->{store}->get_client()->capability()->{xconversations}); - xlog $self, "generating email A"; - $exp{A} = $self->make_message("Email A"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + xlog $self, "generating email A"; + $exp{A} = $self->make_message("Email A"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - xlog $self, "generating email C referencing A"; - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ]); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + xlog $self, "generating email C referencing A"; + $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ]); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - $res = $jmap->CallMethods([['Email/query', { collapseThreads => JSON::true }, "R1"]]); - $self->assert_equals(JSON::true, $res->[0][1]->{collapseThreads}); + $res = $jmap->CallMethods( + [ [ 'Email/query', { collapseThreads => JSON::true }, "R1" ] ]); + $self->assert_equals(JSON::true, $res->[0][1]->{collapseThreads}); - $res = $jmap->CallMethods([['Email/query', { collapseThreads => JSON::false }, "R1"]]); - $self->assert_equals(JSON::false, $res->[0][1]->{collapseThreads}); + $res = $jmap->CallMethods( + [ [ 'Email/query', { collapseThreads => JSON::false }, "R1" ] ]); + $self->assert_equals(JSON::false, $res->[0][1]->{collapseThreads}); - $res = $jmap->CallMethods([['Email/query', { collapseThreads => undef }, "R1"]]); - $self->assert_null($res->[0][1]->{collapseThreads}); + $res = $jmap->CallMethods( + [ [ 'Email/query', { collapseThreads => undef }, "R1" ] ]); + $self->assert_null($res->[0][1]->{collapseThreads}); - $res = $jmap->CallMethods([['Email/query', { }, "R1"]]); - $self->assert_equals(JSON::false, $res->[0][1]->{collapseThreads}); + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + $self->assert_equals(JSON::false, $res->[0][1]->{collapseThreads}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_download b/cassandane/tiny-tests/JMAPEmail/misc_download index 42ca3ce55c..6c254fa353 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_download +++ b/cassandane/tiny-tests/JMAPEmail/misc_download @@ -2,52 +2,55 @@ use Cassandane::Tiny; sub test_misc_download - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; - # Generate an email to have some blob ids - xlog $self, "Generate an email in $inbox via IMAP"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "sub", - body => "" - . "--sub\r\n" - . "Content-Type: text/plain; charset=UTF-8\r\n" - . "some text" - . "\r\n--sub\r\n" - . "Content-Type: image/jpeg\r\n" - . "Content-Transfer-Encoding: base64\r\n" . "\r\n" - . "beefc0de" - . "\r\n--sub\r\n" - . "Content-Type: image/png\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . "f00bae==" - . "\r\n--sub--\r\n", - ); + # Generate an email to have some blob ids + xlog $self, "Generate an email in $inbox via IMAP"; + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "sub", + body => "" + . "--sub\r\n" + . "Content-Type: text/plain; charset=UTF-8\r\n" + . "some text" + . "\r\n--sub\r\n" + . "Content-Type: image/jpeg\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "beefc0de" + . "\r\n--sub\r\n" + . "Content-Type: image/png\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "f00bae==" + . "\r\n--sub--\r\n", + ); - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; - xlog $self, "get email"; - $res = $jmap->CallMethods([['Email/get', { - ids => $ids, - properties => ['bodyStructure'], - }, "R1"]]); - my $msg = $res->[0][1]{list}[0]; + xlog $self, "get email"; + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => $ids, + properties => ['bodyStructure'], + }, + "R1" + ] ]); + my $msg = $res->[0][1]{list}[0]; - my $blobid1 = $msg->{bodyStructure}{subParts}[1]{blobId}; - my $blobid2 = $msg->{bodyStructure}{subParts}[2]{blobId}; - $self->assert_not_null($blobid1); - $self->assert_not_null($blobid2); + my $blobid1 = $msg->{bodyStructure}{subParts}[1]{blobId}; + my $blobid2 = $msg->{bodyStructure}{subParts}[2]{blobId}; + $self->assert_not_null($blobid1); + $self->assert_not_null($blobid2); - $res = $jmap->Download('cassandane', $blobid1); - $self->assert_str_equals("beefc0de", encode_base64($res->{content}, '')); + $res = $jmap->Download('cassandane', $blobid1); + $self->assert_str_equals("beefc0de", encode_base64($res->{content}, '')); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_download_shared b/cassandane/tiny-tests/JMAPEmail/misc_download_shared index dcf4cc8cb8..765c872401 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_download_shared +++ b/cassandane/tiny-tests/JMAPEmail/misc_download_shared @@ -2,53 +2,54 @@ use Cassandane::Tiny; sub test_misc_download_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - my $inbox = 'INBOX'; - - my $admintalk = $self->{adminstore}->get_client(); - - xlog $self, "Create shared mailboxes"; - $self->{instance}->create_user("foo"); - $admintalk->create("user.foo.A") or die; - $admintalk->setacl("user.foo.A", "cassandane", "lr") or die; - $admintalk->create("user.foo.B") or die; - $admintalk->setacl("user.foo.B", "cassandane", "lr") or die; - - xlog $self, "Create email in shared mailbox"; - $self->{adminstore}->set_folder('user.foo.B'); - $self->make_message("foo", store => $self->{adminstore}) or die; - - xlog $self, "get email blobId"; - my $res = $jmap->CallMethods([ - ['Email/query', { accountId => 'foo'}, 'R1'], - ['Email/get', { - accountId => 'foo', - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - properties => ['blobId'], - }, 'R2'], - ]); - my $blobId = $res->[1][1]->{list}[0]{blobId}; - - xlog $self, "download email as blob"; - $res = $jmap->Download('foo', $blobId); - - xlog $self, "Unshare mailbox"; - $admintalk->setacl("user.foo.B", "cassandane", "") or die; - - my %Headers = ( - 'Authorization' => $jmap->auth_header(), - ); - my $httpRes = $jmap->ua->get($jmap->downloaduri('foo', $blobId), - { headers => \%Headers }); - $self->assert_str_equals('404', $httpRes->{status}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + my $inbox = 'INBOX'; + + my $admintalk = $self->{adminstore}->get_client(); + + xlog $self, "Create shared mailboxes"; + $self->{instance}->create_user("foo"); + $admintalk->create("user.foo.A") or die; + $admintalk->setacl("user.foo.A", "cassandane", "lr") or die; + $admintalk->create("user.foo.B") or die; + $admintalk->setacl("user.foo.B", "cassandane", "lr") or die; + + xlog $self, "Create email in shared mailbox"; + $self->{adminstore}->set_folder('user.foo.B'); + $self->make_message("foo", store => $self->{adminstore}) or die; + + xlog $self, "get email blobId"; + my $res = $jmap->CallMethods([ + [ 'Email/query', { accountId => 'foo' }, 'R1' ], + [ + 'Email/get', + { + accountId => 'foo', + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + properties => ['blobId'], + }, + 'R2' + ], + ]); + my $blobId = $res->[1][1]->{list}[0]{blobId}; + + xlog $self, "download email as blob"; + $res = $jmap->Download('foo', $blobId); + + xlog $self, "Unshare mailbox"; + $admintalk->setacl("user.foo.B", "cassandane", "") or die; + + my %Headers = ('Authorization' => $jmap->auth_header(),); + my $httpRes = $jmap->ua->get($jmap->downloaduri('foo', $blobId), + { headers => \%Headers }); + $self->assert_str_equals('404', $httpRes->{status}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_emptyids b/cassandane/tiny-tests/JMAPEmail/misc_emptyids index e18dc94c3b..608e16015a 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_emptyids +++ b/cassandane/tiny-tests/JMAPEmail/misc_emptyids @@ -2,28 +2,32 @@ use Cassandane::Tiny; sub test_misc_emptyids - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $res; + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $res; - $imaptalk->create("INBOX.foo") || die; + $imaptalk->create("INBOX.foo") || die; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [] }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [] }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); - $res = $jmap->CallMethods([['Thread/get', { ids => [] }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + $res = $jmap->CallMethods([ [ 'Thread/get', { ids => [] }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); - $res = $jmap->CallMethods([['Email/get', { ids => [] }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + $res = $jmap->CallMethods([ [ 'Email/get', { ids => [] }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); - $res = $jmap->CallMethods([['Identity/get', { ids => [] }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + $res = $jmap->CallMethods([ [ 'Identity/get', { ids => [] }, "R1" ] ]); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); - $res = $jmap->CallMethods([['SearchSnippet/get', { emailIds => [], filter => { text => "foo" } }, "R1"]]); - $self->assert_num_equals(0, scalar @{$res->[0][1]{list}}); + $res = $jmap->CallMethods( + [ [ + 'SearchSnippet/get', { emailIds => [], filter => { text => "foo" } }, + "R1" + ] ] + ); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_refobjects_extended b/cassandane/tiny-tests/JMAPEmail/misc_refobjects_extended index ffaf3d006a..ffca42e72c 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_refobjects_extended +++ b/cassandane/tiny-tests/JMAPEmail/misc_refobjects_extended @@ -2,42 +2,53 @@ use Cassandane::Tiny; sub test_misc_refobjects_extended - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Generate an email in INBOX via IMAP"; - foreach my $i (1..10) { - $self->make_message("Email$i") || die; - } + xlog $self, "Generate an email in INBOX via IMAP"; + foreach my $i (1 .. 10) { + $self->make_message("Email$i") || die; + } - xlog $self, "get email properties using reference"; - my $res = $jmap->CallMethods([ - ['Email/query', { - sort => [{ property => 'receivedAt', isAscending => JSON::false }], - collapseThreads => JSON::true, - position => 0, - limit => 10, - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - properties => [ 'threadId' ], - }, 'R2'], - ['Thread/get', { - '#ids' => { - resultOf => 'R2', - name => 'Email/get', - path => '/list/*/threadId', - }, - }, 'R3'], - ]); - $self->assert_num_equals(10, scalar @{$res->[2][1]{list}}); + xlog $self, "get email properties using reference"; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + sort => [ { property => 'receivedAt', isAscending => JSON::false } ], + collapseThreads => JSON::true, + position => 0, + limit => 10, + }, + 'R1' + ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + properties => ['threadId'], + }, + 'R2' + ], + [ + 'Thread/get', + { + '#ids' => { + resultOf => 'R2', + name => 'Email/get', + path => '/list/*/threadId', + }, + }, + 'R3' + ], + ]); + $self->assert_num_equals(10, scalar @{ $res->[2][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_refobjects_simple b/cassandane/tiny-tests/JMAPEmail/misc_refobjects_simple index 90fb782ad9..d9b61ba171 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_refobjects_simple +++ b/cassandane/tiny-tests/JMAPEmail/misc_refobjects_simple @@ -2,36 +2,43 @@ use Cassandane::Tiny; sub test_misc_refobjects_simple - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "get email state"; - my $res = $jmap->CallMethods([['Email/get', { ids => [] }, "R1"]]); - my $state = $res->[0][1]->{state}; - $self->assert_not_null($state); + xlog $self, "get email state"; + my $res = $jmap->CallMethods([ [ 'Email/get', { ids => [] }, "R1" ] ]); + my $state = $res->[0][1]->{state}; + $self->assert_not_null($state); - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("Email A") || die; + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("Email A") || die; - xlog $self, "get email updates and email using reference"; - $res = $jmap->CallMethods([ - ['Email/changes', { - sinceState => $state, - }, 'R1'], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/changes', - path => '/created', - }, - }, 'R2'], - ]); + xlog $self, "get email updates and email using reference"; + $res = $jmap->CallMethods([ + [ + 'Email/changes', + { + sinceState => $state, + }, + 'R1' + ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/changes', + path => '/created', + }, + }, + 'R2' + ], + ]); - # assert that the changed id equals the id of the returned email - $self->assert_str_equals($res->[0][1]{created}[0], $res->[1][1]{list}[0]{id}); + # assert that the changed id equals the id of the returned email + $self->assert_str_equals($res->[0][1]{created}[0], $res->[1][1]{list}[0]{id}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_set_oldstate b/cassandane/tiny-tests/JMAPEmail/misc_set_oldstate index 3bd159f1aa..a269c09dce 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_set_oldstate +++ b/cassandane/tiny-tests/JMAPEmail/misc_set_oldstate @@ -2,55 +2,63 @@ use Cassandane::Tiny; sub test_misc_set_oldstate - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # Assert that /set returns oldState (null, or a string) - # See https://github.com/cyrusimap/cyrus-imapd/issues/2260 + # Assert that /set returns oldState (null, or a string) + # See https://github.com/cyrusimap/cyrus-imapd/issues/2260 - xlog $self, "create drafts mailbox and email"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }} - }, "R1"], - ]); - $self->assert(exists $res->[0][1]{oldState}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox and email"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ], + ]); + $self->assert(exists $res->[0][1]{oldState}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $draft = { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "foo", - textBody => [{partId => '1' }], - bodyValues => { 1 => { value => "bar" }}, - keywords => { '$draft' => JSON::true }, - }; + my $draft = { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "foo", + textBody => [ { partId => '1' } ], + bodyValues => { 1 => { value => "bar" } }, + keywords => { '$draft' => JSON::true }, + }; - xlog $self, "create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - $self->assert(exists $res->[0][1]{oldState}); - my $msgid = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + $self->assert(exists $res->[0][1]{oldState}); + my $msgid = $res->[0][1]{created}{"1"}{id}; - $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; + $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; - xlog $self, "create email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $msgid, - } - } - }, "R1" ] ] ); - $self->assert(exists $res->[0][1]{oldState}); + xlog $self, "create email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $msgid, + } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{oldState}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_upload b/cassandane/tiny-tests/JMAPEmail/misc_upload index 8b5d2f4008..04dd638d08 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_upload +++ b/cassandane/tiny-tests/JMAPEmail/misc_upload @@ -2,54 +2,67 @@ use Cassandane::Tiny; sub test_misc_upload - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $data = $jmap->Upload("a message with some text", "text/rubbish"); - $self->assert_matches(qr/^G44911b55c3b83ca05db9659d7a8e8b7b/, $data->{blobId}); - $self->assert_num_equals(24, $data->{size}); - $self->assert_str_equals("text/rubbish", $data->{type}); + my $data = $jmap->Upload("a message with some text", "text/rubbish"); + $self->assert_matches(qr/^G44911b55c3b83ca05db9659d7a8e8b7b/, + $data->{blobId}); + $self->assert_num_equals(24, $data->{size}); + $self->assert_str_equals("text/rubbish", $data->{type}); - my $msgresp = $jmap->CallMethods([ - ['Email/set', { create => { "2" => { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo", - textBody => [{partId => '1'}], - htmlBody => [{partId => '2'}], - bodyValues => { - 1 => { + my $msgresp = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + "2" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + htmlBody => [ { partId => '2' } ], + bodyValues => { + 1 => { value => "I'm givin' ya one last chance ta surrenda!" + }, + 2 => { + value => + "I'm givin' ya one last chance ta surrenda!" + }, }, - 2 => { - value => "I'm givin' ya one last chance ta surrenda!" - }, - }, - attachments => [{ - blobId => $data->{blobId}, - name => "test.txt", - }], - keywords => { '$draft' => JSON::true }, - } } }, 'R2'], - ]); + attachments => [ { + blobId => $data->{blobId}, + name => "test.txt", + } ], + keywords => { '$draft' => JSON::true }, + } + } + }, + 'R2' + ], + ]); - $self->assert_not_null($msgresp->[0][1]{created}); + $self->assert_not_null($msgresp->[0][1]{created}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_upload_bin b/cassandane/tiny-tests/JMAPEmail/misc_upload_bin index 26ede16839..64f0b5fe01 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_upload_bin +++ b/cassandane/tiny-tests/JMAPEmail/misc_upload_bin @@ -2,46 +2,59 @@ use Cassandane::Tiny; sub test_misc_upload_bin - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $binary = slurp_file(abs_path('data/logo.gif')); - my $data = $jmap->Upload($binary, "image/gif"); + my $binary = slurp_file(abs_path('data/logo.gif')); + my $data = $jmap->Upload($binary, "image/gif"); - my $msgresp = $jmap->CallMethods([ - ['Email/set', { create => { "2" => { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo", - textBody => [{ partId => '1' }], - bodyValues => { 1 => { value => "I'm givin' ya one last chance ta surrenda!" }}, - attachments => [{ - blobId => $data->{blobId}, - name => "logo.gif", - }], - keywords => { '$draft' => JSON::true }, - } } }, 'R2'], - ]); + my $msgresp = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + "2" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { + 1 => { value => "I'm givin' ya one last chance ta surrenda!" } + }, + attachments => [ { + blobId => $data->{blobId}, + name => "logo.gif", + } ], + keywords => { '$draft' => JSON::true }, + } + } + }, + 'R2' + ], + ]); - $self->assert_not_null($msgresp->[0][1]{created}); + $self->assert_not_null($msgresp->[0][1]{created}); - # XXX - fetch back the parts + # XXX - fetch back the parts } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_upload_download822 b/cassandane/tiny-tests/JMAPEmail/misc_upload_download822 index b502d1eaa4..08fc93b731 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_upload_download822 +++ b/cassandane/tiny-tests/JMAPEmail/misc_upload_download822 @@ -2,12 +2,11 @@ use Cassandane::Tiny; sub test_misc_upload_download822 - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $email = <<'EOF'; + my $email = <<'EOF'; From: "Some Example Sender" To: baseball@vitaead.com Subject: test email @@ -18,11 +17,11 @@ Content-Transfer-Encoding: quoted-printable This is a test email. EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; - my $download = $jmap->Download('cassandane', $blobid); + my $download = $jmap->Download('cassandane', $blobid); - $self->assert_str_equals($email, $download->{content}); + $self->assert_str_equals($email, $download->{content}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_upload_multiaccount b/cassandane/tiny-tests/JMAPEmail/misc_upload_multiaccount index af39adcbad..40a94b02dd 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_upload_multiaccount +++ b/cassandane/tiny-tests/JMAPEmail/misc_upload_multiaccount @@ -2,24 +2,23 @@ use Cassandane::Tiny; sub test_misc_upload_multiaccount - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Create user and share mailbox - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lrwikxd") or die; + # Create user and share mailbox + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lrwikxd") or die; - # Create user but don't share mailbox - $self->{instance}->create_user("bar"); + # Create user but don't share mailbox + $self->{instance}->create_user("bar"); - my @res = $jmap->Upload("an email with some text", "text/rubbish", "foo"); - $self->assert_str_equals('201', $res[0]->{status}); + my @res = $jmap->Upload("an email with some text", "text/rubbish", "foo"); + $self->assert_str_equals('201', $res[0]->{status}); - @res = $jmap->Upload("an email with some text", "text/rubbish", "bar"); - $self->assert_str_equals('404', $res[0]->{status}); + @res = $jmap->Upload("an email with some text", "text/rubbish", "bar"); + $self->assert_str_equals('404', $res[0]->{status}); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_upload_sametype b/cassandane/tiny-tests/JMAPEmail/misc_upload_sametype index 3ec6b4f0ec..0defe541c1 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_upload_sametype +++ b/cassandane/tiny-tests/JMAPEmail/misc_upload_sametype @@ -2,18 +2,17 @@ use Cassandane::Tiny; sub test_misc_upload_sametype - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $lazy = "the quick brown fox jumped over the lazy dog"; + my $lazy = "the quick brown fox jumped over the lazy dog"; - my $data = $jmap->Upload($lazy, "text/plain; charset=us-ascii"); - my $blobid = $data->{blobId}; + my $data = $jmap->Upload($lazy, "text/plain; charset=us-ascii"); + my $blobid = $data->{blobId}; - $data = $jmap->Upload($lazy, "TEXT/PLAIN; charset=US-Ascii"); - my $blobid2 = $data->{blobId}; + $data = $jmap->Upload($lazy, "TEXT/PLAIN; charset=US-Ascii"); + my $blobid2 = $data->{blobId}; - $self->assert_str_equals($blobid, $blobid2); + $self->assert_str_equals($blobid, $blobid2); } diff --git a/cassandane/tiny-tests/JMAPEmail/misc_upload_zero b/cassandane/tiny-tests/JMAPEmail/misc_upload_zero index 39564d5599..c7cad53b08 100644 --- a/cassandane/tiny-tests/JMAPEmail/misc_upload_zero +++ b/cassandane/tiny-tests/JMAPEmail/misc_upload_zero @@ -2,50 +2,62 @@ use Cassandane::Tiny; sub test_misc_upload_zero - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - my $data = $jmap->Upload("", "text/plain"); - $self->assert_matches(qr/^Gda39a3ee5e6b4b0d3255bfef95601890/, $data->{blobId}); - $self->assert_num_equals(0, $data->{size}); - $self->assert_str_equals("text/plain", $data->{type}); + my $data = $jmap->Upload("", "text/plain"); + $self->assert_matches(qr/^Gda39a3ee5e6b4b0d3255bfef95601890/, + $data->{blobId}); + $self->assert_num_equals(0, $data->{size}); + $self->assert_str_equals("text/plain", $data->{type}); - my $msgresp = $jmap->CallMethods([ - ['Email/set', { create => { "2" => { - mailboxIds => { $draftsmbox => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo", - textBody => [{ partId => '1' }], - bodyValues => { - '1' => { + my $msgresp = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + "2" => { + mailboxIds => { $draftsmbox => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { + '1' => { value => "I'm givin' ya one last chance ta surrenda!" - } - }, - attachments => [{ - blobId => $data->{blobId}, - name => "emptyfile.txt", - }], - keywords => { '$draft' => JSON::true }, - } } }, 'R2'], - ]); + } + }, + attachments => [ { + blobId => $data->{blobId}, + name => "emptyfile.txt", + } ], + keywords => { '$draft' => JSON::true }, + } + } + }, + 'R2' + ], + ]); - $self->assert_not_null($msgresp->[0][1]{created}); + $self->assert_not_null($msgresp->[0][1]{created}); } diff --git a/cassandane/tiny-tests/JMAPEmail/replication_email_set_update_snooze b/cassandane/tiny-tests/JMAPEmail/replication_email_set_update_snooze index d844415c1d..25bbeb75bb 100644 --- a/cassandane/tiny-tests/JMAPEmail/replication_email_set_update_snooze +++ b/cassandane/tiny-tests/JMAPEmail/replication_email_set_update_snooze @@ -2,164 +2,212 @@ use Cassandane::Tiny; sub test_replication_email_set_update_snooze - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd - :needs_component_sieve :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # snoozed property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); - - xlog $self, "Get mailbox id of Inbox"; - my $inboxId = $self->getinbox()->{id}; - - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; - - xlog $self, "get email id"; - my $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R2" ] ] ); - my $emailId = $res->[0][1]->{ids}[0]; - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R3" ] ] ); - my $msg = $res->[0][1]->{list}[0]; - my $oldState = $res->[0][1]->{state}; - $self->assert_not_null($msg->{mailboxIds}{$inboxId}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - - xlog $self, "create snooze mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "snoozed", - parentId => undef, - role => "snoozed" - }}}, "R4"] - ]); - $self->assert_not_null($res->[0][1]{created}); - my $snoozedId = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "Move message to snooze mailbox"; - my $maildate = DateTime->now(); - $maildate->add(DateTime::Duration->new(seconds => 30)); - my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "mailboxIds/$inboxId" => undef, - "mailboxIds/$snoozedId" => $JSON::true, - "snoozed" => { "until" => $datestr }, - keywords => { '$flagged' => JSON::true, '$seen' => JSON::true }, - }} - }, 'R5'] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - $res = $jmap->CallMethods([['Email/changes', { sinceState => $oldState }, "R1"]]); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([$emailId], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $oldState = $res->[0][1]{newState}; - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R6" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_null($msg->{mailboxIds}{$inboxId}); - $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_equals($datestr, $msg->{snoozed}{'until'}); - - $self->run_replication(); - $self->check_replication('cassandane'); - - $res = $jmap->CallMethods([['Email/changes', { sinceState => $oldState }, "R1"]]); - $self->assert_str_equals($oldState, $res->[0][1]{newState}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R6" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_null($msg->{mailboxIds}{$inboxId}); - $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_equals($datestr, $msg->{snoozed}{'until'}); - - xlog $self, "Adjust snooze#until"; - $maildate->add(DateTime::Duration->new(seconds => 15)); - $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); - - $res = $jmap->CallMethods([ - ['Email/set', { - update => { $emailId => { - "snoozed" => { - "until" => $datestr, - "setKeywords" => { '$awakened' => $JSON::true } - }, - }} - }, 'R5'] - ]); - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - $res = $jmap->CallMethods([['Email/changes', { sinceState => $oldState }, "R1"]]); - $self->assert_str_not_equals($oldState, $res->[0][1]{newState}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([$emailId], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $oldState = $res->[0][1]{newState}; - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R6" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_null($msg->{mailboxIds}{$inboxId}); - $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_equals($datestr, $msg->{snoozed}{'until'}); - - xlog $self, "make sure replication doesn't revert it!"; - $self->run_replication(); - $self->check_replication('cassandane'); - - $res = $jmap->CallMethods([['Email/changes', { sinceState => $oldState }, "R1"]]); - $self->assert_str_equals($oldState, $res->[0][1]{newState}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R6" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_null($msg->{mailboxIds}{$inboxId}); - $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); - $self->assert_num_equals(1, scalar keys %{$msg->{mailboxIds}}); - $self->assert_equals($datestr, $msg->{snoozed}{'until'}); - - xlog $self, "trigger re-delivery of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 30 ); - - $res = $jmap->CallMethods( [ [ 'Email/get', - { ids => [ $emailId ], - properties => [ 'mailboxIds', 'keywords', 'snoozed' ]}, "R7" ] ] ); - $msg = $res->[0][1]->{list}[0]; - $self->assert_num_equals(3, scalar keys %{$msg->{keywords}}); - $self->assert_equals(JSON::true, $msg->{keywords}{'$awakened'}); - - $res = $jmap->CallMethods([['Email/changes', { sinceState => $oldState }, "R1"]]); - $self->assert_str_not_equals($oldState, $res->[0][1]{newState}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([$emailId], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd + : needs_component_sieve : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # snoozed property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); + + xlog $self, "Get mailbox id of Inbox"; + my $inboxId = $self->getinbox()->{id}; + + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; + + xlog $self, "get email id"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R2" ] ]); + my $emailId = $res->[0][1]->{ids}[0]; + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R3" + ] ]); + my $msg = $res->[0][1]->{list}[0]; + my $oldState = $res->[0][1]->{state}; + $self->assert_not_null($msg->{mailboxIds}{$inboxId}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + + xlog $self, "create snooze mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "snoozed", + parentId => undef, + role => "snoozed" + } + } + }, + "R4" + ] ]); + $self->assert_not_null($res->[0][1]{created}); + my $snoozedId = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "Move message to snooze mailbox"; + my $maildate = DateTime->now(); + $maildate->add(DateTime::Duration->new(seconds => 30)); + my $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + "mailboxIds/$inboxId" => undef, + "mailboxIds/$snoozedId" => $JSON::true, + "snoozed" => { "until" => $datestr }, + keywords => { '$flagged' => JSON::true, '$seen' => JSON::true }, + } + } + }, + 'R5' + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $oldState }, "R1" ] ]); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([$emailId], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $oldState = $res->[0][1]{newState}; + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R6" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_null($msg->{mailboxIds}{$inboxId}); + $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_equals($datestr, $msg->{snoozed}{'until'}); + + $self->run_replication(); + $self->check_replication('cassandane'); + + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $oldState }, "R1" ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{newState}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R6" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_null($msg->{mailboxIds}{$inboxId}); + $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_equals($datestr, $msg->{snoozed}{'until'}); + + xlog $self, "Adjust snooze#until"; + $maildate->add(DateTime::Duration->new(seconds => 15)); + $datestr = $maildate->strftime('%Y-%m-%dT%TZ'); + + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $emailId => { + "snoozed" => { + "until" => $datestr, + "setKeywords" => { '$awakened' => $JSON::true } + }, + } + } + }, + 'R5' + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $oldState }, "R1" ] ]); + $self->assert_str_not_equals($oldState, $res->[0][1]{newState}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([$emailId], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $oldState = $res->[0][1]{newState}; + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R6" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_null($msg->{mailboxIds}{$inboxId}); + $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_equals($datestr, $msg->{snoozed}{'until'}); + + xlog $self, "make sure replication doesn't revert it!"; + $self->run_replication(); + $self->check_replication('cassandane'); + + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $oldState }, "R1" ] ]); + $self->assert_str_equals($oldState, $res->[0][1]{newState}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R6" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_null($msg->{mailboxIds}{$inboxId}); + $self->assert_not_null($msg->{mailboxIds}{$snoozedId}); + $self->assert_num_equals(1, scalar keys %{ $msg->{mailboxIds} }); + $self->assert_equals($datestr, $msg->{snoozed}{'until'}); + + xlog $self, "trigger re-delivery of snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 30); + + $res = $jmap->CallMethods([ [ + 'Email/get', + { + ids => [$emailId], + properties => [ 'mailboxIds', 'keywords', 'snoozed' ] + }, + "R7" + ] ]); + $msg = $res->[0][1]->{list}[0]; + $self->assert_num_equals(3, scalar keys %{ $msg->{keywords} }); + $self->assert_equals(JSON::true, $msg->{keywords}{'$awakened'}); + + $res = $jmap->CallMethods( + [ [ 'Email/changes', { sinceState => $oldState }, "R1" ] ]); + $self->assert_str_not_equals($oldState, $res->[0][1]{newState}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([$emailId], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); } diff --git a/cassandane/tiny-tests/JMAPEmail/search_sharedpart b/cassandane/tiny-tests/JMAPEmail/search_sharedpart index 5e404457ba..e407c2815a 100644 --- a/cassandane/tiny-tests/JMAPEmail/search_sharedpart +++ b/cassandane/tiny-tests/JMAPEmail/search_sharedpart @@ -2,94 +2,108 @@ use Cassandane::Tiny; sub test_search_sharedpart - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $body = "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; - $body .= "\r\n"; - $body .= "This is the lady plain text part."; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; - $body .= "\r\n"; - $body .= "

This is the lady html part.

"; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; + my $body = "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $body .= "\r\n"; + $body .= "This is the lady plain text part."; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; + $body .= "\r\n"; + $body .= "

This is the lady html part.

"; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; - $self->make_message("lady subject", - mime_type => "multipart/alternative", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body - ) || die; + $self->make_message( + "lady subject", + mime_type => "multipart/alternative", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body + ) || die; - $body = "--h8h89737fe04d3bde348\r\n"; - $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; - $body .= "\r\n"; - $body .= "This is the foobar plain text part."; - $body .= "\r\n"; - $body .= "--h8h89737fe04d3bde348\r\n"; - $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; - $body .= "\r\n"; - $body .= "

This is the lady html part.

"; - $body .= "\r\n"; - $body .= "--h8h89737fe04d3bde348--\r\n"; + $body = "--h8h89737fe04d3bde348\r\n"; + $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $body .= "\r\n"; + $body .= "This is the foobar plain text part."; + $body .= "\r\n"; + $body .= "--h8h89737fe04d3bde348\r\n"; + $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; + $body .= "\r\n"; + $body .= "

This is the lady html part.

"; + $body .= "\r\n"; + $body .= "--h8h89737fe04d3bde348--\r\n"; - $self->make_message("foobar subject", - mime_type => "multipart/alternative", - mime_boundary => "h8h89737fe04d3bde348", - body => $body - ) || die; + $self->make_message( + "foobar subject", + mime_type => "multipart/alternative", + mime_boundary => "h8h89737fe04d3bde348", + body => $body + ) || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { text => "foobar" }, + findMatchingParts => JSON::true, + }, + "R1" + ], + ], + $using + ); + my $emailIds = $res->[0][1]{ids}; + my $partIds = $res->[0][1]{partIds}; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => {text => "foobar"}, - findMatchingParts => JSON::true, - },"R1"], - ], $using); - my $emailIds = $res->[0][1]{ids}; - my $partIds = $res->[0][1]{partIds}; + my $fooId = $emailIds->[0]; - my $fooId = $emailIds->[0]; + $self->assert_num_equals(1, scalar @$emailIds); + $self->assert_num_equals(1, scalar keys %$partIds); + $self->assert_num_equals(1, scalar @{ $partIds->{$fooId} }); + $self->assert_str_equals("1", $partIds->{$fooId}[0]); - $self->assert_num_equals(1, scalar @$emailIds); - $self->assert_num_equals(1, scalar keys %$partIds); - $self->assert_num_equals(1, scalar @{$partIds->{$fooId}}); - $self->assert_str_equals("1", $partIds->{$fooId}[0]); + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { text => "lady" }, + findMatchingParts => JSON::true, + }, + "R1" + ], + ], + $using + ); + $emailIds = $res->[0][1]{ids}; + $partIds = $res->[0][1]{partIds}; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => {text => "lady"}, - findMatchingParts => JSON::true, - }, "R1"], - ], $using); - $emailIds = $res->[0][1]{ids}; - $partIds = $res->[0][1]{partIds}; + my ($ladyId) = grep { $_ ne $fooId } @$emailIds; - my ($ladyId) = grep { $_ ne $fooId } @$emailIds; - - $self->assert_num_equals(2, scalar @$emailIds); - $self->assert_num_equals(2, scalar keys %$partIds); - $self->assert_num_equals(1, scalar @{$partIds->{$fooId}}); - $self->assert_num_equals(2, scalar @{$partIds->{$ladyId}}); - $self->assert_not_null(grep { $_ eq "2" } @{$partIds->{$fooId}}); - $self->assert_not_null(grep { $_ eq "1" } @{$partIds->{$ladyId}}); - $self->assert_not_null(grep { $_ eq "2" } @{$partIds->{$ladyId}}); + $self->assert_num_equals(2, scalar @$emailIds); + $self->assert_num_equals(2, scalar keys %$partIds); + $self->assert_num_equals(1, scalar @{ $partIds->{$fooId} }); + $self->assert_num_equals(2, scalar @{ $partIds->{$ladyId} }); + $self->assert_not_null(grep { $_ eq "2" } @{ $partIds->{$fooId} }); + $self->assert_not_null(grep { $_ eq "1" } @{ $partIds->{$ladyId} }); + $self->assert_not_null(grep { $_ eq "2" } @{ $partIds->{$ladyId} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get index 68bdebcef5..2125368b19 100644 --- a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get +++ b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get @@ -2,110 +2,125 @@ use Cassandane::Tiny; sub test_searchsnippet_get - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - xlog $self, "create emails"; - my %params = ( - body => "A simple message", - ); - $res = $self->make_message("Message foo", %params) || die; + xlog $self, "create emails"; + my %params = (body => "A simple message",); + $res = $self->make_message("Message foo", %params) || die; - %params = ( - body => "" - . "In the context of electronic mail, messages are viewed as having an\r\n" - . "envelope and contents. The envelope contains whatever information is\r\n" - . "needed to accomplish transmission and delivery. (See [RFC5321] for a\r\n" - . "discussion of the envelope.) The contents comprise the object to be\r\n" - . "delivered to the recipient. This specification applies only to the\r\n" - . "format and some of the semantics of message contents. It contains no\r\n" - . "specification of the information in the envelope.i\r\n" - . "\r\n" - . "However, some message systems may use information from the contents\r\n" - . "to create the envelope. It is intended that this specification\r\n" - . "facilitate the acquisition of such information by programs.\r\n" - . "\r\n" - . "This specification is intended as a definition of what message\r\n" - . "content format is to be passed between systems. Though some message\r\n" - . "systems locally store messages in this format (which eliminates the\r\n" - . "need for translation between formats) and others use formats that\r\n" - . "differ from the one specified in this specification, local storage is\r\n" - . "outside of the scope of this specification.\r\n" - . "\r\n" - . "This paragraph is not part of the specification, it has been added to\r\n" - . "contain the most mentions of the word message. Messages are processed\r\n" - . "by messaging systems, which is the message of this paragraph.\r\n" - . "Don't interpret too much into this message.\r\n", - ); - $self->make_message("Message bar", %params) || die; - %params = ( - body => "This body doesn't contain any of the search terms.\r\n", - ); - $self->make_message("A subject without any matching search term", %params) || die; + %params + = ( body => "" + . "In the context of electronic mail, messages are viewed as having an\r\n" + . "envelope and contents. The envelope contains whatever information is\r\n" + . "needed to accomplish transmission and delivery. (See [RFC5321] for a\r\n" + . "discussion of the envelope.) The contents comprise the object to be\r\n" + . "delivered to the recipient. This specification applies only to the\r\n" + . "format and some of the semantics of message contents. It contains no\r\n" + . "specification of the information in the envelope.i\r\n" . "\r\n" + . "However, some message systems may use information from the contents\r\n" + . "to create the envelope. It is intended that this specification\r\n" + . "facilitate the acquisition of such information by programs.\r\n" + . "\r\n" + . "This specification is intended as a definition of what message\r\n" + . "content format is to be passed between systems. Though some message\r\n" + . "systems locally store messages in this format (which eliminates the\r\n" + . "need for translation between formats) and others use formats that\r\n" + . "differ from the one specified in this specification, local storage is\r\n" + . "outside of the scope of this specification.\r\n" . "\r\n" + . "This paragraph is not part of the specification, it has been added to\r\n" + . "contain the most mentions of the word message. Messages are processed\r\n" + . "by messaging systems, which is the message of this paragraph.\r\n" + . "Don't interpret too much into this message.\r\n",); + $self->make_message("Message bar", %params) || die; + %params = (body => "This body doesn't contain any of the search terms.\r\n",); + $self->make_message("A subject without any matching search term", %params) + || die; - $self->make_message("Message baz", %params) || die; - %params = ( - body => "This body doesn't contain any of the search terms.\r\n", - ); - $self->make_message("A subject with message", %params) || die; + $self->make_message("Message baz", %params) || die; + %params = (body => "This body doesn't contain any of the search terms.\r\n",); + $self->make_message("A subject with message", %params) || die; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "fetch email ids"; - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2' ], - ]); + xlog $self, "fetch email ids"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); - my %m = map { $_->{subject} => $_ } @{$res->[1][1]{list}}; - my $foo = $m{"Message foo"}->{id}; - my $bar = $m{"Message bar"}->{id}; - my $baz = $m{"Message baz"}->{id}; - $self->assert_not_null($foo); - $self->assert_not_null($bar); - $self->assert_not_null($baz); + my %m = map { $_->{subject} => $_ } @{ $res->[1][1]{list} }; + my $foo = $m{"Message foo"}->{id}; + my $bar = $m{"Message bar"}->{id}; + my $baz = $m{"Message baz"}->{id}; + $self->assert_not_null($foo); + $self->assert_not_null($bar); + $self->assert_not_null($baz); - xlog $self, "fetch snippets"; - $res = $jmap->CallMethods([['SearchSnippet/get', { - emailIds => [ $foo, $bar ], - filter => { text => "message" }, - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{list}}); - $self->assert_null($res->[0][1]->{notFound}); - %m = map { $_->{emailId} => $_ } @{$res->[0][1]{list}}; - $self->assert_not_null($m{$foo}); - $self->assert_not_null($m{$bar}); + xlog $self, "fetch snippets"; + $res = $jmap->CallMethods([ [ + 'SearchSnippet/get', + { + emailIds => [ $foo, $bar ], + filter => { text => "message" }, + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{list} }); + $self->assert_null($res->[0][1]->{notFound}); + %m = map { $_->{emailId} => $_ } @{ $res->[0][1]{list} }; + $self->assert_not_null($m{$foo}); + $self->assert_not_null($m{$bar}); - %m = map { $_->{emailId} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_not_equals(-1, index($m{$foo}->{subject}, "Message foo")); - $self->assert_num_not_equals(-1, index($m{$foo}->{preview}, "A simple message")); - $self->assert_num_not_equals(-1, index($m{$bar}->{subject}, "Message bar")); - $self->assert_num_not_equals(-1, index($m{$bar}->{preview}, "" + %m = map { $_->{emailId} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_not_equals(-1, + index($m{$foo}->{subject}, "Message foo")); + $self->assert_num_not_equals(-1, + index($m{$foo}->{preview}, "A simple message")); + $self->assert_num_not_equals(-1, + index($m{$bar}->{subject}, "Message bar")); + $self->assert_num_not_equals( + -1, + index( + $m{$bar}->{preview}, + "" . "Messages are processed by messaging systems," - )); + ) + ); - xlog $self, "fetch snippets with one unknown id"; - $res = $jmap->CallMethods([['SearchSnippet/get', { - emailIds => [ $foo, "bam" ], - filter => { text => "message" }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{notFound}}); + xlog $self, "fetch snippets with one unknown id"; + $res = $jmap->CallMethods([ [ + 'SearchSnippet/get', + { + emailIds => [ $foo, "bam" ], + filter => { text => "message" }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{notFound} }); - xlog $self, "fetch snippets with only a matching subject"; - $res = $jmap->CallMethods([['SearchSnippet/get', { - emailIds => [ $baz ], - filter => { text => "message" }, - }, "R1"]]); - $self->assert_not_null($res->[0][1]->{list}[0]->{subject}); - $self->assert(exists $res->[0][1]->{list}[0]->{preview}); + xlog $self, "fetch snippets with only a matching subject"; + $res = $jmap->CallMethods([ [ + 'SearchSnippet/get', + { + emailIds => [$baz], + filter => { text => "message" }, + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]->{list}[0]->{subject}); + $self->assert(exists $res->[0][1]->{list}[0]->{preview}); } diff --git a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_attachment b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_attachment index 8f3ab20462..5316d962fd 100644 --- a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_attachment +++ b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_attachment @@ -2,144 +2,181 @@ use Cassandane::Tiny; sub test_searchsnippet_get_attachment - :min_version_3_3 :needs_component_jmap :needs_search_xapian - :needs_component_sieve :SearchAttachmentExtractor :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $instance = $self->{instance}; + : min_version_3_3 : needs_component_jmap : needs_search_xapian + : needs_component_sieve : SearchAttachmentExtractor : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $instance = $self->{instance}; - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); - # Start a dummy extractor server. - my %seenPath; - my $handler = sub { - my ($conn, $req) = @_; - if ($req->method eq 'HEAD') { - my $res = HTTP::Response->new(204); - $res->content(""); - $conn->send_response($res); - } elsif ($seenPath{$req->uri->path}) { - my $res = HTTP::Response->new(200); - $res->header("Keep-Alive" => "timeout=1"); # Force client timeout - $res->content("dog cat bat"); - $conn->send_response($res); - } else { - $conn->send_error(404); - $seenPath{$req->uri->path} = 1; - } - }; - $instance->start_httpd($handler, $uri->port()); - - # Append an email with PDF attachment text "dog cat bat". - my $file = "data/dogcatbat.pdf.b64"; - open my $input, '<', $file or die "can't open $file: $!"; - my $body = "" - ."\r\n--boundary_1\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."text body" - ."\r\n--boundary_1\r\n" - ."Content-Type: application/pdf\r\n" - ."Content-Transfer-Encoding: BASE64\r\n" - . "\r\n"; - while (<$input>) { - chomp; - $body .= $_ . "\r\n"; + # Start a dummy extractor server. + my %seenPath; + my $handler = sub { + my ($conn, $req) = @_; + if ($req->method eq 'HEAD') { + my $res = HTTP::Response->new(204); + $res->content(""); + $conn->send_response($res); + } elsif ($seenPath{ $req->uri->path }) { + my $res = HTTP::Response->new(200); + $res->header("Keep-Alive" => "timeout=1"); # Force client timeout + $res->content("dog cat bat"); + $conn->send_response($res); + } else { + $conn->send_error(404); + $seenPath{ $req->uri->path } = 1; } - $body .= "\r\n--boundary_1--\r\n"; - close $input or die "can't close $file: $!"; + }; + $instance->start_httpd($handler, $uri->port()); + + # Append an email with PDF attachment text "dog cat bat". + my $file = "data/dogcatbat.pdf.b64"; + open my $input, '<', $file or die "can't open $file: $!"; + my $body + = "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "text body" + . "\r\n--boundary_1\r\n" + . "Content-Type: application/pdf\r\n" + . "Content-Transfer-Encoding: BASE64\r\n" . "\r\n"; + while (<$input>) { + chomp; + $body .= $_ . "\r\n"; + } + $body .= "\r\n--boundary_1--\r\n"; + close $input or die "can't close $file: $!"; - $self->make_message("msg1", - mime_type => "multipart/related", - mime_boundary => "boundary_1", - body => $body - ) || die; + $self->make_message( + "msg1", + mime_type => "multipart/related", + mime_boundary => "boundary_1", + body => $body + ) || die; - # Run squatter - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v'); + # Run squatter + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-v'); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + ]; - # Test 0: query attachmentbody - my $filter = { attachmentBody => "cat" }; - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => $filter, - findMatchingParts => JSON::true, - }, "R1"], - ], $using); - my $emailIds = $res->[0][1]{ids}; - $self->assert_num_equals(1, scalar @{$emailIds}); - my $partIds = $res->[0][1]{partIds}; - $self->assert_not_null($partIds); + # Test 0: query attachmentbody + my $filter = { attachmentBody => "cat" }; + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => $filter, + findMatchingParts => JSON::true, + }, + "R1" + ], + ], + $using + ); + my $emailIds = $res->[0][1]{ids}; + $self->assert_num_equals(1, scalar @{$emailIds}); + my $partIds = $res->[0][1]{partIds}; + $self->assert_not_null($partIds); - # Test 1: pass partIds - $res = $jmap->CallMethods([['SearchSnippet/get', { - emailIds => $emailIds, - partIds => $partIds, - filter => $filter - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - my $snippet = $res->[0][1]->{list}[0]; - $self->assert_str_equals("dog cat bat", $snippet->{preview}); + # Test 1: pass partIds + $res = $jmap->CallMethods( + [ [ + 'SearchSnippet/get', + { + emailIds => $emailIds, + partIds => $partIds, + filter => $filter + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + my $snippet = $res->[0][1]->{list}[0]; + $self->assert_str_equals("dog cat bat", $snippet->{preview}); - # Test 2: pass null partids - $res = $jmap->CallMethods([['SearchSnippet/get', { - emailIds => $emailIds, - partIds => { - $emailIds->[0] => undef - }, - filter => $filter - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $snippet = $res->[0][1]->{list}[0]; - $self->assert_null($snippet->{preview}); + # Test 2: pass null partids + $res = $jmap->CallMethods( + [ [ + 'SearchSnippet/get', + { + emailIds => $emailIds, + partIds => { + $emailIds->[0] => undef + }, + filter => $filter + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $snippet = $res->[0][1]->{list}[0]; + $self->assert_null($snippet->{preview}); - # Sleep 1 sec to force Cyrus to timeout the client connection - sleep(1); + # Sleep 1 sec to force Cyrus to timeout the client connection + sleep(1); - # Test 3: pass no partids - $res = $jmap->CallMethods([['SearchSnippet/get', { - emailIds => $emailIds, - filter => $filter - }, "R1"]], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $snippet = $res->[0][1]->{list}[0]; - $self->assert_null($snippet->{preview}); + # Test 3: pass no partids + $res = $jmap->CallMethods( + [ [ + 'SearchSnippet/get', + { + emailIds => $emailIds, + filter => $filter + }, + "R1" + ] ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $snippet = $res->[0][1]->{list}[0]; + $self->assert_null($snippet->{preview}); - # Test 4: test null partids for header-only match - $filter = { - text => "msg1" - }; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => $filter, - findMatchingParts => JSON::true, - }, "R1"], - ], $using); - $emailIds = $res->[0][1]{ids}; - $self->assert_num_equals(1, scalar @{$emailIds}); - $partIds = $res->[0][1]{partIds}; - my $findMatchingParts = { - $emailIds->[0] => undef - }; - $self->assert_deep_equals($findMatchingParts, $partIds); + # Test 4: test null partids for header-only match + $filter = { text => "msg1" }; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => $filter, + findMatchingParts => JSON::true, + }, + "R1" + ], + ], + $using + ); + $emailIds = $res->[0][1]{ids}; + $self->assert_num_equals(1, scalar @{$emailIds}); + $partIds = $res->[0][1]{partIds}; + my $findMatchingParts = { $emailIds->[0] => undef }; + $self->assert_deep_equals($findMatchingParts, $partIds); - # Test 5: query text - $filter = { text => "cat" }; - $res = $jmap->CallMethods([ - ['Email/query', { - filter => $filter, - findMatchingParts => JSON::true, - }, "R1"], - ], $using); - $emailIds = $res->[0][1]{ids}; - $self->assert_num_equals(1, scalar @{$emailIds}); - $partIds = $res->[0][1]{partIds}; - $self->assert_not_null($partIds); + # Test 5: query text + $filter = { text => "cat" }; + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => $filter, + findMatchingParts => JSON::true, + }, + "R1" + ], + ], + $using + ); + $emailIds = $res->[0][1]{ids}; + $self->assert_num_equals(1, scalar @{$emailIds}); + $partIds = $res->[0][1]{partIds}; + $self->assert_not_null($partIds); } diff --git a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_attachments b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_attachments index 9d7d842de3..9b0bb0c921 100644 --- a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_attachments +++ b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_attachments @@ -2,33 +2,33 @@ use Cassandane::Tiny; sub test_searchsnippet_get_attachments - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :SearchAttachmentExtractor :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $instance = $self->{instance}; - - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); - - # Start a dummy extractor server. - my $handler = sub { - my ($conn, $req) = @_; - if ($req->method eq 'HEAD') { - my $res = HTTP::Response->new(204); - $res->content(""); - $conn->send_response($res); - } else { - my $res = HTTP::Response->new(200); - $res->header("Keep-Alive" => "timeout=1"); # Force client timeout - $res->content("attachment body"); - $conn->send_response($res); - } - }; - $instance->start_httpd($handler, $uri->port()); - - my $rawMessage = <<'EOF'; + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : SearchAttachmentExtractor : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $instance = $self->{instance}; + + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); + + # Start a dummy extractor server. + my $handler = sub { + my ($conn, $req) = @_; + if ($req->method eq 'HEAD') { + my $res = HTTP::Response->new(204); + $res->content(""); + $conn->send_response($res); + } else { + my $res = HTTP::Response->new(200); + $res->header("Keep-Alive" => "timeout=1"); # Force client timeout + $res->content("attachment body"); + $conn->send_response($res); + } + }; + $instance->start_httpd($handler, $uri->port()); + + my $rawMessage = <<'EOF'; From: To: to@local Reply-To: replyto@local @@ -59,126 +59,164 @@ ZGF0YQ== --6c3338934661485f87537c19b5f9d933-- EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; - - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - ]; - - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - text => 'December', - }, - findMatchingParts => JSON::true, - }, 'R1'], - ['SearchSnippet/get', { - '#emailIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - '#partIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/partIds', - }, - '#filter' => { - resultOf => 'R1', - name => 'Email/query', - path => '/filter', - }, - }, 'R2'], - ], $using); - - $self->assert_not_null($res->[1][1]{list}[0]); - $self->assert_null($res->[1][1]{list}[0]{preview}); - - my $matches = $res->[1][1]{list}[0]{attachments}; - $self->assert_num_equals(1, scalar keys %{$matches}); - $self->assert_not_null($matches->{3}{blobId}); - delete($matches->{3}{blobId}); - - $self->assert_deep_equals({ - 3 => { - name => 'December.pdf', - type => 'application/pdf', + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; + + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + ]; + + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + text => 'December', + }, + findMatchingParts => JSON::true, }, - }, $matches); - - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - text => 'body', - }, - findMatchingParts => JSON::true, - }, 'R1'], - ['SearchSnippet/get', { - '#emailIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - '#partIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/partIds', - }, - '#filter' => { - resultOf => 'R1', - name => 'Email/query', - path => '/filter', - }, - }, 'R2'], - ], $using); - - $self->assert_not_null($res->[1][1]{list}[0]); - $self->assert_not_null($res->[1][1]{list}[0]{preview}); - - $matches = $res->[1][1]{list}[0]{attachments}; - $self->assert_num_equals(2, scalar keys %{$matches}); - $self->assert_not_null($matches->{2}{blobId}); - delete($matches->{2}{blobId}); - $self->assert_not_null($matches->{3}{blobId}); - delete($matches->{3}{blobId}); - - $self->assert_deep_equals({ - 2 => { - name => 'November.jpg', - type => 'image/jpg', + 'R1' + ], + [ + 'SearchSnippet/get', + { + '#emailIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + '#partIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/partIds', + }, + '#filter' => { + resultOf => 'R1', + name => 'Email/query', + path => '/filter', + }, }, - 3 => { - name => 'December.pdf', - type => 'application/pdf', + 'R2' + ], + ], + $using + ); + + $self->assert_not_null($res->[1][1]{list}[0]); + $self->assert_null($res->[1][1]{list}[0]{preview}); + + my $matches = $res->[1][1]{list}[0]{attachments}; + $self->assert_num_equals(1, scalar keys %{$matches}); + $self->assert_not_null($matches->{3}{blobId}); + delete($matches->{3}{blobId}); + + $self->assert_deep_equals( + { + 3 => { + name => 'December.pdf', + type => 'application/pdf', + }, + }, + $matches + ); + + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + text => 'body', + }, + findMatchingParts => JSON::true, }, - }, $matches); - - $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - text => 'body', - }, - findMatchingParts => JSON::false, - }, 'R1'], - ['SearchSnippet/get', { - '#emailIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - '#filter' => { - resultOf => 'R1', - name => 'Email/query', - path => '/filter', - }, - }, 'R2'], - ], $using); - $self->assert_not_null($res->[1][1]{list}[0]); - $self->assert_null($res->[1][1]{list}[0]{attachments}); + 'R1' + ], + [ + 'SearchSnippet/get', + { + '#emailIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + '#partIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/partIds', + }, + '#filter' => { + resultOf => 'R1', + name => 'Email/query', + path => '/filter', + }, + }, + 'R2' + ], + ], + $using + ); + + $self->assert_not_null($res->[1][1]{list}[0]); + $self->assert_not_null($res->[1][1]{list}[0]{preview}); + + $matches = $res->[1][1]{list}[0]{attachments}; + $self->assert_num_equals(2, scalar keys %{$matches}); + $self->assert_not_null($matches->{2}{blobId}); + delete($matches->{2}{blobId}); + $self->assert_not_null($matches->{3}{blobId}); + delete($matches->{3}{blobId}); + + $self->assert_deep_equals( + { + 2 => { + name => 'November.jpg', + type => 'image/jpg', + }, + 3 => { + name => 'December.pdf', + type => 'application/pdf', + }, + }, + $matches + ); + + $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + text => 'body', + }, + findMatchingParts => JSON::false, + }, + 'R1' + ], + [ + 'SearchSnippet/get', + { + '#emailIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + '#filter' => { + resultOf => 'R1', + name => 'Email/query', + path => '/filter', + }, + }, + 'R2' + ], + ], + $using + ); + $self->assert_not_null($res->[1][1]{list}[0]); + $self->assert_null($res->[1][1]{list}[0]{attachments}); } diff --git a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_regression b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_regression index d24233c8df..e76e25997d 100644 --- a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_regression +++ b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_regression @@ -2,52 +2,58 @@ use Cassandane::Tiny; sub test_searchsnippet_get_regression - :min_version_3_1 :needs_component_sieve :needs_component_jmap - :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap + : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $body = "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; - $body .= "\r\n"; - $body .= "This is the lady plain text part."; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348\r\n"; - $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; - $body .= "\r\n"; - $body .= "

This is the lady html part.

"; - $body .= "\r\n"; - $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; - $self->make_message("lady subject", - mime_type => "multipart/alternative", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body - ) || die; + my $body = "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; + $body .= "\r\n"; + $body .= "This is the lady plain text part."; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348\r\n"; + $body .= "Content-Type: text/html;charset=\"UTF-8\"\r\n"; + $body .= "\r\n"; + $body .= "

This is the lady html part.

"; + $body .= "\r\n"; + $body .= "--047d7b33dd729737fe04d3bde348--\r\n"; + $self->make_message( + "lady subject", + mime_type => "multipart/alternative", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body + ) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $using = [ - 'https://cyrusimap.org/ns/jmap/performance', - 'https://cyrusimap.org/ns/jmap/debug', - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - ]; + my $using = [ + 'https://cyrusimap.org/ns/jmap/performance', + 'https://cyrusimap.org/ns/jmap/debug', + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + ]; - my $res = $jmap->CallMethods([ - ['Email/query', { filter => {text => "lady"}}, "R1"], - ], $using); - my $emailIds = $res->[0][1]{ids}; - my $partIds = $res->[0][1]{partIds}; + my $res = $jmap->CallMethods( + [ [ 'Email/query', { filter => { text => "lady" } }, "R1" ], ], $using); + my $emailIds = $res->[0][1]{ids}; + my $partIds = $res->[0][1]{partIds}; - $res = $jmap->CallMethods([ - ['SearchSnippet/get', { - emailIds => $emailIds, - filter => { text => "lady" }, - }, 'R2'], - ], $using); - $self->assert_num_equals(1, scalar @{$res->[0][1]{list}}); + $res = $jmap->CallMethods( + [ + [ + 'SearchSnippet/get', + { + emailIds => $emailIds, + filter => { text => "lady" }, + }, + 'R2' + ], + ], + $using + ); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_shared b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_shared index 059e4b4c5f..9b78338ddf 100644 --- a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_shared +++ b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_shared @@ -2,64 +2,70 @@ use Cassandane::Tiny; sub test_searchsnippet_get_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "create user and share mailboxes"; - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lr") or die; - $admintalk->create("user.foo.box1") or die; - $admintalk->setacl("user.foo.box1", "cassandane", "lr") or die; + xlog $self, "create user and share mailboxes"; + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lr") or die; + $admintalk->create("user.foo.box1") or die; + $admintalk->setacl("user.foo.box1", "cassandane", "lr") or die; - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => 'foo' }, "R1"]]); - my $inboxid = $res->[0][1]{list}[0]{id}; + my $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => 'foo' }, "R1" ] ]); + my $inboxid = $res->[0][1]{list}[0]{id}; - xlog $self, "create emails in shared account"; - $self->{adminstore}->set_folder('user.foo'); - my %params = ( - body => "A simple email", - ); - $res = $self->make_message("Email foo", %params, store => $self->{adminstore}) || die; - $self->{adminstore}->set_folder('user.foo.box1'); - %params = ( - body => "Another simple email", - ); - $res = $self->make_message("Email bar", %params, store => $self->{adminstore}) || die; + xlog $self, "create emails in shared account"; + $self->{adminstore}->set_folder('user.foo'); + my %params = (body => "A simple email",); + $res = $self->make_message("Email foo", %params, store => $self->{adminstore}) + || die; + $self->{adminstore}->set_folder('user.foo.box1'); + %params = (body => "Another simple email",); + $res = $self->make_message("Email bar", %params, store => $self->{adminstore}) + || die; - xlog $self, "run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "fetch email ids"; - $res = $jmap->CallMethods([ - ['Email/query', { accountId => 'foo' }, "R1"], - ['Email/get', { - accountId => 'foo', - '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } - }, 'R2' ], - ]); + xlog $self, "fetch email ids"; + $res = $jmap->CallMethods([ + [ 'Email/query', { accountId => 'foo' }, "R1" ], + [ + 'Email/get', + { + accountId => 'foo', + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } + }, + 'R2' + ], + ]); - my %m = map { $_->{subject} => $_ } @{$res->[1][1]{list}}; - my $foo = $m{"Email foo"}->{id}; - my $bar = $m{"Email bar"}->{id}; - $self->assert_not_null($foo); - $self->assert_not_null($bar); + my %m = map { $_->{subject} => $_ } @{ $res->[1][1]{list} }; + my $foo = $m{"Email foo"}->{id}; + my $bar = $m{"Email bar"}->{id}; + $self->assert_not_null($foo); + $self->assert_not_null($bar); - xlog $self, "remove read rights for mailbox containing email $bar"; - $admintalk->setacl("user.foo.box1", "cassandane", "") or die; + xlog $self, "remove read rights for mailbox containing email $bar"; + $admintalk->setacl("user.foo.box1", "cassandane", "") or die; - xlog $self, "fetch snippets"; - $res = $jmap->CallMethods([['SearchSnippet/get', { - accountId => 'foo', - emailIds => [ $foo, $bar ], - filter => { text => "simple" }, - }, "R1"]]); - $self->assert_str_equals($foo, $res->[0][1]->{list}[0]{emailId}); - $self->assert_str_equals($bar, $res->[0][1]->{notFound}[0]); + xlog $self, "fetch snippets"; + $res = $jmap->CallMethods([ [ + 'SearchSnippet/get', + { + accountId => 'foo', + emailIds => [ $foo, $bar ], + filter => { text => "simple" }, + }, + "R1" + ] ]); + $self->assert_str_equals($foo, $res->[0][1]->{list}[0]{emailId}); + $self->assert_str_equals($bar, $res->[0][1]->{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_text_rtf b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_text_rtf index d0597ef84a..013436020f 100644 --- a/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_text_rtf +++ b/cassandane/tiny-tests/JMAPEmail/searchsnippet_get_text_rtf @@ -2,42 +2,42 @@ use Cassandane::Tiny; sub test_searchsnippet_get_text_rtf - :min_version_3_4 :needs_component_jmap :JMAPExtensions - :SearchAttachmentExtractor -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $instance = $self->{instance}; + : min_version_3_4 : needs_component_jmap : JMAPExtensions + : SearchAttachmentExtractor { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $instance = $self->{instance}; - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); - # Start a dummy extractor server. - my $handler = sub { - my ($conn, $req) = @_; - if ($req->method eq 'HEAD') { - my $res = HTTP::Response->new(204); - $res->content(""); - $conn->send_response($res); - } else { - my $res = HTTP::Response->new(200); - $res->header("Keep-Alive" => "timeout=1"); # Force client timeout - $res->content("This is an RTF attachment with formatting."); - $conn->send_response($res); - } - }; - $instance->start_httpd($handler, $uri->port()); + # Start a dummy extractor server. + my $handler = sub { + my ($conn, $req) = @_; + if ($req->method eq 'HEAD') { + my $res = HTTP::Response->new(204); + $res->content(""); + $conn->send_response($res); + } else { + my $res = HTTP::Response->new(200); + $res->header("Keep-Alive" => "timeout=1"); # Force client timeout + $res->content("This is an RTF attachment with formatting."); + $conn->send_response($res); + } + }; + $instance->start_httpd($handler, $uri->port()); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:submission', - 'https://cyrusimap.org/ns/jmap/mail', - 'https://cyrusimap.org/ns/jmap/debug', - 'https://cyrusimap.org/ns/jmap/performance', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:submission', + 'https://cyrusimap.org/ns/jmap/mail', + 'https://cyrusimap.org/ns/jmap/debug', + 'https://cyrusimap.org/ns/jmap/performance', + ]; - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: from@local To: to@local Subject: test @@ -104,37 +104,49 @@ XGxvY2gKLn0KXHBhciB9 test EOF - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - text => 'formatting', - }, - findMatchingParts => JSON::true, - }, 'R1'], - ['SearchSnippet/get', { - '#emailIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids', - }, - '#filter' => { - resultOf => 'R1', - name => 'Email/query', - path => '/filter', - }, - '#partIds' => { - resultOf => 'R1', - name => 'Email/query', - path => '/partIds', - }, - }, 'R2'], - ], $using); + my $res = $jmap->CallMethods( + [ + [ + 'Email/query', + { + filter => { + text => 'formatting', + }, + findMatchingParts => JSON::true, + }, + 'R1' + ], + [ + 'SearchSnippet/get', + { + '#emailIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids', + }, + '#filter' => { + resultOf => 'R1', + name => 'Email/query', + path => '/filter', + }, + '#partIds' => { + resultOf => 'R1', + name => 'Email/query', + path => '/partIds', + }, + }, + 'R2' + ], + ], + $using + ); - $self->assert_str_equals('This is an RTF attachment with formatting.', - $res->[1][1]{list}[0]{preview}); + $self->assert_str_equals( + 'This is an RTF attachment with formatting.', + $res->[1][1]{list}[0]{preview}); } diff --git a/cassandane/tiny-tests/JMAPEmail/searchsnippet_search_maxsize b/cassandane/tiny-tests/JMAPEmail/searchsnippet_search_maxsize index 6785fe7c95..d7425afc83 100644 --- a/cassandane/tiny-tests/JMAPEmail/searchsnippet_search_maxsize +++ b/cassandane/tiny-tests/JMAPEmail/searchsnippet_search_maxsize @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_searchsnippet_search_maxsize - :min_version_3_5 :needs_component_sieve :needs_component_jmap - :JMAPExtensions :SearchMaxSize4k -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_sieve : needs_component_jmap + : JMAPExtensions : SearchMaxSize4k { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $rawMessage = <<'EOF'; + my $rawMessage = <<'EOF'; From: from@local To: to@local Subject: test @@ -19,63 +18,79 @@ Content-Type: text/plain; charset="UTF-8" EOF - xlog "Index overlong text"; - my $kbody = "xxx\n" x 1023; - $kbody .= "foo\n"; # last line of included text - $kbody .= "bar\n"; # first line of excluded text - $rawMessage .= $kbody; - $rawMessage =~ s/\r?\n/\r\n/gs; - $imap->append('INBOX', $rawMessage) || die $@; + xlog "Index overlong text"; + my $kbody = "xxx\n" x 1023; + $kbody .= "foo\n"; # last line of included text + $kbody .= "bar\n"; # first line of excluded text + $rawMessage .= $kbody; + $rawMessage =~ s/\r?\n/\r\n/gs; + $imap->append('INBOX', $rawMessage) || die $@; - xlog "Assert indexer only processes maxsize bytes of text"; - $self->{instance}->getsyslog(); # clear syslog - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - my @lines = $self->{instance}->getsyslog(qr/Xapian: truncating/); - $self->assert_num_equals(1, scalar @lines); + xlog "Assert indexer only processes maxsize bytes of text"; + $self->{instance}->getsyslog(); # clear syslog + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + my @lines = $self->{instance}->getsyslog(qr/Xapian: truncating/); + $self->assert_num_equals(1, scalar @lines); - my $res = $jmap->CallMethods([ - ['Email/query', { - filter => { - body => 'foo', - }, - }, "R1"], - ['Email/query', { - filter => { - body => 'bar', - }, - }, "R2"], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_num_equals(0, scalar @{$res->[1][1]{ids}}); - my $emailId = $res->[0][1]{ids}[0]; + my $res = $jmap->CallMethods([ + [ + 'Email/query', + { + filter => { + body => 'foo', + }, + }, + "R1" + ], + [ + 'Email/query', + { + filter => { + body => 'bar', + }, + }, + "R2" + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_num_equals(0, scalar @{ $res->[1][1]{ids} }); + my $emailId = $res->[0][1]{ids}[0]; - # Note: test assumes Cyrus charset buffer to flush every 4096 bytes + # Note: test assumes Cyrus charset buffer to flush every 4096 bytes - xlog "Assert snippet generator only processes maxsize bytes of text"; - $self->{instance}->getsyslog(); # clear syslog - $res = $jmap->CallMethods([ - ['SearchSnippet/get', { - emailIds => [ $emailId ], - filter => { - body => 'foo', - }, - }, 'R3'], - ]); - $self->assert_not_null($res->[0][1]{list}[0]{preview}); - @lines = $self->{instance}->getsyslog(qr/Xapian: truncating/); - $self->assert_num_equals(1, scalar @lines); + xlog "Assert snippet generator only processes maxsize bytes of text"; + $self->{instance}->getsyslog(); # clear syslog + $res = $jmap->CallMethods([ + [ + 'SearchSnippet/get', + { + emailIds => [$emailId], + filter => { + body => 'foo', + }, + }, + 'R3' + ], + ]); + $self->assert_not_null($res->[0][1]{list}[0]{preview}); + @lines = $self->{instance}->getsyslog(qr/Xapian: truncating/); + $self->assert_num_equals(1, scalar @lines); - xlog "Assert snippet generator only processes maxsize bytes of text"; - $self->{instance}->getsyslog(); # clear syslog - $res = $jmap->CallMethods([ - ['SearchSnippet/get', { - emailIds => [ $emailId ], - filter => { - body => 'bar', - }, - }, 'R3'], - ]); - $self->assert_null($res->[0][1]{list}[0]{preview}); - @lines = $self->{instance}->getsyslog(qr/Xapian: truncating/); - $self->assert_num_equals(1, scalar @lines); + xlog "Assert snippet generator only processes maxsize bytes of text"; + $self->{instance}->getsyslog(); # clear syslog + $res = $jmap->CallMethods([ + [ + 'SearchSnippet/get', + { + emailIds => [$emailId], + filter => { + body => 'bar', + }, + }, + 'R3' + ], + ]); + $self->assert_null($res->[0][1]{list}[0]{preview}); + @lines = $self->{instance}->getsyslog(qr/Xapian: truncating/); + $self->assert_num_equals(1, scalar @lines); } diff --git a/cassandane/tiny-tests/JMAPEmail/thread_changes b/cassandane/tiny-tests/JMAPEmail/thread_changes index 5c5840a06b..edcf54d57a 100644 --- a/cassandane/tiny-tests/JMAPEmail/thread_changes +++ b/cassandane/tiny-tests/JMAPEmail/thread_changes @@ -2,231 +2,288 @@ use Cassandane::Tiny; sub test_thread_changes - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; - my %params; - my $dt; - my $draftsmbox; - my $state; - my $threadA; - my $threadB; - - my $imaptalk = $self->{store}->get_client(); - - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $draftsmbox = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsmbox); - - xlog $self, "Generate an email in drafts via IMAP"; - $self->{store}->set_folder("INBOX.drafts"); - $self->make_message("Email A") || die; - - xlog $self, "get thread state"; - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, 'R2' ], - ]); - $res = $jmap->CallMethods([ - ['Thread/get', { 'ids' => [ $res->[1][1]{list}[0]{threadId} ] }, 'R1'], - ]); - $state = $res->[0][1]->{state}; - $self->assert_not_null($state); - - xlog $self, "get thread updates"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - - xlog $self, "generating email A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -3)); - $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - - xlog $self, "get thread updates"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - $threadA = $res->[0][1]{created}[0]; - - xlog $self, "generating email C referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -2)); - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "c"); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "get thread updates"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($threadA, $res->[0][1]{updated}[0]); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - - xlog $self, "get thread updates (expect no changes)"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B", body => "b"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - - xlog $self, "generating email D referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -1)); - $exp{D} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "d"); - $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "generating email E referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(minutes => -30)); - $exp{E} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "e"); - $exp{E}->set_attributes(uid => 5, cid => $exp{A}->get_attribute('cid')); - - xlog $self, "get max 1 thread updates"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state, maxChanges => 1 }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::true, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_str_not_equals($threadA, $res->[0][1]{created}[0]); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - $threadB = $res->[0][1]{created}[0]; - - xlog $self, "get max 2 thread updates"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state, maxChanges => 2 }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($threadA, $res->[0][1]{updated}[0]); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - - xlog $self, "fetch emails"; - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]); - - # Map messages by body contents - my %m = map { $_->{bodyValues}{$_->{textBody}[0]{partId}}{value} => $_ } @{$res->[1][1]{list}}; - my $msgA = $m{"a"}; - my $msgB = $m{"b"}; - my $msgC = $m{"c"}; - my $msgD = $m{"d"}; - my $msgE = $m{"e"}; - $self->assert_not_null($msgA); - $self->assert_not_null($msgB); - $self->assert_not_null($msgC); - $self->assert_not_null($msgD); - $self->assert_not_null($msgE); - - xlog $self, "destroy email b, update email d"; - $res = $jmap->CallMethods([['Email/set', { - destroy => [ $msgB->{id} ], - update => { $msgD->{id} => { 'keywords/$foo' => JSON::true }}, - }, "R1"]]); - $self->assert_str_equals($msgB->{id}, $res->[0][1]{destroyed}[0]); - $self->assert(exists $res->[0][1]->{updated}{$msgD->{id}}); - - xlog $self, "get thread updates"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($threadA, $res->[0][1]{updated}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($threadB, $res->[0][1]{destroyed}[0]); - $state = $res->[0][1]->{newState}; - - xlog $self, "destroy emails c and e"; - $res = $jmap->CallMethods([['Email/set', { - destroy => [ $msgC->{id}, $msgE->{id} ], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get thread updates, fetch threads"; - $res = $jmap->CallMethods([ - ['Thread/changes', { sinceState => $state }, "R1"], - ['Thread/get', { '#ids' => { resultOf => 'R1', name => 'Thread/changes', path => '/updated' }}, 'R2'], - ]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($threadA, $res->[0][1]{updated}[0]); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]->{newState}; - - $self->assert_str_equals('Thread/get', $res->[1][0]); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_str_equals($threadA, $res->[1][1]{list}[0]->{id}); - - xlog $self, "destroy emails a and d"; - $res = $jmap->CallMethods([['Email/set', { - destroy => [ $msgA->{id}, $msgD->{id} ], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "get thread updates"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($threadA, $res->[0][1]{destroyed}[0]); - $state = $res->[0][1]->{newState}; - - xlog $self, "get thread updates (expect no changes)"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; + my %params; + my $dt; + my $draftsmbox; + my $state; + my $threadA; + my $threadB; + + my $imaptalk = $self->{store}->get_client(); + + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $draftsmbox = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftsmbox); + + xlog $self, "Generate an email in drafts via IMAP"; + $self->{store}->set_folder("INBOX.drafts"); + $self->make_message("Email A") || die; + + xlog $self, "get thread state"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' } }, + 'R2' + ], + ]); + $res = $jmap->CallMethods([ + [ 'Thread/get', { 'ids' => [ $res->[1][1]{list}[0]{threadId} ] }, 'R1' ], + ]); + $state = $res->[0][1]->{state}; + $self->assert_not_null($state); + + xlog $self, "get thread updates"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + + xlog $self, "generating email A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -3)); + $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + + xlog $self, "get thread updates"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + $threadA = $res->[0][1]{created}[0]; + + xlog $self, "generating email C referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -2)); + $exp{C} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "c" + ); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "get thread updates"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($threadA, $res->[0][1]{updated}[0]); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + + xlog $self, "get thread updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B", body => "b"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + + xlog $self, "generating email D referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -1)); + $exp{D} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "d" + ); + $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "generating email E referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(minutes => -30)); + $exp{E} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "e" + ); + $exp{E}->set_attributes(uid => 5, cid => $exp{A}->get_attribute('cid')); + + xlog $self, "get max 1 thread updates"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state, maxChanges => 1 }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::true, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_str_not_equals($threadA, $res->[0][1]{created}[0]); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + $threadB = $res->[0][1]{created}[0]; + + xlog $self, "get max 2 thread updates"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state, maxChanges => 2 }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($threadA, $res->[0][1]{updated}[0]); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + + xlog $self, "fetch emails"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); + + # Map messages by body contents + my %m = map { $_->{bodyValues}{ $_->{textBody}[0]{partId} }{value} => $_ } + @{ $res->[1][1]{list} }; + my $msgA = $m{"a"}; + my $msgB = $m{"b"}; + my $msgC = $m{"c"}; + my $msgD = $m{"d"}; + my $msgE = $m{"e"}; + $self->assert_not_null($msgA); + $self->assert_not_null($msgB); + $self->assert_not_null($msgC); + $self->assert_not_null($msgD); + $self->assert_not_null($msgE); + + xlog $self, "destroy email b, update email d"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + destroy => [ $msgB->{id} ], + update => { $msgD->{id} => { 'keywords/$foo' => JSON::true } }, + }, + "R1" + ] ]); + $self->assert_str_equals($msgB->{id}, $res->[0][1]{destroyed}[0]); + $self->assert(exists $res->[0][1]->{updated}{ $msgD->{id} }); + + xlog $self, "get thread updates"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($threadA, $res->[0][1]{updated}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($threadB, $res->[0][1]{destroyed}[0]); + $state = $res->[0][1]->{newState}; + + xlog $self, "destroy emails c and e"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + destroy => [ $msgC->{id}, $msgE->{id} ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get thread updates, fetch threads"; + $res = $jmap->CallMethods([ + [ 'Thread/changes', { sinceState => $state }, "R1" ], + [ + 'Thread/get', + { + '#ids' => + { resultOf => 'R1', name => 'Thread/changes', path => '/updated' } + }, + 'R2' + ], + ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($threadA, $res->[0][1]{updated}[0]); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]->{newState}; + + $self->assert_str_equals('Thread/get', $res->[1][0]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_str_equals($threadA, $res->[1][1]{list}[0]->{id}); + + xlog $self, "destroy emails a and d"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + destroy => [ $msgA->{id}, $msgD->{id} ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "get thread updates"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($threadA, $res->[0][1]{destroyed}[0]); + $state = $res->[0][1]->{newState}; + + xlog $self, "get thread updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); } diff --git a/cassandane/tiny-tests/JMAPEmail/thread_get b/cassandane/tiny-tests/JMAPEmail/thread_get index 987eb58f95..d7596e238e 100644 --- a/cassandane/tiny-tests/JMAPEmail/thread_get +++ b/cassandane/tiny-tests/JMAPEmail/thread_get @@ -2,108 +2,136 @@ use Cassandane::Tiny; sub test_thread_get - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; - my %params; - my $dt; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; + my %params; + my $dt; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $drafts = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($drafts); + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + my $drafts = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($drafts); - xlog $self, "generating email A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -3)); - $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); - $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); + xlog $self, "generating email A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -3)); + $exp{A} = $self->make_message("Email A", date => $dt, body => "a"); + $exp{A}->set_attributes(uid => 1, cid => $exp{A}->make_cid()); - xlog $self, "generating email B"; - $exp{B} = $self->make_message("Email B", body => "b"); - $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); + xlog $self, "generating email B"; + $exp{B} = $self->make_message("Email B", body => "b"); + $exp{B}->set_attributes(uid => 2, cid => $exp{B}->make_cid()); - xlog $self, "generating email C referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -2)); - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "c"); - $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); + xlog $self, "generating email C referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -2)); + $exp{C} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "c" + ); + $exp{C}->set_attributes(uid => 3, cid => $exp{A}->get_attribute('cid')); - xlog $self, "generating email D referencing A"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -1)); - $exp{D} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "d"); - $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); + xlog $self, "generating email D referencing A"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -1)); + $exp{D} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "d" + ); + $exp{D}->set_attributes(uid => 4, cid => $exp{A}->get_attribute('cid')); - xlog $self, "fetch emails"; - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]); + xlog $self, "fetch emails"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); - # Map messages by body contents - my %m = map { $_->{bodyValues}{$_->{textBody}[0]{partId}}{value} => $_ } @{$res->[1][1]{list}}; - my $msgA = $m{"a"}; - my $msgB = $m{"b"}; - my $msgC = $m{"c"}; - my $msgD = $m{"d"}; - $self->assert_not_null($msgA); - $self->assert_not_null($msgB); - $self->assert_not_null($msgC); - $self->assert_not_null($msgD); + # Map messages by body contents + my %m = map { $_->{bodyValues}{ $_->{textBody}[0]{partId} }{value} => $_ } + @{ $res->[1][1]{list} }; + my $msgA = $m{"a"}; + my $msgB = $m{"b"}; + my $msgC = $m{"c"}; + my $msgD = $m{"d"}; + $self->assert_not_null($msgA); + $self->assert_not_null($msgB); + $self->assert_not_null($msgC); + $self->assert_not_null($msgD); - %m = map { $_->{threadId} => 1 } @{$res->[1][1]{list}}; - my @threadids = keys %m; + %m = map { $_->{threadId} => 1 } @{ $res->[1][1]{list} }; + my @threadids = keys %m; - xlog $self, "create draft replying to email A"; - $res = $jmap->CallMethods( - [[ 'Email/set', { create => { "1" => { - mailboxIds => {$drafts => JSON::true}, - inReplyTo => $msgA->{messageId}, - from => [ { name => "", email => "sam\@acme.local" } ], - to => [ { name => "", email => "bugs\@acme.local" } ], - subject => "Re: Email A", - textBody => [{ partId => '1' }], - bodyValues => { 1 => { value => "I'm givin' ya one last chance ta surrenda!" }}, - keywords => { '$draft' => JSON::true }, - }}}, "R1" ]]); - my $draftid = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftid); + xlog $self, "create draft replying to email A"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + create => { + "1" => { + mailboxIds => { $drafts => JSON::true }, + inReplyTo => $msgA->{messageId}, + from => [ { name => "", email => "sam\@acme.local" } ], + to => [ { name => "", email => "bugs\@acme.local" } ], + subject => "Re: Email A", + textBody => [ { partId => '1' } ], + bodyValues => + { 1 => { value => "I'm givin' ya one last chance ta surrenda!" } }, + keywords => { '$draft' => JSON::true }, + } + } + }, + "R1" + ] ]); + my $draftid = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftid); - xlog $self, "get threads"; - $res = $jmap->CallMethods([['Thread/get', { ids => \@threadids }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); + xlog $self, "get threads"; + $res = $jmap->CallMethods([ [ 'Thread/get', { ids => \@threadids }, "R1" ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); - %m = map { $_->{id} => $_ } @{$res->[0][1]{list}}; - my $threadA = $m{$msgA->{threadId}}; - my $threadB = $m{$msgB->{threadId}}; + %m = map { $_->{id} => $_ } @{ $res->[0][1]{list} }; + my $threadA = $m{ $msgA->{threadId} }; + my $threadB = $m{ $msgB->{threadId} }; - # Assert all emails are listed - $self->assert_num_equals(4, scalar @{$threadA->{emailIds}}); - $self->assert_num_equals(1, scalar @{$threadB->{emailIds}}); + # Assert all emails are listed + $self->assert_num_equals(4, scalar @{ $threadA->{emailIds} }); + $self->assert_num_equals(1, scalar @{ $threadB->{emailIds} }); - # Assert sort order by date - $self->assert_str_equals($msgA->{id}, $threadA->{emailIds}[0]); - $self->assert_str_equals($msgC->{id}, $threadA->{emailIds}[1]); - $self->assert_str_equals($msgD->{id}, $threadA->{emailIds}[2]); - $self->assert_str_equals($draftid, $threadA->{emailIds}[3]); + # Assert sort order by date + $self->assert_str_equals($msgA->{id}, $threadA->{emailIds}[0]); + $self->assert_str_equals($msgC->{id}, $threadA->{emailIds}[1]); + $self->assert_str_equals($msgD->{id}, $threadA->{emailIds}[2]); + $self->assert_str_equals($draftid, $threadA->{emailIds}[3]); } diff --git a/cassandane/tiny-tests/JMAPEmail/thread_get_onemsg b/cassandane/tiny-tests/JMAPEmail/thread_get_onemsg index 0da885051c..76f6d386cd 100644 --- a/cassandane/tiny-tests/JMAPEmail/thread_get_onemsg +++ b/cassandane/tiny-tests/JMAPEmail/thread_get_onemsg @@ -2,36 +2,41 @@ use Cassandane::Tiny; sub test_thread_get_onemsg - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $jmap = $self->{jmap}; - my $res; - my $draftsmbox; - my $state; - my $threadA; - my $threadB; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $jmap = $self->{jmap}; + my $res; + my $draftsmbox; + my $state; + my $threadA; + my $threadB; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $draftsmbox = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsmbox); + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $draftsmbox = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftsmbox); - xlog $self, "get thread state"; - $res = $jmap->CallMethods([['Thread/get', { ids => [ 'no' ] }, "R1"]]); - $state = $res->[0][1]->{state}; - $self->assert_not_null($state); + xlog $self, "get thread state"; + $res = $jmap->CallMethods([ [ 'Thread/get', { ids => ['no'] }, "R1" ] ]); + $state = $res->[0][1]->{state}; + $self->assert_not_null($state); - my $email = <<'EOF'; + my $email = <<'EOF'; Return-Path: Received: from gateway (gateway.vmtom.com [10.0.0.1]) by ahost (ahost.vmtom.com[10.0.0.2]); Wed, 07 Dec 2016 11:43:25 +1100 @@ -49,23 +54,28 @@ X-Cassandane-Unique: 294f71c341218d36d4bda75aad56599b7be3d15b a EOF - $email =~ s/\r?\n/\r\n/gs; - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; - xlog $self, "import email from blob $blobid"; - $res = $jmap->CallMethods([['Email/import', { - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$draftsmbox => JSON::true}, - keywords => { - '$draft' => JSON::true, - }, - }, + $email =~ s/\r?\n/\r\n/gs; + my $data = $jmap->Upload($email, "message/rfc822"); + my $blobid = $data->{blobId}; + xlog $self, "import email from blob $blobid"; + $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobid, + mailboxIds => { $draftsmbox => JSON::true }, + keywords => { + '$draft' => JSON::true, + }, }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - xlog $self, "get thread updates"; - $res = $jmap->CallMethods([['Thread/changes', { sinceState => $state }, "R1"]]); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + xlog $self, "get thread updates"; + $res = $jmap->CallMethods( + [ [ 'Thread/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); } diff --git a/cassandane/tiny-tests/JMAPEmail/thread_get_shared b/cassandane/tiny-tests/JMAPEmail/thread_get_shared index 90610193dc..3d6f85dea1 100644 --- a/cassandane/tiny-tests/JMAPEmail/thread_get_shared +++ b/cassandane/tiny-tests/JMAPEmail/thread_get_shared @@ -2,71 +2,85 @@ use Cassandane::Tiny; sub test_thread_get_shared - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Create user and share mailbox A but not B - xlog $self, "Create shared mailbox"; - $self->{instance}->create_user("other"); - $admintalk->create("user.other.A") or die; - $admintalk->setacl("user.other.A", "cassandane", "lr") or die; - $admintalk->create("user.other.B") or die; + # Create user and share mailbox A but not B + xlog $self, "Create shared mailbox"; + $self->{instance}->create_user("other"); + $admintalk->create("user.other.A") or die; + $admintalk->setacl("user.other.A", "cassandane", "lr") or die; + $admintalk->create("user.other.B") or die; - # Create message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - my $msg1 = $self->make_message("EmailA", store => $self->{adminstore}) or die; + # Create message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + my $msg1 = $self->make_message("EmailA", store => $self->{adminstore}) or die; - # move the message to mailbox B - $admintalk->select("user.other.A"); - $admintalk->move("1:*", "user.other.B"); + # move the message to mailbox B + $admintalk->select("user.other.A"); + $admintalk->move("1:*", "user.other.B"); - # Reply-to message in mailbox A - $self->{adminstore}->set_folder('user.other.A'); - my $msg2 = $self->make_message("Re: EmailA", ( - references => [ $msg1 ], - store => $self->{adminstore}, - )) or die; + # Reply-to message in mailbox A + $self->{adminstore}->set_folder('user.other.A'); + my $msg2 = $self->make_message( + "Re: EmailA", + ( + references => [$msg1], + store => $self->{adminstore}, + ) + ) or die; - my @fetchThreadMethods = [ - ['Email/query', { - accountId => 'other', - collapseThreads => JSON::true, - }, "R1"], - ['Email/get', { - accountId => 'other', - properties => ['threadId'], - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ['Thread/get', { - accountId => 'other', - '#ids' => { - resultOf => 'R2', - name => 'Email/get', - path => '/list/*/threadId' - }, - }, 'R3' ], - ]; + my @fetchThreadMethods = [ + [ + 'Email/query', + { + accountId => 'other', + collapseThreads => JSON::true, + }, + "R1" + ], + [ + 'Email/get', + { + accountId => 'other', + properties => ['threadId'], + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + [ + 'Thread/get', + { + accountId => 'other', + '#ids' => { + resultOf => 'R2', + name => 'Email/get', + path => '/list/*/threadId' + }, + }, + 'R3' + ], + ]; - # Fetch Thread - my $res = $jmap->CallMethods(@fetchThreadMethods); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_num_equals(1, scalar @{$res->[2][1]{list}[0]{emailIds}}); + # Fetch Thread + my $res = $jmap->CallMethods(@fetchThreadMethods); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_num_equals(1, scalar @{ $res->[2][1]{list}[0]{emailIds} }); - # Now share mailbox B - $admintalk->setacl("user.other.B", "cassandane", "lr") or die; - $res = $jmap->CallMethods(@fetchThreadMethods); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_num_equals(2, scalar @{$res->[2][1]{list}[0]{emailIds}}); + # Now share mailbox B + $admintalk->setacl("user.other.B", "cassandane", "lr") or die; + $res = $jmap->CallMethods(@fetchThreadMethods); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_num_equals(2, scalar @{ $res->[2][1]{list}[0]{emailIds} }); } diff --git a/cassandane/tiny-tests/JMAPEmail/thread_latearrival_drafts b/cassandane/tiny-tests/JMAPEmail/thread_latearrival_drafts index 45c9a28372..0a45d19ce9 100644 --- a/cassandane/tiny-tests/JMAPEmail/thread_latearrival_drafts +++ b/cassandane/tiny-tests/JMAPEmail/thread_latearrival_drafts @@ -2,142 +2,195 @@ use Cassandane::Tiny; sub test_thread_latearrival_drafts - :min_version_3_1 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; - my %exp; - my $dt; - my $res; - my $state; - - my $jmap = $self->{jmap}; - - my $imaptalk = $self->{store}->get_client(); - - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -8)); - $exp{A} = $self->make_message("Email A", date => $dt, body => 'a') || die; - - xlog $self, "get thread state"; - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, properties => ['threadId'] }, 'R2' ], - ['Thread/get', { '#ids' => { resultOf => 'R2', name => 'Email/get', path => '/list/*/threadId' } }, 'R3'], - ]); - $state = $res->[2][1]{state}; - $self->assert_not_null($state); - my $threadid = $res->[2][1]{list}[0]{id}; - $self->assert_not_null($threadid); - - my $inreplyheader = [['In-Reply-To' => $exp{A}->messageid()]]; - - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsmbox); - - xlog $self, "generating email B"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -5)); - $exp{B} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "b"); - - xlog $self, "generating email C"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -2)); - $exp{C} = $self->make_message("Re: Email A", references => [ $exp{A}, $exp{B} ], date => $dt, body => "c"); - - xlog $self, "generating email D (before C)"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -3)); - $exp{D} = $self->make_message("Re: Email A", extra_headers => $inreplyheader, date => $dt, body => "d"); - - xlog $self, "Generate draft email E replying to A"; - $self->{store}->set_folder("INBOX.drafts"); - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -4)); - $exp{E} = $self->{gen}->generate(subject => "Re: Email A", extra_headers => $inreplyheader, date => $dt, body => "e"); - $self->{store}->write_begin(); - $self->{store}->write_message($exp{E}, flags => ["\\Draft"]); - $self->{store}->write_end(); - - xlog $self, "fetch emails"; - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]); - - # Map messages by body contents - my %m = map { $_->{bodyValues}{$_->{textBody}[0]{partId}}{value} => $_ } @{$res->[1][1]{list}}; - my $msgA = $m{"a"}; - my $msgB = $m{"b"}; - my $msgC = $m{"c"}; - my $msgD = $m{"d"}; - my $msgE = $m{"e"}; - $self->assert_not_null($msgA); - $self->assert_not_null($msgB); - $self->assert_not_null($msgC); - $self->assert_not_null($msgD); - $self->assert_not_null($msgE); - - my %map = ( - A => $msgA->{id}, - B => $msgB->{id}, - C => $msgC->{id}, - D => $msgD->{id}, - E => $msgE->{id}, - ); - - # check thread ordering - $res = $jmap->CallMethods([ - ['Thread/get', { 'ids' => [$threadid] }, 'R3'], - ]); - $self->assert_deep_equals([$map{A},$map{B},$map{E},$map{D},$map{C}], - $res->[0][1]{list}[0]{emailIds}); - - # now deliver something late that's earlier than the draft - - xlog $self, "generating email F (late arrival)"; - $dt = DateTime->now(); - $dt->add(DateTime::Duration->new(hours => -6)); - $exp{F} = $self->make_message("Re: Email A", references => [ $exp{A} ], date => $dt, body => "f"); - - xlog $self, "fetch emails"; - $res = $jmap->CallMethods([ - ['Email/query', { }, "R1"], - ['Email/get', { - '#ids' => { - resultOf => 'R1', - name => 'Email/query', - path => '/ids' - }, - fetchAllBodyValues => JSON::true, - }, 'R2' ], - ]); - - # Map messages by body contents - %m = map { $_->{bodyValues}{$_->{textBody}[0]{partId}}{value} => $_ } @{$res->[1][1]{list}}; - my $msgF = $m{"f"}; - $self->assert_not_null($msgF); - - $map{F} = $msgF->{id}; - - # check thread ordering - this message should appear after F and before B - $res = $jmap->CallMethods([ - ['Thread/get', { 'ids' => [$threadid] }, 'R3'], - ]); - $self->assert_deep_equals([$map{A},$map{F},$map{B},$map{E},$map{D},$map{C}], - $res->[0][1]{list}[0]{emailIds}); + : min_version_3_1 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; + my %exp; + my $dt; + my $res; + my $state; + + my $jmap = $self->{jmap}; + + my $imaptalk = $self->{store}->get_client(); + + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -8)); + $exp{A} = $self->make_message("Email A", date => $dt, body => 'a') || die; + + xlog $self, "get thread state"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { resultOf => 'R1', name => 'Email/query', path => '/ids' }, + properties => ['threadId'] + }, + 'R2' + ], + [ + 'Thread/get', + { + '#ids' => + { resultOf => 'R2', name => 'Email/get', path => '/list/*/threadId' } + }, + 'R3' + ], + ]); + $state = $res->[2][1]{state}; + $self->assert_not_null($state); + my $threadid = $res->[2][1]{list}[0]{id}; + $self->assert_not_null($threadid); + + my $inreplyheader = [ [ 'In-Reply-To' => $exp{A}->messageid() ] ]; + + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + my $draftsmbox = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($draftsmbox); + + xlog $self, "generating email B"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -5)); + $exp{B} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "b" + ); + + xlog $self, "generating email C"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -2)); + $exp{C} = $self->make_message( + "Re: Email A", + references => [ $exp{A}, $exp{B} ], + date => $dt, + body => "c" + ); + + xlog $self, "generating email D (before C)"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -3)); + $exp{D} = $self->make_message( + "Re: Email A", + extra_headers => $inreplyheader, + date => $dt, + body => "d" + ); + + xlog $self, "Generate draft email E replying to A"; + $self->{store}->set_folder("INBOX.drafts"); + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -4)); + $exp{E} = $self->{gen}->generate( + subject => "Re: Email A", + extra_headers => $inreplyheader, + date => $dt, + body => "e" + ); + $self->{store}->write_begin(); + $self->{store}->write_message($exp{E}, flags => ["\\Draft"]); + $self->{store}->write_end(); + + xlog $self, "fetch emails"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); + + # Map messages by body contents + my %m = map { $_->{bodyValues}{ $_->{textBody}[0]{partId} }{value} => $_ } + @{ $res->[1][1]{list} }; + my $msgA = $m{"a"}; + my $msgB = $m{"b"}; + my $msgC = $m{"c"}; + my $msgD = $m{"d"}; + my $msgE = $m{"e"}; + $self->assert_not_null($msgA); + $self->assert_not_null($msgB); + $self->assert_not_null($msgC); + $self->assert_not_null($msgD); + $self->assert_not_null($msgE); + + my %map = ( + A => $msgA->{id}, + B => $msgB->{id}, + C => $msgC->{id}, + D => $msgD->{id}, + E => $msgE->{id}, + ); + + # check thread ordering + $res + = $jmap->CallMethods([ [ 'Thread/get', { 'ids' => [$threadid] }, 'R3' ], ]); + $self->assert_deep_equals([ $map{A}, $map{B}, $map{E}, $map{D}, $map{C} ], + $res->[0][1]{list}[0]{emailIds}); + + # now deliver something late that's earlier than the draft + + xlog $self, "generating email F (late arrival)"; + $dt = DateTime->now(); + $dt->add(DateTime::Duration->new(hours => -6)); + $exp{F} = $self->make_message( + "Re: Email A", + references => [ $exp{A} ], + date => $dt, + body => "f" + ); + + xlog $self, "fetch emails"; + $res = $jmap->CallMethods([ + [ 'Email/query', {}, "R1" ], + [ + 'Email/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Email/query', + path => '/ids' + }, + fetchAllBodyValues => JSON::true, + }, + 'R2' + ], + ]); + + # Map messages by body contents + %m = map { $_->{bodyValues}{ $_->{textBody}[0]{partId} }{value} => $_ } + @{ $res->[1][1]{list} }; + my $msgF = $m{"f"}; + $self->assert_not_null($msgF); + + $map{F} = $msgF->{id}; + + # check thread ordering - this message should appear after F and before B + $res + = $jmap->CallMethods([ [ 'Thread/get', { 'ids' => [$threadid] }, 'R3' ], ]); + $self->assert_deep_equals( + [ $map{A}, $map{F}, $map{B}, $map{E}, $map{D}, $map{C} ], + $res->[0][1]{list}[0]{emailIds}); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_cancel_creation b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_cancel_creation index e8bc61f096..a632368b8f 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_cancel_creation +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_cancel_creation @@ -2,141 +2,171 @@ use Cassandane::Tiny; sub test_emailsubmission_cancel_creation - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityId = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityId); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityId = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityId); - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; - $imap->create("INBOX.B") or die; - $res = $jmap->CallMethods([ - ['Mailbox/get', { properties => ['name'], }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $mboxByName{A}->{id}; - my $mboxIdB = $mboxByName{B}->{id}; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; + $imap->create("INBOX.B") or die; + $res = $jmap->CallMethods([ + [ 'Mailbox/get', { properties => ['name'], }, "R1" ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $mboxByName{A}->{id}; + my $mboxIdB = $mboxByName{B}->{id}; - xlog $self, "create, send and update email"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'm1' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'hello', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'world', - } - }, - }, + xlog $self, "create, send and update email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'm1' => { + mailboxIds => { + $mboxIdA => JSON::true, }, - }, 'R1'], - [ 'EmailSubmission/set', { - create => { - 's1' => { - identityId => $identityId, - emailId => '#m1', - envelope => { - mailFrom => { - email => 'foo@local', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [{ - email => 'bar@local', - }], - }, - }, - }, - onSuccessUpdateEmail => { - '#s1' => { - mailboxIds => { - $mboxIdB => JSON::true, - }, - }, - }, - }, 'R2' ], - [ 'Email/get', { - ids => ['#m1'], - properties => ['mailboxIds'], - }, 'R3'], - ]); + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'hello', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, + }, + }, + 'R1' + ], + [ + 'EmailSubmission/set', + { + create => { + 's1' => { + identityId => $identityId, + emailId => '#m1', + envelope => { + mailFrom => { + email => 'foo@local', + parameters => { + "holdfor" => "30", + } + }, + rcptTo => [ { + email => 'bar@local', + } ], + }, + }, + }, + onSuccessUpdateEmail => { + '#s1' => { + mailboxIds => { + $mboxIdB => JSON::true, + }, + }, + }, + }, + 'R2' + ], + [ + 'Email/get', + { + ids => ['#m1'], + properties => ['mailboxIds'], + }, + 'R3' + ], + ]); - xlog $self, "event gets added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); + xlog $self, "event gets added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); - my $emailId = $res->[0][1]->{created}{m1}{id}; - $self->assert_not_null($emailId); - my $msgSubId = $res->[1][1]->{created}{s1}{id}; - $self->assert_not_null($msgSubId); - $self->assert(exists $res->[2][1]{updated}{$emailId}); - $self->assert_num_equals(1, scalar keys %{$res->[3][1]{list}[0]{mailboxIds}}); - $self->assert(exists $res->[3][1]{list}[0]{mailboxIds}{$mboxIdB}); + my $emailId = $res->[0][1]->{created}{m1}{id}; + $self->assert_not_null($emailId); + my $msgSubId = $res->[1][1]->{created}{s1}{id}; + $self->assert_not_null($msgSubId); + $self->assert(exists $res->[2][1]{updated}{$emailId}); + $self->assert_num_equals(1, + scalar keys %{ $res->[3][1]{list}[0]{mailboxIds} }); + $self->assert(exists $res->[3][1]{list}[0]{mailboxIds}{$mboxIdB}); - xlog $self, "cancel the send and revert the mailbox"; - $res = $jmap->CallMethods([ - [ 'EmailSubmission/set', { - update => { - $msgSubId => { - undoStatus => 'canceled', - } - }, - onSuccessUpdateEmail => { - $msgSubId => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - }, + xlog $self, "cancel the send and revert the mailbox"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + update => { + $msgSubId => { + undoStatus => 'canceled', + } + }, + onSuccessUpdateEmail => { + $msgSubId => { + mailboxIds => { + $mboxIdA => JSON::true, }, - }, 'R2' ], - [ 'Email/get', { - ids => [$emailId], - properties => ['mailboxIds'], - }, 'R3'], - ]); + }, + }, + }, + 'R2' + ], + [ + 'Email/get', + { + ids => [$emailId], + properties => ['mailboxIds'], + }, + 'R3' + ], + ]); - $self->assert(exists $res->[0][1]{updated}{$msgSubId}); - $self->assert(exists $res->[1][1]{updated}{$emailId}); - $self->assert_num_equals(1, scalar keys %{$res->[2][1]{list}[0]{mailboxIds}}); - $self->assert(exists $res->[2][1]{list}[0]{mailboxIds}{$mboxIdA}); + $self->assert(exists $res->[0][1]{updated}{$msgSubId}); + $self->assert(exists $res->[1][1]{updated}{$emailId}); + $self->assert_num_equals(1, + scalar keys %{ $res->[2][1]{list}[0]{mailboxIds} }); + $self->assert(exists $res->[2][1]{list}[0]{mailboxIds}{$mboxIdA}); - xlog $self, "event is no longer in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "event is no longer in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); - xlog $self, "destroy and destroy the email too"; - $res = $jmap->CallMethods([ - [ 'EmailSubmission/set', { - destroy => [$msgSubId], - onSuccessDestroyEmail => [$msgSubId], - }, 'R2' ], - [ 'Email/get', { - ids => [$emailId], - properties => ['mailboxIds'], - }, 'R3'], - ]); + xlog $self, "destroy and destroy the email too"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + destroy => [$msgSubId], + onSuccessDestroyEmail => [$msgSubId], + }, + 'R2' + ], + [ + 'Email/get', + { + ids => [$emailId], + properties => ['mailboxIds'], + }, + 'R3' + ], + ]); - $self->assert_str_equals($msgSubId, $res->[0][1]{destroyed}[0]); - $self->assert_str_equals($emailId, $res->[1][1]{destroyed}[0]); - $self->assert_str_equals($emailId, $res->[2][1]{notFound}[0]); + $self->assert_str_equals($msgSubId, $res->[0][1]{destroyed}[0]); + $self->assert_str_equals($emailId, $res->[1][1]{destroyed}[0]); + $self->assert_str_equals($emailId, $res->[2][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_changes b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_changes index 84958fe36d..f87a41c961 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_changes +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_changes @@ -2,56 +2,67 @@ use Cassandane::Tiny; sub test_emailsubmission_changes - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); - - xlog $self, "get current email submission state"; - $res = $jmap->CallMethods([['EmailSubmission/get', { }, "R1"]]); - my $state = $res->[0][1]->{state}; - $self->assert_not_null($state); - - xlog $self, "get email submission updates"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/changes', { - sinceState => $state, - }, "R1" ] ] ); - $self->assert_deep_equals([], $res->[0][1]->{created}); - $self->assert_deep_equals([], $res->[0][1]->{updated}); - $self->assert_deep_equals([], $res->[0][1]->{destroyed}); - - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email") or die; - - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; - - xlog $self, "create email submission but don't update state"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - } - } - }, "R1" ] ] ); - my $subid = $res->[0][1]{created}{1}{id}; - $self->assert_not_null($subid); - - xlog $self, "get email submission updates"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/changes', { - sinceState => $state, - }, "R1" ] ] ); - $self->assert_deep_equals([$subid], $res->[0][1]->{created}); - $self->assert_deep_equals([], $res->[0][1]->{updated}); - $self->assert_deep_equals([], $res->[0][1]->{destroyed}); - - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); + + xlog $self, "get current email submission state"; + $res = $jmap->CallMethods([ [ 'EmailSubmission/get', {}, "R1" ] ]); + my $state = $res->[0][1]->{state}; + $self->assert_not_null($state); + + xlog $self, "get email submission updates"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/changes', + { + sinceState => $state, + }, + "R1" + ] ]); + $self->assert_deep_equals([], $res->[0][1]->{created}); + $self->assert_deep_equals([], $res->[0][1]->{updated}); + $self->assert_deep_equals([], $res->[0][1]->{destroyed}); + + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email") or die; + + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; + + xlog $self, "create email submission but don't update state"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + } + } + }, + "R1" + ] ]); + my $subid = $res->[0][1]{created}{1}{id}; + $self->assert_not_null($subid); + + xlog $self, "get email submission updates"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/changes', + { + sinceState => $state, + }, + "R1" + ] ]); + $self->assert_deep_equals([$subid], $res->[0][1]->{created}); + $self->assert_deep_equals([], $res->[0][1]->{updated}); + $self->assert_deep_equals([], $res->[0][1]->{destroyed}); + + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccess_invalid_subids b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccess_invalid_subids index 40455ae2e6..82c4322233 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccess_invalid_subids +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccess_invalid_subids @@ -2,18 +2,22 @@ use Cassandane::Tiny; sub test_emailsubmission_onsuccess_invalid_subids - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "set email submission with invalid submission ids"; - my $res = $jmap->CallMethods([['EmailSubmission/set', { - onSuccessUpdateEmail => { - 'foo' => { mailboxIds => { 'INBOX' => JSON::true } } }, - onSuccessDestroyEmail => [ 'bar' ] - }, "R1"]]); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("invalidProperties", $res->[0][1]{type}); - $self->assert_str_equals("R1", $res->[0][2]); + xlog $self, "set email submission with invalid submission ids"; + my $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + onSuccessUpdateEmail => { + 'foo' => { mailboxIds => { 'INBOX' => JSON::true } } + }, + onSuccessDestroyEmail => ['bar'] + }, + "R1" + ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("invalidProperties", $res->[0][1]{type}); + $self->assert_str_equals("R1", $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccess_not_using b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccess_not_using index 031b810df8..8a1e9d76e0 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccess_not_using +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccess_not_using @@ -2,34 +2,40 @@ use Cassandane::Tiny; sub test_emailsubmission_onsuccess_not_using - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; - xlog $self, "get identity id"; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); + xlog $self, "get identity id"; + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; - xlog $self, "create email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { + xlog $self, "create email submission"; + $res = $jmap->CallMethods( + [ [ + 'EmailSubmission/set', + { create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - } + '1' => { + identityId => $identityid, + emailId => $emailid, + } }, - onSuccessDestroyEmail => [ '1' ], - }, "R1"]], ['urn:ietf:params:jmap:submission']); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); - $self->assert_str_equals("R1", $res->[0][2]); + onSuccessDestroyEmail => ['1'], + }, + "R1" + ] ], + ['urn:ietf:params:jmap:submission'] + ); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); + $self->assert_str_equals("R1", $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccessdestroy b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccessdestroy index 61705abcf1..203d487e74 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccessdestroy +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_onsuccessdestroy @@ -2,89 +2,106 @@ use Cassandane::Tiny; sub test_emailsubmission_onsuccessdestroy - :min_version_3_9 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_9 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email") or die; + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email") or die; - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; - xlog $self, "create email submission with bad onSuccess"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - } - }, - onSuccessDestroyEmail => {} - }, "R1" ] ] ); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); - $self->assert_str_equals("onSuccessDestroyEmail", - $res->[0][1]{arguments}[0]); + xlog $self, "create email submission with bad onSuccess"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + } + }, + onSuccessDestroyEmail => {} + }, + "R1" + ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); + $self->assert_str_equals("onSuccessDestroyEmail", $res->[0][1]{arguments}[0]); - xlog $self, "create email submission with bad onSuccess"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - } - }, - onSuccessDestroyEmail => "foo" - }, "R1" ] ] ); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); - $self->assert_str_equals("onSuccessDestroyEmail", - $res->[0][1]{arguments}[0]); + xlog $self, "create email submission with bad onSuccess"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + } + }, + onSuccessDestroyEmail => "foo" + }, + "R1" + ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); + $self->assert_str_equals("onSuccessDestroyEmail", $res->[0][1]{arguments}[0]); - xlog $self, "create email submission with bad onSuccess"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - } - }, - onSuccessDestroyEmail => [ 1 ] - }, "R1" ] ] ); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); - $self->assert_str_equals("onSuccessDestroyEmail[0]", - $res->[0][1]{arguments}[0]); + xlog $self, "create email submission with bad onSuccess"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + } + }, + onSuccessDestroyEmail => [1] + }, + "R1" + ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("invalidArguments", $res->[0][1]{type}); + $self->assert_str_equals("onSuccessDestroyEmail[0]", + $res->[0][1]{arguments}[0]); - xlog $self, "create email submission with no onSuccess"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - } - }, - }, "R1" ] ] ); - my $msgsubid = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($msgsubid); + xlog $self, "create email submission with no onSuccess"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + } + }, + }, + "R1" + ] ]); + my $msgsubid = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($msgsubid); - xlog $self, "create email submission with NULL onSuccess"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '2' => { - identityId => $identityid, - emailId => $emailid, - } - }, - onSuccessDestroyEmail => JSON::null - }, "R1" ] ] ); - $msgsubid = $res->[0][1]->{created}{2}{id}; - $self->assert_not_null($msgsubid); + xlog $self, "create email submission with NULL onSuccess"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '2' => { + identityId => $identityid, + emailId => $emailid, + } + }, + onSuccessDestroyEmail => JSON::null + }, + "R1" + ] ]); + $msgsubid = $res->[0][1]->{created}{2}{id}; + $self->assert_not_null($msgsubid); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_query b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_query index 822ea51e39..576fb2f5db 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_query +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_query @@ -2,22 +2,22 @@ use Cassandane::Tiny; sub test_emailsubmission_query - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "get email submission list (no arguments)"; - my $res = $jmap->CallMethods([['EmailSubmission/query', { }, "R1"]]); - $self->assert_null($res->[0][1]{filter}); - $self->assert_null($res->[0][1]{sort}); - $self->assert_not_null($res->[0][1]{queryState}); - $self->assert_equals(JSON::false, $res->[0][1]{canCalculateChanges}); - $self->assert_num_equals(0, $res->[0][1]{position}); - $self->assert_num_equals(0, $res->[0][1]{total}); - $self->assert_not_null($res->[0][1]{ids}); + xlog $self, "get email submission list (no arguments)"; + my $res = $jmap->CallMethods([ [ 'EmailSubmission/query', {}, "R1" ] ]); + $self->assert_null($res->[0][1]{filter}); + $self->assert_null($res->[0][1]{sort}); + $self->assert_not_null($res->[0][1]{queryState}); + $self->assert_equals(JSON::false, $res->[0][1]{canCalculateChanges}); + $self->assert_num_equals(0, $res->[0][1]{position}); + $self->assert_num_equals(0, $res->[0][1]{total}); + $self->assert_not_null($res->[0][1]{ids}); - xlog $self, "get email submission list (error arguments)"; - $res = $jmap->CallMethods([['EmailSubmission/query', { filter => 1 }, "R1"]]); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + xlog $self, "get email submission list (error arguments)"; + $res = $jmap->CallMethods( + [ [ 'EmailSubmission/query', { filter => 1 }, "R1" ] ]); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_query_long b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_query_long index ea4f102f99..1fa9f405f6 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_query_long +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_query_long @@ -2,88 +2,109 @@ use Cassandane::Tiny; sub test_emailsubmission_query_long - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # created and onSend properties - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # created and onSend properties + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; - xlog $self, "Generate emails via IMAP"; - $self->make_message("foo1", body => "an email") or die; - $self->make_message("foo2", body => "an email") or die; - $self->make_message("foo3", body => "an email") or die; + xlog $self, "Generate emails via IMAP"; + $self->make_message("foo1", body => "an email") or die; + $self->make_message("foo2", body => "an email") or die; + $self->make_message("foo3", body => "an email") or die; - xlog $self, "get email ids"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid1 = $res->[0][1]->{ids}[0]; - my $emailid2 = $res->[0][1]->{ids}[1]; - my $emailid3 = $res->[0][1]->{ids}[2]; + xlog $self, "get email ids"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid1 = $res->[0][1]->{ids}[0]; + my $emailid2 = $res->[0][1]->{ids}[1]; + my $emailid3 = $res->[0][1]->{ids}[2]; - xlog $self, "create an email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid1, - } - } - }, "R1" ] ] ); - my $msgsubid1 = $res->[0][1]->{created}{1}{id}; + xlog $self, "create an email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid1, + } + } + }, + "R1" + ] ]); + my $msgsubid1 = $res->[0][1]->{created}{1}{id}; - sleep 1; + sleep 1; - my $now = DateTime->now(); - my $datestr = $now->strftime('%Y-%m-%dT%TZ'); + my $now = DateTime->now(); + my $datestr = $now->strftime('%Y-%m-%dT%TZ'); - xlog $self, "create 2 more email submissions"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '2' => { - identityId => $identityid, - emailId => $emailid2, - }, - '3' => { - identityId => $identityid, - emailId => $emailid3, - } - } - }, "R1" ] ] ); - my $msgsubid2 = $res->[0][1]->{created}{2}{id}; - my $msgsubid3 = $res->[0][1]->{created}{3}{id}; + xlog $self, "create 2 more email submissions"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '2' => { + identityId => $identityid, + emailId => $emailid2, + }, + '3' => { + identityId => $identityid, + emailId => $emailid3, + } + } + }, + "R1" + ] ]); + my $msgsubid2 = $res->[0][1]->{created}{2}{id}; + my $msgsubid3 = $res->[0][1]->{created}{3}{id}; - xlog $self, "filter email submission list based on created time"; - $res = $jmap->CallMethods([['EmailSubmission/query', { - filter => { - createdBefore => $datestr, - } - }, "R1"]]); + xlog $self, "filter email submission list based on created time"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/query', + { + filter => { + createdBefore => $datestr, + } + }, + "R1" + ] ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals($msgsubid1, $res->[0][1]{ids}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals($msgsubid1, $res->[0][1]{ids}[0]); - xlog $self, "filter email submission list based on undoStatus"; - $res = $jmap->CallMethods([['EmailSubmission/query', { - filter => { - undoStatus => 'pending', - } - }, "R1"]]); + xlog $self, "filter email submission list based on undoStatus"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/query', + { + filter => { + undoStatus => 'pending', + } + }, + "R1" + ] ]); - $self->assert_num_equals(0, scalar @{$res->[0][1]->{ids}}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]->{ids} }); - xlog $self, "sort email submission list based on created"; - $res = $jmap->CallMethods([['EmailSubmission/query', { - sort => [{ property => "created", - isAscending => JSON::false }], - }, "R1"]]); + xlog $self, "sort email submission list based on created"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/query', + { + sort => [ { + property => "created", + isAscending => JSON::false + } ], + }, + "R1" + ] ]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_equals($msgsubid1, $res->[0][1]{ids}[2]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_equals($msgsubid1, $res->[0][1]{ids}[2]); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_querychanges b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_querychanges index a279b9b7f3..db505e91b6 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_querychanges +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_querychanges @@ -2,22 +2,25 @@ use Cassandane::Tiny; sub test_emailsubmission_querychanges - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog $self, "get current email submission state"; - my $res = $jmap->CallMethods([['EmailSubmission/query', { }, "R1"]]); - my $state = $res->[0][1]->{queryState}; - $self->assert_not_null($state); + xlog $self, "get current email submission state"; + my $res = $jmap->CallMethods([ [ 'EmailSubmission/query', {}, "R1" ] ]); + my $state = $res->[0][1]->{queryState}; + $self->assert_not_null($state); - xlog $self, "get email submission list updates (empty filter)"; - $res = $jmap->CallMethods([['EmailSubmission/queryChanges', { - filter => {}, - sinceQueryState => $state, - }, "R1"]]); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("cannotCalculateChanges", $res->[0][1]{type}); - $self->assert_str_equals("R1", $res->[0][2]); + xlog $self, "get email submission list updates (empty filter)"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/queryChanges', + { + filter => {}, + sinceQueryState => $state, + }, + "R1" + ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("cannotCalculateChanges", $res->[0][1]{type}); + $self->assert_str_equals("R1", $res->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send index efe005264a..3868fa25f9 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send @@ -2,294 +2,341 @@ use Cassandane::Tiny; sub test_emailsubmission_scheduled_send - :min_version_3_7 :needs_component_jmap :needs_component_calalarmd :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : needs_component_calalarmd : + JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # created and onSend properties - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # created and onSend properties + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - xlog $self, "Create Drafts, Scheduled, and Sent mailboxes"; - my $res = $jmap->CallMethods([ - [ 'Identity/get', {}, "R0" ], - [ 'Mailbox/set', { - create => { - "1" => { - name => "Drafts", - role => "drafts" - }, - "2" => { - name => "Scheduled", - role => "scheduled" - }, - "3" => { - name => "Sent", - role => "sent" - } - } - }, "R1"], - ]); - my $identityid = $res->[0][1]->{list}[0]->{id}; - my $draftsid = $res->[1][1]{created}{"1"}{id}; - my $schedid = $res->[1][1]{created}{"2"}{id}; - my $sentid = $res->[1][1]{created}{"3"}{id}; + xlog $self, "Create Drafts, Scheduled, and Sent mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Identity/get', {}, "R0" ], + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "Drafts", + role => "drafts" + }, + "2" => { + name => "Scheduled", + role => "scheduled" + }, + "3" => { + name => "Sent", + role => "sent" + } + } + }, + "R1" + ], + ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + my $draftsid = $res->[1][1]{created}{"1"}{id}; + my $schedid = $res->[1][1]{created}{"2"}{id}; + my $sentid = $res->[1][1]{created}{"3"}{id}; - xlog $self, "Verify Scheduled mailbox rights"; - my $myRights = $res->[1][1]{created}{"2"}{myRights}; - $self->assert_deep_equals({ - mayReadItems => JSON::true, - mayAddItems => JSON::false, - mayRemoveItems => JSON::false, - mayCreateChild => JSON::false, - mayDelete => JSON::false, - maySubmit => JSON::false, - maySetSeen => JSON::true, - maySetKeywords => JSON::true, - mayAdmin => JSON::false, - mayRename => JSON::false - }, $myRights); + xlog $self, "Verify Scheduled mailbox rights"; + my $myRights = $res->[1][1]{created}{"2"}{myRights}; + $self->assert_deep_equals( + { + mayReadItems => JSON::true, + mayAddItems => JSON::false, + mayRemoveItems => JSON::false, + mayCreateChild => JSON::false, + mayDelete => JSON::false, + maySubmit => JSON::false, + maySetSeen => JSON::true, + maySetKeywords => JSON::true, + mayAdmin => JSON::false, + mayRename => JSON::false + }, + $myRights + ); - xlog $self, "Try to create a child of Scheduled mailbox"; - $res = $jmap->CallMethods([ - [ 'Mailbox/set', { - create => { - "1" => { - name => "foo", - parentId => "$schedid" - } - } - }, "R1"] - ]); - $self->assert_not_null($res->[0][1]->{notCreated}{1}); + xlog $self, "Try to create a child of Scheduled mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + parentId => "$schedid" + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]->{notCreated}{1}); - xlog $self, "Try to destroy Scheduled mailbox"; - $res = $jmap->CallMethods([ - [ 'Mailbox/set', { - destroy => [ "$schedid" ] - }, "R1"] - ]); - $self->assert_not_null($res->[0][1]->{notDestroyed}{$schedid}); + xlog $self, "Try to destroy Scheduled mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + destroy => ["$schedid"] + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]->{notDestroyed}{$schedid}); - xlog $self, "Create 2 draft emails and one in the Scheduled mailbox"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'm1' => { - mailboxIds => { - $draftsid => JSON::true, - }, - keywords => { - '$draft' => JSON::true, - }, - from => [{ - name => '', email => 'cassandane@local' - }], - to => [{ - name => '', email => 'foo@local' - }], - subject => 'foo', - }, - 'm2' => { - mailboxIds => { - $draftsid => JSON::true, - }, - keywords => { - '$draft' => JSON::true, - }, - from => [{ - name => '', email => 'cassandane@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'bar', - }, - 'm3' => { - mailboxIds => { - $schedid => JSON::true, - }, - from => [{ - name => '', email => 'cassandane@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'fail', - }, + xlog $self, "Create 2 draft emails and one in the Scheduled mailbox"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'm1' => { + mailboxIds => { + $draftsid => JSON::true, + }, + keywords => { + '$draft' => JSON::true, + }, + from => [ { + name => '', + email => 'cassandane@local' + } ], + to => [ { + name => '', + email => 'foo@local' + } ], + subject => 'foo', + }, + 'm2' => { + mailboxIds => { + $draftsid => JSON::true, + }, + keywords => { + '$draft' => JSON::true, }, - }, 'R1'], - ]); - my $emailid1 = $res->[0][1]->{created}{m1}{id}; - my $emailid2 = $res->[0][1]->{created}{m2}{id}; - $self->assert_not_null($res->[0][1]->{notCreated}{m3}); + from => [ { + name => '', + email => 'cassandane@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'bar', + }, + 'm3' => { + mailboxIds => { + $schedid => JSON::true, + }, + from => [ { + name => '', + email => 'cassandane@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'fail', + }, + }, + }, + 'R1' + ], + ]); + my $emailid1 = $res->[0][1]->{created}{m1}{id}; + my $emailid2 = $res->[0][1]->{created}{m2}{id}; + $self->assert_not_null($res->[0][1]->{notCreated}{m3}); - xlog $self, "Create 2 email submissions"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid1, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [ - { - email => 'rcpt1@localhost', - }], - }, - onSend => { - moveToMailboxId => $sentid, - setKeywords => { '$Sent' => $JSON::true }, - } - }, - '2' => { - identityId => $identityid, - emailId => $emailid2, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [ - { - email => 'rcpt2@localhost', - }], - }, - onSend => { - moveToMailboxId => $sentid, - setKeywords => { '$Sent' => $JSON::true }, - } + xlog $self, "Create 2 email submissions"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid1, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", } + }, + rcptTo => [ { + email => 'rcpt1@localhost', + } ], }, - onSuccessUpdateEmail => { - '#1' => { - "mailboxIds/$draftsid" => JSON::null, - "mailboxIds/$schedid" => $JSON::true, - 'keywords/$Draft' => JSON::null - }, - '#2' => { - "mailboxIds/$draftsid" => JSON::null, - "mailboxIds/$schedid" => $JSON::true, - 'keywords/$Draft' => JSON::null + onSend => { + moveToMailboxId => $sentid, + setKeywords => { '$Sent' => $JSON::true }, + } + }, + '2' => { + identityId => $identityid, + emailId => $emailid2, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", } + }, + rcptTo => [ { + email => 'rcpt2@localhost', + } ], + }, + onSend => { + moveToMailboxId => $sentid, + setKeywords => { '$Sent' => $JSON::true }, } - }, "R1" ], - [ "Email/get", { - ids => ["$emailid1"], - properties => ["mailboxIds", "keywords"], - }, "R2"], - ] ); + } + }, + onSuccessUpdateEmail => { + '#1' => { + "mailboxIds/$draftsid" => JSON::null, + "mailboxIds/$schedid" => $JSON::true, + 'keywords/$Draft' => JSON::null + }, + '#2' => { + "mailboxIds/$draftsid" => JSON::null, + "mailboxIds/$schedid" => $JSON::true, + 'keywords/$Draft' => JSON::null + } + } + }, + "R1" + ], + [ + "Email/get", + { + ids => ["$emailid1"], + properties => [ "mailboxIds", "keywords" ], + }, + "R2" + ], + ]); - xlog $self, "Check create and onSuccessUpdateEmail results"; - my $msgsubid1 = $res->[0][1]->{created}{1}{id}; - my $msgsubid2 = $res->[0][1]->{created}{2}{id}; - $self->assert_str_equals('pending', $res->[0][1]->{created}{1}{undoStatus}); + xlog $self, "Check create and onSuccessUpdateEmail results"; + my $msgsubid1 = $res->[0][1]->{created}{1}{id}; + my $msgsubid2 = $res->[0][1]->{created}{2}{id}; + $self->assert_str_equals('pending', $res->[0][1]->{created}{1}{undoStatus}); - $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid1}); + $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid1}); - $self->assert_equals(JSON::true, - $res->[2][1]->{list}[0]->{mailboxIds}{$schedid}); - $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); + $self->assert_equals(JSON::true, + $res->[2][1]->{list}[0]->{mailboxIds}{$schedid}); + $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); - xlog $self, "Verify 2 events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(2, scalar @$alarmdata); + xlog $self, "Verify 2 events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(2, scalar @$alarmdata); - xlog $self, "Try to destroy email in Scheduled mailbox"; - $res = $jmap->CallMethods([ - [ 'Email/set', { - destroy => [ "$emailid1" ] - }, "R1"] - ]); - $self->assert_not_null($res->[0][1]->{notDestroyed}{$emailid1}); + xlog $self, "Try to destroy email in Scheduled mailbox"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + destroy => ["$emailid1"] + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]->{notDestroyed}{$emailid1}); - xlog $self, "Cancel email submission 2"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/set', { - update => { - $msgsubid2 => { - undoStatus => 'canceled', - } + xlog $self, "Cancel email submission 2"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + update => { + $msgsubid2 => { + undoStatus => 'canceled', + } + }, + onSuccessUpdateEmail => { + $msgsubid2 => { + mailboxIds => { + "$draftsid" => JSON::true }, - onSuccessUpdateEmail => { - $msgsubid2 => { - mailboxIds => { - "$draftsid" => JSON::true - }, - keywords => { - '$Draft' => JSON::true - } - } + keywords => { + '$Draft' => JSON::true } - }, "R1" ], - [ "Email/get", { - ids => ["$emailid2"], - properties => ["mailboxIds", "keywords"], - }, "R2"], - ] ); - - xlog $self, "Check update and onSuccessUpdateEmail results"; - $self->assert_not_null($res->[0][1]->{updated}{$msgsubid2}); + } + } + }, + "R1" + ], + [ + "Email/get", + { + ids => ["$emailid2"], + properties => [ "mailboxIds", "keywords" ], + }, + "R2" + ], + ]); - $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid2}); + xlog $self, "Check update and onSuccessUpdateEmail results"; + $self->assert_not_null($res->[0][1]->{updated}{$msgsubid2}); - $self->assert_equals(JSON::true, - $res->[2][1]->{list}[0]->{keywords}{'$draft'}); - $self->assert_equals(JSON::true, - $res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); - $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$schedid}); + $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid2}); - - xlog $self, "Destroy canceled email submission 2 (now in Drafts) "; - $res = $jmap->CallMethods( [ [ 'Email/set', { - destroy => [ $emailid2 ], - }, "R1" ] ] ); - $self->assert_str_equals($emailid2, $res->[0][1]->{destroyed}[0]); + $self->assert_equals(JSON::true, + $res->[2][1]->{list}[0]->{keywords}{'$draft'}); + $self->assert_equals(JSON::true, + $res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); + $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$schedid}); + xlog $self, "Destroy canceled email submission 2 (now in Drafts) "; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + destroy => [$emailid2], + }, + "R1" + ] ]); + $self->assert_str_equals($emailid2, $res->[0][1]->{destroyed}[0]); - xlog $self, "Verify an event was removed from the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); + xlog $self, "Verify an event was removed from the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); - xlog $self, "Trigger delivery of email submission"; - my $now = DateTime->now(); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60 ); + xlog $self, "Trigger delivery of email submission"; + my $now = DateTime->now(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - xlog $self, "Check onSend results"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/get', { - ids => [ $msgsubid1 ] - }, "R1"], - [ "Email/get", { - ids => ["$emailid1"], - properties => ["mailboxIds", "keywords"], - }, "R2"], - ] ); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); + xlog $self, "Check onSend results"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/get', + { + ids => [$msgsubid1] + }, + "R1" + ], + [ + "Email/get", + { + ids => ["$emailid1"], + properties => [ "mailboxIds", "keywords" ], + }, + "R2" + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); - $self->assert_equals(JSON::true, - $res->[1][1]->{list}[0]->{mailboxIds}{$sentid}); - $self->assert_null($res->[1][1]->{list}[0]->{mailboxIds}{$schedid}); + $self->assert_equals(JSON::true, + $res->[1][1]->{list}[0]->{mailboxIds}{$sentid}); + $self->assert_null($res->[1][1]->{list}[0]->{mailboxIds}{$schedid}); - $self->assert_equals(JSON::true, - $res->[1][1]->{list}[0]->{keywords}{'$sent'}); - $self->assert_equals(JSON::null, - $res->[1][1]->{list}[0]->{keywords}{'$draft'}); + $self->assert_equals(JSON::true, + $res->[1][1]->{list}[0]->{keywords}{'$sent'}); + $self->assert_equals(JSON::null, + $res->[1][1]->{list}[0]->{keywords}{'$draft'}); - xlog $self, "Verify no events left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "Verify no events left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_fail b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_fail index 1e989c9e4c..4a2cf5bfe1 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_fail +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_fail @@ -2,172 +2,198 @@ use Cassandane::Tiny; sub test_emailsubmission_scheduled_send_fail - :min_version_3_7 :needs_component_jmap :needs_component_calalarmd :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # created and onSend properties - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); - - # clean notification cache - $self->{instance}->getnotify(); - - xlog $self, "Create Drafts, Scheduled, and Sent mailboxes"; - my $res = $jmap->CallMethods([ - [ 'Identity/get', {}, "R0" ], - [ 'Mailbox/set', { - create => { - "1" => { - name => "Drafts", - role => "drafts" - }, - "2" => { - name => "Scheduled", - role => "scheduled" - }, - "3" => { - name => "Sent", - role => "sent" - } - } - }, "R1"], - ]); - my $identityid = $res->[0][1]->{list}[0]->{id}; - my $draftsid = $res->[1][1]{created}{"1"}{id}; - my $schedid = $res->[1][1]{created}{"2"}{id}; - my $sentid = $res->[1][1]{created}{"3"}{id}; - - xlog $self, "Create draft email"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'm1' => { - mailboxIds => { - $draftsid => JSON::true, - }, - keywords => { - '$draft' => JSON::true, - }, - from => [{ - name => '', email => 'cassandane@local' - }], - to => [{ - name => '', email => 'foo@local' - }], - subject => 'foo', - }, + : min_version_3_7 : needs_component_jmap : needs_component_calalarmd : + JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # created and onSend properties + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); + + # clean notification cache + $self->{instance}->getnotify(); + + xlog $self, "Create Drafts, Scheduled, and Sent mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Identity/get', {}, "R0" ], + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "Drafts", + role => "drafts" + }, + "2" => { + name => "Scheduled", + role => "scheduled" + }, + "3" => { + name => "Sent", + role => "sent" + } + } + }, + "R1" + ], + ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + my $draftsid = $res->[1][1]{created}{"1"}{id}; + my $schedid = $res->[1][1]{created}{"2"}{id}; + my $sentid = $res->[1][1]{created}{"3"}{id}; + + xlog $self, "Create draft email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'm1' => { + mailboxIds => { + $draftsid => JSON::true, }, - }, 'R1'], - ]); - my $emailid1 = $res->[0][1]->{created}{m1}{id}; - - xlog $self, "Create email submission"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid1, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [ - { - email => 'rcpt1@localhost', - }], - }, - onSend => { - moveToMailboxId => $sentid, - setKeywords => { '$Sent' => $JSON::true }, - } - } + keywords => { + '$draft' => JSON::true, }, - onSuccessUpdateEmail => { - '#1' => { - "mailboxIds/$draftsid" => JSON::null, - "mailboxIds/$schedid" => $JSON::true, - 'keywords/$Draft' => JSON::null + from => [ { + name => '', + email => 'cassandane@local' + } ], + to => [ { + name => '', + email => 'foo@local' + } ], + subject => 'foo', + }, + }, + }, + 'R1' + ], + ]); + my $emailid1 = $res->[0][1]->{created}{m1}{id}; + + xlog $self, "Create email submission"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid1, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", } + }, + rcptTo => [ { + email => 'rcpt1@localhost', + } ], + }, + onSend => { + moveToMailboxId => $sentid, + setKeywords => { '$Sent' => $JSON::true }, } - }, "R1" ], - [ "Email/get", { - ids => ["$emailid1"], - properties => ["mailboxIds", "keywords"], - }, "R2"], - ] ); - - xlog $self, "Check create and onSuccessUpdateEmail results"; - my $msgsubid1 = $res->[0][1]->{created}{1}{id}; - $self->assert_str_equals('pending', $res->[0][1]->{created}{1}{undoStatus}); - - $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid1}); - - $self->assert_equals(JSON::true, - $res->[2][1]->{list}[0]->{mailboxIds}{$schedid}); - $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); - - xlog $self, "Verify 1 event was added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); - - xlog $self, "Set up a permanent SMTP failre"; - $self->{instance}->set_smtpd({ begin_data => ["554", "5.3.0 [jmapError:forbiddenToSend] try later"] }); - - xlog $self, "Trigger delivery of email submission"; - my $now = DateTime->now(); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60 ); - - xlog $self, "Make sure message was moved back to Drafts"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/get', { - ids => [ $msgsubid1 ] - }, "R1"], - [ "Email/get", { - ids => ["$emailid1"], - properties => ["mailboxIds", "keywords"], - }, "R2"], - ] ); - - $self->assert_equals(JSON::true, - $res->[1][1]->{list}[0]->{mailboxIds}{$draftsid}); - $self->assert_null($res->[1][1]->{list}[0]->{mailboxIds}{$schedid}); - - $self->assert_equals(JSON::true, - $res->[1][1]->{list}[0]->{keywords}{'$draft'}); - $self->assert_equals(JSON::null, - $res->[1][1]->{list}[0]->{keywords}{'$sent'}); - - xlog $self, "Verify 1 event left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); - - xlog $self, "Trigger delivery of unscheduled notification"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 360 ); - - xlog $self, "Verify notification was sent"; - my $data = $self->{instance}->getnotify(); - my $unscheduled; - foreach (@$data) { - my $event = decode_json($_->{MESSAGE}); - if ($event->{event} eq "MessagesUnscheduled") { - $unscheduled = $event; + } + }, + onSuccessUpdateEmail => { + '#1' => { + "mailboxIds/$draftsid" => JSON::null, + "mailboxIds/$schedid" => $JSON::true, + 'keywords/$Draft' => JSON::null + } } + }, + "R1" + ], + [ + "Email/get", + { + ids => ["$emailid1"], + properties => [ "mailboxIds", "keywords" ], + }, + "R2" + ], + ]); + + xlog $self, "Check create and onSuccessUpdateEmail results"; + my $msgsubid1 = $res->[0][1]->{created}{1}{id}; + $self->assert_str_equals('pending', $res->[0][1]->{created}{1}{undoStatus}); + + $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid1}); + + $self->assert_equals(JSON::true, + $res->[2][1]->{list}[0]->{mailboxIds}{$schedid}); + $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); + + xlog $self, "Verify 1 event was added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); + + xlog $self, "Set up a permanent SMTP failre"; + $self->{instance}->set_smtpd( + { begin_data => [ "554", "5.3.0 [jmapError:forbiddenToSend] try later" ] }); + + xlog $self, "Trigger delivery of email submission"; + my $now = DateTime->now(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + + xlog $self, "Make sure message was moved back to Drafts"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/get', + { + ids => [$msgsubid1] + }, + "R1" + ], + [ + "Email/get", + { + ids => ["$emailid1"], + properties => [ "mailboxIds", "keywords" ], + }, + "R2" + ], + ]); + + $self->assert_equals(JSON::true, + $res->[1][1]->{list}[0]->{mailboxIds}{$draftsid}); + $self->assert_null($res->[1][1]->{list}[0]->{mailboxIds}{$schedid}); + + $self->assert_equals(JSON::true, + $res->[1][1]->{list}[0]->{keywords}{'$draft'}); + $self->assert_equals(JSON::null, + $res->[1][1]->{list}[0]->{keywords}{'$sent'}); + + xlog $self, "Verify 1 event left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); + + xlog $self, "Trigger delivery of unscheduled notification"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 360); + + xlog $self, "Verify notification was sent"; + my $data = $self->{instance}->getnotify(); + my $unscheduled; + foreach (@$data) { + my $event = decode_json($_->{MESSAGE}); + if ($event->{event} eq "MessagesUnscheduled") { + $unscheduled = $event; } - $self->assert_not_null($unscheduled); - $self->assert_str_equals("cassandane", $unscheduled->{userId}); - $self->assert_num_equals(1, $unscheduled->{count}); - - xlog $self, "Verify no events left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + } + $self->assert_not_null($unscheduled); + $self->assert_str_equals("cassandane", $unscheduled->{userId}); + $self->assert_num_equals(1, $unscheduled->{count}); + + xlog $self, "Verify no events left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_no_move b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_no_move index 6f7dcdb58f..e4a1bdb2be 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_no_move +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_no_move @@ -2,143 +2,168 @@ use Cassandane::Tiny; sub test_emailsubmission_scheduled_send_no_move - :min_version_3_7 :needs_component_jmap :needs_component_calalarmd :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : needs_component_calalarmd : + JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # created and onSend properties - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # created and onSend properties + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "Create Drafts, Scheduled, and Sent mailboxes"; - my $res = $jmap->CallMethods([ - [ 'Identity/get', {}, "R0" ], - [ 'Mailbox/set', { - create => { - "1" => { - name => "Drafts", - role => "drafts" - }, - "2" => { - name => "Scheduled", - role => "scheduled" - }, - "3" => { - name => "Sent", - role => "sent" - } - } - }, "R1"], - ]); - my $identityid = $res->[0][1]->{list}[0]->{id}; - my $draftsid = $res->[1][1]{created}{"1"}{id}; - my $schedid = $res->[1][1]{created}{"2"}{id}; - my $sentid = $res->[1][1]{created}{"3"}{id}; + xlog $self, "Create Drafts, Scheduled, and Sent mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Identity/get', {}, "R0" ], + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "Drafts", + role => "drafts" + }, + "2" => { + name => "Scheduled", + role => "scheduled" + }, + "3" => { + name => "Sent", + role => "sent" + } + } + }, + "R1" + ], + ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + my $draftsid = $res->[1][1]{created}{"1"}{id}; + my $schedid = $res->[1][1]{created}{"2"}{id}; + my $sentid = $res->[1][1]{created}{"3"}{id}; - xlog $self, "Create draft email"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'm1' => { - mailboxIds => { - $draftsid => JSON::true, - }, - keywords => { - '$draft' => JSON::true, - }, - from => [{ - name => '', email => 'cassandane@local' - }], - to => [{ - name => '', email => 'foo@local' - }], - subject => 'foo', - }, + xlog $self, "Create draft email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'm1' => { + mailboxIds => { + $draftsid => JSON::true, }, - }, 'R1'], - ]); - my $emailid1 = $res->[0][1]->{created}{m1}{id}; + keywords => { + '$draft' => JSON::true, + }, + from => [ { + name => '', + email => 'cassandane@local' + } ], + to => [ { + name => '', + email => 'foo@local' + } ], + subject => 'foo', + }, + }, + }, + 'R1' + ], + ]); + my $emailid1 = $res->[0][1]->{created}{m1}{id}; - xlog $self, "Create email submission"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid1, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [ - { - email => 'rcpt1@localhost', - }], - }, - onSend => { - moveToMailboxId => JSON::null, - } + xlog $self, "Create email submission"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid1, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", } + }, + rcptTo => [ { + email => 'rcpt1@localhost', + } ], }, - onSuccessUpdateEmail => { - '#1' => { - "mailboxIds/$draftsid" => JSON::null, - "mailboxIds/$schedid" => $JSON::true, - 'keywords/$Draft' => JSON::null - } + onSend => { + moveToMailboxId => JSON::null, } - }, "R1" ], - [ "Email/get", { - ids => ["$emailid1"], - properties => ["mailboxIds", "keywords"], - }, "R2"], - ] ); + } + }, + onSuccessUpdateEmail => { + '#1' => { + "mailboxIds/$draftsid" => JSON::null, + "mailboxIds/$schedid" => $JSON::true, + 'keywords/$Draft' => JSON::null + } + } + }, + "R1" + ], + [ + "Email/get", + { + ids => ["$emailid1"], + properties => [ "mailboxIds", "keywords" ], + }, + "R2" + ], + ]); - xlog $self, "Check create and onSuccessUpdateEmail results"; - my $msgsubid1 = $res->[0][1]->{created}{1}{id}; - $self->assert_str_equals('pending', $res->[0][1]->{created}{1}{undoStatus}); + xlog $self, "Check create and onSuccessUpdateEmail results"; + my $msgsubid1 = $res->[0][1]->{created}{1}{id}; + $self->assert_str_equals('pending', $res->[0][1]->{created}{1}{undoStatus}); - $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid1}); + $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid1}); - $self->assert_equals(JSON::true, - $res->[2][1]->{list}[0]->{mailboxIds}{$schedid}); - $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); + $self->assert_equals(JSON::true, + $res->[2][1]->{list}[0]->{mailboxIds}{$schedid}); + $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); - xlog $self, "Verify 1 event was added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); + xlog $self, "Verify 1 event was added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); - xlog $self, "Trigger delivery of email submission"; - my $now = DateTime->now(); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60 ); + xlog $self, "Trigger delivery of email submission"; + my $now = DateTime->now(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - xlog $self, "Check onSend results"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/get', { - ids => [ $msgsubid1 ] - }, "R1"], - [ "Email/get", { - ids => [ $emailid1 ], - properties => ["mailboxIds"], - }, "R2"], - ] ); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); + xlog $self, "Check onSend results"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/get', + { + ids => [$msgsubid1] + }, + "R1" + ], + [ + "Email/get", + { + ids => [$emailid1], + properties => ["mailboxIds"], + }, + "R2" + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); - $self->assert_num_equals(1, scalar @{$res->[1][1]->{notFound}}); - $self->assert_equals($emailid1, $res->[1][1]->{notFound}[0]); + $self->assert_num_equals(1, scalar @{ $res->[1][1]->{notFound} }); + $self->assert_equals($emailid1, $res->[1][1]->{notFound}[0]); - xlog $self, "Verify no events left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "Verify no events left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_null_onsend b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_null_onsend index 19b156404a..372a1b3683 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_null_onsend +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_scheduled_send_null_onsend @@ -2,134 +2,155 @@ use Cassandane::Tiny; sub test_emailsubmission_scheduled_send_null_onsend - :min_version_3_7 :needs_component_jmap :needs_component_calalarmd :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_7 : needs_component_jmap : needs_component_calalarmd : + JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # created and onSend properties - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # created and onSend properties + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - # clean notification cache - $self->{instance}->getnotify(); + # clean notification cache + $self->{instance}->getnotify(); - xlog $self, "Create Drafts, Scheduled, and Sent mailboxes"; - my $res = $jmap->CallMethods([ - [ 'Identity/get', {}, "R0" ], - [ 'Mailbox/set', { - create => { - "1" => { - name => "Drafts", - role => "drafts" - }, - "2" => { - name => "Scheduled", - role => "scheduled" - }, - "3" => { - name => "Sent", - role => "sent" - } - } - }, "R1"], - ]); - my $identityid = $res->[0][1]->{list}[0]->{id}; - my $draftsid = $res->[1][1]{created}{"1"}{id}; - my $schedid = $res->[1][1]{created}{"2"}{id}; - my $sentid = $res->[1][1]{created}{"3"}{id}; + xlog $self, "Create Drafts, Scheduled, and Sent mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Identity/get', {}, "R0" ], + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "Drafts", + role => "drafts" + }, + "2" => { + name => "Scheduled", + role => "scheduled" + }, + "3" => { + name => "Sent", + role => "sent" + } + } + }, + "R1" + ], + ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + my $draftsid = $res->[1][1]{created}{"1"}{id}; + my $schedid = $res->[1][1]{created}{"2"}{id}; + my $sentid = $res->[1][1]{created}{"3"}{id}; - xlog $self, "Create draft email"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'm1' => { - mailboxIds => { - $draftsid => JSON::true, - }, - keywords => { - '$draft' => JSON::true, - }, - from => [{ - name => '', email => 'cassandane@local' - }], - to => [{ - name => '', email => 'foo@local' - }], - subject => 'foo', - }, + xlog $self, "Create draft email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'm1' => { + mailboxIds => { + $draftsid => JSON::true, + }, + keywords => { + '$draft' => JSON::true, }, - }, 'R1'], - ]); - my $emailid1 = $res->[0][1]->{created}{m1}{id}; + from => [ { + name => '', + email => 'cassandane@local' + } ], + to => [ { + name => '', + email => 'foo@local' + } ], + subject => 'foo', + }, + }, + }, + 'R1' + ], + ]); + my $emailid1 = $res->[0][1]->{created}{m1}{id}; - xlog $self, "Create email submission"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/set', { - create => { - '1' => { - emailId => $emailid1, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [ - { - email => 'rcpt1@localhost', - }], - }, - onSend => JSON::null + xlog $self, "Create email submission"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + create => { + '1' => { + emailId => $emailid1, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", } + }, + rcptTo => [ { + email => 'rcpt1@localhost', + } ], }, - onSuccessUpdateEmail => { - '#1' => { - "mailboxIds/$draftsid" => JSON::null, - "mailboxIds/$sentid" => $JSON::true, - 'keywords/$Draft' => JSON::null - } - } - }, "R1" ], - [ "Email/get", { - ids => ["$emailid1"], - properties => ["mailboxIds", "keywords"], - }, "R2"], - ] ); + onSend => JSON::null + } + }, + onSuccessUpdateEmail => { + '#1' => { + "mailboxIds/$draftsid" => JSON::null, + "mailboxIds/$sentid" => $JSON::true, + 'keywords/$Draft' => JSON::null + } + } + }, + "R1" + ], + [ + "Email/get", + { + ids => ["$emailid1"], + properties => [ "mailboxIds", "keywords" ], + }, + "R2" + ], + ]); - xlog $self, "Check create and onSuccessUpdateEmail results"; - my $msgsubid1 = $res->[0][1]->{created}{1}{id}; - $self->assert_str_equals('pending', $res->[0][1]->{created}{1}{undoStatus}); + xlog $self, "Check create and onSuccessUpdateEmail results"; + my $msgsubid1 = $res->[0][1]->{created}{1}{id}; + $self->assert_str_equals('pending', $res->[0][1]->{created}{1}{undoStatus}); - $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid1}); + $self->assert_equals(JSON::null, $res->[1][1]->{updated}{emailid1}); - $self->assert_equals(JSON::true, - $res->[2][1]->{list}[0]->{mailboxIds}{$sentid}); - $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); - $self->assert_null($res->[1][1]->{list}[0]->{mailboxIds}{$schedid}); + $self->assert_equals(JSON::true, + $res->[2][1]->{list}[0]->{mailboxIds}{$sentid}); + $self->assert_null($res->[2][1]->{list}[0]->{mailboxIds}{$draftsid}); + $self->assert_null($res->[1][1]->{list}[0]->{mailboxIds}{$schedid}); - xlog $self, "Verify 1 event was added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); + xlog $self, "Verify 1 event was added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); - xlog $self, "Trigger delivery of email submission"; - my $now = DateTime->now(); - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $now->epoch() + 60 ); + xlog $self, "Trigger delivery of email submission"; + my $now = DateTime->now(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); - xlog $self, "Check results"; - $res = $jmap->CallMethods( [ - [ 'EmailSubmission/get', { - ids => [ $msgsubid1 ] - }, "R1"], - ] ); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); + xlog $self, "Check results"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/get', + { + ids => [$msgsubid1] + }, + "R1" + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); - xlog $self, "Verify no events left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "Verify no events left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set index 2f8626e197..48a049029c 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set @@ -2,62 +2,79 @@ use Cassandane::Tiny; sub test_emailsubmission_set - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); - - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email") or die; - - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; - - xlog $self, "create email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - } - } - }, "R1" ] ] ); - my $msgsubid = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($msgsubid); - - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); - - xlog $self, "get email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/get', { - ids => [ $msgsubid ], - }, "R1" ] ] ); - $self->assert_str_equals($msgsubid, $res->[0][1]->{list}[0]{id}); - - xlog $self, "update email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - update => { - $msgsubid => { - undoStatus => 'canceled', - } - } - }, "R1" ] ] ); - $self->assert_str_equals('cannotUnsend', $res->[0][1]->{notUpdated}{$msgsubid}{type}); - - xlog $self, "destroy email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - destroy => [ $msgsubid ], - }, "R1" ] ] ); - $self->assert_str_equals($msgsubid, $res->[0][1]->{destroyed}[0]); - - xlog $self, "make sure #jmapsubmission folder isn't visible via IMAP"; - my $talk = $self->{store}->get_client(); - my @list = $talk->list('', '*'); - $self->assert_num_equals(0, scalar grep { $_->[2] eq 'INBOX.#jmapsubmission' } @list); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); + + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email") or die; + + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; + + xlog $self, "create email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + } + } + }, + "R1" + ] ]); + my $msgsubid = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($msgsubid); + + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); + + xlog $self, "get email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/get', + { + ids => [$msgsubid], + }, + "R1" + ] ]); + $self->assert_str_equals($msgsubid, $res->[0][1]->{list}[0]{id}); + + xlog $self, "update email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + update => { + $msgsubid => { + undoStatus => 'canceled', + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('cannotUnsend', + $res->[0][1]->{notUpdated}{$msgsubid}{type}); + + xlog $self, "destroy email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + destroy => [$msgsubid], + }, + "R1" + ] ]); + $self->assert_str_equals($msgsubid, $res->[0][1]->{destroyed}[0]); + + xlog $self, "make sure #jmapsubmission folder isn't visible via IMAP"; + my $talk = $self->{store}->get_client(); + my @list = $talk->list('', '*'); + $self->assert_num_equals(0, + scalar grep { $_->[2] eq 'INBOX.#jmapsubmission' } @list); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_bad_futurerelease b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_bad_futurerelease index 2b5a879596..6ddb33b852 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_bad_futurerelease +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_bad_futurerelease @@ -2,158 +2,182 @@ use Cassandane::Tiny; sub test_emailsubmission_set_bad_futurerelease - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; - xlog $self, "create email submissions"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => JSON::null - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, + xlog $self, "create email submissions"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => JSON::null + } }, - '2' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "" - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + }, + '2' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "" + } }, - '3' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => " " - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + }, + '3' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => " " + } }, - '4' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30a" - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + }, + '4' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30a" + } }, - '5' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holduntil" => undef - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + }, + '5' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holduntil" => undef + } }, - '6' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holduntil" => [] - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + }, + '6' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holduntil" => [] + } }, - '7' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holduntil" => "" - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, - } - } - }, "R1" ] ] ); - my $errType = $res->[0][1]->{notCreated}{1}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $errType = $res->[0][1]->{notCreated}{2}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $errType = $res->[0][1]->{notCreated}{3}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $errType = $res->[0][1]->{notCreated}{4}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $errType = $res->[0][1]->{notCreated}{5}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $errType = $res->[0][1]->{notCreated}{6}{type}; - $self->assert_str_equals("invalidProperties", $errType); - $errType = $res->[0][1]->{notCreated}{7}{type}; - $self->assert_str_equals("invalidProperties", $errType); + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + }, + '7' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holduntil" => "" + } + }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + } + } + }, + "R1" + ] ]); + my $errType = $res->[0][1]->{notCreated}{1}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $errType = $res->[0][1]->{notCreated}{2}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $errType = $res->[0][1]->{notCreated}{3}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $errType = $res->[0][1]->{notCreated}{4}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $errType = $res->[0][1]->{notCreated}{5}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $errType = $res->[0][1]->{notCreated}{6}{type}; + $self->assert_str_equals("invalidProperties", $errType); + $errType = $res->[0][1]->{notCreated}{7}{type}; + $self->assert_str_equals("invalidProperties", $errType); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_creationid b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_creationid index 6c61b17b14..8795abdab8 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_creationid +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_creationid @@ -2,82 +2,95 @@ use Cassandane::Tiny; sub test_emailsubmission_set_creationid - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityId = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityId); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityId = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityId); - xlog $self, "create mailboxes"; - $imap->create("INBOX.A") or die; - $imap->create("INBOX.B") or die; - $res = $jmap->CallMethods([ - ['Mailbox/get', { properties => ['name'], }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdA = $mboxByName{A}->{id}; - my $mboxIdB = $mboxByName{B}->{id}; + xlog $self, "create mailboxes"; + $imap->create("INBOX.A") or die; + $imap->create("INBOX.B") or die; + $res = $jmap->CallMethods([ + [ 'Mailbox/get', { properties => ['name'], }, "R1" ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdA = $mboxByName{A}->{id}; + my $mboxIdB = $mboxByName{B}->{id}; - xlog $self, "create, send and update email"; - $res = $jmap->CallMethods([ - ['Email/set', { - create => { - 'm1' => { - mailboxIds => { - $mboxIdA => JSON::true, - }, - from => [{ - name => '', email => 'foo@local' - }], - to => [{ - name => '', email => 'bar@local' - }], - subject => 'hello', - bodyStructure => { - type => 'text/plain', - partId => 'part1', - }, - bodyValues => { - part1 => { - value => 'world', - } - }, - }, + xlog $self, "create, send and update email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { + create => { + 'm1' => { + mailboxIds => { + $mboxIdA => JSON::true, }, - }, 'R1'], - [ 'EmailSubmission/set', { - create => { - 's1' => { - identityId => $identityId, - emailId => '#m1', - } - }, - onSuccessUpdateEmail => { - '#s1' => { - mailboxIds => { - $mboxIdB => JSON::true, - }, - }, - }, - }, 'R2' ], - [ 'Email/get', { - ids => ['#m1'], - properties => ['mailboxIds'], - }, 'R3'], - ]); - my $emailId = $res->[0][1]->{created}{m1}{id}; - $self->assert_not_null($emailId); - my $msgSubId = $res->[1][1]->{created}{s1}{id}; - $self->assert_not_null($msgSubId); - $self->assert(exists $res->[2][1]{updated}{$emailId}); - $self->assert_num_equals(1, scalar keys %{$res->[3][1]{list}[0]{mailboxIds}}); - $self->assert(exists $res->[3][1]{list}[0]{mailboxIds}{$mboxIdB}); + from => [ { + name => '', + email => 'foo@local' + } ], + to => [ { + name => '', + email => 'bar@local' + } ], + subject => 'hello', + bodyStructure => { + type => 'text/plain', + partId => 'part1', + }, + bodyValues => { + part1 => { + value => 'world', + } + }, + }, + }, + }, + 'R1' + ], + [ + 'EmailSubmission/set', + { + create => { + 's1' => { + identityId => $identityId, + emailId => '#m1', + } + }, + onSuccessUpdateEmail => { + '#s1' => { + mailboxIds => { + $mboxIdB => JSON::true, + }, + }, + }, + }, + 'R2' + ], + [ + 'Email/get', + { + ids => ['#m1'], + properties => ['mailboxIds'], + }, + 'R3' + ], + ]); + my $emailId = $res->[0][1]->{created}{m1}{id}; + $self->assert_not_null($emailId); + my $msgSubId = $res->[1][1]->{created}{s1}{id}; + $self->assert_not_null($msgSubId); + $self->assert(exists $res->[2][1]{updated}{$emailId}); + $self->assert_num_equals(1, + scalar keys %{ $res->[3][1]{list}[0]{mailboxIds} }); + $self->assert(exists $res->[3][1]{list}[0]{mailboxIds}{$mboxIdB}); - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_fail_some_recipients b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_fail_some_recipients index dbc323832f..0b6b6917a7 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_fail_some_recipients +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_fail_some_recipients @@ -2,61 +2,76 @@ use Cassandane::Tiny; sub test_emailsubmission_set_fail_some_recipients - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwith 10 recipients\r\n") or die; + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwith 10 recipients\r\n") + or die; - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; - xlog $self, "create email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }, { - email => 'rcpt3@fail.to.deliver', - }, { - email => 'rcpt4@localhost', - }, { - email => 'rcpt5@fail.to.deliver', - }, { - email => 'rcpt6@fail.to.deliver', - }, { - email => 'rcpt7@localhost', - }, { - email => 'rcpt8@localhost', - }, { - email => 'rcpt9@fail.to.deliver', - }, { - email => 'rcpt10@localhost', - }], - }, - } - } - }, "R1" ] ] ); - my $errType = $res->[0][1]->{notCreated}{1}{type}; - $self->assert_str_equals("invalidRecipients", $errType); + xlog $self, "create email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + }, + { + email => 'rcpt3@fail.to.deliver', + }, + { + email => 'rcpt4@localhost', + }, + { + email => 'rcpt5@fail.to.deliver', + }, + { + email => 'rcpt6@fail.to.deliver', + }, + { + email => 'rcpt7@localhost', + }, + { + email => 'rcpt8@localhost', + }, + { + email => 'rcpt9@fail.to.deliver', + }, + { + email => 'rcpt10@localhost', + } + ], + }, + } + } + }, + "R1" + ] ]); + my $errType = $res->[0][1]->{notCreated}{1}{type}; + $self->assert_str_equals("invalidRecipients", $errType); - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_futurerelease b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_futurerelease index 28e0f75d80..b20a644ea2 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_futurerelease +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_futurerelease @@ -2,157 +2,190 @@ use Cassandane::Tiny; sub test_emailsubmission_set_futurerelease - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); - - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; - - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; - - xlog $self, "create email submissions"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); + + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; + + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; + + xlog $self, "create email submissions"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", + } }, - '2' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, - } - } - }, "R1" ] ] ); - my $msgsubid1 = $res->[0][1]->{created}{1}{id}; - my $msgsubid2 = $res->[0][1]->{created}{2}{id}; - $self->assert_not_null($msgsubid1); - $self->assert_not_null($msgsubid2); - - xlog $self, "event were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(2, scalar @$alarmdata); - - $res = $jmap->CallMethods([['EmailSubmission/get', { ids => undef }, "R2"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); - $self->assert_str_equals('pending', $res->[0][1]->{list}[0]->{undoStatus}); - $self->assert_str_equals('pending', $res->[0][1]->{list}[1]->{undoStatus}); - my $state = $res->[0][1]->{state}; - - xlog $self, "cancel first email submission"; - $res = $jmap->CallMethods([ - ['EmailSubmission/set', { - update => { $msgsubid1 => { - "undoStatus" => "canceled", - }}, - }, 'R3'], - ]); - - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - xlog $self, "one event left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); - - $res = $jmap->CallMethods([['EmailSubmission/get', { ids => [ $msgsubid1 ] }, "R4"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); - $self->assert_str_equals('canceled', $res->[0][1]->{list}[0]->{undoStatus}); - - xlog $self, "destroy first email submission"; - $res = $jmap->CallMethods([ - ['EmailSubmission/set', { - destroy => [ $msgsubid1 ] - }, 'R5'], - ]); - - $self->assert_not_null($res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{notDestroyed}); - - xlog $self, "one event left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); - - $res = $jmap->CallMethods([['EmailSubmission/get', { ids => undef }, "R6"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); - - xlog $self, "set up a send block"; - $self->{instance}->set_smtpd({ begin_data => ["451", "4.3.0 [jmapError:forbiddenToSend] try later"] }); - - xlog $self, "attempt delivery of the second email"; - my $now = DateTime->now(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60 ); - - xlog $self, "still pending"; - $res = $jmap->CallMethods([['EmailSubmission/get', { ids => [ $msgsubid2 ] }, "R7"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); - $self->assert_str_equals('pending', $res->[0][1]->{list}[0]->{undoStatus}); - - xlog $self, "one event left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); - - xlog $self, "clear the send block"; - $self->{instance}->set_smtpd(); - - xlog $self, "trigger delivery of second email submission"; - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 600 ); - - $res = $jmap->CallMethods([['EmailSubmission/get', { ids => [ $msgsubid2 ] }, "R7"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); - $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); - - xlog $self, "no events left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); - - xlog $self, "attempt to cancel second email submission (should fail)"; - $res = $jmap->CallMethods([ - ['EmailSubmission/set', { - update => { $msgsubid2 => { - "undoStatus" => "canceled", - }}, - }, 'R8'], - ]); - - $self->assert_null($res->[0][1]{updated}); - $self->assert_not_null($res->[0][1]{notUpdated}); + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + }, + '2' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", + } + }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + } + } + }, + "R1" + ] ]); + my $msgsubid1 = $res->[0][1]->{created}{1}{id}; + my $msgsubid2 = $res->[0][1]->{created}{2}{id}; + $self->assert_not_null($msgsubid1); + $self->assert_not_null($msgsubid2); + + xlog $self, "event were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(2, scalar @$alarmdata); + + $res + = $jmap->CallMethods([ [ 'EmailSubmission/get', { ids => undef }, "R2" ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); + $self->assert_str_equals('pending', $res->[0][1]->{list}[0]->{undoStatus}); + $self->assert_str_equals('pending', $res->[0][1]->{list}[1]->{undoStatus}); + my $state = $res->[0][1]->{state}; + + xlog $self, "cancel first email submission"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + update => { + $msgsubid1 => { + "undoStatus" => "canceled", + } + }, + }, + 'R3' + ], + ]); + + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + xlog $self, "one event left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); + + $res = $jmap->CallMethods( + [ [ 'EmailSubmission/get', { ids => [$msgsubid1] }, "R4" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); + $self->assert_str_equals('canceled', $res->[0][1]->{list}[0]->{undoStatus}); + + xlog $self, "destroy first email submission"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + destroy => [$msgsubid1] + }, + 'R5' + ], + ]); + + $self->assert_not_null($res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{notDestroyed}); + + xlog $self, "one event left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); + + $res + = $jmap->CallMethods([ [ 'EmailSubmission/get', { ids => undef }, "R6" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); + + xlog $self, "set up a send block"; + $self->{instance}->set_smtpd( + { begin_data => [ "451", "4.3.0 [jmapError:forbiddenToSend] try later" ] }); + + xlog $self, "attempt delivery of the second email"; + my $now = DateTime->now(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 60); + + xlog $self, "still pending"; + $res = $jmap->CallMethods( + [ [ 'EmailSubmission/get', { ids => [$msgsubid2] }, "R7" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); + $self->assert_str_equals('pending', $res->[0][1]->{list}[0]->{undoStatus}); + + xlog $self, "one event left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); + + xlog $self, "clear the send block"; + $self->{instance}->set_smtpd(); + + xlog $self, "trigger delivery of second email submission"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 600); + + $res = $jmap->CallMethods( + [ [ 'EmailSubmission/get', { ids => [$msgsubid2] }, "R7" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); + $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); + + xlog $self, "no events left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); + + xlog $self, "attempt to cancel second email submission (should fail)"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + update => { + $msgsubid2 => { + "undoStatus" => "canceled", + } + }, + }, + 'R8' + ], + ]); + + $self->assert_null($res->[0][1]{updated}); + $self->assert_not_null($res->[0][1]{notUpdated}); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_issue2285 b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_issue2285 index 89f2123bd7..dfd261ba61 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_issue2285 +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_issue2285 @@ -2,67 +2,71 @@ use Cassandane::Tiny; sub test_emailsubmission_set_issue2285 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - my $inboxid = $self->getinbox()->{id}; + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + my $inboxid = $self->getinbox()->{id}; - xlog $self, "Create email"; - $res = $jmap->CallMethods([ - [ 'Email/set', { + xlog $self, "Create email"; + $res = $jmap->CallMethods([ + [ + 'Email/set', + { create => { - 'k40' => { - 'bcc' => undef, - 'cc' => undef, - 'attachments' => undef, - 'subject' => 'zlskdjgh', - 'keywords' => { - '$Seen' => JSON::true, - '$Draft' => JSON::true - }, - textBody => [{partId => '1'}], - bodyValues => { '1' => { value => 'lsdkgjh' }}, - 'to' => [ - { - 'email' => 'foo@bar.com', - 'name' => '' - } - ], - 'from' => [ - { - 'email' => 'fooalias1@robmtest.vm', - 'name' => 'some name' - } - ], - 'receivedAt' => '2018-03-06T03:49:04Z', - 'mailboxIds' => { - $inboxid => JSON::true, - }, - } + 'k40' => { + 'bcc' => undef, + 'cc' => undef, + 'attachments' => undef, + 'subject' => 'zlskdjgh', + 'keywords' => { + '$Seen' => JSON::true, + '$Draft' => JSON::true + }, + textBody => [ { partId => '1' } ], + bodyValues => { '1' => { value => 'lsdkgjh' } }, + 'to' => [ { + 'email' => 'foo@bar.com', + 'name' => '' + } ], + 'from' => [ { + 'email' => 'fooalias1@robmtest.vm', + 'name' => 'some name' + } ], + 'receivedAt' => '2018-03-06T03:49:04Z', + 'mailboxIds' => { + $inboxid => JSON::true, + }, + } } - }, "R1" ], - [ 'EmailSubmission/set', { + }, + "R1" + ], + [ + 'EmailSubmission/set', + { create => { - 'k41' => { - identityId => $identityid, - emailId => '#k40', - envelope => undef, - }, + 'k41' => { + identityId => $identityid, + emailId => '#k40', + envelope => undef, + }, }, - onSuccessDestroyEmail => [ '#k41' ], - }, "R2" ] ] ); - $self->assert_str_equals('EmailSubmission/set', $res->[1][0]); - $self->assert_not_null($res->[1][1]->{created}{'k41'}{id}); - $self->assert_str_equals('R2', $res->[1][2]); - $self->assert_str_equals('Email/set', $res->[2][0]); - $self->assert_not_null($res->[2][1]->{destroyed}[0]); - $self->assert_str_equals('R2', $res->[2][2]); + onSuccessDestroyEmail => ['#k41'], + }, + "R2" + ] + ]); + $self->assert_str_equals('EmailSubmission/set', $res->[1][0]); + $self->assert_not_null($res->[1][1]->{created}{'k41'}{id}); + $self->assert_str_equals('R2', $res->[1][2]); + $self->assert_str_equals('Email/set', $res->[2][0]); + $self->assert_not_null($res->[2][1]->{destroyed}[0]); + $self->assert_str_equals('R2', $res->[2][2]); - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_message_too_large b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_message_too_large index e0e27d1c46..e4b2439fec 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_message_too_large +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_message_too_large @@ -2,44 +2,49 @@ use Cassandane::Tiny; sub test_emailsubmission_set_message_too_large - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); - xlog $self, "Generate an email via IMAP"; - my $x = "x"; - $self->make_message("foo", body => "an email\r\nwith 10k+ octet body\r\n" . $x x 10000) or die; + xlog $self, "Generate an email via IMAP"; + my $x = "x"; + $self->make_message("foo", + body => "an email\r\nwith 10k+ octet body\r\n" . $x x 10000) + or die; - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; - xlog $self, "create email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }], - }, - } - } - }, "R1" ] ] ); - my $errType = $res->[0][1]->{notCreated}{1}{type}; - $self->assert_str_equals("tooLarge", $errType); + xlog $self, "create email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + }, + rcptTo => [ { + email => 'rcpt1@localhost', + } ], + }, + } + } + }, + "R1" + ] ]); + my $errType = $res->[0][1]->{notCreated}{1}{type}; + $self->assert_str_equals("tooLarge", $errType); - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_smtp_rejection b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_smtp_rejection index 041e865112..7ce494cc40 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_smtp_rejection +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_smtp_rejection @@ -2,46 +2,52 @@ use Cassandane::Tiny; sub test_emailsubmission_set_smtp_rejection - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwith 11 recipients\r\n") or die; + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwith 11 recipients\r\n") + or die; - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; - $self->{instance}->set_smtpd({ begin_data => ["554", "5.3.0 [jmapError:forbiddenToSend] bad egg"] }); + $self->{instance}->set_smtpd( + { begin_data => [ "554", "5.3.0 [jmapError:forbiddenToSend] bad egg" ] }); - xlog $self, "create email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }], - }, - } - } - }, "R1" ] ] ); - my $errType = $res->[0][1]->{notCreated}{1}{type}; - $self->assert_str_equals("forbiddenToSend", $errType); - $self->assert_str_equals("bad egg", $res->[0][1]->{notCreated}{1}{description}); + xlog $self, "create email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + }, + rcptTo => [ { + email => 'rcpt1@localhost', + } ], + }, + } + } + }, + "R1" + ] ]); + my $errType = $res->[0][1]->{notCreated}{1}{type}; + $self->assert_str_equals("forbiddenToSend", $errType); + $self->assert_str_equals("bad egg", + $res->[0][1]->{notCreated}{1}{description}); - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_too_many_recipients b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_too_many_recipients index db83a06c78..984b307aa8 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_too_many_recipients +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_too_many_recipients @@ -2,63 +2,79 @@ use Cassandane::Tiny; sub test_emailsubmission_set_too_many_recipients - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwith 11 recipients\r\n") or die; + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwith 11 recipients\r\n") + or die; - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; - xlog $self, "create email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }, { - email => 'rcpt3@localhost', - }, { - email => 'rcpt4@localhost', - }, { - email => 'rcpt5@localhost', - }, { - email => 'rcpt6@localhost', - }, { - email => 'rcpt7@localhost', - }, { - email => 'rcpt8@localhost', - }, { - email => 'rcpt9@localhost', - }, { - email => 'rcpt10@localhost', - }, { - email => 'rcpt11@localhost', - }], - }, - } - } - }, "R1" ] ] ); - my $errType = $res->[0][1]->{notCreated}{1}{type}; - $self->assert_str_equals("tooManyRecipients", $errType); + xlog $self, "create email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + }, + { + email => 'rcpt3@localhost', + }, + { + email => 'rcpt4@localhost', + }, + { + email => 'rcpt5@localhost', + }, + { + email => 'rcpt6@localhost', + }, + { + email => 'rcpt7@localhost', + }, + { + email => 'rcpt8@localhost', + }, + { + email => 'rcpt9@localhost', + }, + { + email => 'rcpt10@localhost', + }, + { + email => 'rcpt11@localhost', + } + ], + }, + } + } + }, + "R1" + ] ]); + my $errType = $res->[0][1]->{notCreated}{1}{type}; + $self->assert_str_equals("tooManyRecipients", $errType); - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_with_envelope b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_with_envelope index d7634e2f06..e3e901c467 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_with_envelope +++ b/cassandane/tiny-tests/JMAPEmailSubmission/emailsubmission_set_with_envelope @@ -2,45 +2,51 @@ use Cassandane::Tiny; sub test_emailsubmission_set_with_envelope - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; - xlog $self, "create email submission"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, - } - } - }, "R1" ] ] ); - my $msgsubid = $res->[0][1]->{created}{1}{id}; - $self->assert_not_null($msgsubid); + xlog $self, "create email submission"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + } + } + }, + "R1" + ] ]); + my $msgsubid = $res->[0][1]->{created}{1}{id}; + $self->assert_not_null($msgsubid); - xlog $self, "no events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); + xlog $self, "no events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); } diff --git a/cassandane/tiny-tests/JMAPEmailSubmission/replication_emailsubmission_set_futurerelease b/cassandane/tiny-tests/JMAPEmailSubmission/replication_emailsubmission_set_futurerelease index 97801b02a7..91167fe2b8 100644 --- a/cassandane/tiny-tests/JMAPEmailSubmission/replication_emailsubmission_set_futurerelease +++ b/cassandane/tiny-tests/JMAPEmailSubmission/replication_emailsubmission_set_futurerelease @@ -2,145 +2,168 @@ use Cassandane::Tiny; sub test_replication_emailsubmission_set_futurerelease - :min_version_3_1 :needs_component_jmap :needs_component_calalarmd -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $res = $jmap->CallMethods( [ [ 'Identity/get', {}, "R1" ] ] ); - my $identityid = $res->[0][1]->{list}[0]->{id}; - $self->assert_not_null($identityid); - - xlog $self, "Generate an email via IMAP"; - $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; - - xlog $self, "get email id"; - $res = $jmap->CallMethods( [ [ 'Email/query', {}, "R1" ] ] ); - my $emailid = $res->[0][1]->{ids}[0]; - - xlog $self, "create email submissions"; - $res = $jmap->CallMethods( [ [ 'EmailSubmission/set', { - create => { - '1' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, + : min_version_3_1 : needs_component_jmap : needs_component_calalarmd { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $res = $jmap->CallMethods([ [ 'Identity/get', {}, "R1" ] ]); + my $identityid = $res->[0][1]->{list}[0]->{id}; + $self->assert_not_null($identityid); + + xlog $self, "Generate an email via IMAP"; + $self->make_message("foo", body => "an email\r\nwithCRLF\r\n") or die; + + xlog $self, "get email id"; + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $emailid = $res->[0][1]->{ids}[0]; + + xlog $self, "create email submissions"; + $res = $jmap->CallMethods([ [ + 'EmailSubmission/set', + { + create => { + '1' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", + } }, - '2' => { - identityId => $identityid, - emailId => $emailid, - envelope => { - mailFrom => { - email => 'from@localhost', - parameters => { - "holdfor" => "30", - } - }, - rcptTo => [{ - email => 'rcpt1@localhost', - }, { - email => 'rcpt2@localhost', - }], - }, - } - } - }, "R1" ] ] ); - my $msgsubid1 = $res->[0][1]->{created}{1}{id}; - my $msgsubid2 = $res->[0][1]->{created}{2}{id}; - $self->assert_not_null($msgsubid1); - $self->assert_not_null($msgsubid2); - - xlog $self, "events were added to the alarmdb"; - my $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(2, scalar @$alarmdata); - - xlog $self, "events aren't in replica alarmdb yet"; - my $replicadata = $self->{replica}->getalarmdb(); - $self->assert_num_equals(0, scalar @$replicadata); - - $self->run_replication(); - - xlog $self, "events are still in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(2, scalar @$alarmdata); - - xlog $self, "events are now in replica alarmdb"; - $replicadata = $self->{replica}->getalarmdb(); - $self->assert_num_equals(2, scalar @$replicadata); - - xlog $self, "cancel first email submission"; - $res = $jmap->CallMethods([ - ['EmailSubmission/set', { - update => { $msgsubid1 => { - "undoStatus" => "canceled", - }}, - }, 'R3'], - ]); - - $self->assert_not_null($res->[0][1]{updated}); - $self->assert_null($res->[0][1]{notUpdated}); - - xlog $self, "one event left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); - - $self->run_replication(); - - xlog $self, "one event left in the alarmdb"; - $replicadata = $self->{replica}->getalarmdb(); - $self->assert_num_equals(1, scalar @$replicadata); - - $res = $jmap->CallMethods([['EmailSubmission/get', { ids => [ $msgsubid1 ] }, "R4"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); - $self->assert_str_equals('canceled', $res->[0][1]->{list}[0]->{undoStatus}); - - xlog $self, "destroy first email submission"; - $res = $jmap->CallMethods([ - ['EmailSubmission/set', { - destroy => [ $msgsubid1 ] - }, 'R5'], - ]); - - $self->assert_not_null($res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{notDestroyed}); - - xlog $self, "one event left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(1, scalar @$alarmdata); - - $res = $jmap->CallMethods([['EmailSubmission/get', { ids => undef }, "R6"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); - - xlog $self, "trigger delivery of second email submission"; - my $now = DateTime->now(); - $self->{instance}->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 120 ); - - $res = $jmap->CallMethods([['EmailSubmission/get', { ids => [ $msgsubid2 ] }, "R7"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{list}}); - $self->assert_deep_equals([], $res->[0][1]->{notFound}); - $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); - - xlog $self, "no events left in the alarmdb"; - $alarmdata = $self->{instance}->getalarmdb(); - $self->assert_num_equals(0, scalar @$alarmdata); - - $self->run_replication(); - - xlog $self, "no replica events left in the alarmdb"; - $replicadata = $self->{replica}->getalarmdb(); - $self->assert_num_equals(0, scalar @$replicadata); + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + }, + '2' => { + identityId => $identityid, + emailId => $emailid, + envelope => { + mailFrom => { + email => 'from@localhost', + parameters => { + "holdfor" => "30", + } + }, + rcptTo => [ + { + email => 'rcpt1@localhost', + }, + { + email => 'rcpt2@localhost', + } + ], + }, + } + } + }, + "R1" + ] ]); + my $msgsubid1 = $res->[0][1]->{created}{1}{id}; + my $msgsubid2 = $res->[0][1]->{created}{2}{id}; + $self->assert_not_null($msgsubid1); + $self->assert_not_null($msgsubid2); + + xlog $self, "events were added to the alarmdb"; + my $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(2, scalar @$alarmdata); + + xlog $self, "events aren't in replica alarmdb yet"; + my $replicadata = $self->{replica}->getalarmdb(); + $self->assert_num_equals(0, scalar @$replicadata); + + $self->run_replication(); + + xlog $self, "events are still in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(2, scalar @$alarmdata); + + xlog $self, "events are now in replica alarmdb"; + $replicadata = $self->{replica}->getalarmdb(); + $self->assert_num_equals(2, scalar @$replicadata); + + xlog $self, "cancel first email submission"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + update => { + $msgsubid1 => { + "undoStatus" => "canceled", + } + }, + }, + 'R3' + ], + ]); + + $self->assert_not_null($res->[0][1]{updated}); + $self->assert_null($res->[0][1]{notUpdated}); + + xlog $self, "one event left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); + + $self->run_replication(); + + xlog $self, "one event left in the alarmdb"; + $replicadata = $self->{replica}->getalarmdb(); + $self->assert_num_equals(1, scalar @$replicadata); + + $res = $jmap->CallMethods( + [ [ 'EmailSubmission/get', { ids => [$msgsubid1] }, "R4" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); + $self->assert_str_equals('canceled', $res->[0][1]->{list}[0]->{undoStatus}); + + xlog $self, "destroy first email submission"; + $res = $jmap->CallMethods([ + [ + 'EmailSubmission/set', + { + destroy => [$msgsubid1] + }, + 'R5' + ], + ]); + + $self->assert_not_null($res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{notDestroyed}); + + xlog $self, "one event left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(1, scalar @$alarmdata); + + $res + = $jmap->CallMethods([ [ 'EmailSubmission/get', { ids => undef }, "R6" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); + + xlog $self, "trigger delivery of second email submission"; + my $now = DateTime->now(); + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $now->epoch() + 120); + + $res = $jmap->CallMethods( + [ [ 'EmailSubmission/get', { ids => [$msgsubid2] }, "R7" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{list} }); + $self->assert_deep_equals([], $res->[0][1]->{notFound}); + $self->assert_str_equals('final', $res->[0][1]->{list}[0]->{undoStatus}); + + xlog $self, "no events left in the alarmdb"; + $alarmdata = $self->{instance}->getalarmdb(); + $self->assert_num_equals(0, scalar @$alarmdata); + + $self->run_replication(); + + xlog $self, "no replica events left in the alarmdb"; + $replicadata = $self->{replica}->getalarmdb(); + $self->assert_num_equals(0, scalar @$replicadata); } diff --git a/cassandane/tiny-tests/JMAPMailbox/cyr_237 b/cassandane/tiny-tests/JMAPMailbox/cyr_237 index e8988e8789..ef3b5e587d 100644 --- a/cassandane/tiny-tests/JMAPMailbox/cyr_237 +++ b/cassandane/tiny-tests/JMAPMailbox/cyr_237 @@ -2,62 +2,69 @@ use Cassandane::Tiny; sub test_cyr_237 - :min_version_3_3 :needs_component_jmap :NoAltNameSpace :JMAPExtensions -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); - - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - ]; - - xlog $self, "Create \\Scheduled mailbox"; - my $res = $jmap->CallMethods([ - [ 'Mailbox/set', { - create => { - "1" => { - name => "Scheduled", - role => "scheduled" - } - } - }, "R1"], - ]); - - xlog $self, "Upload something (to create #jmap)"; - my $data = $jmap->Upload("some text", "text/plain"); - - my $acl = $admin->getacl("user.cassandane.#jmap"); - my %map = @$acl; - $self->assert_str_equals('lrswipkxtecdan', $map{cassandane}); - $self->assert_null($map{sharee}); - $self->assert_null($map{'-anyone'}); - - my $inboxId = $self->getinbox()->{id}; - $self->assert_not_null($inboxId); - - xlog $self, "Share INBOX"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $inboxId => { - shareWith => { - sharee => { - mayRead => JSON::true, - mayWrite => JSON::true, - }, - }, - }, + : min_version_3_3 : needs_component_jmap : NoAltNameSpace : JMAPExtensions { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); + + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + ]; + + xlog $self, "Create \\Scheduled mailbox"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "Scheduled", + role => "scheduled" + } + } + }, + "R1" + ], + ]); + + xlog $self, "Upload something (to create #jmap)"; + my $data = $jmap->Upload("some text", "text/plain"); + + my $acl = $admin->getacl("user.cassandane.#jmap"); + my %map = @$acl; + $self->assert_str_equals('lrswipkxtecdan', $map{cassandane}); + $self->assert_null($map{sharee}); + $self->assert_null($map{'-anyone'}); + + my $inboxId = $self->getinbox()->{id}; + $self->assert_not_null($inboxId); + + xlog $self, "Share INBOX"; + $res = $jmap->CallMethods( + [ [ + 'Mailbox/set', + { + update => { + $inboxId => { + shareWith => { + sharee => { + mayRead => JSON::true, + mayWrite => JSON::true, + }, }, - }, 'R2'] - ], $using); - - $acl = $admin->getacl("user.cassandane.#jmap"); - %map = @$acl; - $self->assert_str_equals('lrswipkxtecdan', $map{cassandane}); - $self->assert_str_equals('lrswitedn', $map{sharee}); - $self->assert_null($map{'-anyone'}); + }, + }, + }, + 'R2' + ] ], + $using + ); + + $acl = $admin->getacl("user.cassandane.#jmap"); + %map = @$acl; + $self->assert_str_equals('lrswipkxtecdan', $map{cassandane}); + $self->assert_str_equals('lrswitedn', $map{sharee}); + $self->assert_null($map{'-anyone'}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes index 5f80d0477f..b8c7a96f9e 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes @@ -2,154 +2,177 @@ use Cassandane::Tiny; sub test_mailbox_changes - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $state; - my $res; - my %m; - my $inbox; - my $foo; - my $drafts; - - xlog $self, "get mailbox list"; - $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $state = $res->[0][1]->{state}; - $self->assert_not_null($state); - %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $inbox = $m{"Inbox"}->{id}; - $self->assert_not_null($inbox); - - xlog $self, "get mailbox updates (expect error)"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => 0 }, "R1"]]); - $self->assert_str_equals("cannotCalculateChanges", $res->[0][1]->{type}); - - xlog $self, "get mailbox updates (expect no changes)"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{updatedProperties}); - - xlog $self, "create mailbox via IMAP"; - $imaptalk->create("INBOX.foo") - or die "Cannot create mailbox INBOX.foo: $@"; - - xlog $self, "get mailbox list"; - $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $foo = $m{"foo"}->{id}; - $self->assert_not_null($foo); - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals($foo, $res->[0][1]{created}[0]); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{updatedProperties}); - $state = $res->[0][1]->{newState}; - - xlog $self, "create drafts mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $drafts = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($drafts); - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - $self->assert_str_equals($drafts, $res->[0][1]{created}[0]); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{updatedProperties}); - $state = $res->[0][1]->{newState}; - - xlog $self, "rename mailbox foo to bar"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { update => { $foo => { - name => "bar", - sortOrder => 20 - }}}, "R1"] - ]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{updated}}); - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($foo, $res->[0][1]{updated}[0]); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{updatedProperties}); - $state = $res->[0][1]->{newState}; - - xlog $self, "delete mailbox bar"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [ $foo ], - }, "R1"] - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - - xlog $self, "rename mailbox drafts to stfard"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { $drafts => { name => "stfard" } }, - }, "R1"] - ]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{updated}}); - - xlog $self, "get mailbox updates, limit to 1"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state, maxChanges => 1 }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::true, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_str_equals($foo, $res->[0][1]{destroyed}[0]); - $self->assert_null($res->[0][1]{updatedProperties}); - $state = $res->[0][1]->{newState}; - - xlog $self, "get mailbox updates, limit to 1"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state, maxChanges => 1 }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_str_equals($drafts, $res->[0][1]{updated}[0]); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{updatedProperties}); - $state = $res->[0][1]->{newState}; - - xlog $self, "get mailbox updates (expect no changes)"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{updatedProperties}); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $state; + my $res; + my %m; + my $inbox; + my $foo; + my $drafts; + + xlog $self, "get mailbox list"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $state = $res->[0][1]->{state}; + $self->assert_not_null($state); + %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $inbox = $m{"Inbox"}->{id}; + $self->assert_not_null($inbox); + + xlog $self, "get mailbox updates (expect error)"; + $res + = $jmap->CallMethods([ [ 'Mailbox/changes', { sinceState => 0 }, "R1" ] ]); + $self->assert_str_equals("cannotCalculateChanges", $res->[0][1]->{type}); + + xlog $self, "get mailbox updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{updatedProperties}); + + xlog $self, "create mailbox via IMAP"; + $imaptalk->create("INBOX.foo") + or die "Cannot create mailbox INBOX.foo: $@"; + + xlog $self, "get mailbox list"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $foo = $m{"foo"}->{id}; + $self->assert_not_null($foo); + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals($foo, $res->[0][1]{created}[0]); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{updatedProperties}); + $state = $res->[0][1]->{newState}; + + xlog $self, "create drafts mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $drafts = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($drafts); + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + $self->assert_str_equals($drafts, $res->[0][1]{created}[0]); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{updatedProperties}); + $state = $res->[0][1]->{newState}; + + xlog $self, "rename mailbox foo to bar"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $foo => { + name => "bar", + sortOrder => 20 + } + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{updated} }); + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($foo, $res->[0][1]{updated}[0]); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{updatedProperties}); + $state = $res->[0][1]->{newState}; + + xlog $self, "delete mailbox bar"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + destroy => [$foo], + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + + xlog $self, "rename mailbox drafts to stfard"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { $drafts => { name => "stfard" } }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{updated} }); + + xlog $self, "get mailbox updates, limit to 1"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state, maxChanges => 1 }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::true, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_str_equals($foo, $res->[0][1]{destroyed}[0]); + $self->assert_null($res->[0][1]{updatedProperties}); + $state = $res->[0][1]->{newState}; + + xlog $self, "get mailbox updates, limit to 1"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state, maxChanges => 1 }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_str_equals($drafts, $res->[0][1]{updated}[0]); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{updatedProperties}); + $state = $res->[0][1]->{newState}; + + xlog $self, "get mailbox updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{updatedProperties}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_counts b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_counts index 246ac4cc4d..6575258ff9 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_counts +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_counts @@ -2,127 +2,152 @@ use Cassandane::Tiny; sub test_mailbox_changes_counts - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_not_null($res->[0][1]{created}); - my $mboxid = $res->[0][1]{created}{"1"}{id}; - my $state = $res->[0][1]{newState}; - - my $draft = { - mailboxIds => { $mboxid => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo", - textBody => [{partId=>'1'}], - bodyValues => { 1 => { value => "foo" }}, - keywords => { + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + xlog $self, "create drafts mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "drafts", + parentId => undef, + role => "drafts" + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_not_null($res->[0][1]{created}); + my $mboxid = $res->[0][1]{created}{"1"}{id}; + my $state = $res->[0][1]{newState}; + + my $draft = { + mailboxIds => { $mboxid => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { 1 => { value => "foo" } }, + keywords => { + '$draft' => JSON::true, + }, + }; + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $state = $res->[0][1]{newState}; + + xlog $self, "Create a draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + my $msgid = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "update email"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $msgid => { + keywords => { '$draft' => JSON::true, - }, - }; - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $state = $res->[0][1]{newState}; - - xlog $self, "Create a draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - my $msgid = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "update email"; - $res = $jmap->CallMethods([['Email/set', { - update => { $msgid => { - keywords => { - '$draft' => JSON::true, - '$seen' => JSON::true - } - } - } - }, "R1"]]); - $self->assert(exists $res->[0][1]->{updated}{$msgid}); - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updatedProperties}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_not_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]{newState}; - - xlog $self, "update mailbox"; - $res = $jmap->CallMethods([['Mailbox/set', { update => { $mboxid => { name => "bar" }}}, "R1"]]); - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_null($res->[0][1]{updatedProperties}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_not_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]{newState}; - - xlog $self, "update email"; - $res = $jmap->CallMethods([['Email/set', { update => { $msgid => { 'keywords/$flagged' => JSON::true }} - }, "R1"]]); - $self->assert(exists $res->[0][1]->{updated}{$msgid}); - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updatedProperties}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_not_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]{newState}; - - xlog $self, "update mailbox"; - $res = $jmap->CallMethods([['Mailbox/set', { update => { $mboxid => { name => "baz" }}}, "R1"]]); - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_null($res->[0][1]{updatedProperties}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_not_equals(0, scalar @{$res->[0][1]{updated}}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]{newState}; - - xlog $self, "get mailbox updates (expect no changes)"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]{newState}); - $self->assert_null($res->[0][1]{updatedProperties}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]{newState}; - - $draft->{subject} = "memo2"; - - xlog $self, "Create another draft"; - $res = $jmap->CallMethods([['Email/set', { create => { "1" => $draft }}, "R1"]]); - $msgid = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{updatedProperties}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_num_not_equals(0, scalar $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $state = $res->[0][1]{newState}; + '$seen' => JSON::true + } + } + } + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]->{updated}{$msgid}); + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updatedProperties}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_not_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]{newState}; + + xlog $self, "update mailbox"; + $res + = $jmap->CallMethods( + [ [ 'Mailbox/set', { update => { $mboxid => { name => "bar" } } }, "R1" ] ] + ); + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_null($res->[0][1]{updatedProperties}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_not_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]{newState}; + + xlog $self, "update email"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { update => { $msgid => { 'keywords/$flagged' => JSON::true } } }, "R1" + ] ]); + $self->assert(exists $res->[0][1]->{updated}{$msgid}); + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updatedProperties}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_not_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]{newState}; + + xlog $self, "update mailbox"; + $res + = $jmap->CallMethods( + [ [ 'Mailbox/set', { update => { $mboxid => { name => "baz" } } }, "R1" ] ] + ); + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_null($res->[0][1]{updatedProperties}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_not_equals(0, scalar @{ $res->[0][1]{updated} }); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]{newState}; + + xlog $self, "get mailbox updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]{newState}); + $self->assert_null($res->[0][1]{updatedProperties}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]{newState}; + + $draft->{subject} = "memo2"; + + xlog $self, "Create another draft"; + $res = $jmap->CallMethods( + [ [ 'Email/set', { create => { "1" => $draft } }, "R1" ] ]); + $msgid = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get mailbox updates"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{updatedProperties}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_num_not_equals(0, scalar $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $state = $res->[0][1]{newState}; } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_notes b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_notes index 881f3fc155..5dd889a075 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_notes +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_notes @@ -2,48 +2,45 @@ use Cassandane::Tiny; sub test_mailbox_changes_notes - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $state; - my $res; - my %m; - my $inbox; + my $jmap = $self->{jmap}; + my $state; + my $res; + my %m; + my $inbox; - xlog $self, "get mailbox list"; - $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $state = $res->[0][1]->{state}; - $self->assert_not_null($state); - %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $inbox = $m{"Inbox"}->{id}; - $self->assert_not_null($inbox); + xlog $self, "get mailbox list"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $state = $res->[0][1]->{state}; + $self->assert_not_null($state); + %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $inbox = $m{"Inbox"}->{id}; + $self->assert_not_null($inbox); - # we need 'https://cyrusimap.org/ns/jmap/notes' capability - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/notes'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/notes' capability + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/notes'; + $jmap->DefaultUsing(\@using); - # force creation of notes mailbox prior to creating notes - $res = $jmap->CallMethods([ - ['Note/set', { - }, "R0"] - ]); + # force creation of notes mailbox prior to creating notes + $res = $jmap->CallMethods([ [ 'Note/set', {}, "R0" ] ]); - xlog "create note"; - $res = $jmap->CallMethods([['Note/set', - { create => { "1" => {title => "foo"}, } }, - "R1"]]); - $self->assert_not_null($res); + xlog "create note"; + $res = $jmap->CallMethods([ [ + 'Note/set', { create => { "1" => { title => "foo" }, } }, "R1" + ] ]); + $self->assert_not_null($res); - xlog $self, "get mailbox updates (expect no changes)"; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]{updatedProperties}); + xlog $self, "get mailbox updates (expect no changes)"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_equals(JSON::false, $res->[0][1]->{hasMoreChanges}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]{updatedProperties}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_on_thread_counts b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_on_thread_counts index d8af89e114..41b51d115f 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_on_thread_counts +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_on_thread_counts @@ -2,29 +2,32 @@ use Cassandane::Tiny; sub test_mailbox_changes_on_thread_counts - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - $imap->uid(1); + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + $imap->uid(1); - xlog "Set up mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { }, 'R1'], - ['Mailbox/set', { - create => { - "a" => { name => "a", parentId => undef }, - "b" => { name => "b", parentId => undef }, - }, - }, 'R2'], - ]); - my %ids = map { $_ => $res->[1][1]{created}{$_}{id} } - keys %{$res->[1][1]{created}}; + xlog "Set up mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], + [ + 'Mailbox/set', + { + create => { + "a" => { name => "a", parentId => undef }, + "b" => { name => "b", parentId => undef }, + }, + }, + 'R2' + ], + ]); + my %ids = map { $_ => $res->[1][1]{created}{$_}{id} } + keys %{ $res->[1][1]{created} }; - xlog "Set up messages"; - my %raw = ( - A => <<"EOF", + xlog "Set up messages"; + my %raw = ( + A => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -35,7 +38,7 @@ Content-Type: text/plain\r \r test A\r EOF - B => <<"EOF", + B => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -48,7 +51,7 @@ In-Reply-To: \r \r test B\r EOF - C => <<"EOF", + C => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -61,7 +64,7 @@ In-Reply-To: \r \r test C\r EOF - D => <<"EOF", + D => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -72,37 +75,35 @@ Content-Type: text/plain\r \r test D\r EOF - ); + ); - # threads: - # T1: A B C - # T2: D + # threads: + # T1: A B C + # T2: D - xlog $self, "Set up all the emails in all the folders"; - $imap->append('INBOX.a', "(\\Seen)", $raw{A}) || die $@; - $imap->append('INBOX.a', "()", $raw{B}) || die $@; - $imap->append('INBOX.b', "(\\Seen)", $raw{C}) || die $@; - $imap->append('INBOX.a', "()", $raw{D}) || die $@; + xlog $self, "Set up all the emails in all the folders"; + $imap->append('INBOX.a', "(\\Seen)", $raw{A}) || die $@; + $imap->append('INBOX.a', "()", $raw{B}) || die $@; + $imap->append('INBOX.b', "(\\Seen)", $raw{C}) || die $@; + $imap->append('INBOX.a', "()", $raw{D}) || die $@; - # expectation: - # A (a:1, seen) - # B (a:2, unseen) - # C (b:1, seen) - # D (a:3 unseen) + # expectation: + # A (a:1, seen) + # B (a:2, unseen) + # C (b:1, seen) + # D (a:3 unseen) - my $predata = $jmap->CallMethods([ - ['Mailbox/get', { }, 'R1'], - ]); + my $predata = $jmap->CallMethods([ [ 'Mailbox/get', {}, 'R1' ], ]); - xlog $self, "mark thread seen"; - $imap->select("INBOX.a"); - $imap->store(2, "+flags", "\\Seen"); + xlog $self, "mark thread seen"; + $imap->select("INBOX.a"); + $imap->store(2, "+flags", "\\Seen"); - my $postdata = $jmap->CallMethods([ - ['Mailbox/changes', { sinceState => $predata->[0][1]{state} }, 'R1'], - ]); + my $postdata = $jmap->CallMethods([ + [ 'Mailbox/changes', { sinceState => $predata->[0][1]{state} }, 'R1' ], + ]); - my %changed = map { $_ => 1 } @{$postdata->[0][1]{updated}}; - $self->assert_not_null($changed{$ids{a}}); - $self->assert_not_null($changed{$ids{b}}); + my %changed = map { $_ => 1 } @{ $postdata->[0][1]{updated} }; + $self->assert_not_null($changed{ $ids{a} }); + $self->assert_not_null($changed{ $ids{b} }); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_rename b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_rename index 6faa2d3e2c..d21e9f2b7a 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_rename +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_rename @@ -2,50 +2,52 @@ use Cassandane::Tiny; sub test_mailbox_changes_rename - :min_version_3_5 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - - - $imap->create('INBOX.foo'); - - my $res = $jmap->CallMethods([ - ['Mailbox/get', { }, 'R1'], - ]); - my $fooId; - if ($res->[0][1]{list}[0]{name} eq 'foo') { - $fooId = $res->[0][1]{list}[0]{id}; - } - else { - $fooId = $res->[0][1]{list}[1]{id}; - } - $self->assert_not_null($fooId); - my $state = $res->[0][1]{state}; - $self->assert_not_null($state); - - $imap->create('INBOX.bar'); - - $res = $jmap->CallMethods([ - ['Mailbox/changes', { - sinceState => $state, - }, 'R1'], - ]); - my $barId = $res->[0][1]{created}[0]; - $self->assert_not_null($barId); - $state = $res->[0][1]{newState}; - $self->assert_not_null($state); - - - $imap->rename('INBOX.foo', 'INBOX.bar.foo'); - - $res = $jmap->CallMethods([ - ['Mailbox/changes', { - sinceState => $state, - }, 'R1'], - ]); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([$fooId], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); + : min_version_3_5 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + + $imap->create('INBOX.foo'); + + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, 'R1' ], ]); + my $fooId; + if ($res->[0][1]{list}[0]{name} eq 'foo') { + $fooId = $res->[0][1]{list}[0]{id}; + } else { + $fooId = $res->[0][1]{list}[1]{id}; + } + $self->assert_not_null($fooId); + my $state = $res->[0][1]{state}; + $self->assert_not_null($state); + + $imap->create('INBOX.bar'); + + $res = $jmap->CallMethods([ + [ + 'Mailbox/changes', + { + sinceState => $state, + }, + 'R1' + ], + ]); + my $barId = $res->[0][1]{created}[0]; + $self->assert_not_null($barId); + $state = $res->[0][1]{newState}; + $self->assert_not_null($state); + + $imap->rename('INBOX.foo', 'INBOX.bar.foo'); + + $res = $jmap->CallMethods([ + [ + 'Mailbox/changes', + { + sinceState => $state, + }, + 'R1' + ], + ]); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([$fooId], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_shared b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_shared index e562dd96e5..44662bad53 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_shared +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_changes_shared @@ -2,117 +2,144 @@ use Cassandane::Tiny; sub test_mailbox_changes_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - # Create user and share mailbox - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lrwkxd") or die; - - xlog $self, "get mailbox list"; - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => 'foo' }, "R1"]]); - my $state = $res->[0][1]->{state}; - $self->assert_not_null($state); - - xlog $self, "get mailbox updates (expect no changes)"; - $res = $jmap->CallMethods([['Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_equals($state, $res->[0][1]->{newState}); - $self->assert_null($res->[0][1]->{updatedProperties}); - - xlog $self, "create mailbox box1 via IMAP"; - $admintalk->create("user.foo.box1") or die; - $admintalk->setacl("user.foo.box1", "cassandane", "lrwkxd") or die; - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{created}}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]->{updatedProperties}); - $state = $res->[0][1]->{newState}; - my $box1 = $res->[0][1]->{created}[0]; - - xlog $self, "destroy mailbox via JMAP"; - $res = $jmap->CallMethods([['Mailbox/set', { accountId => "foo", destroy => [ $box1 ] }, 'R1' ]]); - $self->assert_str_equals($box1, $res->[0][1]{destroyed}[0]); - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{destroyed}}); - $self->assert_str_equals($box1, $res->[0][1]->{destroyed}[0]); - $self->assert_null($res->[0][1]->{updatedProperties}); - $state = $res->[0][1]->{newState}; - - xlog $self, "create mailbox box2 via IMAP"; - $admintalk->create("user.foo.box2") or die; - $admintalk->setacl("user.foo.box2", "cassandane", "lrwkxinepd") or die; - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{created}}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_null($res->[0][1]->{updatedProperties}); - $state = $res->[0][1]->{newState}; - - my $box2 = $res->[0][1]->{created}[0]; - - xlog $self, "Create a draft"; - my $draft = { - mailboxIds => { $box2 => JSON::true }, - from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ] , - to => [ - { name => "Bugs Bunny", email => "bugs\@acme.local" }, - ], - subject => "Memo", - textBody => [{partId=>'1'}], - bodyValues => { 1 => { value => "foo" }}, - keywords => { - '$draft' => JSON::true, - }, - }; - $res = $jmap->CallMethods([['Email/set', { - accountId => 'foo', - create => { "1" => $draft } - }, "R1"]]); - my $msgid = $res->[0][1]{created}{"1"}{id}; - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([$box2], $res->[0][1]{updated}); - $self->assert_deep_equals([], $res->[0][1]{destroyed}); - $self->assert_not_null($res->[0][1]->{updatedProperties}); - $state = $res->[0][1]->{newState}; - - xlog $self, "Remove lookup rights on box2"; - $admintalk->setacl("user.foo.box2", "cassandane", "") or die; - - xlog $self, "get mailbox updates"; - $res = $jmap->CallMethods([['Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{oldState}); - $self->assert_str_not_equals($state, $res->[0][1]->{newState}); - $self->assert_deep_equals([], $res->[0][1]{created}); - $self->assert_deep_equals([], $res->[0][1]{updated}); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{destroyed}}); - $self->assert_str_equals($box2, $res->[0][1]->{destroyed}[0]); - $self->assert_null($res->[0][1]->{updatedProperties}); - $state = $res->[0][1]->{newState}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + # Create user and share mailbox + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lrwkxd") or die; + + xlog $self, "get mailbox list"; + my $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => 'foo' }, "R1" ] ]); + my $state = $res->[0][1]->{state}; + $self->assert_not_null($state); + + xlog $self, "get mailbox updates (expect no changes)"; + $res + = $jmap->CallMethods( + [ [ + 'Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_equals($state, $res->[0][1]->{newState}); + $self->assert_null($res->[0][1]->{updatedProperties}); + + xlog $self, "create mailbox box1 via IMAP"; + $admintalk->create("user.foo.box1") or die; + $admintalk->setacl("user.foo.box1", "cassandane", "lrwkxd") or die; + + xlog $self, "get mailbox updates"; + $res + = $jmap->CallMethods( + [ [ + 'Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{created} }); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]->{updatedProperties}); + $state = $res->[0][1]->{newState}; + my $box1 = $res->[0][1]->{created}[0]; + + xlog $self, "destroy mailbox via JMAP"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/set', { accountId => "foo", destroy => [$box1] }, 'R1' ] ]); + $self->assert_str_equals($box1, $res->[0][1]{destroyed}[0]); + + xlog $self, "get mailbox updates"; + $res + = $jmap->CallMethods( + [ [ + 'Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{destroyed} }); + $self->assert_str_equals($box1, $res->[0][1]->{destroyed}[0]); + $self->assert_null($res->[0][1]->{updatedProperties}); + $state = $res->[0][1]->{newState}; + + xlog $self, "create mailbox box2 via IMAP"; + $admintalk->create("user.foo.box2") or die; + $admintalk->setacl("user.foo.box2", "cassandane", "lrwkxinepd") or die; + + xlog $self, "get mailbox updates"; + $res + = $jmap->CallMethods( + [ [ + 'Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{created} }); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_null($res->[0][1]->{updatedProperties}); + $state = $res->[0][1]->{newState}; + + my $box2 = $res->[0][1]->{created}[0]; + + xlog $self, "Create a draft"; + my $draft = { + mailboxIds => { $box2 => JSON::true }, + from => [ { name => "Yosemite Sam", email => "sam\@acme.local" } ], + to => [ { name => "Bugs Bunny", email => "bugs\@acme.local" }, ], + subject => "Memo", + textBody => [ { partId => '1' } ], + bodyValues => { 1 => { value => "foo" } }, + keywords => { + '$draft' => JSON::true, + }, + }; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + accountId => 'foo', + create => { "1" => $draft } + }, + "R1" + ] ]); + my $msgid = $res->[0][1]{created}{"1"}{id}; + + xlog $self, "get mailbox updates"; + $res + = $jmap->CallMethods( + [ [ + 'Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([$box2], $res->[0][1]{updated}); + $self->assert_deep_equals([], $res->[0][1]{destroyed}); + $self->assert_not_null($res->[0][1]->{updatedProperties}); + $state = $res->[0][1]->{newState}; + + xlog $self, "Remove lookup rights on box2"; + $admintalk->setacl("user.foo.box2", "cassandane", "") or die; + + xlog $self, "get mailbox updates"; + $res + = $jmap->CallMethods( + [ [ + 'Mailbox/changes', { accountId => 'foo', sinceState => $state }, "R1" ] ] + ); + $self->assert_str_equals($state, $res->[0][1]->{oldState}); + $self->assert_str_not_equals($state, $res->[0][1]->{newState}); + $self->assert_deep_equals([], $res->[0][1]{created}); + $self->assert_deep_equals([], $res->[0][1]{updated}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{destroyed} }); + $self->assert_str_equals($box2, $res->[0][1]->{destroyed}[0]); + $self->assert_null($res->[0][1]->{updatedProperties}); + $state = $res->[0][1]->{newState}; } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_counts b/cassandane/tiny-tests/JMAPMailbox/mailbox_counts index 5b1cf2a83c..ee833d5955 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_counts +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_counts @@ -2,35 +2,38 @@ use Cassandane::Tiny; sub test_mailbox_counts - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - $imap->uid(1); - my ($maj, $min) = Cassandane::Instance->get_version(); + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + $imap->uid(1); + my ($maj, $min) = Cassandane::Instance->get_version(); - xlog "Set up mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { }, 'R1'], - ['Mailbox/set', { - create => { - "a" => { name => "a", parentId => undef }, - "b" => { name => "b", parentId => undef }, - "trash" => { - name => "Trash", - parentId => undef, - role => "trash" - } - }, - }, 'R2'], - ]); - my %ids = map { $_ => $res->[1][1]{created}{$_}{id} } - keys %{$res->[1][1]{created}}; + xlog "Set up mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], + [ + 'Mailbox/set', + { + create => { + "a" => { name => "a", parentId => undef }, + "b" => { name => "b", parentId => undef }, + "trash" => { + name => "Trash", + parentId => undef, + role => "trash" + } + }, + }, + 'R2' + ], + ]); + my %ids = map { $_ => $res->[1][1]{created}{$_}{id} } + keys %{ $res->[1][1]{created} }; - xlog "Append same message twice to inbox"; - my %raw = ( - A => <<"EOF", + xlog "Append same message twice to inbox"; + my %raw = ( + A => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -41,7 +44,7 @@ Content-Type: text/plain\r \r test A\r EOF - B => <<"EOF", + B => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -54,7 +57,7 @@ In-Reply-To: \r \r test B\r EOF - C => <<"EOF", + C => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -67,7 +70,7 @@ In-Reply-To: \r \r test C\r EOF - D => <<"EOF", + D => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -78,7 +81,7 @@ Content-Type: text/plain\r \r test D\r EOF - E => <<"EOF", + E => <<"EOF", From: \r To: to\@local\r Subject: test3\r @@ -90,7 +93,7 @@ Content-Type: text/plain\r \r test E\r EOF - F => <<"EOF", + F => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -101,7 +104,7 @@ Content-Type: text/plain\r \r test F\r EOF - G => <<"EOF", + G => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -113,158 +116,168 @@ Content-Type: text/plain\r \r test D\r EOF - ); + ); - # threads: - # T1: A B C - # T2: D - # T3: E - # T4: F G (in-reply-to E, but different subject) + # threads: + # T1: A B C + # T2: D + # T3: E + # T4: F G (in-reply-to E, but different subject) - xlog $self, "Set up all the emails in all the folders"; - $imap->append('INBOX.a', "(\\Seen)", $raw{A}) || die $@; - $imap->append('INBOX.a', "()", $raw{A}) || die $@; - $imap->append('INBOX.a', "(\\Seen)", $raw{C}) || die $@; - $imap->append('INBOX.a', "(\\Seen)", $raw{D}) || die $@; - $imap->append('INBOX.a', "()", $raw{E}) || die $@; - $imap->append('INBOX.a', "(\\Seen)", $raw{F}) || die $@; - $imap->append('INBOX.b', "()", $raw{B}) || die $@; - $imap->append('INBOX.b', "(\\Seen)", $raw{C}) || die $@; - $imap->append('INBOX.b', "(\\Seen)", $raw{E}) || die $@; - $imap->append('INBOX.Trash', "(\\Seen)", $raw{G}) || die $@; + xlog $self, "Set up all the emails in all the folders"; + $imap->append('INBOX.a', "(\\Seen)", $raw{A}) || die $@; + $imap->append('INBOX.a', "()", $raw{A}) || die $@; + $imap->append('INBOX.a', "(\\Seen)", $raw{C}) || die $@; + $imap->append('INBOX.a', "(\\Seen)", $raw{D}) || die $@; + $imap->append('INBOX.a', "()", $raw{E}) || die $@; + $imap->append('INBOX.a', "(\\Seen)", $raw{F}) || die $@; + $imap->append('INBOX.b', "()", $raw{B}) || die $@; + $imap->append('INBOX.b', "(\\Seen)", $raw{C}) || die $@; + $imap->append('INBOX.b', "(\\Seen)", $raw{E}) || die $@; + $imap->append('INBOX.Trash', "(\\Seen)", $raw{G}) || die $@; - # expectation: - # A (a:1, seen - a:2, unseen) == unseen - # B (b:1, unseen) - # C (a:3, seen - b:2, seen) - # D (a:4, seen) - # E (a:5, unseen - b:3, seen) == unseen - # F (a:6, seen) - # G (trash:1, seen) + # expectation: + # A (a:1, seen - a:2, unseen) == unseen + # B (b:1, unseen) + # C (a:3, seen - b:2, seen) + # D (a:4, seen) + # E (a:5, unseen - b:3, seen) == unseen + # F (a:6, seen) + # G (trash:1, seen) - # T1 in (a,b) unseen - # T2 in a, seen - # T3 in (a,b) unseen - # T4 in (a,trash) seen + # T1 in (a,b) unseen + # T2 in a, seen + # T3 in (a,b) unseen + # T4 in (a,trash) seen - if ($maj > 3 || ($maj == 3 && $min >= 6)) { - $self->_check_counts('Initial Test', - a => [ 5, 2, 4, 2 ], - b => [ 3, 2, 2, 2 ], - trash => [ 1, 0, 1, 0 ], - ); - } else { - $self->_check_counts('Initial Test', - a => [ 5, 2, 4, 2 ], - b => [ 3, 1, 2, 2 ], - trash => [ 1, 0, 1, 0 ], - ); - } + if ($maj > 3 || ($maj == 3 && $min >= 6)) { + $self->_check_counts( + 'Initial Test', + a => [ 5, 2, 4, 2 ], + b => [ 3, 2, 2, 2 ], + trash => [ 1, 0, 1, 0 ], + ); + } else { + $self->_check_counts( + 'Initial Test', + a => [ 5, 2, 4, 2 ], + b => [ 3, 1, 2, 2 ], + trash => [ 1, 0, 1, 0 ], + ); + } - xlog $self, "Move half an email to Trash"; - $imap->select("INBOX.a"); - $imap->move("2", "INBOX.Trash"); + xlog $self, "Move half an email to Trash"; + $imap->select("INBOX.a"); + $imap->move("2", "INBOX.Trash"); - # expectation: - # A (a:1, seen - trash:2, unseen) == unseen in trash, seen in inbox - # B (b:1, unseen) - # C (a:3, seen - b:2, seen) - # D (a:4, seen) - # E (a:5, unseen - b:3, seen) == unseen - # F (a:6, seen) - # G (trash:1, seen) + # expectation: + # A (a:1, seen - trash:2, unseen) == unseen in trash, seen in inbox + # B (b:1, unseen) + # C (a:3, seen - b:2, seen) + # D (a:4, seen) + # E (a:5, unseen - b:3, seen) == unseen + # F (a:6, seen) + # G (trash:1, seen) - if ($maj > 3 || ($maj == 3 && $min >= 6)) { - $self->_check_counts('After first move', - a => [ 5, 1, 4, 2 ], - b => [ 3, 2, 2, 2 ], - trash => [ 2, 1, 2, 1 ], - ); - } else { - $self->_check_counts('After first move', - a => [ 5, 1, 4, 2 ], - b => [ 3, 1, 2, 2 ], - trash => [ 2, 1, 2, 1 ], - ); - } + if ($maj > 3 || ($maj == 3 && $min >= 6)) { + $self->_check_counts( + 'After first move', + a => [ 5, 1, 4, 2 ], + b => [ 3, 2, 2, 2 ], + trash => [ 2, 1, 2, 1 ], + ); + } else { + $self->_check_counts( + 'After first move', + a => [ 5, 1, 4, 2 ], + b => [ 3, 1, 2, 2 ], + trash => [ 2, 1, 2, 1 ], + ); + } - xlog $self, "Mark the bits of the thread OUTSIDE Trash all seen"; - $imap->select("INBOX.b"); - $imap->store("1", "+flags", "(\\Seen)"); + xlog $self, "Mark the bits of the thread OUTSIDE Trash all seen"; + $imap->select("INBOX.b"); + $imap->store("1", "+flags", "(\\Seen)"); - # expectation: - # A (a:1, seen - trash:2, unseen) == unseen in trash, seen in inbox - # B (b:1, seen) - # C (a:3, seen - b:2, seen) - # D (a:4, seen) - # E (a:5, unseen - b:3, seen) == unseen - # F (a:6, seen) - # G (trash:1, seen) + # expectation: + # A (a:1, seen - trash:2, unseen) == unseen in trash, seen in inbox + # B (b:1, seen) + # C (a:3, seen - b:2, seen) + # D (a:4, seen) + # E (a:5, unseen - b:3, seen) == unseen + # F (a:6, seen) + # G (trash:1, seen) - if ($maj > 3 || ($maj == 3 && $min >= 6)) { - $self->_check_counts('Second change', - a => [ 5, 1, 4, 1 ], - b => [ 3, 1, 2, 1 ], - trash => [ 2, 1, 2, 1 ], - ); - } else { - $self->_check_counts('Second change', - a => [ 5, 1, 4, 1 ], - b => [ 3, 0, 2, 1 ], - trash => [ 2, 1, 2, 1 ], - ); - } + if ($maj > 3 || ($maj == 3 && $min >= 6)) { + $self->_check_counts( + 'Second change', + a => [ 5, 1, 4, 1 ], + b => [ 3, 1, 2, 1 ], + trash => [ 2, 1, 2, 1 ], + ); + } else { + $self->_check_counts( + 'Second change', + a => [ 5, 1, 4, 1 ], + b => [ 3, 0, 2, 1 ], + trash => [ 2, 1, 2, 1 ], + ); + } - xlog $self, "Delete a message we don't care about"; - $imap->select("INBOX.b"); - $imap->store("1", "+flags", "(\\Deleted)"); - $imap->expunge(); + xlog $self, "Delete a message we don't care about"; + $imap->select("INBOX.b"); + $imap->store("1", "+flags", "(\\Deleted)"); + $imap->expunge(); - # expectation: - # A (a:1, seen - trash:2, unseen) == unseen in trash, seen in inbox - # C (a:3, seen - b:2, seen) - # D (a:4, seen) - # E (a:5, unseen - b:3, seen) == unseen - # F (a:6, seen) - # G (trash:1, seen) + # expectation: + # A (a:1, seen - trash:2, unseen) == unseen in trash, seen in inbox + # C (a:3, seen - b:2, seen) + # D (a:4, seen) + # E (a:5, unseen - b:3, seen) == unseen + # F (a:6, seen) + # G (trash:1, seen) - if ($maj > 3 || ($maj == 3 && $min >= 6)) { - $self->_check_counts('Third change', - a => [ 5, 1, 4, 1 ], - b => [ 2, 1, 2, 1 ], - trash => [ 2, 1, 2, 1 ], - ); - } else { - $self->_check_counts('Third change', - a => [ 5, 1, 4, 1 ], - b => [ 2, 0, 2, 1 ], - trash => [ 2, 1, 2, 1 ], - ); - } + if ($maj > 3 || ($maj == 3 && $min >= 6)) { + $self->_check_counts( + 'Third change', + a => [ 5, 1, 4, 1 ], + b => [ 2, 1, 2, 1 ], + trash => [ 2, 1, 2, 1 ], + ); + } else { + $self->_check_counts( + 'Third change', + a => [ 5, 1, 4, 1 ], + b => [ 2, 0, 2, 1 ], + trash => [ 2, 1, 2, 1 ], + ); + } - xlog $self, "Delete some more"; - $imap->select("INBOX.a"); - $imap->store("1,3,6", "+flags", "(\\Deleted)"); - $imap->expunge(); + xlog $self, "Delete some more"; + $imap->select("INBOX.a"); + $imap->store("1,3,6", "+flags", "(\\Deleted)"); + $imap->expunge(); - # expectation: - # A (trash:2, unseen) == unseen in trash - # C (b:2, seen) - # D (a:4, seen) - # E (a:5, unseen - b:3, seen) == unseen - # G (trash:1, seen) + # expectation: + # A (trash:2, unseen) == unseen in trash + # C (b:2, seen) + # D (a:4, seen) + # E (a:5, unseen - b:3, seen) == unseen + # G (trash:1, seen) - if ($maj > 3 || ($maj == 3 && $min >= 6)) { - $self->_check_counts('Forth change', - a => [ 2, 1, 2, 1 ], - b => [ 2, 1, 2, 1 ], - trash => [ 2, 1, 2, 1 ], - ); - } else { - $self->_check_counts('Forth change', - a => [ 2, 1, 2, 1 ], - b => [ 2, 0, 2, 1 ], - trash => [ 2, 1, 2, 1 ], - ); - } + if ($maj > 3 || ($maj == 3 && $min >= 6)) { + $self->_check_counts( + 'Forth change', + a => [ 2, 1, 2, 1 ], + b => [ 2, 1, 2, 1 ], + trash => [ 2, 1, 2, 1 ], + ); + } else { + $self->_check_counts( + 'Forth change', + a => [ 2, 1, 2, 1 ], + b => [ 2, 0, 2, 1 ], + trash => [ 2, 1, 2, 1 ], + ); + } } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_counts_add_remove b/cassandane/tiny-tests/JMAPMailbox/mailbox_counts_add_remove index 811bb3f79e..56e81ab003 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_counts_add_remove +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_counts_add_remove @@ -2,29 +2,32 @@ use Cassandane::Tiny; sub test_mailbox_counts_add_remove - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - $imap->uid(1); - - xlog "Set up mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { }, 'R1'], - ['Mailbox/set', { - create => { - "a" => { name => "a", parentId => undef }, - "b" => { name => "b", parentId => undef }, - }, - }, 'R2'], - ]); - my %ids = map { $_ => $res->[1][1]{created}{$_}{id} } - keys %{$res->[1][1]{created}}; - - xlog "Set up messages"; - my %raw = ( - A => <<"EOF", + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + $imap->uid(1); + + xlog "Set up mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], + [ + 'Mailbox/set', + { + create => { + "a" => { name => "a", parentId => undef }, + "b" => { name => "b", parentId => undef }, + }, + }, + 'R2' + ], + ]); + my %ids = map { $_ => $res->[1][1]{created}{$_}{id} } + keys %{ $res->[1][1]{created} }; + + xlog "Set up messages"; + my %raw = ( + A => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -35,7 +38,7 @@ Content-Type: text/plain\r \r test A\r EOF - B => <<"EOF", + B => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -48,7 +51,7 @@ In-Reply-To: \r \r test B\r EOF - C => <<"EOF", + C => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -61,7 +64,7 @@ In-Reply-To: \r \r test C\r EOF - D => <<"EOF", + D => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -72,99 +75,105 @@ Content-Type: text/plain\r \r test D\r EOF - ); - - # threads: - # T1: A B C - # T2: D - - xlog $self, "Set up all the emails in all the folders"; - $imap->append('INBOX.a', "(\\Seen)", $raw{A}) || die $@; - $imap->append('INBOX.a', "()", $raw{B}) || die $@; - $imap->append('INBOX.a', "(\\Seen)", $raw{C}) || die $@; - $imap->append('INBOX.a', "()", $raw{D}) || die $@; - - # expectation: - # A (a:1, seen) - # B (a:2, unseen) - # C (a:3, seen) - # D (a:4 unseen) - - $self->_check_counts('Initial Test', - a => [ 4, 2, 2, 2 ], - b => [ 0, 0, 0, 0 ], - ); - - xlog $self, "Move email to b"; - $imap->select("INBOX.a"); - $imap->move("3", "INBOX.b"); - - # expectation: - # A (a:1, seen) - # B (a:2, unseen) - # C (b:1, seen) - # D (a:4 unseen) - - $self->_check_counts('After first move', - a => [ 3, 2, 2, 2 ], - b => [ 1, 0, 1, 1 ], - ); - - xlog $self, "mark seen"; - $imap->store(2, "+flags", "\\Seen"); - - # expectation: - # A (a:1, seen) - # B (a:2, seen) - # C (b:1, seen) - # D (a:4 unseen) - - $self->_check_counts('After mark seen', - a => [ 3, 1, 2, 1 ], - b => [ 1, 0, 1, 0 ], - ); - - xlog $self, "move other"; - $imap->move("4", "INBOX.b"); - - # expectation: - # A (a:1, seen) - # B (a:2, seen) - # C (b:1, seen) - # D (b:2 unseen) - - $self->_check_counts('After move other', - a => [ 2, 0, 1, 0 ], - b => [ 2, 1, 2, 1 ], - ); - - xlog $self, "move first back"; - $imap->select("INBOX.b"); - $imap->move("1", "INBOX.a"); - - # expectation: - # A (a:1, seen) - # B (a:2, seen) - # C (a:5, seen) - # D (b:2 unseen) - - $self->_check_counts('After move first back', - a => [ 3, 0, 1, 0 ], - b => [ 1, 1, 1, 1 ], - ); - - xlog $self, "mark unseen again (different email)"; - $imap->select("INBOX.a"); - $imap->store(1, "-flags", "\\Seen"); - - # expectation: - # A (a:1, unseen) - # B (a:2, seen) - # C (a:5, seen) - # D (b:2 unseen) - - $self->_check_counts('After mark unseen again', - a => [ 3, 1, 1, 1 ], - b => [ 1, 1, 1, 1 ], - ); + ); + + # threads: + # T1: A B C + # T2: D + + xlog $self, "Set up all the emails in all the folders"; + $imap->append('INBOX.a', "(\\Seen)", $raw{A}) || die $@; + $imap->append('INBOX.a', "()", $raw{B}) || die $@; + $imap->append('INBOX.a', "(\\Seen)", $raw{C}) || die $@; + $imap->append('INBOX.a', "()", $raw{D}) || die $@; + + # expectation: + # A (a:1, seen) + # B (a:2, unseen) + # C (a:3, seen) + # D (a:4 unseen) + + $self->_check_counts( + 'Initial Test', + a => [ 4, 2, 2, 2 ], + b => [ 0, 0, 0, 0 ], + ); + + xlog $self, "Move email to b"; + $imap->select("INBOX.a"); + $imap->move("3", "INBOX.b"); + + # expectation: + # A (a:1, seen) + # B (a:2, unseen) + # C (b:1, seen) + # D (a:4 unseen) + + $self->_check_counts( + 'After first move', + a => [ 3, 2, 2, 2 ], + b => [ 1, 0, 1, 1 ], + ); + + xlog $self, "mark seen"; + $imap->store(2, "+flags", "\\Seen"); + + # expectation: + # A (a:1, seen) + # B (a:2, seen) + # C (b:1, seen) + # D (a:4 unseen) + + $self->_check_counts( + 'After mark seen', + a => [ 3, 1, 2, 1 ], + b => [ 1, 0, 1, 0 ], + ); + + xlog $self, "move other"; + $imap->move("4", "INBOX.b"); + + # expectation: + # A (a:1, seen) + # B (a:2, seen) + # C (b:1, seen) + # D (b:2 unseen) + + $self->_check_counts( + 'After move other', + a => [ 2, 0, 1, 0 ], + b => [ 2, 1, 2, 1 ], + ); + + xlog $self, "move first back"; + $imap->select("INBOX.b"); + $imap->move("1", "INBOX.a"); + + # expectation: + # A (a:1, seen) + # B (a:2, seen) + # C (a:5, seen) + # D (b:2 unseen) + + $self->_check_counts( + 'After move first back', + a => [ 3, 0, 1, 0 ], + b => [ 1, 1, 1, 1 ], + ); + + xlog $self, "mark unseen again (different email)"; + $imap->select("INBOX.a"); + $imap->store(1, "-flags", "\\Seen"); + + # expectation: + # A (a:1, unseen) + # B (a:2, seen) + # C (a:5, seen) + # D (b:2 unseen) + + $self->_check_counts( + 'After mark unseen again', + a => [ 3, 1, 1, 1 ], + b => [ 1, 1, 1, 1 ], + ); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get b/cassandane/tiny-tests/JMAPMailbox/mailbox_get index 194b7d3a0d..0649e07517 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get @@ -2,79 +2,78 @@ use Cassandane::Tiny; sub test_mailbox_get - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.foo") - or die "Cannot create mailbox INBOX.foo: $@"; + $imaptalk->create("INBOX.foo") + or die "Cannot create mailbox INBOX.foo: $@"; - $imaptalk->create("INBOX.foo.bar") - or die "Cannot create mailbox INBOX.foo.bar: $@"; + $imaptalk->create("INBOX.foo.bar") + or die "Cannot create mailbox INBOX.foo.bar: $@"; - xlog $self, "get existing mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "get existing mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(3, scalar keys %m); - my $inbox = $m{"Inbox"}; - my $foo = $m{"foo"}; - my $bar = $m{"bar"}; + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(3, scalar keys %m); + my $inbox = $m{"Inbox"}; + my $foo = $m{"foo"}; + my $bar = $m{"bar"}; - # INBOX - $self->assert_str_equals("Inbox", $inbox->{name}); - $self->assert_null($inbox->{parentId}); - $self->assert_str_equals("inbox", $inbox->{role}); - $self->assert_num_equals(1, $inbox->{sortOrder}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::false, $inbox->{myRights}->{mayRename}); - $self->assert_equals(JSON::false, $inbox->{myRights}->{mayDelete}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{maySetSeen}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{maySetKeywords}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{maySubmit}); - $self->assert_num_equals(0, $inbox->{totalEmails}); - $self->assert_num_equals(0, $inbox->{unreadEmails}); - $self->assert_num_equals(0, $inbox->{totalThreads}); - $self->assert_num_equals(0, $inbox->{unreadThreads}); + # INBOX + $self->assert_str_equals("Inbox", $inbox->{name}); + $self->assert_null($inbox->{parentId}); + $self->assert_str_equals("inbox", $inbox->{role}); + $self->assert_num_equals(1, $inbox->{sortOrder}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::false, $inbox->{myRights}->{mayRename}); + $self->assert_equals(JSON::false, $inbox->{myRights}->{mayDelete}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{maySetSeen}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{maySetKeywords}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{maySubmit}); + $self->assert_num_equals(0, $inbox->{totalEmails}); + $self->assert_num_equals(0, $inbox->{unreadEmails}); + $self->assert_num_equals(0, $inbox->{totalThreads}); + $self->assert_num_equals(0, $inbox->{unreadThreads}); - # INBOX.foo - $self->assert_str_equals("foo", $foo->{name}); - $self->assert_null($foo->{parentId}); - $self->assert_null($foo->{role}); - $self->assert_num_equals(10, $foo->{sortOrder}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayRename}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayDelete}); - $self->assert_num_equals(0, $foo->{totalEmails}); - $self->assert_num_equals(0, $foo->{unreadEmails}); - $self->assert_num_equals(0, $foo->{totalThreads}); - $self->assert_num_equals(0, $foo->{unreadThreads}); + # INBOX.foo + $self->assert_str_equals("foo", $foo->{name}); + $self->assert_null($foo->{parentId}); + $self->assert_null($foo->{role}); + $self->assert_num_equals(10, $foo->{sortOrder}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayRename}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayDelete}); + $self->assert_num_equals(0, $foo->{totalEmails}); + $self->assert_num_equals(0, $foo->{unreadEmails}); + $self->assert_num_equals(0, $foo->{totalThreads}); + $self->assert_num_equals(0, $foo->{unreadThreads}); - # INBOX.foo.bar - $self->assert_str_equals("bar", $bar->{name}); - $self->assert_str_equals($foo->{id}, $bar->{parentId}); - $self->assert_null($bar->{role}); - $self->assert_num_equals(10, $bar->{sortOrder}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayRename}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayDelete}); - $self->assert_num_equals(0, $bar->{totalEmails}); - $self->assert_num_equals(0, $bar->{unreadEmails}); - $self->assert_num_equals(0, $bar->{totalThreads}); - $self->assert_num_equals(0, $bar->{unreadThreads}); + # INBOX.foo.bar + $self->assert_str_equals("bar", $bar->{name}); + $self->assert_str_equals($foo->{id}, $bar->{parentId}); + $self->assert_null($bar->{role}); + $self->assert_num_equals(10, $bar->{sortOrder}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayRename}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayDelete}); + $self->assert_num_equals(0, $bar->{totalEmails}); + $self->assert_num_equals(0, $bar->{unreadEmails}); + $self->assert_num_equals(0, $bar->{totalThreads}); + $self->assert_num_equals(0, $bar->{unreadThreads}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_ids b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_ids index daa30bd934..f5963ebbf9 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_ids +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_ids @@ -2,39 +2,39 @@ use Cassandane::Tiny; sub test_mailbox_get_ids - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.foo") || die; - - xlog $self, "get all mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $inbox = $m{"Inbox"}; - my $foo = $m{"foo"}; - $self->assert_not_null($inbox); - $self->assert_not_null($foo); - - xlog $self, "get foo and unknown mailbox"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$foo->{id}, "nope"] }, "R1"]]); - $self->assert_str_equals($foo->{id}, $res->[0][1]{list}[0]->{id}); - $self->assert_str_equals("nope", $res->[0][1]{notFound}[0]); - - xlog $self, "get mailbox with erroneous id"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [123]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - my $err = $res->[0][1]; - $self->assert_str_equals('invalidArguments', $err->{type}); - $self->assert_str_equals('ids[0]', $err->{arguments}[0]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.foo") || die; + + xlog $self, "get all mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $inbox = $m{"Inbox"}; + my $foo = $m{"foo"}; + $self->assert_not_null($inbox); + $self->assert_not_null($foo); + + xlog $self, "get foo and unknown mailbox"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { ids => [ $foo->{id}, "nope" ] }, "R1" ] ]); + $self->assert_str_equals($foo->{id}, $res->[0][1]{list}[0]->{id}); + $self->assert_str_equals("nope", $res->[0][1]{notFound}[0]); + + xlog $self, "get mailbox with erroneous id"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [123] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + my $err = $res->[0][1]; + $self->assert_str_equals('invalidArguments', $err->{type}); + $self->assert_str_equals('ids[0]', $err->{arguments}[0]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_inbox_sub b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_inbox_sub index 08416518d5..ea983f70d3 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_inbox_sub +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_inbox_sub @@ -2,79 +2,78 @@ use Cassandane::Tiny; sub test_mailbox_get_inbox_sub - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.INBOX.foo") - or die "Cannot create mailbox INBOX.INBOX.foo: $@"; + $imaptalk->create("INBOX.INBOX.foo") + or die "Cannot create mailbox INBOX.INBOX.foo: $@"; - $imaptalk->create("INBOX.INBOX.foo.bar") - or die "Cannot create mailbox INBOX.INBOX.foo.bar: $@"; + $imaptalk->create("INBOX.INBOX.foo.bar") + or die "Cannot create mailbox INBOX.INBOX.foo.bar: $@"; - xlog $self, "get existing mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "get existing mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(3, scalar keys %m); - my $inbox = $m{"Inbox"}; - my $foo = $m{"foo"}; - my $bar = $m{"bar"}; + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(3, scalar keys %m); + my $inbox = $m{"Inbox"}; + my $foo = $m{"foo"}; + my $bar = $m{"bar"}; - # INBOX - $self->assert_str_equals("Inbox", $inbox->{name}); - $self->assert_null($inbox->{parentId}); - $self->assert_str_equals("inbox", $inbox->{role}); - $self->assert_num_equals(1, $inbox->{sortOrder}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::false, $inbox->{myRights}->{mayRename}); - $self->assert_equals(JSON::false, $inbox->{myRights}->{mayDelete}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{maySetSeen}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{maySetKeywords}); - $self->assert_equals(JSON::true, $inbox->{myRights}->{maySubmit}); - $self->assert_num_equals(0, $inbox->{totalEmails}); - $self->assert_num_equals(0, $inbox->{unreadEmails}); - $self->assert_num_equals(0, $inbox->{totalThreads}); - $self->assert_num_equals(0, $inbox->{unreadThreads}); + # INBOX + $self->assert_str_equals("Inbox", $inbox->{name}); + $self->assert_null($inbox->{parentId}); + $self->assert_str_equals("inbox", $inbox->{role}); + $self->assert_num_equals(1, $inbox->{sortOrder}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::false, $inbox->{myRights}->{mayRename}); + $self->assert_equals(JSON::false, $inbox->{myRights}->{mayDelete}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{maySetSeen}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{maySetKeywords}); + $self->assert_equals(JSON::true, $inbox->{myRights}->{maySubmit}); + $self->assert_num_equals(0, $inbox->{totalEmails}); + $self->assert_num_equals(0, $inbox->{unreadEmails}); + $self->assert_num_equals(0, $inbox->{totalThreads}); + $self->assert_num_equals(0, $inbox->{unreadThreads}); - # INBOX.INBOX.foo - $self->assert_str_equals("foo", $foo->{name}); - $self->assert_str_equals($inbox->{id}, $foo->{parentId}); - $self->assert_null($foo->{role}); - $self->assert_num_equals(10, $foo->{sortOrder}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayRename}); - $self->assert_equals(JSON::true, $foo->{myRights}->{mayDelete}); - $self->assert_num_equals(0, $foo->{totalEmails}); - $self->assert_num_equals(0, $foo->{unreadEmails}); - $self->assert_num_equals(0, $foo->{totalThreads}); - $self->assert_num_equals(0, $foo->{unreadThreads}); + # INBOX.INBOX.foo + $self->assert_str_equals("foo", $foo->{name}); + $self->assert_str_equals($inbox->{id}, $foo->{parentId}); + $self->assert_null($foo->{role}); + $self->assert_num_equals(10, $foo->{sortOrder}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayRename}); + $self->assert_equals(JSON::true, $foo->{myRights}->{mayDelete}); + $self->assert_num_equals(0, $foo->{totalEmails}); + $self->assert_num_equals(0, $foo->{unreadEmails}); + $self->assert_num_equals(0, $foo->{totalThreads}); + $self->assert_num_equals(0, $foo->{unreadThreads}); - # INBOX.INBOX.foo.bar - $self->assert_str_equals("bar", $bar->{name}); - $self->assert_str_equals($foo->{id}, $bar->{parentId}); - $self->assert_null($bar->{role}); - $self->assert_num_equals(10, $bar->{sortOrder}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayRename}); - $self->assert_equals(JSON::true, $bar->{myRights}->{mayDelete}); - $self->assert_num_equals(0, $bar->{totalEmails}); - $self->assert_num_equals(0, $bar->{unreadEmails}); - $self->assert_num_equals(0, $bar->{totalThreads}); - $self->assert_num_equals(0, $bar->{unreadThreads}); + # INBOX.INBOX.foo.bar + $self->assert_str_equals("bar", $bar->{name}); + $self->assert_str_equals($foo->{id}, $bar->{parentId}); + $self->assert_null($bar->{role}); + $self->assert_num_equals(10, $bar->{sortOrder}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayRename}); + $self->assert_equals(JSON::true, $bar->{myRights}->{mayDelete}); + $self->assert_num_equals(0, $bar->{totalEmails}); + $self->assert_num_equals(0, $bar->{unreadEmails}); + $self->assert_num_equals(0, $bar->{totalThreads}); + $self->assert_num_equals(0, $bar->{unreadThreads}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_inboxsub b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_inboxsub index 5979f26899..53aa8ee3cc 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_inboxsub +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_inboxsub @@ -2,43 +2,42 @@ use Cassandane::Tiny; sub test_mailbox_get_inboxsub - :min_version_3_1 :needs_component_jmap :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : JMAPExtensions : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # isSeenShared property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # isSeenShared property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - xlog $self, "Create INBOX subfolder via IMAP"; - $imap->create("INBOX.INBOX.foo") or die; + xlog $self, "Create INBOX subfolder via IMAP"; + $imap->create("INBOX.INBOX.foo") or die; - xlog $self, "Get mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); + xlog $self, "Get mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxfoo = $mboxByName{"foo"}; - my $inbox = $mboxByName{"Inbox"}; + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxfoo = $mboxByName{"foo"}; + my $inbox = $mboxByName{"Inbox"}; - $self->assert_str_equals('foo', $mboxfoo->{name}); - $self->assert_str_equals($inbox->{id}, $mboxfoo->{parentId}); - $self->assert_null($mboxfoo->{role}); - $self->assert_num_equals(10, $mboxfoo->{sortOrder}); - $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayRename}); - $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayDelete}); - $self->assert_num_equals(0, $mboxfoo->{totalEmails}); - $self->assert_num_equals(0, $mboxfoo->{unreadEmails}); - $self->assert_num_equals(0, $mboxfoo->{totalThreads}); - $self->assert_num_equals(0, $mboxfoo->{unreadThreads}); - $self->assert_num_equals(JSON::false, $mboxfoo->{isSeenShared}); + $self->assert_str_equals('foo', $mboxfoo->{name}); + $self->assert_str_equals($inbox->{id}, $mboxfoo->{parentId}); + $self->assert_null($mboxfoo->{role}); + $self->assert_num_equals(10, $mboxfoo->{sortOrder}); + $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayRename}); + $self->assert_equals(JSON::true, $mboxfoo->{myRights}->{mayDelete}); + $self->assert_num_equals(0, $mboxfoo->{totalEmails}); + $self->assert_num_equals(0, $mboxfoo->{unreadEmails}); + $self->assert_num_equals(0, $mboxfoo->{totalThreads}); + $self->assert_num_equals(0, $mboxfoo->{unreadThreads}); + $self->assert_num_equals(JSON::false, $mboxfoo->{isSeenShared}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_intermediate b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_intermediate index 7208d90561..265425ba42 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_intermediate +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_intermediate @@ -2,42 +2,42 @@ use Cassandane::Tiny; sub test_mailbox_get_intermediate - :min_version_3_1 :max_version_3_4 :needs_component_jmap :JMAPExtensions :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap : JMAPExtensions : + NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # isSeenShared property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # isSeenShared property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - xlog $self, "Create intermediate mailbox via IMAP"; - $imap->create("INBOX.A.Z") or die; + xlog $self, "Create intermediate mailbox via IMAP"; + $imap->create("INBOX.A.Z") or die; - xlog $self, "Get mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $self->assert_num_equals(3, scalar @{$res->[0][1]{list}}); + xlog $self, "Get mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{list} }); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxA = $mboxByName{"A"}; + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxA = $mboxByName{"A"}; - $self->assert_str_equals('A', $mboxA->{name}); - $self->assert_null($mboxA->{parentId}); - $self->assert_null($mboxA->{role}); - $self->assert_num_equals(0, $mboxA->{sortOrder}, 0); - $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayRename}); - $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayDelete}); - $self->assert_num_equals(0, $mboxA->{totalEmails}); - $self->assert_num_equals(0, $mboxA->{unreadEmails}); - $self->assert_num_equals(0, $mboxA->{totalThreads}); - $self->assert_num_equals(0, $mboxA->{unreadThreads}); - $self->assert_num_equals(JSON::false, $mboxA->{isSeenShared}); + $self->assert_str_equals('A', $mboxA->{name}); + $self->assert_null($mboxA->{parentId}); + $self->assert_null($mboxA->{role}); + $self->assert_num_equals(0, $mboxA->{sortOrder}, 0); + $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayRename}); + $self->assert_equals(JSON::true, $mboxA->{myRights}->{mayDelete}); + $self->assert_num_equals(0, $mboxA->{totalEmails}); + $self->assert_num_equals(0, $mboxA->{unreadEmails}); + $self->assert_num_equals(0, $mboxA->{totalThreads}); + $self->assert_num_equals(0, $mboxA->{unreadThreads}); + $self->assert_num_equals(JSON::false, $mboxA->{isSeenShared}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_nocalendars b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_nocalendars index 84963b24ab..eb6c61b17e 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_nocalendars +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_nocalendars @@ -2,43 +2,51 @@ use Cassandane::Tiny; sub test_mailbox_get_nocalendars - :min_version_3_1 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - # asserts that changes on special mailboxes such as calendars - # aren't listed as regular mailboxes + # asserts that changes on special mailboxes such as calendars + # aren't listed as regular mailboxes - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'urn:ietf:params:jmap:calendars', - 'https://cyrusimap.org/ns/jmap/calendars', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', + 'urn:ietf:params:jmap:mail', + 'urn:ietf:params:jmap:calendars', + 'https://cyrusimap.org/ns/jmap/calendars', + ]; - xlog $self, "get existing mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]], $using); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - my $mboxes = $res->[0][1]{list}; + xlog $self, "get existing mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ], $using); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + my $mboxes = $res->[0][1]{list}; - xlog $self, "create calendar"; - $res = $jmap->CallMethods([ - ['Calendar/set', { create => { "1" => { - name => "foo", - color => "coral", - sortOrder => 2, - isVisible => \1 - }}}, "R1"] - ], $using); - $self->assert_not_null($res->[0][1]{created}); + xlog $self, "create calendar"; + $res = $jmap->CallMethods( + [ [ + 'Calendar/set', + { + create => { + "1" => { + name => "foo", + color => "coral", + sortOrder => 2, + isVisible => \1 + } + } + }, + "R1" + ] ], + $using + ); + $self->assert_not_null($res->[0][1]{created}); - xlog $self, "get updated mailboxes"; - $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]], $using); - $self->assert_not_null($res); - $self->assert_num_equals(scalar @{$mboxes}, scalar @{$res->[0][1]{list}}); + xlog $self, "get updated mailboxes"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ], $using); + $self->assert_not_null($res); + $self->assert_num_equals(scalar @{$mboxes}, scalar @{ $res->[0][1]{list} }); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_properties b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_properties index fe0b23b168..35fffcd325 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_properties +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_properties @@ -2,39 +2,41 @@ use Cassandane::Tiny; sub test_mailbox_get_properties - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "get mailboxes with name property"; - my $res = $jmap->CallMethods([['Mailbox/get', { properties => ["name"]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - my $inbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("Inbox", $inbox->{name}); - $self->assert_num_equals(2, scalar keys %{$inbox}); # id and name - - xlog $self, "get mailboxes with erroneous property"; - $res = $jmap->CallMethods([['Mailbox/get', { properties => ["name", 123]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - my $err = $res->[0][1]; - $self->assert_str_equals("invalidArguments", $err->{type}); - $self->assert_str_equals("properties[1]", $err->{arguments}[0]); - - xlog $self, "get mailboxes with unknown property"; - $res = $jmap->CallMethods([['Mailbox/get', { properties => ["name", "123"]}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - $err = $res->[0][1]; - $self->assert_str_equals("invalidArguments", $err->{type}); - $self->assert_str_equals("properties[1:123]", $err->{arguments}[0]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "get mailboxes with name property"; + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => ["name"] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + my $inbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("Inbox", $inbox->{name}); + $self->assert_num_equals(2, scalar keys %{$inbox}); # id and name + + xlog $self, "get mailboxes with erroneous property"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => [ "name", 123 ] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + my $err = $res->[0][1]; + $self->assert_str_equals("invalidArguments", $err->{type}); + $self->assert_str_equals("properties[1]", $err->{arguments}[0]); + + xlog $self, "get mailboxes with unknown property"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => [ "name", "123" ] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + $err = $res->[0][1]; + $self->assert_str_equals("invalidArguments", $err->{type}); + $self->assert_str_equals("properties[1:123]", $err->{arguments}[0]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared index 4b51f5af4a..9d4040b707 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared @@ -2,76 +2,79 @@ use Cassandane::Tiny; sub test_mailbox_get_shared - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Create user and share mailbox - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lr") or die; - $admintalk->create("user.foo.box1") or die; - $admintalk->setacl("user.foo.box1", "cassandane", "lr") or die; + # Create user and share mailbox + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lr") or die; + $admintalk->create("user.foo.box1") or die; + $admintalk->setacl("user.foo.box1", "cassandane", "lr") or die; - $self->{instance}->create_user("foobar"); - $admintalk->setacl("user.foobar", "cassandane", "lr") or die; - $admintalk->create("user.foobar.box2") or die; - $admintalk->setacl("user.foobar.box2", "cassandane", "lr") or die; + $self->{instance}->create_user("foobar"); + $admintalk->setacl("user.foobar", "cassandane", "lr") or die; + $admintalk->create("user.foobar.box2") or die; + $admintalk->setacl("user.foobar.box2", "cassandane", "lr") or die; - # Create user but do not share mailbox - $self->{instance}->create_user("bar"); + # Create user but do not share mailbox + $self->{instance}->create_user("bar"); - # Get our own Inbox id - my $inbox = $self->getinbox(); + # Get our own Inbox id + my $inbox = $self->getinbox(); - my $foostore = Cassandane::IMAPMessageStore->new( - host => $self->{store}->{host}, - port => $self->{store}->{port}, - username => 'foo', - password => 'testpw', - verbose => $self->{store}->{verbose}, - ); - my $footalk = $foostore->get_client(); + my $foostore = Cassandane::IMAPMessageStore->new( + host => $self->{store}->{host}, + port => $self->{store}->{port}, + username => 'foo', + password => 'testpw', + verbose => $self->{store}->{verbose}, + ); + my $footalk = $foostore->get_client(); - $footalk->setmetadata("INBOX.box1", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->setmetadata("INBOX.box1", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - xlog $self, "get mailboxes for foo account"; - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => "foo" }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); + xlog $self, "get mailboxes for foo account"; + my $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => "foo" }, "R1" ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); - my %m = map { lc($_->{name}) => $_ } @{$res->[0][1]{list}}; - my $fooInbox = $m{'inbox'}; - $self->assert_str_not_equals($inbox->{id}, $fooInbox->{id}); - $self->assert_str_equals('inbox', $fooInbox->{role}); - my $box1 = $m{'box1'}; - $self->assert_str_equals('trash', $box1->{role}); + my %m = map { lc($_->{name}) => $_ } @{ $res->[0][1]{list} }; + my $fooInbox = $m{'inbox'}; + $self->assert_str_not_equals($inbox->{id}, $fooInbox->{id}); + $self->assert_str_equals('inbox', $fooInbox->{role}); + my $box1 = $m{'box1'}; + $self->assert_str_equals('trash', $box1->{role}); - xlog $self, "get mailboxes for inaccessible bar account"; - $res = $jmap->CallMethods([['Mailbox/get', { accountId => "bar" }, "R1"]]); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); + xlog $self, "get mailboxes for inaccessible bar account"; + $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => "bar" }, "R1" ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); - xlog $self, "get mailboxes for inexistent account"; - $res = $jmap->CallMethods([['Mailbox/get', { accountId => "baz" }, "R1"]]); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); + xlog $self, "get mailboxes for inexistent account"; + $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => "baz" }, "R1" ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); - xlog $self, "get mailboxes for visible account"; - $res = $jmap->CallMethods([['Mailbox/get', { accountId => "foobar" }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); - %m = map { lc($_->{name}) => $_ } @{$res->[0][1]{list}}; - $self->assert_not_null($m{inbox}); - $self->assert_not_null($m{box2}); - $self->assert_null($m{inbox}{parentId}); - $self->assert_null($m{box2}{parentId}); + xlog $self, "get mailboxes for visible account"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { accountId => "foobar" }, "R1" ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); + %m = map { lc($_->{name}) => $_ } @{ $res->[0][1]{list} }; + $self->assert_not_null($m{inbox}); + $self->assert_not_null($m{box2}); + $self->assert_null($m{inbox}{parentId}); + $self->assert_null($m{box2}{parentId}); - $self->assert_equals(JSON::true, $m{inbox}{myRights}{mayReadItems}); - $self->assert_equals(JSON::true, $m{box2}{myRights}{mayReadItems}); - $self->assert_equals(JSON::false, $m{inbox}{myRights}{mayAddItems}); - $self->assert_equals(JSON::false, $m{box2}{myRights}{mayAddItems}); + $self->assert_equals(JSON::true, $m{inbox}{myRights}{mayReadItems}); + $self->assert_equals(JSON::true, $m{box2}{myRights}{mayReadItems}); + $self->assert_equals(JSON::false, $m{inbox}{myRights}{mayAddItems}); + $self->assert_equals(JSON::false, $m{box2}{myRights}{mayAddItems}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared_inbox b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared_inbox index a112b2e8af..39135e5b79 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared_inbox +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared_inbox @@ -2,74 +2,77 @@ use Cassandane::Tiny; sub test_mailbox_get_shared_inbox - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Create user and share mailbox - $self->{instance}->create_user("foo"); - $admintalk->setacl("user.foo", "cassandane", "lr") or die; - $admintalk->create("user.foo.box1") or die; - $admintalk->setacl("user.foo.box1", "cassandane", "lr") or die; + # Create user and share mailbox + $self->{instance}->create_user("foo"); + $admintalk->setacl("user.foo", "cassandane", "lr") or die; + $admintalk->create("user.foo.box1") or die; + $admintalk->setacl("user.foo.box1", "cassandane", "lr") or die; - $self->{instance}->create_user("foobar"); - $admintalk->create("user.foobar.INBOX.box2") or die; - $admintalk->setacl("user.foobar.INBOX.box2", "cassandane", "lr") or die; + $self->{instance}->create_user("foobar"); + $admintalk->create("user.foobar.INBOX.box2") or die; + $admintalk->setacl("user.foobar.INBOX.box2", "cassandane", "lr") or die; - # Create user but do not share mailbox - $self->{instance}->create_user("bar"); + # Create user but do not share mailbox + $self->{instance}->create_user("bar"); - # Get our own Inbox id - my $inbox = $self->getinbox(); + # Get our own Inbox id + my $inbox = $self->getinbox(); - my $foostore = Cassandane::IMAPMessageStore->new( - host => $self->{store}->{host}, - port => $self->{store}->{port}, - username => 'foo', - password => 'testpw', - verbose => $self->{store}->{verbose}, - ); - my $footalk = $foostore->get_client(); + my $foostore = Cassandane::IMAPMessageStore->new( + host => $self->{store}->{host}, + port => $self->{store}->{port}, + username => 'foo', + password => 'testpw', + verbose => $self->{store}->{verbose}, + ); + my $footalk = $foostore->get_client(); - $footalk->setmetadata("INBOX.box1", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->setmetadata("INBOX.box1", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - xlog $self, "get mailboxes for foo account"; - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => "foo" }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); + xlog $self, "get mailboxes for foo account"; + my $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => "foo" }, "R1" ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); - my %m = map { lc($_->{name}) => $_ } @{$res->[0][1]{list}}; - my $fooInbox = $m{'inbox'}; - $self->assert_str_not_equals($inbox->{id}, $fooInbox->{id}); - $self->assert_str_equals('inbox', $fooInbox->{role}); - my $box1 = $m{'box1'}; - $self->assert_str_equals('trash', $box1->{role}); + my %m = map { lc($_->{name}) => $_ } @{ $res->[0][1]{list} }; + my $fooInbox = $m{'inbox'}; + $self->assert_str_not_equals($inbox->{id}, $fooInbox->{id}); + $self->assert_str_equals('inbox', $fooInbox->{role}); + my $box1 = $m{'box1'}; + $self->assert_str_equals('trash', $box1->{role}); - xlog $self, "get mailboxes for inaccessible bar account"; - $res = $jmap->CallMethods([['Mailbox/get', { accountId => "bar" }, "R1"]]); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); + xlog $self, "get mailboxes for inaccessible bar account"; + $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => "bar" }, "R1" ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); - xlog $self, "get mailboxes for inexistent account"; - $res = $jmap->CallMethods([['Mailbox/get', { accountId => "baz" }, "R1"]]); - $self->assert_str_equals("error", $res->[0][0]); - $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); + xlog $self, "get mailboxes for inexistent account"; + $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => "baz" }, "R1" ] ]); + $self->assert_str_equals("error", $res->[0][0]); + $self->assert_str_equals("accountNotFound", $res->[0][1]{type}); - xlog $self, "get mailboxes for visible account"; - $res = $jmap->CallMethods([['Mailbox/get', { accountId => "foobar" }, "R1"]]); - %m = map { lc($_->{name}) => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); - $self->assert_not_null($m{inbox}); - $self->assert_not_null($m{box2}); - $self->assert_equals(JSON::false, $m{inbox}{myRights}{mayReadItems}); - $self->assert_equals(JSON::true, $m{box2}{myRights}{mayReadItems}); - $self->assert_equals(JSON::false, $m{inbox}{myRights}{mayAddItems}); - $self->assert_equals(JSON::false, $m{box2}{myRights}{mayAddItems}); - $self->assert_null($m{inbox}{parentId}); - $self->assert_str_equals($m{inbox}{id}, $m{box2}{parentId}); + xlog $self, "get mailboxes for visible account"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { accountId => "foobar" }, "R1" ] ]); + %m = map { lc($_->{name}) => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); + $self->assert_not_null($m{inbox}); + $self->assert_not_null($m{box2}); + $self->assert_equals(JSON::false, $m{inbox}{myRights}{mayReadItems}); + $self->assert_equals(JSON::true, $m{box2}{myRights}{mayReadItems}); + $self->assert_equals(JSON::false, $m{inbox}{myRights}{mayAddItems}); + $self->assert_equals(JSON::false, $m{box2}{myRights}{mayAddItems}); + $self->assert_null($m{inbox}{parentId}); + $self->assert_str_equals($m{inbox}{id}, $m{box2}{parentId}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared_parents b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared_parents index eb51abf323..83756ed587 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared_parents +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_shared_parents @@ -2,38 +2,38 @@ use Cassandane::Tiny; sub test_mailbox_get_shared_parents - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # Create shared account and mailboxes - $self->{instance}->create_user("foo"); - $admintalk->create("user.foo.box1") or die; - $admintalk->create("user.foo.box1.box11") or die; - $admintalk->create("user.foo.box1.box11.box111") or die; - $admintalk->create("user.foo.box1.box12") or die; - $admintalk->create("user.foo.box2") or die; - $admintalk->create("user.foo.box3") or die; - $admintalk->create("user.foo.box3.box31") or die; - $admintalk->create("user.foo.box3.box32") or die; + # Create shared account and mailboxes + $self->{instance}->create_user("foo"); + $admintalk->create("user.foo.box1") or die; + $admintalk->create("user.foo.box1.box11") or die; + $admintalk->create("user.foo.box1.box11.box111") or die; + $admintalk->create("user.foo.box1.box12") or die; + $admintalk->create("user.foo.box2") or die; + $admintalk->create("user.foo.box3") or die; + $admintalk->create("user.foo.box3.box31") or die; + $admintalk->create("user.foo.box3.box32") or die; - # Share mailboxes - $admintalk->setacl("user.foo.box1.box11", "cassandane", "lr") or die; - $admintalk->setacl("user.foo.box3.box32", "cassandane", "lr") or die; + # Share mailboxes + $admintalk->setacl("user.foo.box1.box11", "cassandane", "lr") or die; + $admintalk->setacl("user.foo.box3.box32", "cassandane", "lr") or die; - xlog $self, "get mailboxes for foo account"; - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => "foo" }, "R1"]]); - $self->assert_num_equals(4, scalar @{$res->[0][1]{list}}); + xlog $self, "get mailboxes for foo account"; + my $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => "foo" }, "R1" ] ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{list} }); - # Assert rights - my %m = map { lc($_->{name}) => $_ } @{$res->[0][1]{list}}; - $self->assert_equals(JSON::false, $m{box1}->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $m{box11}->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::false, $m{box3}->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $m{box32}->{myRights}->{mayReadItems}); + # Assert rights + my %m = map { lc($_->{name}) => $_ } @{ $res->[0][1]{list} }; + $self->assert_equals(JSON::false, $m{box1}->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $m{box11}->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::false, $m{box3}->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $m{box32}->{myRights}->{mayReadItems}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_specialuse b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_specialuse index d22b28a271..98101b03df 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_get_specialuse +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_get_specialuse @@ -2,49 +2,48 @@ use Cassandane::Tiny; sub test_mailbox_get_specialuse - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.Archive", "(USE (\\Archive))") || die; - $imaptalk->create("INBOX.Drafts", "(USE (\\Drafts))") || die; - $imaptalk->create("INBOX.Spam", "(USE (\\Junk))") || die; - $imaptalk->create("INBOX.Sent", "(USE (\\Sent))") || die; - $imaptalk->create("INBOX.Trash", "(USE (\\Trash))") || die; - - xlog $self, "get mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $inbox = $m{"Inbox"}; - my $archive = $m{"Archive"}; - my $drafts = $m{"Drafts"}; - my $junk = $m{"Spam"}; - my $sent = $m{"Sent"}; - my $trash = $m{"Trash"}; - - $self->assert_str_equals("Archive", $archive->{name}); - $self->assert_str_equals("archive", $archive->{role}); - - $self->assert_str_equals("Drafts", $drafts->{name}); - $self->assert_null($drafts->{parentId}); - $self->assert_str_equals("drafts", $drafts->{role}); - - $self->assert_str_equals("Spam", $junk->{name}); - $self->assert_null($junk->{parentId}); - $self->assert_str_equals("junk", $junk->{role}); - - $self->assert_str_equals("Sent", $sent->{name}); - $self->assert_null($sent->{parentId}); - $self->assert_str_equals("sent", $sent->{role}); - - $self->assert_str_equals("Trash", $trash->{name}); - $self->assert_null($trash->{parentId}); - $self->assert_str_equals("trash", $trash->{role}); + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.Archive", "(USE (\\Archive))") || die; + $imaptalk->create("INBOX.Drafts", "(USE (\\Drafts))") || die; + $imaptalk->create("INBOX.Spam", "(USE (\\Junk))") || die; + $imaptalk->create("INBOX.Sent", "(USE (\\Sent))") || die; + $imaptalk->create("INBOX.Trash", "(USE (\\Trash))") || die; + + xlog $self, "get mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $inbox = $m{"Inbox"}; + my $archive = $m{"Archive"}; + my $drafts = $m{"Drafts"}; + my $junk = $m{"Spam"}; + my $sent = $m{"Sent"}; + my $trash = $m{"Trash"}; + + $self->assert_str_equals("Archive", $archive->{name}); + $self->assert_str_equals("archive", $archive->{role}); + + $self->assert_str_equals("Drafts", $drafts->{name}); + $self->assert_null($drafts->{parentId}); + $self->assert_str_equals("drafts", $drafts->{role}); + + $self->assert_str_equals("Spam", $junk->{name}); + $self->assert_null($junk->{parentId}); + $self->assert_str_equals("junk", $junk->{role}); + + $self->assert_str_equals("Sent", $sent->{name}); + $self->assert_null($sent->{parentId}); + $self->assert_str_equals("sent", $sent->{role}); + + $self->assert_str_equals("Trash", $trash->{name}); + $self->assert_null($trash->{parentId}); + $self->assert_str_equals("trash", $trash->{role}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_ignore_notes_subfolders b/cassandane/tiny-tests/JMAPMailbox/mailbox_ignore_notes_subfolders index 8c3c591b1a..c12f13d694 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_ignore_notes_subfolders +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_ignore_notes_subfolders @@ -2,41 +2,34 @@ use Cassandane::Tiny; sub test_mailbox_ignore_notes_subfolders - :min_version_3_7 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog 'Fetch inbox id'; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { }, 'R1'] - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - my $inboxId = $res->[0][1]{ids}[0]; + xlog 'Fetch inbox id'; + my $res = $jmap->CallMethods([ [ 'Mailbox/query', {}, 'R1' ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + my $inboxId = $res->[0][1]{ids}[0]; - xlog 'Create Notes mailbox'; - $imap->create("Notes", "(USE (\\XNotes))") or die "$!"; + xlog 'Create Notes mailbox'; + $imap->create("Notes", "(USE (\\XNotes))") or die "$!"; - xlog 'Assert Notes folder is invisible'; - $res = $jmap->CallMethods([ - ['Mailbox/query', { }, 'R1'], - ['Mailbox/get', { }, 'R2'] - ]); - $self->assert_deep_equals([$inboxId], $res->[0][1]{ids}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_str_equals($inboxId, $res->[1][1]{list}[0]{id}); + xlog 'Assert Notes folder is invisible'; + $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], [ 'Mailbox/get', {}, 'R2' ] ]); + $self->assert_deep_equals([$inboxId], $res->[0][1]{ids}); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_str_equals($inboxId, $res->[1][1]{list}[0]{id}); - xlog 'Create subfolder in Notes folder'; - $imap->create("Notes.Sub") or die "$!"; + xlog 'Create subfolder in Notes folder'; + $imap->create("Notes.Sub") or die "$!"; - xlog 'Assert Notes folders are invisible'; - $res = $jmap->CallMethods([ - ['Mailbox/query', { }, 'R1'], - ['Mailbox/get', { }, 'R2'] - ]); - $self->assert_deep_equals([$inboxId], $res->[0][1]{ids}); - $self->assert_num_equals(1, scalar @{$res->[1][1]{list}}); - $self->assert_str_equals($inboxId, $res->[1][1]{list}[0]{id}); + xlog 'Assert Notes folders are invisible'; + $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], [ 'Mailbox/get', {}, 'R2' ] ]); + $self->assert_deep_equals([$inboxId], $res->[0][1]{ids}); + $self->assert_num_equals(1, scalar @{ $res->[1][1]{list} }); + $self->assert_str_equals($inboxId, $res->[1][1]{list}[0]{id}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_intermediary_imaprename_preservetree b/cassandane/tiny-tests/JMAPMailbox/mailbox_intermediary_imaprename_preservetree index 256fc2f9e0..33b405cd63 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_intermediary_imaprename_preservetree +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_intermediary_imaprename_preservetree @@ -2,53 +2,60 @@ use Cassandane::Tiny; sub test_mailbox_intermediary_imaprename_preservetree - :min_version_3_1 :max_version_3_4 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.i2.i3.foo") or die; - $imap->create("INBOX.i1.i2.bar") or die; - my $res = $jmap->CallMethods([['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"]]); + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.i2.i3.foo") or die; + $imap->create("INBOX.i1.i2.bar") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); - xlog $self, "Assert mailbox tree"; - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(6, scalar keys %mboxByName); - $self->assert_not_null($mboxByName{'Inbox'}); - $self->assert_not_null($mboxByName{'i1'}); - $self->assert_not_null($mboxByName{'i2'}); - $self->assert_not_null($mboxByName{'i3'}); - $self->assert_not_null($mboxByName{'foo'}); - $self->assert_not_null($mboxByName{'bar'}); - $self->assert_null($mboxByName{i1}->{parentId}); - $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i2}->{parentId}); - $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{i3}->{parentId}); - $self->assert_str_equals($mboxByName{i3}->{id}, $mboxByName{foo}->{parentId}); - $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{bar}->{parentId}); + xlog $self, "Assert mailbox tree"; + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(6, scalar keys %mboxByName); + $self->assert_not_null($mboxByName{'Inbox'}); + $self->assert_not_null($mboxByName{'i1'}); + $self->assert_not_null($mboxByName{'i2'}); + $self->assert_not_null($mboxByName{'i3'}); + $self->assert_not_null($mboxByName{'foo'}); + $self->assert_not_null($mboxByName{'bar'}); + $self->assert_null($mboxByName{i1}->{parentId}); + $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i2}->{parentId}); + $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{i3}->{parentId}); + $self->assert_str_equals($mboxByName{i3}->{id}, $mboxByName{foo}->{parentId}); + $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{bar}->{parentId}); - xlog $self, "Rename mailbox"; - $imap->rename("INBOX.i1.i2.i3.foo", "INBOX.i1.i4.baz") or die; + xlog $self, "Rename mailbox"; + $imap->rename("INBOX.i1.i2.i3.foo", "INBOX.i1.i4.baz") or die; - xlog $self, "Assert mailbox tree"; - $res = $jmap->CallMethods([['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"]]); - %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(6, scalar keys %mboxByName); - $self->assert_not_null($mboxByName{'Inbox'}); - $self->assert_not_null($mboxByName{'i1'}); - $self->assert_not_null($mboxByName{'i2'}); - $self->assert_not_null($mboxByName{'i4'}); - $self->assert_not_null($mboxByName{'bar'}); - $self->assert_not_null($mboxByName{'baz'}); - $self->assert_null($mboxByName{i1}->{parentId}); - $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i2}->{parentId}); - $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i4}->{parentId}); - $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{bar}->{parentId}); - $self->assert_str_equals($mboxByName{i4}->{id}, $mboxByName{baz}->{parentId}); + xlog $self, "Assert mailbox tree"; + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(6, scalar keys %mboxByName); + $self->assert_not_null($mboxByName{'Inbox'}); + $self->assert_not_null($mboxByName{'i1'}); + $self->assert_not_null($mboxByName{'i2'}); + $self->assert_not_null($mboxByName{'i4'}); + $self->assert_not_null($mboxByName{'bar'}); + $self->assert_not_null($mboxByName{'baz'}); + $self->assert_null($mboxByName{i1}->{parentId}); + $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i2}->{parentId}); + $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i4}->{parentId}); + $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{bar}->{parentId}); + $self->assert_str_equals($mboxByName{i4}->{id}, $mboxByName{baz}->{parentId}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_intermediate_no_emails b/cassandane/tiny-tests/JMAPMailbox/mailbox_intermediate_no_emails index 0de3d4f0ed..55990ca57f 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_intermediate_no_emails +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_intermediate_no_emails @@ -3,48 +3,62 @@ use Cassandane::Tiny; # This is to test for a bug where a query against an intermediate mailbox was returning all emails! sub test_mailbox_intermediate_no_emails - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $store = $self->{store}; - my $talk = $store->get_client(); - - xlog $self, "Generate emails in INBOX via IMAP"; - $self->make_message("Email A") || die; - $self->make_message("Email B") || die; - $self->make_message("Email C") || die; - - xlog $self, "Create a deep folder"; - $talk->create("INBOX.Inter.Mediate"); - - xlog $self, "Generate one email in the deep mailbox via IMAP"; - $store->set_folder("INBOX.Inter.Mediate"); - $self->make_message("Email D") || die; - - xlog $self, "get mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my %byname = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; - - xlog $self, "three emails in the Inbox"; - $res = $jmap->CallMethods([['Email/query', - { filter => { inMailbox => $byname{Inbox} }, - calculateTotal => JSON::true }, "R1"]]); - $self->assert_num_equals(3, $res->[0][1]{total}); - $self->assert_num_equals(3, scalar @{$res->[0][1]{ids}}); - - xlog $self, "no emails in the Intermediate mailbox"; - $res = $jmap->CallMethods([['Email/query', - { filter => { inMailbox => $byname{Inter} }, - calculateTotal => JSON::true }, "R1"]]); - $self->assert_num_equals(0, $res->[0][1]{total}); - $self->assert_num_equals(0, scalar @{$res->[0][1]{ids}}); - - xlog $self, "one email in the deep mailbox"; - $res = $jmap->CallMethods([['Email/query', - { filter => { inMailbox => $byname{Mediate} }, - calculateTotal => JSON::true }, "R1"]]); - $self->assert_num_equals(1, $res->[0][1]{total}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $store = $self->{store}; + my $talk = $store->get_client(); + + xlog $self, "Generate emails in INBOX via IMAP"; + $self->make_message("Email A") || die; + $self->make_message("Email B") || die; + $self->make_message("Email C") || die; + + xlog $self, "Create a deep folder"; + $talk->create("INBOX.Inter.Mediate"); + + xlog $self, "Generate one email in the deep mailbox via IMAP"; + $store->set_folder("INBOX.Inter.Mediate"); + $self->make_message("Email D") || die; + + xlog $self, "get mailboxes"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my %byname = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; + + xlog $self, "three emails in the Inbox"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { inMailbox => $byname{Inbox} }, + calculateTotal => JSON::true + }, + "R1" + ] ]); + $self->assert_num_equals(3, $res->[0][1]{total}); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "no emails in the Intermediate mailbox"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { inMailbox => $byname{Inter} }, + calculateTotal => JSON::true + }, + "R1" + ] ]); + $self->assert_num_equals(0, $res->[0][1]{total}); + $self->assert_num_equals(0, scalar @{ $res->[0][1]{ids} }); + + xlog $self, "one email in the deep mailbox"; + $res = $jmap->CallMethods([ [ + 'Email/query', + { + filter => { inMailbox => $byname{Mediate} }, + calculateTotal => JSON::true + }, + "R1" + ] ]); + $self->assert_num_equals(1, $res->[0][1]{total}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_move_to_deleted_parent b/cassandane/tiny-tests/JMAPMailbox/mailbox_move_to_deleted_parent index f819e3603f..f8b4dc88a3 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_move_to_deleted_parent +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_move_to_deleted_parent @@ -2,20 +2,19 @@ use Cassandane::Tiny; sub test_mailbox_move_to_deleted_parent - :min_version_3_6 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_6 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Assert mailboxes are created in the right order. - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - }, - content => '{ + # Assert mailboxes are created in the right order. + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + }, + content => '{ "using" : ["urn:ietf:params:jmap:mail"], "methodCalls" : [["Mailbox/set", { "create" : { @@ -31,45 +30,57 @@ sub test_mailbox_move_to_deleted_parent } }, "R1"]] }', - }; - my $RawResponse = $jmap->ua->post($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert($RawResponse->{success}); + }; + my $RawResponse = $jmap->ua->post($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert($RawResponse->{success}); - my $res = eval { decode_json($RawResponse->{content}) }; - $res = $res->{methodResponses}; - $self->assert_not_null($res->[0][1]{created}{A}); - $self->assert_not_null($res->[0][1]{created}{B}); - $self->assert_not_null($res->[0][1]{created}{C}); - my $idA = $res->[0][1]{created}{A}{id}; - my $idB = $res->[0][1]{created}{B}{id}; - my $idC = $res->[0][1]{created}{C}{id}; + my $res = eval { decode_json($RawResponse->{content}) }; + $res = $res->{methodResponses}; + $self->assert_not_null($res->[0][1]{created}{A}); + $self->assert_not_null($res->[0][1]{created}{B}); + $self->assert_not_null($res->[0][1]{created}{C}); + my $idA = $res->[0][1]{created}{A}{id}; + my $idB = $res->[0][1]{created}{B}{id}; + my $idC = $res->[0][1]{created}{C}{id}; - # Destroy "A" - $res = $jmap->CallMethods([['Mailbox/set', { - destroy => [ $idA ], - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); - $self->assert_null($res->[0][1]{notDestroyed}); + # Destroy "A" + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + destroy => [$idA], + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_null($res->[0][1]{notDestroyed}); - # Try to move "B" under a non-existant mailbox - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $idB => { parentId => "nosuchid" }, - } - }, "R1"]]); - $self->assert_null($res->[0][1]{updated}); - $self->assert_not_null($res->[0][1]{notUpdated}); + # Try to move "B" under a non-existant mailbox + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $idB => { parentId => "nosuchid" }, + } + }, + "R1" + ] ]); + $self->assert_null($res->[0][1]{updated}); + $self->assert_not_null($res->[0][1]{notUpdated}); - # Try to move "B" under "A" - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $idB => { parentId => $idA }, - } - }, "R1"]]); - $self->assert_null($res->[0][1]{updated}); - $self->assert_not_null($res->[0][1]{notUpdated}); + # Try to move "B" under "A" + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $idB => { parentId => $idA }, + } + }, + "R1" + ] ]); + $self->assert_null($res->[0][1]{updated}); + $self->assert_not_null($res->[0][1]{notUpdated}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_query b/cassandane/tiny-tests/JMAPMailbox/mailbox_query index a15689e80f..104a4cd347 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_query +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_query @@ -2,142 +2,167 @@ use Cassandane::Tiny; sub test_mailbox_query - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - - xlog $self, "list mailboxes without filter"; - my $res = $jmap->CallMethods([['Mailbox/query', {}, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals('Mailbox/query', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - - xlog $self, "create mailboxes"; - $imaptalk->create("INBOX.A") || die; - $imaptalk->create("INBOX.B") || die; - - xlog $self, "fetch mailboxes"; - $res = $jmap->CallMethods([['Mailbox/get', { }, 'R1' ]]); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; - - xlog $self, "list mailboxes without filter and sort by name ascending"; - $res = $jmap->CallMethods([['Mailbox/query', { - sort => [{ property => "name" }]}, - "R1"]]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'A'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[1]); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[2]); - - xlog $self, "list mailboxes without filter and sort by name descending"; - $res = $jmap->CallMethods([['Mailbox/query', { - sort => [{ property => "name", isAscending => JSON::false}], - }, "R1"]]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[1]); - $self->assert_str_equals($mboxids{'A'}, $res->[0][1]{ids}[2]); - - xlog $self, "filter mailboxes by hasAnyRole == true"; - $res = $jmap->CallMethods([['Mailbox/query', {filter => {hasAnyRole => JSON::true}}, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[0]); - - xlog $self, "filter mailboxes by hasAnyRole == false"; - $res = $jmap->CallMethods([['Mailbox/query', { - filter => {hasAnyRole => JSON::false}, - sort => [{ property => "name"}], - }, "R1"]]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'A'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[1]); - - xlog $self, "create mailbox underneath A"; - $imaptalk->create("INBOX.A.AA") || die; - - xlog $self, "(re)fetch mailboxes"; - $res = $jmap->CallMethods([['Mailbox/get', { }, 'R1' ]]); - %mboxids = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; - - xlog $self, "filter mailboxes by parentId"; - $res = $jmap->CallMethods([['Mailbox/query', {filter => {parentId => $mboxids{'A'}}}, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'AA'}, $res->[0][1]{ids}[0]); - - # Without windowing the name-sorted results are: A, AA, B, Inbox - - xlog $self, "list mailboxes (with limit)"; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sort => [{ property => "name" }], - limit => 1, - }, "R1"] - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'A'}, $res->[0][1]{ids}[0]); - $self->assert_num_equals(0, $res->[0][1]->{position}); - - xlog $self, "list mailboxes (with anchor and limit)"; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sort => [{ property => "name" }], - anchor => $mboxids{'B'}, - limit => 2, - }, "R1"] - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); - $self->assert_num_equals(2, $res->[0][1]->{position}); - - xlog $self, "list mailboxes (with positive anchor offset)"; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sort => [{ property => "name" }], - anchor => $mboxids{'AA'}, - anchorOffset => 1, - }, "R1"] - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); - $self->assert_num_equals(2, $res->[0][1]->{position}); - - xlog $self, "list mailboxes (with negative anchor offset)"; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sort => [{ property => "name" }], - anchor => $mboxids{'B'}, - anchorOffset => -1, - }, "R1"] - ]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'AA'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[1]); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[2]); - $self->assert_num_equals(1, $res->[0][1]->{position}); - - xlog $self, "list mailboxes (with position)"; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sort => [{ property => "name" }], - position => 3, - }, "R1"] - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[0]); - - xlog $self, "list mailboxes (with negative position)"; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sort => [{ property => "name" }], - position => -2, - }, "R1"] - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + + xlog $self, "list mailboxes without filter"; + my $res = $jmap->CallMethods([ [ 'Mailbox/query', {}, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals('Mailbox/query', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + + xlog $self, "create mailboxes"; + $imaptalk->create("INBOX.A") || die; + $imaptalk->create("INBOX.B") || die; + + xlog $self, "fetch mailboxes"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, 'R1' ] ]); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; + + xlog $self, "list mailboxes without filter and sort by name ascending"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ] + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'A'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[1]); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[2]); + + xlog $self, "list mailboxes without filter and sort by name descending"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name", isAscending => JSON::false } ], + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[1]); + $self->assert_str_equals($mboxids{'A'}, $res->[0][1]{ids}[2]); + + xlog $self, "filter mailboxes by hasAnyRole == true"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/query', { filter => { hasAnyRole => JSON::true } }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[0]); + + xlog $self, "filter mailboxes by hasAnyRole == false"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { hasAnyRole => JSON::false }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'A'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[1]); + + xlog $self, "create mailbox underneath A"; + $imaptalk->create("INBOX.A.AA") || die; + + xlog $self, "(re)fetch mailboxes"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, 'R1' ] ]); + %mboxids = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; + + xlog $self, "filter mailboxes by parentId"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/query', { filter => { parentId => $mboxids{'A'} } }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'AA'}, $res->[0][1]{ids}[0]); + + # Without windowing the name-sorted results are: A, AA, B, Inbox + + xlog $self, "list mailboxes (with limit)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ], + limit => 1, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'A'}, $res->[0][1]{ids}[0]); + $self->assert_num_equals(0, $res->[0][1]->{position}); + + xlog $self, "list mailboxes (with anchor and limit)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ], + anchor => $mboxids{'B'}, + limit => 2, + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); + $self->assert_num_equals(2, $res->[0][1]->{position}); + + xlog $self, "list mailboxes (with positive anchor offset)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ], + anchor => $mboxids{'AA'}, + anchorOffset => 1, + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); + $self->assert_num_equals(2, $res->[0][1]->{position}); + + xlog $self, "list mailboxes (with negative anchor offset)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ], + anchor => $mboxids{'B'}, + anchorOffset => -1, + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'AA'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[1]); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[2]); + $self->assert_num_equals(1, $res->[0][1]->{position}); + + xlog $self, "list mailboxes (with position)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ], + position => 3, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[0]); + + xlog $self, "list mailboxes (with negative position)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ], + position => -2, + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'B'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_filterastree b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_filterastree index 357fa15253..aaaff8b41f 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_filterastree +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_filterastree @@ -2,54 +2,57 @@ use Cassandane::Tiny; sub test_mailbox_query_filterastree - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - - $imaptalk->create("INBOX.A") || die; - $imaptalk->create("INBOX.A.A1") || die; - $imaptalk->create("INBOX.B") || die; - $imaptalk->create("INBOX.B.X") || die; - $imaptalk->create("INBOX.C") || die; - $imaptalk->create("INBOX.C.C1") || die; - - my $res = $jmap->CallMethods([['Mailbox/get', { properties => ["name"] }, 'R1' ]]); - $self->assert_num_equals(7, scalar @{$res->[0][1]{list}}); - my %mboxIds = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; - - $res = $jmap->CallMethods([ - ['Mailbox/query', { - filter => { - operator => 'NOT', - conditions => [{ - name => 'B' - }] - }, - filterAsTree => JSON::true, - sort => [{ property => 'name' }], - sortAsTree => JSON::true, - }, "R1"] - ]); - - my $wantMboxIds = [ - $mboxIds{'A'}, $mboxIds{'A1'}, $mboxIds{'C'}, $mboxIds{'C1'}, - ]; - $self->assert_deep_equals($wantMboxIds, $res->[0][1]->{ids}); - - $res = $jmap->CallMethods([ - ['Mailbox/query', { - filter => { - name => '1', - }, - filterAsTree => JSON::true, - sort => [{ property => 'name' }], - sortAsTree => JSON::true, - }, "R1"] - ]); - - $wantMboxIds = [ ]; # Can't match anything because top-level is missing - $self->assert_deep_equals($wantMboxIds, $res->[0][1]->{ids}); + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + + $imaptalk->create("INBOX.A") || die; + $imaptalk->create("INBOX.A.A1") || die; + $imaptalk->create("INBOX.B") || die; + $imaptalk->create("INBOX.B.X") || die; + $imaptalk->create("INBOX.C") || die; + $imaptalk->create("INBOX.C.C1") || die; + + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => ["name"] }, 'R1' ] ]); + $self->assert_num_equals(7, scalar @{ $res->[0][1]{list} }); + my %mboxIds = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; + + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { + operator => 'NOT', + conditions => [ { + name => 'B' + } ] + }, + filterAsTree => JSON::true, + sort => [ { property => 'name' } ], + sortAsTree => JSON::true, + }, + "R1" + ] ]); + + my $wantMboxIds + = [ $mboxIds{'A'}, $mboxIds{'A1'}, $mboxIds{'C'}, $mboxIds{'C1'}, ]; + $self->assert_deep_equals($wantMboxIds, $res->[0][1]->{ids}); + + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { + name => '1', + }, + filterAsTree => JSON::true, + sort => [ { property => 'name' } ], + sortAsTree => JSON::true, + }, + "R1" + ] ]); + + $wantMboxIds = []; # Can't match anything because top-level is missing + $self->assert_deep_equals($wantMboxIds, $res->[0][1]->{ids}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_filteroperator b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_filteroperator index 4f6b9b9155..9dcb83131f 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_filteroperator +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_filteroperator @@ -2,128 +2,164 @@ use Cassandane::Tiny; sub test_mailbox_query_filteroperator - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailbox tree"; - $imaptalk->create("INBOX.Ham") || die; - $imaptalk->create("INBOX.Spam", "(USE (\\Junk))") || die; - $imaptalk->create("INBOX.Ham.Zonk") || die; - $imaptalk->create("INBOX.Ham.Bonk") || die; + xlog $self, "create mailbox tree"; + $imaptalk->create("INBOX.Ham") || die; + $imaptalk->create("INBOX.Spam", "(USE (\\Junk))") || die; + $imaptalk->create("INBOX.Ham.Zonk") || die; + $imaptalk->create("INBOX.Ham.Bonk") || die; - xlog $self, "(re)fetch mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', { properties => ["name"] }, 'R1' ]]); - $self->assert_num_equals(5, scalar @{$res->[0][1]{list}}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; - $self->assert(exists $mboxids{'Inbox'}); + xlog $self, "(re)fetch mailboxes"; + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => ["name"] }, 'R1' ] ]); + $self->assert_num_equals(5, scalar @{ $res->[0][1]{list} }); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; + $self->assert(exists $mboxids{'Inbox'}); - xlog $self, "Subscribe mailbox Ham"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxids{'Ham'} => { - isSubscribed => JSON::true, - }, - }, - }, 'R1'] - ]); - $self->assert(exists $res->[0][1]{updated}{$mboxids{'Ham'}}); + xlog $self, "Subscribe mailbox Ham"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $mboxids{'Ham'} => { + isSubscribed => JSON::true, + }, + }, + }, + 'R1' + ] ]); + $self->assert(exists $res->[0][1]{updated}{ $mboxids{'Ham'} }); - xlog $self, "make sure subscribing changed state"; - $self->assert_not_equals($res->[0][1]{oldState}, $res->[0][1]{newState}); + xlog $self, "make sure subscribing changed state"; + $self->assert_not_equals($res->[0][1]{oldState}, $res->[0][1]{newState}); - my $state = $res->[0][1]{oldState}; - $res = $jmap->CallMethods([['Mailbox/changes', { sinceState => $state }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{updated}}); - $self->assert_equals($res->[0][1]{updated}[0], $mboxids{'Ham'}); - $self->assert_null($res->[0][1]{updatedProperties}); + my $state = $res->[0][1]{oldState}; + $res = $jmap->CallMethods( + [ [ 'Mailbox/changes', { sinceState => $state }, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{updated} }); + $self->assert_equals($res->[0][1]{updated}[0], $mboxids{'Ham'}); + $self->assert_null($res->[0][1]{updatedProperties}); - xlog $self, "list mailboxes filtered by parentId OR role"; - $res = $jmap->CallMethods([['Mailbox/query', { - filter => { - operator => "OR", - conditions => [{ - parentId => $mboxids{'Ham'}, - }, { - hasAnyRole => JSON::true, - }], - }, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Bonk'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); - $self->assert_str_equals($mboxids{'Spam'}, $res->[0][1]{ids}[2]); - $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[3]); + xlog $self, "list mailboxes filtered by parentId OR role"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { + operator => "OR", + conditions => [ + { + parentId => $mboxids{'Ham'}, + }, + { + hasAnyRole => JSON::true, + } + ], + }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Bonk'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); + $self->assert_str_equals($mboxids{'Spam'}, $res->[0][1]{ids}[2]); + $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[3]); - xlog $self, "list mailboxes filtered by name"; - $res = $jmap->CallMethods([['Mailbox/query', { - filter => { - name => 'Zonk', - }, - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[0]); + xlog $self, "list mailboxes filtered by name"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { + name => 'Zonk', + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[0]); - xlog $self, "list mailboxes filtered by isSubscribed"; - $res = $jmap->CallMethods([['Mailbox/query', { - filter => { - isSubscribed => JSON::true, - }, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Ham'}, $res->[0][1]{ids}[0]); + xlog $self, "list mailboxes filtered by isSubscribed"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { + isSubscribed => JSON::true, + }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Ham'}, $res->[0][1]{ids}[0]); - xlog $self, "list mailboxes filtered by isSubscribed is false"; - $res = $jmap->CallMethods([['Mailbox/query', { - filter => { - isSubscribed => JSON::false, - }, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_num_equals(4, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Bonk'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); - $self->assert_str_equals($mboxids{'Spam'}, $res->[0][1]{ids}[2]); - $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[3]); + xlog $self, "list mailboxes filtered by isSubscribed is false"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { + isSubscribed => JSON::false, + }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Bonk'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); + $self->assert_str_equals($mboxids{'Spam'}, $res->[0][1]{ids}[2]); + $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[3]); - xlog $self, "list mailboxes filtered by parentId AND hasAnyRole false"; - $res = $jmap->CallMethods([['Mailbox/query', { - filter => { - operator => "AND", - conditions => [{ - parentId => JSON::null, - }, { - hasAnyRole => JSON::false, - }], - }, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Ham'}, $res->[0][1]{ids}[0]); + xlog $self, "list mailboxes filtered by parentId AND hasAnyRole false"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { + operator => "AND", + conditions => [ + { + parentId => JSON::null, + }, + { + hasAnyRole => JSON::false, + } + ], + }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Ham'}, $res->[0][1]{ids}[0]); - xlog $self, "list mailboxes filtered by NOT (parentId AND role)"; - $res = $jmap->CallMethods([['Mailbox/query', { - filter => { - operator => "NOT", - conditions => [{ - operator => "AND", - conditions => [{ - parentId => JSON::null, - }, { - hasAnyRole => JSON::true, - }], - }], - }, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Bonk'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'Ham'}, $res->[0][1]{ids}[1]); - $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[2]); + xlog $self, "list mailboxes filtered by NOT (parentId AND role)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { + operator => "NOT", + conditions => [ { + operator => "AND", + conditions => [ + { + parentId => JSON::null, + }, + { + hasAnyRole => JSON::true, + } + ], + } ], + }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Bonk'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'Ham'}, $res->[0][1]{ids}[1]); + $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[2]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_issue2286 b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_issue2286 index 9b83b62d53..fb8ccde179 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_issue2286 +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_issue2286 @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_mailbox_query_issue2286 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - xlog $self, "list mailboxes without filter"; - my $res = $jmap->CallMethods([['Mailbox/query', { limit => -5 }, "R1"]]); - $self->assert_str_equals('error', $res->[0][0]); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + xlog $self, "list mailboxes without filter"; + my $res = $jmap->CallMethods([ [ 'Mailbox/query', { limit => -5 }, "R1" ] ]); + $self->assert_str_equals('error', $res->[0][0]); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_limit_zero b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_limit_zero index 5a52e9d512..fad1d32ae3 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_limit_zero +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_limit_zero @@ -2,16 +2,13 @@ use Cassandane::Tiny; sub test_mailbox_query_limit_zero - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - xlog $self, "list mailboxes with limit 0"; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { limit => 0 }, "R1"] - ]); - $self->assert_deep_equals([], $res->[0][1]->{ids}); + xlog $self, "list mailboxes with limit 0"; + my $res = $jmap->CallMethods([ [ 'Mailbox/query', { limit => 0 }, "R1" ] ]); + $self->assert_deep_equals([], $res->[0][1]->{ids}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_name b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_name index d8704ddbc8..bed797387f 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_name +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_name @@ -2,30 +2,32 @@ use Cassandane::Tiny; sub test_mailbox_query_name - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.Ham") || die; - $imaptalk->create("INBOX.Spam", "(USE (\\Junk))") || die; - $imaptalk->create("INBOX.Ham.Zonk") || die; - $imaptalk->create("INBOX.Ham.Bonk") || die; + $imaptalk->create("INBOX.Ham") || die; + $imaptalk->create("INBOX.Spam", "(USE (\\Junk))") || die; + $imaptalk->create("INBOX.Ham.Zonk") || die; + $imaptalk->create("INBOX.Ham.Bonk") || die; - my $res = $jmap->CallMethods([['Mailbox/get', { properties => ["name"] }, 'R1' ]]); - $self->assert_num_equals(5, scalar @{$res->[0][1]{list}}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; - $self->assert(exists $mboxids{'Inbox'}); + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => ["name"] }, 'R1' ] ]); + $self->assert_num_equals(5, scalar @{ $res->[0][1]{list} }); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; + $self->assert(exists $mboxids{'Inbox'}); - $res = $jmap->CallMethods([ - ['Mailbox/query', { - filter => { name => 'onk' }, - sort => [{ property => "name" }], - }, "R1"] - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Bonk'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[1]); + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { name => 'onk' }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Bonk'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'Zonk'}, $res->[0][1]{ids}[1]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_parentid_null b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_parentid_null index 792832b86e..f245405acc 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_parentid_null +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_parentid_null @@ -2,34 +2,36 @@ use Cassandane::Tiny; sub test_mailbox_query_parentid_null - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - xlog $self, "create mailbox tree"; - $imaptalk->create("INBOX.Ham") || die; - $imaptalk->create("INBOX.Spam", "(USE (\\Junk))") || die; - $imaptalk->create("INBOX.Ham.Zonk") || die; - $imaptalk->create("INBOX.Ham.Bonk") || die; + xlog $self, "create mailbox tree"; + $imaptalk->create("INBOX.Ham") || die; + $imaptalk->create("INBOX.Spam", "(USE (\\Junk))") || die; + $imaptalk->create("INBOX.Ham.Zonk") || die; + $imaptalk->create("INBOX.Ham.Bonk") || die; - xlog $self, "(re)fetch mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', { properties => ["name"] }, 'R1' ]]); - $self->assert_num_equals(5, scalar @{$res->[0][1]{list}}); - my %mboxids = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; - $self->assert(exists $mboxids{'Inbox'}); + xlog $self, "(re)fetch mailboxes"; + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => ["name"] }, 'R1' ] ]); + $self->assert_num_equals(5, scalar @{ $res->[0][1]{list} }); + my %mboxids = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; + $self->assert(exists $mboxids{'Inbox'}); - xlog $self, "list mailboxes, filtered by parentId null"; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - filter => { parentId => undef }, - sort => [{ property => "name" }], - }, "R1"] - ]); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{ids}}); - $self->assert_str_equals($mboxids{'Ham'}, $res->[0][1]{ids}[0]); - $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); - $self->assert_str_equals($mboxids{'Spam'}, $res->[0][1]{ids}[2]); + xlog $self, "list mailboxes, filtered by parentId null"; + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => { parentId => undef }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{ids} }); + $self->assert_str_equals($mboxids{'Ham'}, $res->[0][1]{ids}[0]); + $self->assert_str_equals($mboxids{'Inbox'}, $res->[0][1]{ids}[1]); + $self->assert_str_equals($mboxids{'Spam'}, $res->[0][1]{ids}[2]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_sortastree b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_sortastree index 69834812bf..6b3c73f48f 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_query_sortastree +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_query_sortastree @@ -2,56 +2,56 @@ use Cassandane::Tiny; sub test_mailbox_query_sortastree - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.A") || die; - $imaptalk->create("INBOX.A.A1") || die; - $imaptalk->create("INBOX.A.A2") || die; - $imaptalk->create("INBOX.A.A2.A2A") || die; - $imaptalk->create("INBOX.B") || die; - $imaptalk->create("INBOX.C") || die; - $imaptalk->create("INBOX.C.C1") || die; - $imaptalk->create("INBOX.C.C1.C1A") || die; - $imaptalk->create("INBOX.C.C2") || die; - $imaptalk->create("INBOX.D") || die; + $imaptalk->create("INBOX.A") || die; + $imaptalk->create("INBOX.A.A1") || die; + $imaptalk->create("INBOX.A.A2") || die; + $imaptalk->create("INBOX.A.A2.A2A") || die; + $imaptalk->create("INBOX.B") || die; + $imaptalk->create("INBOX.C") || die; + $imaptalk->create("INBOX.C.C1") || die; + $imaptalk->create("INBOX.C.C1.C1A") || die; + $imaptalk->create("INBOX.C.C2") || die; + $imaptalk->create("INBOX.D") || die; - my $res = $jmap->CallMethods([['Mailbox/get', { properties => ["name"] }, 'R1' ]]); - $self->assert_num_equals(11, scalar @{$res->[0][1]{list}}); - my %mboxIds = map { $_->{name} => $_->{id} } @{$res->[0][1]{list}}; + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => ["name"] }, 'R1' ] ]); + $self->assert_num_equals(11, scalar @{ $res->[0][1]{list} }); + my %mboxIds = map { $_->{name} => $_->{id} } @{ $res->[0][1]{list} }; - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sortAsTree => JSON::true, - sort => [{ property => 'name' }] - }, "R1"] - ]); + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sortAsTree => JSON::true, + sort => [ { property => 'name' } ] + }, + "R1" + ] ]); - my $wantMboxIds = [ - $mboxIds{'A'}, $mboxIds{'A1'}, $mboxIds{'A2'}, $mboxIds{'A2A'}, - $mboxIds{'B'}, - $mboxIds{'C'}, $mboxIds{'C1'}, $mboxIds{'C1A'}, $mboxIds{'C2'}, - $mboxIds{'D'}, - $mboxIds{'Inbox'}, - ]; - $self->assert_deep_equals($wantMboxIds, $res->[0][1]->{ids}); + my $wantMboxIds = [ + $mboxIds{'A'}, $mboxIds{'A1'}, $mboxIds{'A2'}, $mboxIds{'A2A'}, + $mboxIds{'B'}, $mboxIds{'C'}, $mboxIds{'C1'}, $mboxIds{'C1A'}, + $mboxIds{'C2'}, $mboxIds{'D'}, $mboxIds{'Inbox'}, + ]; + $self->assert_deep_equals($wantMboxIds, $res->[0][1]->{ids}); - $res = $jmap->CallMethods([ - ['Mailbox/query', { - sortAsTree => JSON::true, - sort => [{ property => 'name', isAscending => JSON::false }] - }, "R1"] - ]); - $wantMboxIds = [ - $mboxIds{'Inbox'}, - $mboxIds{'D'}, - $mboxIds{'C'}, $mboxIds{'C2'}, $mboxIds{'C1'}, $mboxIds{'C1A'}, - $mboxIds{'B'}, - $mboxIds{'A'}, $mboxIds{'A2'}, $mboxIds{'A2A'}, $mboxIds{'A1'}, - ]; - $self->assert_deep_equals($wantMboxIds, $res->[0][1]->{ids}); + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sortAsTree => JSON::true, + sort => [ { property => 'name', isAscending => JSON::false } ] + }, + "R1" + ] ]); + $wantMboxIds = [ + $mboxIds{'Inbox'}, $mboxIds{'D'}, $mboxIds{'C'}, $mboxIds{'C2'}, + $mboxIds{'C1'}, $mboxIds{'C1A'}, $mboxIds{'B'}, $mboxIds{'A'}, + $mboxIds{'A2'}, $mboxIds{'A2A'}, $mboxIds{'A1'}, + ]; + $self->assert_deep_equals($wantMboxIds, $res->[0][1]->{ids}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_intermediary_added b/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_intermediary_added index 13f51d97a5..e84ee48a3b 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_intermediary_added +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_intermediary_added @@ -2,40 +2,50 @@ use Cassandane::Tiny; sub test_mailbox_querychanges_intermediary_added - :min_version_3_1 :max_version_3_4 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : max_version_3_4 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Fetch initial mailbox state"; - my $res = $jmap->CallMethods([['Mailbox/query', { - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{ids}}); - $self->assert_equals(JSON::true, $res->[0][1]->{canCalculateChanges}); - my $state = $res->[0][1]->{queryState}; - $self->assert_not_null($state); + xlog $self, "Fetch initial mailbox state"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{ids} }); + $self->assert_equals(JSON::true, $res->[0][1]->{canCalculateChanges}); + my $state = $res->[0][1]->{queryState}; + $self->assert_not_null($state); - xlog $self, "Create intermediate mailboxes via IMAP"; - $imap->create("INBOX.A.B.Z") or die; + xlog $self, "Create intermediate mailboxes via IMAP"; + $imap->create("INBOX.A.B.Z") or die; - xlog $self, "Fetch updated mailbox state"; - $res = $jmap->CallMethods([['Mailbox/queryChanges', { - sinceQueryState => $state, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); - my @ids = map { $_->{id} } @{$res->[0][1]->{added}}; - $self->assert_num_equals(3, scalar @ids); + xlog $self, "Fetch updated mailbox state"; + $res = $jmap->CallMethods([ [ + 'Mailbox/queryChanges', + { + sinceQueryState => $state, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); + my @ids = map { $_->{id} } @{ $res->[0][1]->{added} }; + $self->assert_num_equals(3, scalar @ids); - xlog $self, "Make sure intermediate mailboxes got reported"; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - ids => \@ids, properties => ['name'], - }, "R1"] - ]); - $self->assert_not_null('A', $res->[0][1]{list}[0]{name}); - $self->assert_not_null('B', $res->[0][1]{list}[1]{name}); - $self->assert_not_null('Z', $res->[0][1]{list}[2]{name}); + xlog $self, "Make sure intermediate mailboxes got reported"; + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + ids => \@ids, + properties => ['name'], + }, + "R1" + ] ]); + $self->assert_not_null('A', $res->[0][1]{list}[0]{name}); + $self->assert_not_null('B', $res->[0][1]{list}[1]{name}); + $self->assert_not_null('Z', $res->[0][1]{list}[2]{name}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_intermediary_removed b/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_intermediary_removed index daf0a45be4..93bc0f3930 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_intermediary_removed +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_intermediary_removed @@ -2,32 +2,39 @@ use Cassandane::Tiny; sub test_mailbox_querychanges_intermediary_removed - :min_version_3_1 :max_version_3_4 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + : min_version_3_1 : max_version_3_4 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create intermediate mailboxes via IMAP"; - $imap->create("INBOX.A.B.Z") or die; + xlog $self, "Create intermediate mailboxes via IMAP"; + $imap->create("INBOX.A.B.Z") or die; - xlog $self, "Fetch initial mailbox state"; - my $res = $jmap->CallMethods([['Mailbox/query', { - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_num_equals(4, scalar @{$res->[0][1]{ids}}); - $self->assert_equals(JSON::true, $res->[0][1]->{canCalculateChanges}); - my $state = $res->[0][1]->{queryState}; - $self->assert_not_null($state); + xlog $self, "Fetch initial mailbox state"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_num_equals(4, scalar @{ $res->[0][1]{ids} }); + $self->assert_equals(JSON::true, $res->[0][1]->{canCalculateChanges}); + my $state = $res->[0][1]->{queryState}; + $self->assert_not_null($state); - xlog $self, "Delete intermediate mailboxes via IMAP"; - $imap->delete("INBOX.A.B.Z") or die; + xlog $self, "Delete intermediate mailboxes via IMAP"; + $imap->delete("INBOX.A.B.Z") or die; - xlog $self, "Fetch updated mailbox state"; - $res = $jmap->CallMethods([['Mailbox/queryChanges', { - sinceQueryState => $state, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); - $self->assert_num_equals(3, scalar @{$res->[0][1]->{removed}}); + xlog $self, "Fetch updated mailbox state"; + $res = $jmap->CallMethods([ [ + 'Mailbox/queryChanges', + { + sinceQueryState => $state, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); + $self->assert_num_equals(3, scalar @{ $res->[0][1]->{removed} }); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_name b/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_name index e86975ef78..43f6578d75 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_name +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_name @@ -2,116 +2,148 @@ use Cassandane::Tiny; sub test_mailbox_querychanges_name - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $inboxId = $self->getinbox()->{id}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $inboxId = $self->getinbox()->{id}; - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - 1 => { - parentId => $inboxId, - name => 'A', - }, - 2 => { - parentId => $inboxId, - name => 'B', - }, - 3 => { - parentId => $inboxId, - name => 'C', - }, + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + 1 => { + parentId => $inboxId, + name => 'A', }, - }, "R1"]]); - my $mboxId1 = $res->[0][1]{created}{1}{id}; - my $mboxId2 = $res->[0][1]{created}{2}{id}; - my $mboxId3 = $res->[0][1]{created}{3}{id}; - $self->assert_not_null($mboxId1); - $self->assert_not_null($mboxId2); - $self->assert_not_null($mboxId3); + 2 => { + parentId => $inboxId, + name => 'B', + }, + 3 => { + parentId => $inboxId, + name => 'C', + }, + }, + }, + "R1" + ] ]); + my $mboxId1 = $res->[0][1]{created}{1}{id}; + my $mboxId2 = $res->[0][1]{created}{2}{id}; + my $mboxId3 = $res->[0][1]{created}{3}{id}; + $self->assert_not_null($mboxId1); + $self->assert_not_null($mboxId2); + $self->assert_not_null($mboxId3); - $res = $jmap->CallMethods([['Mailbox/query', { + $res = $jmap->CallMethods([ + [ + 'Mailbox/query', + { filter => { parentId => $inboxId }, - sort => [{ property => "name" }], - }, "R1"], + sort => [ { property => "name" } ], + }, + "R1" + ], [ - 'Mailbox/get', { '#ids' => { - resultOf => 'R1', - name => 'Mailbox/query', - path => '/ids' - }, - }, 'R2' - ]]); - my $state = $res->[0][1]->{queryState}; - $self->assert_not_null($state); - $self->assert_equals(JSON::true, $res->[0][1]->{canCalculateChanges}); + 'Mailbox/get', + { + '#ids' => { + resultOf => 'R1', + name => 'Mailbox/query', + path => '/ids' + }, + }, + 'R2' + ] + ]); + my $state = $res->[0][1]->{queryState}; + $self->assert_not_null($state); + $self->assert_equals(JSON::true, $res->[0][1]->{canCalculateChanges}); - $res = $jmap->CallMethods([['Mailbox/queryChanges', { - sinceQueryState => $state, - filter => { parentId => $inboxId }, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{newQueryState}); + $res = $jmap->CallMethods([ [ + 'Mailbox/queryChanges', + { + sinceQueryState => $state, + filter => { parentId => $inboxId }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_str_equals($state, $res->[0][1]->{newQueryState}); - # Move mailbox 1 to end of the list - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $mboxId1 => { - name => 'Z', - }, + # Move mailbox 1 to end of the list + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $mboxId1 => { + name => 'Z', }, - }, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$mboxId1}); + }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$mboxId1}); - $res = $jmap->CallMethods([['Mailbox/queryChanges', { - sinceQueryState => $state, - filter => { parentId => $inboxId }, - sort => [{ property => "name" }], - }, "R1"]]); - $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{removed}}); - $self->assert_str_equals($mboxId1, $res->[0][1]{removed}[0]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{added}}); - $self->assert_str_equals($mboxId1, $res->[0][1]{added}[0]{id}); + $res = $jmap->CallMethods([ [ + 'Mailbox/queryChanges', + { + sinceQueryState => $state, + filter => { parentId => $inboxId }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); + $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{removed} }); + $self->assert_str_equals($mboxId1, $res->[0][1]{removed}[0]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{added} }); + $self->assert_str_equals($mboxId1, $res->[0][1]{added}[0]{id}); - # position 0 -> the tombstone from 'A' - # position 1 -> keep 'B' - # position 2 -> keep 'Z' - # position 3 -> new mailbox name 'Z' - $self->assert_num_equals(3, $res->[0][1]{added}[0]{index}); - $state = $res->[0][1]->{newQueryState}; + # position 0 -> the tombstone from 'A' + # position 1 -> keep 'B' + # position 2 -> keep 'Z' + # position 3 -> new mailbox name 'Z' + $self->assert_num_equals(3, $res->[0][1]{added}[0]{index}); + $state = $res->[0][1]->{newQueryState}; - # Keep mailbox 2 at start of the list and remove mailbox 3 - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $mboxId2 => { - name => 'Y', - }, + # Keep mailbox 2 at start of the list and remove mailbox 3 + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $mboxId2 => { + name => 'Y', }, - destroy => [$mboxId3], - }, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$mboxId2}); - $self->assert_str_equals($mboxId3, $res->[0][1]{destroyed}[0]); + }, + destroy => [$mboxId3], + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$mboxId2}); + $self->assert_str_equals($mboxId3, $res->[0][1]{destroyed}[0]); - $res = $jmap->CallMethods([['Mailbox/queryChanges', { - sinceQueryState => $state, - filter => { parentId => $inboxId }, - sort => [{ property => "name" }], - }, "R1"]]); + $res = $jmap->CallMethods([ [ + 'Mailbox/queryChanges', + { + sinceQueryState => $state, + filter => { parentId => $inboxId }, + sort => [ { property => "name" } ], + }, + "R1" + ] ]); - $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{removed}}); - my %removed = map { $_ => 1 } @{$res->[0][1]{removed}}; - $self->assert(exists $removed{$mboxId2}); - $self->assert(exists $removed{$mboxId3}); + $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{removed} }); + my %removed = map { $_ => 1 } @{ $res->[0][1]{removed} }; + $self->assert(exists $removed{$mboxId2}); + $self->assert(exists $removed{$mboxId3}); - # position 0 -> null - # position 1 -> tombstone from 'B' - # position 2 -> deleted 'C' - # position 3 -> splice in 'Y' - # position 4 -> new position of 'Z' - $self->assert_num_equals(1, scalar @{$res->[0][1]{added}}); - $self->assert_str_equals($mboxId2, $res->[0][1]{added}[0]{id}); - $self->assert_num_equals(3, $res->[0][1]{added}[0]{index}); + # position 0 -> null + # position 1 -> tombstone from 'B' + # position 2 -> deleted 'C' + # position 3 -> splice in 'Y' + # position 4 -> new position of 'Z' + $self->assert_num_equals(1, scalar @{ $res->[0][1]{added} }); + $self->assert_str_equals($mboxId2, $res->[0][1]{added}[0]{id}); + $self->assert_num_equals(3, $res->[0][1]{added}[0]{index}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_role b/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_role index af8b90c0d9..8cca03ecc6 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_role +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_querychanges_role @@ -2,79 +2,101 @@ use Cassandane::Tiny; sub test_mailbox_querychanges_role - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $inboxId = $self->getinbox()->{id}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $inboxId = $self->getinbox()->{id}; - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - 1 => { - parentId => $inboxId, - name => 'A', - }, - 2 => { - parentId => $inboxId, - name => 'B', - role => 'xspecialuse', - }, - 3 => { - parentId => $inboxId, - name => 'C', - role => 'junk', - }, + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + 1 => { + parentId => $inboxId, + name => 'A', }, - }, "R1"]]); - my $mboxId1 = $res->[0][1]{created}{1}{id}; - my $mboxId2 = $res->[0][1]{created}{2}{id}; - my $mboxId3 = $res->[0][1]{created}{3}{id}; - $self->assert_not_null($mboxId1); - $self->assert_not_null($mboxId2); - $self->assert_not_null($mboxId3); + 2 => { + parentId => $inboxId, + name => 'B', + role => 'xspecialuse', + }, + 3 => { + parentId => $inboxId, + name => 'C', + role => 'junk', + }, + }, + }, + "R1" + ] ]); + my $mboxId1 = $res->[0][1]{created}{1}{id}; + my $mboxId2 = $res->[0][1]{created}{2}{id}; + my $mboxId3 = $res->[0][1]{created}{3}{id}; + $self->assert_not_null($mboxId1); + $self->assert_not_null($mboxId2); + $self->assert_not_null($mboxId3); - my $filter = { hasAnyRole => JSON::true, }; - my $sort = [{ property => "name" }]; + my $filter = { hasAnyRole => JSON::true, }; + my $sort = [ { property => "name" } ]; - $res = $jmap->CallMethods([['Mailbox/query', { - filter => $filter, sort => $sort, - }, "R1"]]); - my $state = $res->[0][1]->{queryState}; - $self->assert_not_null($state); - $self->assert_equals(JSON::true, $res->[0][1]->{canCalculateChanges}); + $res = $jmap->CallMethods([ [ + 'Mailbox/query', + { + filter => $filter, + sort => $sort, + }, + "R1" + ] ]); + my $state = $res->[0][1]->{queryState}; + $self->assert_not_null($state); + $self->assert_equals(JSON::true, $res->[0][1]->{canCalculateChanges}); - $res = $jmap->CallMethods([['Mailbox/queryChanges', { - sinceQueryState => $state, - filter => $filter, sort => $sort, - }, "R1"]]); - $self->assert_str_equals($state, $res->[0][1]->{newQueryState}); + $res = $jmap->CallMethods([ [ + 'Mailbox/queryChanges', + { + sinceQueryState => $state, + filter => $filter, + sort => $sort, + }, + "R1" + ] ]); + $self->assert_str_equals($state, $res->[0][1]->{newQueryState}); - # Remove mailbox 2 from results and add mailbox 1 - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $mboxId1 => { - role => 'trash', - }, - $mboxId2 => { - role => undef, - }, + # Remove mailbox 2 from results and add mailbox 1 + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $mboxId1 => { + role => 'trash', + }, + $mboxId2 => { + role => undef, }, - }, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$mboxId1}); - $self->assert(exists $res->[0][1]{updated}{$mboxId2}); + }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$mboxId1}); + $self->assert(exists $res->[0][1]{updated}{$mboxId2}); - $res = $jmap->CallMethods([['Mailbox/queryChanges', { - sinceQueryState => $state, - filter => $filter, sort => $sort, - }, "R1"]]); + $res = $jmap->CallMethods([ [ + 'Mailbox/queryChanges', + { + sinceQueryState => $state, + filter => $filter, + sort => $sort, + }, + "R1" + ] ]); - $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); - $self->assert_num_equals(2, scalar @{$res->[0][1]{removed}}); - my %removed = map { $_ => 1 } @{$res->[0][1]{removed}}; - $self->assert(exists $removed{$mboxId1}); - $self->assert(exists $removed{$mboxId2}); + $self->assert_str_not_equals($state, $res->[0][1]->{newQueryState}); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{removed} }); + my %removed = map { $_ => 1 } @{ $res->[0][1]{removed} }; + $self->assert(exists $removed{$mboxId1}); + $self->assert(exists $removed{$mboxId2}); - $self->assert_num_equals(1, scalar @{$res->[0][1]{added}}); - $self->assert_str_equals($mboxId1, $res->[0][1]{added}[0]{id}); - $self->assert_num_equals(0, $res->[0][1]{added}[0]{index}); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{added} }); + $self->assert_str_equals($mboxId1, $res->[0][1]{added}[0]{id}); + $self->assert_num_equals(0, $res->[0][1]{added}[0]{index}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set b/cassandane/tiny-tests/JMAPMailbox/mailbox_set index 5b2e7caf1b..7094654a6f 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set @@ -2,82 +2,91 @@ use Cassandane::Tiny; sub test_mailbox_set - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get inbox"; - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("Inbox", $inbox->{name}); + xlog $self, "get inbox"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("Inbox", $inbox->{name}); - my $state = $res->[0][1]{state}; + my $state = $res->[0][1]{state}; - xlog $self, "create mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "foo", - role => undef - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{created}); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + role => undef + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{created}); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "get mailbox $id"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id] }, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{list}[0]->{id}); + xlog $self, "get mailbox $id"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{list}[0]->{id}); - my $mbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("foo", $mbox->{name}); - $self->assert_null($mbox->{parentId}); - $self->assert_null($mbox->{role}); - $self->assert_num_equals(10, $mbox->{sortOrder}); - $self->assert_equals(JSON::true, $mbox->{myRights}->{mayReadItems}); - $self->assert_equals(JSON::true, $mbox->{myRights}->{mayAddItems}); - $self->assert_equals(JSON::true, $mbox->{myRights}->{mayRemoveItems}); - $self->assert_equals(JSON::true, $mbox->{myRights}->{mayCreateChild}); - $self->assert_equals(JSON::true, $mbox->{myRights}->{mayRename}); - $self->assert_equals(JSON::true, $mbox->{myRights}->{mayDelete}); - $self->assert_num_equals(0, $mbox->{totalEmails}); - $self->assert_num_equals(0, $mbox->{unreadEmails}); - $self->assert_num_equals(0, $mbox->{totalThreads}); - $self->assert_num_equals(0, $mbox->{unreadThreads}); + my $mbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("foo", $mbox->{name}); + $self->assert_null($mbox->{parentId}); + $self->assert_null($mbox->{role}); + $self->assert_num_equals(10, $mbox->{sortOrder}); + $self->assert_equals(JSON::true, $mbox->{myRights}->{mayReadItems}); + $self->assert_equals(JSON::true, $mbox->{myRights}->{mayAddItems}); + $self->assert_equals(JSON::true, $mbox->{myRights}->{mayRemoveItems}); + $self->assert_equals(JSON::true, $mbox->{myRights}->{mayCreateChild}); + $self->assert_equals(JSON::true, $mbox->{myRights}->{mayRename}); + $self->assert_equals(JSON::true, $mbox->{myRights}->{mayDelete}); + $self->assert_num_equals(0, $mbox->{totalEmails}); + $self->assert_num_equals(0, $mbox->{unreadEmails}); + $self->assert_num_equals(0, $mbox->{totalThreads}); + $self->assert_num_equals(0, $mbox->{unreadThreads}); - xlog $self, "update mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { update => { $id => { - name => "bar", - sortOrder => 20 - }}}, "R1"] - ]); + xlog $self, "update mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $id => { + name => "bar", + sortOrder => 20 + } + } + }, + "R1" + ] ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert(exists $res->[0][1]{updated}{$id}); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert(exists $res->[0][1]{updated}{$id}); - xlog $self, "get mailbox $id"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id] }, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{list}[0]->{id}); - $mbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("bar", $mbox->{name}); - $self->assert_num_equals(20, $mbox->{sortOrder}); + xlog $self, "get mailbox $id"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{list}[0]->{id}); + $mbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("bar", $mbox->{name}); + $self->assert_num_equals(20, $mbox->{sortOrder}); - xlog $self, "destroy mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { destroy => [ $id ] }, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy mailbox"; + $res = $jmap->CallMethods([ [ 'Mailbox/set', { destroy => [$id] }, "R1" ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); - xlog $self, "get mailbox $id"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id] }, "R1"]]); - $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); + xlog $self, "get mailbox $id"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{notFound}[0]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_create_serverset_props b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_create_serverset_props index 5065de5aca..e51dd68f23 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_create_serverset_props +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_create_serverset_props @@ -2,51 +2,51 @@ use Cassandane::Tiny; sub test_mailbox_set_create_serverset_props - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "can't create mailbox with server-set props"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxB => { - name => 'A', - role => undef, - # server-set properties - totalEmails => 0, - unreadEmails => 0, - totalThreads => 0, - unreadThreads => 0, - myRights => { - mayReadItems => JSON::true, - mayAddItems => JSON::true, - mayRemoveItems => JSON::true, - mayCreateChild => JSON::true, - mayDelete => JSON::true, - maySubmit => JSON::true, - maySetSeen => JSON::true, - maySetKeywords => JSON::true, - mayAdmin => JSON::true, - mayRename => JSON::true, - }, - }, + xlog "can't create mailbox with server-set props"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxB => { + name => 'A', + role => undef, + # server-set properties + totalEmails => 0, + unreadEmails => 0, + totalThreads => 0, + unreadThreads => 0, + myRights => { + mayReadItems => JSON::true, + mayAddItems => JSON::true, + mayRemoveItems => JSON::true, + mayCreateChild => JSON::true, + mayDelete => JSON::true, + maySubmit => JSON::true, + maySetSeen => JSON::true, + maySetKeywords => JSON::true, + mayAdmin => JSON::true, + mayRename => JSON::true, }, - }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notCreated}{mboxB}{type}); - my @wantInvalidProps = ( - 'myRights', - 'totalEmails', - 'unreadEmails', - 'totalThreads', - 'unreadThreads', - ); - my @gotInvalidProps = @{$res->[0][1]{notCreated}{mboxB}{properties}}; - @wantInvalidProps = sort @wantInvalidProps; - @gotInvalidProps = sort @gotInvalidProps; - $self->assert_deep_equals(\@wantInvalidProps, \@gotInvalidProps); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{mboxB}{type}); + my @wantInvalidProps = ( + 'myRights', 'totalEmails', 'unreadEmails', 'totalThreads', + 'unreadThreads', + ); + my @gotInvalidProps = @{ $res->[0][1]{notCreated}{mboxB}{properties} }; + @wantInvalidProps = sort @wantInvalidProps; + @gotInvalidProps = sort @gotInvalidProps; + $self->assert_deep_equals(\@wantInvalidProps, \@gotInvalidProps); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_create_specialuse_nochildren b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_create_specialuse_nochildren index 22e9307773..efd88150d2 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_create_specialuse_nochildren +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_create_specialuse_nochildren @@ -2,80 +2,95 @@ use Cassandane::Tiny; sub test_mailbox_set_create_specialuse_nochildren - :min_version_3_7 :needs_component_jmap :NoStartInstances -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap : NoStartInstances { + my ($self) = @_; - $self->{instance}->{config}->set('specialuse_nochildren' => '\\Trash'); - $self->_start_instances(); - $self->_setup_http_service_objects(); - $self->setup_default_using(); + $self->{instance}->{config}->set('specialuse_nochildren' => '\\Trash'); + $self->_start_instances(); + $self->_setup_http_service_objects(); + $self->setup_default_using(); - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - # set up a Trash folder with \Trash special-use annotation - my $res = $jmap->CallMethods([[ 'Mailbox/set', { - create => { - trashmbox => { - name => 'Trash', - role => 'trash', - } + # set up a Trash folder with \Trash special-use annotation + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + trashmbox => { + name => 'Trash', + role => 'trash', } - }, 'R1']]); - my $trash_id = $res->[0][1]->{created}->{trashmbox}->{id}; - $self->assert_not_null($trash_id); + } + }, + 'R1' + ] ]); + my $trash_id = $res->[0][1]->{created}->{trashmbox}->{id}; + $self->assert_not_null($trash_id); - # should not be able to create a child of \Trash - $res = $jmap->CallMethods([['Mailbox/set', { - create => { - 1 => { - parentId => $trash_id, - name => 'child', - }, + # should not be able to create a child of \Trash + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + 1 => { + parentId => $trash_id, + name => 'child', }, - }, "R1"]]); + }, + }, + "R1" + ] ]); - # XXX that syslogs an IOERROR, surprisingly -- ignore it - $self->{instance}->getsyslog(); + # XXX that syslogs an IOERROR, surprisingly -- ignore it + $self->{instance}->getsyslog(); - $self->assert_null($res->[0][1]->{created}); - $self->assert_deep_equals({ 1 => { type => 'forbidden' } }, - $res->[0][1]->{notCreated}); + $self->assert_null($res->[0][1]->{created}); + $self->assert_deep_equals({ 1 => { type => 'forbidden' } }, + $res->[0][1]->{notCreated}); - # what if we remove the annotation - # (doing this with IMAP because JMAP can't simply remove it) - $imaptalk->setmetadata("Trash", "/private/specialuse", undef); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + # what if we remove the annotation + # (doing this with IMAP because JMAP can't simply remove it) + $imaptalk->setmetadata("Trash", "/private/specialuse", undef); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - # should be able to create the child now - $res = $jmap->CallMethods([['Mailbox/set', { - create => { - 1 => { - parentId => $trash_id, - name => 'child', - }, + # should be able to create the child now + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + 1 => { + parentId => $trash_id, + name => 'child', }, - }, "R1"]]); - $self->assert_not_null($res->[0][1]->{created}->{1}->{id}); + }, + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]->{created}->{1}->{id}); - # should not be able to add the annotation back - $imaptalk->setmetadata("Trash", "/private/specialuse", '\\Trash'); - $self->assert_equals('no', $imaptalk->get_last_completion_response()); + # should not be able to add the annotation back + $imaptalk->setmetadata("Trash", "/private/specialuse", '\\Trash'); + $self->assert_equals('no', $imaptalk->get_last_completion_response()); - # should not be able to add the JMAP role back either - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $trash_id => { - role => 'trash', - } + # should not be able to add the JMAP role back either + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $trash_id => { + role => 'trash', } - }, "R1"]]); + } + }, + "R1" + ] ]); - $self->assert_not_null($res->[0][1]->{notUpdated}->{$trash_id}); - $self->assert_null($res->[0][1]->{updated}); - $self->assert_str_equals('invalidProperties', - $res->[0][1]->{notUpdated}{$trash_id}{type}); - $self->assert_deep_equals(['role'], - $res->[0][1]->{notUpdated}{$trash_id}{properties}); + $self->assert_not_null($res->[0][1]->{notUpdated}->{$trash_id}); + $self->assert_null($res->[0][1]->{updated}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]->{notUpdated}{$trash_id}{type}); + $self->assert_deep_equals(['role'], + $res->[0][1]->{notUpdated}{$trash_id}{properties}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_create b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_create index dd26141a0a..82a095c8a9 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_create +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_create @@ -2,33 +2,36 @@ use Cassandane::Tiny; sub test_mailbox_set_cycle_in_create - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # Attempt to create cyclic mailboxes. This should fail. - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - A => { - name => 'A', - parentId => '#C', - role => undef, - }, - B => { - name => 'B', - parentId => '#A', - role => undef, - }, - C => { - name => 'C', - parentId => '#B', - role => undef, - } + # Attempt to create cyclic mailboxes. This should fail. + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + A => { + name => 'A', + parentId => '#C', + role => undef, + }, + B => { + name => 'B', + parentId => '#A', + role => undef, + }, + C => { + name => 'C', + parentId => '#B', + role => undef, } - }, "R1"]]); - $self->assert_num_equals(3, scalar keys %{$res->[0][1]{notCreated}}); - $self->assert(exists $res->[0][1]{notCreated}{'A'}); - $self->assert(exists $res->[0][1]{notCreated}{'B'}); - $self->assert(exists $res->[0][1]{notCreated}{'C'}); + } + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar keys %{ $res->[0][1]{notCreated} }); + $self->assert(exists $res->[0][1]{notCreated}{'A'}); + $self->assert(exists $res->[0][1]{notCreated}{'B'}); + $self->assert(exists $res->[0][1]{notCreated}{'C'}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_mboxtree b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_mboxtree index 74b3c2f69d..13a66350c8 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_mboxtree +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_mboxtree @@ -2,27 +2,30 @@ use Cassandane::Tiny; sub test_mailbox_set_cycle_in_mboxtree - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - # Create and get mailbox tree. - $imaptalk->create("INBOX.A") or die; - $imaptalk->create("INBOX.A.B") or die; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my ($idA, $idB) = ($m{"A"}{id}, $m{"B"}{id}); + # Create and get mailbox tree. + $imaptalk->create("INBOX.A") or die; + $imaptalk->create("INBOX.A.B") or die; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my ($idA, $idB) = ($m{"A"}{id}, $m{"B"}{id}); - # Introduce a cycle in the mailbox tree. This should fail. - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $idA => { - parentId => $idB, - }, + # Introduce a cycle in the mailbox tree. This should fail. + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $idA => { + parentId => $idB, }, - }, "R1"]]); - $self->assert(exists $res->[0][1]{notUpdated}{$idA}); + }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{notUpdated}{$idA}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_update b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_update index 83de22dacc..39de3eaf9c 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_update +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_cycle_in_update @@ -2,38 +2,45 @@ use Cassandane::Tiny; sub test_mailbox_set_cycle_in_update - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - # Create and get mailbox tree. - $imaptalk->create("INBOX.A") or die; - $imaptalk->create("INBOX.B") or die; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my ($idA, $idB) = ($m{"A"}{id}, $m{"B"}{id}); + # Create and get mailbox tree. + $imaptalk->create("INBOX.A") or die; + $imaptalk->create("INBOX.B") or die; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my ($idA, $idB) = ($m{"A"}{id}, $m{"B"}{id}); - # Introduce a cycle in the mailbox tree. Since both - # operations could create the cycle, one operation must - # fail and the other succeed. It's not deterministic - # which will, resulting in mailboxes (A, A.B) or (B, B.A). - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $idB => { - parentId => $idA, - }, - $idA => { - parentId => $idB, - }, + # Introduce a cycle in the mailbox tree. Since both + # operations could create the cycle, one operation must + # fail and the other succeed. It's not deterministic + # which will, resulting in mailboxes (A, A.B) or (B, B.A). + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $idB => { + parentId => $idA, }, - }, "R1"]]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{notUpdated}}); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{updated}}); - $self->assert( - (exists $res->[0][1]{notUpdated}{$idA} and exists $res->[0][1]{updated}{$idB}) or - (exists $res->[0][1]{notUpdated}{$idB} and exists $res->[0][1]{updated}{$idA}) - ); + $idA => { + parentId => $idB, + }, + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{notUpdated} }); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{updated} }); + $self->assert( + ( + exists $res->[0][1]{notUpdated}{$idA} + and exists $res->[0][1]{updated}{$idB} + ) + or (exists $res->[0][1]{notUpdated}{$idB} + and exists $res->[0][1]{updated}{$idA}) + ); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_empty b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_empty index a596b35e4a..1221131380 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_empty +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_empty @@ -2,70 +2,90 @@ use Cassandane::Tiny; sub test_mailbox_set_destroy_empty - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $store = $self->{store}; - my $talk = $store->get_client(); + my $store = $self->{store}; + my $talk = $store->get_client(); - xlog $self, "Generate an email in INBOX via IMAP"; - $self->make_message("Email A") || die; + xlog $self, "Generate an email in INBOX via IMAP"; + $self->make_message("Email A") || die; - xlog $self, "get email list"; - my $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - $self->assert_num_equals(1, scalar @{$res->[0][1]->{ids}}); - my $msgid = $res->[0][1]->{ids}[0]; + xlog $self, "get email list"; + my $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]->{ids} }); + my $msgid = $res->[0][1]->{ids}[0]; - xlog $self, "get inbox"; - $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("Inbox", $inbox->{name}); + xlog $self, "get inbox"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("Inbox", $inbox->{name}); - my $state = $res->[0][1]{state}; + my $state = $res->[0][1]{state}; - xlog $self, "create mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "foo", - parentId => $inbox->{id}, - role => undef - }}}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $self->assert_not_null($res->[0][1]{created}); - my $mboxid = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + parentId => $inbox->{id}, + role => undef + } + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $self->assert_not_null($res->[0][1]{created}); + my $mboxid = $res->[0][1]{created}{"1"}{id}; - xlog $self, "copy email to newly created mailbox"; - $res = $jmap->CallMethods([['Email/set', { - update => { $msgid => { mailboxIds => { + xlog $self, "copy email to newly created mailbox"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $msgid => { + mailboxIds => { $inbox->{id} => JSON::true, - $mboxid => JSON::true, - }}}, - }, "R1"]]); - $self->assert_not_null($res->[0][1]{updated}); + $mboxid => JSON::true, + } + } + }, + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{updated}); - xlog $self, "attempt to destroy mailbox with email"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { destroy => [ $mboxid ] }, "R1"] - ]); - $self->assert_not_null($res->[0][1]{notDestroyed}{$mboxid}); - $self->assert_str_equals('mailboxHasEmail', $res->[0][1]{notDestroyed}{$mboxid}{type}); + xlog $self, "attempt to destroy mailbox with email"; + $res + = $jmap->CallMethods([ [ 'Mailbox/set', { destroy => [$mboxid] }, "R1" ] ]); + $self->assert_not_null($res->[0][1]{notDestroyed}{$mboxid}); + $self->assert_str_equals('mailboxHasEmail', + $res->[0][1]{notDestroyed}{$mboxid}{type}); - xlog $self, "remove email from mailbox"; - $res = $jmap->CallMethods([['Email/set', { - update => { $msgid => { mailboxIds => { + xlog $self, "remove email from mailbox"; + $res = $jmap->CallMethods([ [ + 'Email/set', + { + update => { + $msgid => { + mailboxIds => { $inbox->{id} => JSON::true, - }}}, - }, "R1"]]); - $self->assert_not_null($res->[0][1]{updated}); + } + } + }, + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{updated}); - xlog $self, "destroy empty mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { destroy => [ $mboxid ] }, "R1"] - ]); - $self->assert_str_equals($mboxid, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy empty mailbox"; + $res + = $jmap->CallMethods([ [ 'Mailbox/set', { destroy => [$mboxid] }, "R1" ] ]); + $self->assert_str_equals($mboxid, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox index 0a08622108..94ddaf881f 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox @@ -2,99 +2,122 @@ use Cassandane::Tiny; sub test_mailbox_set_destroy_movetomailbox - :min_version_3_3 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $store = $self->{store}; + : min_version_3_3 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $store = $self->{store}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + ]; - xlog "Create mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, - mboxC => { - name => 'C', - }, + xlog "Create mailboxes"; + my $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', }, - }, 'R1'], - ['Email/set', { - create => { - emailA => { - mailboxIds => { - '#mboxA' => JSON::true, - }, - subject => 'emailA', - bodyStructure => { - type => 'text/plain', - partId => '1', - }, - bodyValues => { - 1 => { - value => 'emailA', - } - }, - }, - emailAB => { - mailboxIds => { - '#mboxA' => JSON::true, - '#mboxB' => JSON::true, - }, - subject => 'emailAB', - bodyStructure => { - type => 'text/plain', - partId => '1', - }, - bodyValues => { - 1 => { - value => 'emailAB', - } - }, - }, + mboxB => { + name => 'B', }, - }, 'R2'], - ], $using); - my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxIdA); - my $mboxIdB = $res->[0][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxIdB); - my $mboxIdC = $res->[0][1]{created}{mboxC}{id}; - $self->assert_not_null($mboxIdC); - my $emailIdA = $res->[1][1]{created}{emailA}{id}; - $self->assert_not_null($emailIdA); - my $emailIdAB = $res->[1][1]{created}{emailAB}{id}; - $self->assert_not_null($emailIdAB); + mboxC => { + name => 'C', + }, + }, + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + emailA => { + mailboxIds => { + '#mboxA' => JSON::true, + }, + subject => 'emailA', + bodyStructure => { + type => 'text/plain', + partId => '1', + }, + bodyValues => { + 1 => { + value => 'emailA', + } + }, + }, + emailAB => { + mailboxIds => { + '#mboxA' => JSON::true, + '#mboxB' => JSON::true, + }, + subject => 'emailAB', + bodyStructure => { + type => 'text/plain', + partId => '1', + }, + bodyValues => { + 1 => { + value => 'emailAB', + } + }, + }, + }, + }, + 'R2' + ], + ], + $using + ); + my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxIdA); + my $mboxIdB = $res->[0][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxIdB); + my $mboxIdC = $res->[0][1]{created}{mboxC}{id}; + $self->assert_not_null($mboxIdC); + my $emailIdA = $res->[1][1]{created}{emailA}{id}; + $self->assert_not_null($emailIdA); + my $emailIdAB = $res->[1][1]{created}{emailAB}{id}; + $self->assert_not_null($emailIdAB); - xlog "Destroy mailbox A and move emails to C"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [$mboxIdA], - onDestroyMoveToMailboxIfNoMailbox => $mboxIdC, - }, 'R1'], - ['Email/get', { - ids => [$emailIdA], - properties => ['mailboxIds'], - }, 'R2'], - ['Email/get', { - ids => [$emailIdAB], - properties => ['mailboxIds'], - }, 'R3'], - ], $using); - $self->assert_deep_equals([$mboxIdA], - $res->[0][1]{destroyed}); - $self->assert_deep_equals({$mboxIdC => JSON::true}, - $res->[1][1]{list}[0]{mailboxIds}); - $self->assert_deep_equals({$mboxIdB => JSON::true}, - $res->[2][1]{list}[0]{mailboxIds}); + xlog "Destroy mailbox A and move emails to C"; + $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + destroy => [$mboxIdA], + onDestroyMoveToMailboxIfNoMailbox => $mboxIdC, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [$emailIdA], + properties => ['mailboxIds'], + }, + 'R2' + ], + [ + 'Email/get', + { + ids => [$emailIdAB], + properties => ['mailboxIds'], + }, + 'R3' + ], + ], + $using + ); + $self->assert_deep_equals([$mboxIdA], $res->[0][1]{destroyed}); + $self->assert_deep_equals({ $mboxIdC => JSON::true }, + $res->[1][1]{list}[0]{mailboxIds}); + $self->assert_deep_equals({ $mboxIdB => JSON::true }, + $res->[2][1]{list}[0]{mailboxIds}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox_empty b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox_empty index 2a1968c014..bf53bc8430 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox_empty +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox_empty @@ -2,75 +2,94 @@ use Cassandane::Tiny; sub test_mailbox_set_destroy_movetomailbox_empty - :min_version_3_3 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $store = $self->{store}; + : min_version_3_3 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $store = $self->{store}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + ]; - xlog "Create mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, - mboxC => { - name => 'C', - }, + xlog "Create mailboxes"; + my $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', }, - }, 'R1'], - ['Email/set', { - create => { - emailA => { - mailboxIds => { - '#mboxA' => JSON::true, - }, - subject => 'emailA', - bodyStructure => { - type => 'text/plain', - partId => '1', - }, - bodyValues => { - 1 => { - value => 'emailA', - } - }, - }, + mboxB => { + name => 'B', }, - }, 'R2'], - ], $using); - my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxIdA); - my $mboxIdB = $res->[0][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxIdB); - my $mboxIdC = $res->[0][1]{created}{mboxC}{id}; - $self->assert_not_null($mboxIdC); - my $emailIdA = $res->[1][1]{created}{emailA}{id}; - $self->assert_not_null($emailIdA); + mboxC => { + name => 'C', + }, + }, + }, + 'R1' + ], + [ + 'Email/set', + { + create => { + emailA => { + mailboxIds => { + '#mboxA' => JSON::true, + }, + subject => 'emailA', + bodyStructure => { + type => 'text/plain', + partId => '1', + }, + bodyValues => { + 1 => { + value => 'emailA', + } + }, + }, + }, + }, + 'R2' + ], + ], + $using + ); + my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxIdA); + my $mboxIdB = $res->[0][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxIdB); + my $mboxIdC = $res->[0][1]{created}{mboxC}{id}; + $self->assert_not_null($mboxIdC); + my $emailIdA = $res->[1][1]{created}{emailA}{id}; + $self->assert_not_null($emailIdA); - xlog "Destroy mailbox B and move emails to C"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [$mboxIdB], - onDestroyMoveToMailboxIfNoMailbox => $mboxIdC, - }, 'R1'], - ['Email/get', { - ids => [$emailIdA], - properties => ['mailboxIds'], - }, 'R2'], - ], $using); - $self->assert_deep_equals([$mboxIdB], - $res->[0][1]{destroyed}); - $self->assert_deep_equals({$mboxIdA => JSON::true}, - $res->[1][1]{list}[0]{mailboxIds}); + xlog "Destroy mailbox B and move emails to C"; + $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + destroy => [$mboxIdB], + onDestroyMoveToMailboxIfNoMailbox => $mboxIdC, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [$emailIdA], + properties => ['mailboxIds'], + }, + 'R2' + ], + ], + $using + ); + $self->assert_deep_equals([$mboxIdB], $res->[0][1]{destroyed}); + $self->assert_deep_equals({ $mboxIdA => JSON::true }, + $res->[1][1]{list}[0]{mailboxIds}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox_errors b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox_errors index eb7405e229..b171e1e207 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox_errors +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_movetomailbox_errors @@ -2,56 +2,72 @@ use Cassandane::Tiny; sub test_mailbox_set_destroy_movetomailbox_errors - :min_version_3_3 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + ]; - xlog "Create mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - }, - mboxB => { - name => 'B', - }, + xlog "Create mailboxes"; + my $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', }, - }, 'R1'], - ], $using); - my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxIdA); - my $mboxIdB = $res->[0][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxIdB); + mboxB => { + name => 'B', + }, + }, + }, + 'R1' + ], + ], + $using + ); + my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxIdA); + my $mboxIdB = $res->[0][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxIdB); - xlog "Can't move emails to updated or destroyed mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [$mboxIdA], - onDestroyMoveToMailboxIfNoMailbox => $mboxIdA, - }, 'R1'], - ['Mailbox/set', { - update => { - $mboxIdB => { - role => 'trash', - }, + xlog "Can't move emails to updated or destroyed mailbox"; + $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + destroy => [$mboxIdA], + onDestroyMoveToMailboxIfNoMailbox => $mboxIdA, + }, + 'R1' + ], + [ + 'Mailbox/set', + { + update => { + $mboxIdB => { + role => 'trash', }, - destroy => [$mboxIdA], - onDestroyMoveToMailboxIfNoMailbox => $mboxIdB, - }, 'R2'], - ], $using); - $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); - $self->assert_deep_equals(['onDestroyMoveToMailboxIfNoMailbox'], - $res->[0][1]{arguments}); - $self->assert_str_equals('invalidArguments', $res->[1][1]{type}); - $self->assert_deep_equals(['onDestroyMoveToMailboxIfNoMailbox'], - $res->[1][1]{arguments}); + }, + destroy => [$mboxIdA], + onDestroyMoveToMailboxIfNoMailbox => $mboxIdB, + }, + 'R2' + ], + ], + $using + ); + $self->assert_str_equals('invalidArguments', $res->[0][1]{type}); + $self->assert_deep_equals(['onDestroyMoveToMailboxIfNoMailbox'], + $res->[0][1]{arguments}); + $self->assert_str_equals('invalidArguments', $res->[1][1]{type}); + $self->assert_deep_equals(['onDestroyMoveToMailboxIfNoMailbox'], + $res->[1][1]{arguments}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_removemsgs b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_removemsgs index 254be1da06..bf48488687 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_removemsgs +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_removemsgs @@ -2,71 +2,84 @@ use Cassandane::Tiny; sub test_mailbox_set_destroy_removemsgs - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Create email in inbox and another mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { }, 'R1'], - ['Mailbox/set', { - create => { - mbox => { - name => 'A', - }, + xlog "Create email in inbox and another mailbox"; + my $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], + [ + 'Mailbox/set', + { + create => { + mbox => { + name => 'A', + }, + }, + }, + 'R2' + ], + [ + 'Email/set', + { + create => { + email => { + mailboxIds => { + '$inbox' => JSON::true, + '#mbox' => JSON::true, }, - }, 'R2'], - ['Email/set', { - create => { - email => { - mailboxIds => { - '$inbox' => JSON::true, - '#mbox' => JSON::true, - }, - subject => 'email', - bodyStructure => { - type => 'text/plain', - partId => '1', - }, - bodyValues => { - 1 => { - value => 'email', - } - }, - }, + subject => 'email', + bodyStructure => { + type => 'text/plain', + partId => '1', }, - }, 'R3'], - ]); - my $inboxId = $res->[0][1]{ids}[0]; - $self->assert_not_null($inboxId); - my $mboxId = $res->[1][1]{created}{mbox}{id}; - $self->assert_not_null($mboxId); - my $emailId = $res->[2][1]{created}{email}{id}; - $self->assert_not_null($emailId); + bodyValues => { + 1 => { + value => 'email', + } + }, + }, + }, + }, + 'R3' + ], + ]); + my $inboxId = $res->[0][1]{ids}[0]; + $self->assert_not_null($inboxId); + my $mboxId = $res->[1][1]{created}{mbox}{id}; + $self->assert_not_null($mboxId); + my $emailId = $res->[2][1]{created}{email}{id}; + $self->assert_not_null($emailId); - $self->{instance}->getsyslog(); + $self->{instance}->getsyslog(); - xlog "Destroy mailbox with onDestroyRemoveEmails"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [$mboxId], - onDestroyRemoveEmails => JSON::true, - }, 'R1'], - ['Email/get', { - ids => [$emailId], - properties => ['mailboxIds'], - }, 'R2'], - ]); - $self->assert_deep_equals([$mboxId], $res->[0][1]{destroyed}); - $self->assert_deep_equals({ $inboxId => JSON::true }, - $res->[1][1]{list}[0]{mailboxIds}); + xlog "Destroy mailbox with onDestroyRemoveEmails"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + destroy => [$mboxId], + onDestroyRemoveEmails => JSON::true, + }, + 'R1' + ], + [ + 'Email/get', + { + ids => [$emailId], + properties => ['mailboxIds'], + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$mboxId], $res->[0][1]{destroyed}); + $self->assert_deep_equals({ $inboxId => JSON::true }, + $res->[1][1]{list}[0]{mailboxIds}); - my ($maj, $min) = Cassandane::Instance->get_version(); - if ($maj > 3 || ($maj == 3 && $min >= 7)) { - $self->assert_syslog_matches( - $self->{instance}, - qr{Destroyed mailbox: mboxid=<$mboxId> msgcount=<1>} - ); - } + my ($maj, $min) = Cassandane::Instance->get_version(); + if ($maj > 3 || ($maj == 3 && $min >= 7)) { + $self->assert_syslog_matches($self->{instance}, + qr{Destroyed mailbox: mboxid=<$mboxId> msgcount=<1>}); + } } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_twice b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_twice index fc15b0c831..e34abccc5c 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_twice +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_destroy_twice @@ -2,30 +2,31 @@ use Cassandane::Tiny; sub test_mailbox_set_destroy_twice - :min_version_3_8 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_8 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "foo", - role => undef - }}}, "R1"] - ]); - my $id = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + role => undef + } + } + }, + "R1" + ] ]); + my $id = $res->[0][1]{created}{"1"}{id}; - xlog $self, "destroy mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { destroy => [ $id ] }, "R1"] - ]); - $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); + xlog $self, "destroy mailbox"; + $res = $jmap->CallMethods([ [ 'Mailbox/set', { destroy => [$id] }, "R1" ] ]); + $self->assert_str_equals($id, $res->[0][1]{destroyed}[0]); - xlog $self, "destroy mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { destroy => [ $id ] }, "R1"] - ]); - $self->assert_str_equals("notFound", $res->[0][1]{notDestroyed}{$id}{type}); + xlog $self, "destroy mailbox"; + $res = $jmap->CallMethods([ [ 'Mailbox/set', { destroy => [$id] }, "R1" ] ]); + $self->assert_str_equals("notFound", $res->[0][1]{notDestroyed}{$id}{type}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_extendedprops b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_extendedprops index 81aa97c941..97842a2ff4 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_extendedprops +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_extendedprops @@ -2,83 +2,90 @@ use Cassandane::Tiny; sub test_mailbox_set_extendedprops - :min_version_3_3 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; - # we need 'https://cyrusimap.org/ns/jmap/mail' capability for - # isSeenShared property - my @using = @{ $jmap->DefaultUsing() }; - push @using, 'https://cyrusimap.org/ns/jmap/mail'; - $jmap->DefaultUsing(\@using); + # we need 'https://cyrusimap.org/ns/jmap/mail' capability for + # isSeenShared property + my @using = @{ $jmap->DefaultUsing() }; + push @using, 'https://cyrusimap.org/ns/jmap/mail'; + $jmap->DefaultUsing(\@using); - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "A" => { - name => "A", - }, - "B" => { - name => "B", - isSeenShared => JSON::true, - color => '#ABCDEF', - showAsLabel => JSON::false, - } - } - }, "R1"] - ]); - $self->assert_equals(JSON::false, $res->[0][1]{created}{A}{isSeenShared}); - $self->assert(not exists $res->[0][1]{created}{B}{isSeenShared}); - $self->assert_null($res->[0][1]{created}{A}{color}); - $self->assert(not exists $res->[0][1]{created}{B}{color}); - $self->assert_equals(JSON::true, $res->[0][1]{created}{A}{showAsLabel}); - $self->assert(not exists $res->[0][1]{created}{B}{showAsLabel}); - my $mboxIdA = $res->[0][1]{created}{A}{id}; - my $mboxIdB = $res->[0][1]{created}{B}{id}; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "A" => { + name => "A", + }, + "B" => { + name => "B", + isSeenShared => JSON::true, + color => '#ABCDEF', + showAsLabel => JSON::false, + } + } + }, + "R1" + ] ]); + $self->assert_equals(JSON::false, $res->[0][1]{created}{A}{isSeenShared}); + $self->assert(not exists $res->[0][1]{created}{B}{isSeenShared}); + $self->assert_null($res->[0][1]{created}{A}{color}); + $self->assert(not exists $res->[0][1]{created}{B}{color}); + $self->assert_equals(JSON::true, $res->[0][1]{created}{A}{showAsLabel}); + $self->assert(not exists $res->[0][1]{created}{B}{showAsLabel}); + my $mboxIdA = $res->[0][1]{created}{A}{id}; + my $mboxIdB = $res->[0][1]{created}{B}{id}; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - ids => [$mboxIdA, $mboxIdB], - properties => ['isSeenShared', 'color', 'showAsLabel'], - }, 'R1'] - ]); - $self->assert_equals($mboxIdA, $res->[0][1]{list}[0]{id}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isSeenShared}); - $self->assert_null($res->[0][1]{list}[0]{color}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{showAsLabel}); - $self->assert_equals($mboxIdB, $res->[0][1]{list}[1]{id}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[1]{isSeenShared}); - $self->assert_str_equals('#ABCDEF', $res->[0][1]{list}[1]{color}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[1]{showAsLabel}); + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + ids => [ $mboxIdA, $mboxIdB ], + properties => [ 'isSeenShared', 'color', 'showAsLabel' ], + }, + 'R1' + ] ]); + $self->assert_equals($mboxIdA, $res->[0][1]{list}[0]{id}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isSeenShared}); + $self->assert_null($res->[0][1]{list}[0]{color}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{showAsLabel}); + $self->assert_equals($mboxIdB, $res->[0][1]{list}[1]{id}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[1]{isSeenShared}); + $self->assert_str_equals('#ABCDEF', $res->[0][1]{list}[1]{color}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[1]{showAsLabel}); - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxIdA => { - isSeenShared => JSON::true, - color => '#123456', - showAsLabel => JSON::false, - }, - $mboxIdB => { - isSeenShared => JSON::false, - showAsLabel => JSON::false, - }, - } - }, "R1"] - ]); - $res = $jmap->CallMethods([ - ['Mailbox/get', { - ids => [$mboxIdA, $mboxIdB], - properties => ['isSeenShared', 'color', 'showAsLabel'], - }, 'R1'] - ]); - $self->assert_equals($mboxIdA, $res->[0][1]{list}[0]{id}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isSeenShared}); - $self->assert_str_equals('#123456', $res->[0][1]{list}[0]{color}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{showAsLabel}); - $self->assert_equals($mboxIdB, $res->[0][1]{list}[1]{id}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[1]{isSeenShared}); - $self->assert_str_equals('#ABCDEF', $res->[0][1]{list}[1]{color}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[1]{showAsLabel}); + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $mboxIdA => { + isSeenShared => JSON::true, + color => '#123456', + showAsLabel => JSON::false, + }, + $mboxIdB => { + isSeenShared => JSON::false, + showAsLabel => JSON::false, + }, + } + }, + "R1" + ] ]); + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + ids => [ $mboxIdA, $mboxIdB ], + properties => [ 'isSeenShared', 'color', 'showAsLabel' ], + }, + 'R1' + ] ]); + $self->assert_equals($mboxIdA, $res->[0][1]{list}[0]{id}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isSeenShared}); + $self->assert_str_equals('#123456', $res->[0][1]{list}[0]{color}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{showAsLabel}); + $self->assert_equals($mboxIdB, $res->[0][1]{list}[1]{id}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[1]{isSeenShared}); + $self->assert_str_equals('#ABCDEF', $res->[0][1]{list}[1]{color}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[1]{showAsLabel}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_inbox_children b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_inbox_children index 050bef76e9..e2df66c6b7 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_inbox_children +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_inbox_children @@ -2,77 +2,83 @@ use Cassandane::Tiny; sub test_mailbox_set_inbox_children - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.top") - or die "Cannot create mailbox INBOX.top: $@"; + $imaptalk->create("INBOX.top") + or die "Cannot create mailbox INBOX.top: $@"; - $imaptalk->create("INBOX.INBOX.foo") - or die "Cannot create mailbox INBOX.INBOX.foo: $@"; + $imaptalk->create("INBOX.INBOX.foo") + or die "Cannot create mailbox INBOX.INBOX.foo: $@"; - $imaptalk->create("INBOX.INBOX.foo.bar") - or die "Cannot create mailbox INBOX.INBOX.foo.bar: $@"; + $imaptalk->create("INBOX.INBOX.foo.bar") + or die "Cannot create mailbox INBOX.INBOX.foo.bar: $@"; - xlog $self, "get existing mailboxes"; - my $res = $jmap->CallMethods([['Mailbox/get', { properties => ['name', 'parentId']}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + xlog $self, "get existing mailboxes"; + my $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => [ 'name', 'parentId' ] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(4, scalar keys %m); - my $inbox = $m{"Inbox"}; - my $top = $m{"top"}; - my $foo = $m{"foo"}; - my $bar = $m{"bar"}; + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(4, scalar keys %m); + my $inbox = $m{"Inbox"}; + my $top = $m{"top"}; + my $foo = $m{"foo"}; + my $bar = $m{"bar"}; - # INBOX - $self->assert_null($inbox->{parentId}); - $self->assert_null($top->{parentId}); - $self->assert_str_equals($inbox->{id}, $foo->{parentId}); - $self->assert_str_equals($foo->{id}, $bar->{parentId}); + # INBOX + $self->assert_null($inbox->{parentId}); + $self->assert_null($top->{parentId}); + $self->assert_str_equals($inbox->{id}, $foo->{parentId}); + $self->assert_str_equals($foo->{id}, $bar->{parentId}); - $res = $jmap->CallMethods([['Mailbox/set', { - create => { - 'a' => { name => 'tl', parentId => undef }, - 'b' => { name => 'sl', parentId => $inbox->{id} }, - }, - update => { - $top->{id} => { name => 'B', parentId => $inbox->{id} }, - $foo->{id} => { name => 'C', parentId => undef }, - }, - }, "R1"]]); + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + 'a' => { name => 'tl', parentId => undef }, + 'b' => { name => 'sl', parentId => $inbox->{id} }, + }, + update => { + $top->{id} => { name => 'B', parentId => $inbox->{id} }, + $foo->{id} => { name => 'C', parentId => undef }, + }, + }, + "R1" + ] ]); - $res = $jmap->CallMethods([['Mailbox/get', { properties => ['name', 'parentId']}, "R1"]]); - $self->assert_not_null($res); - $self->assert_str_equals('Mailbox/get', $res->[0][0]); - $self->assert_str_equals('R1', $res->[0][2]); + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { properties => [ 'name', 'parentId' ] }, "R1" ] ]); + $self->assert_not_null($res); + $self->assert_str_equals('Mailbox/get', $res->[0][0]); + $self->assert_str_equals('R1', $res->[0][2]); - %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(6, scalar keys %m); - $inbox = $m{"Inbox"}; - my $b = $m{"B"}; - my $c = $m{"C"}; - $bar = $m{"bar"}; - my $tl = $m{"tl"}; - my $sl = $m{"sl"}; + %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(6, scalar keys %m); + $inbox = $m{"Inbox"}; + my $b = $m{"B"}; + my $c = $m{"C"}; + $bar = $m{"bar"}; + my $tl = $m{"tl"}; + my $sl = $m{"sl"}; - # INBOX - $self->assert_null($inbox->{parentId}); - $self->assert_str_equals($inbox->{id}, $b->{parentId}); - $self->assert_null($c->{parentId}); - $self->assert_str_equals($c->{id}, $bar->{parentId}); - $self->assert_str_equals($inbox->{id}, $sl->{parentId}); - $self->assert_null($tl->{parentId}); + # INBOX + $self->assert_null($inbox->{parentId}); + $self->assert_str_equals($inbox->{id}, $b->{parentId}); + $self->assert_null($c->{parentId}); + $self->assert_str_equals($c->{id}, $bar->{parentId}); + $self->assert_str_equals($inbox->{id}, $sl->{parentId}); + $self->assert_null($tl->{parentId}); - my $list = $imaptalk->list("", "*"); + my $list = $imaptalk->list("", "*"); - my $mb = join(',', sort map { $_->[2] } @$list); + my $mb = join(',', sort map { $_->[2] } @$list); - $self->assert_str_equals("INBOX,INBOX.C,INBOX.C.bar,INBOX.INBOX.B,INBOX.INBOX.sl,INBOX.tl", $mb); + $self->assert_str_equals( + "INBOX,INBOX.C,INBOX.C.bar,INBOX.INBOX.B,INBOX.INBOX.sl,INBOX.tl", $mb); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_annotation b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_annotation index 4211386638..2ad11e68fb 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_annotation +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_annotation @@ -2,53 +2,64 @@ use Cassandane::Tiny; sub test_mailbox_set_intermediary_annotation - :min_version_3_1 :max_version_3_4 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.foo") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId', 'sortOrder'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxId = $mboxByName{'i1'}->{id}; - $self->assert_num_equals(0, $mboxByName{'i1'}->{sortOrder}); - $self->assert_null($mboxByName{'i1'}->{parentId}); + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.foo") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId', 'sortOrder' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxId = $mboxByName{'i1'}->{id}; + $self->assert_num_equals(0, $mboxByName{'i1'}->{sortOrder}); + $self->assert_null($mboxByName{'i1'}->{parentId}); - xlog $self, "Set annotation on intermediate"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxId => { - sortOrder => 7, - }, - } - }, 'R1'], - ['Mailbox/get', { - ids => [$mboxId], - properties => ['name', 'parentId', 'sortOrder'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$mboxId}); - $self->assert_num_equals(7, $res->[1][1]{list}[0]->{sortOrder}); + xlog $self, "Set annotation on intermediate"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxId => { + sortOrder => 7, + }, + } + }, + 'R1' + ], + [ + 'Mailbox/get', + { + ids => [$mboxId], + properties => [ 'name', 'parentId', 'sortOrder' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$mboxId}); + $self->assert_num_equals(7, $res->[1][1]{list}[0]->{sortOrder}); - xlog $self, "Assert mailbox tree"; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(3, scalar keys %mboxByName); - $self->assert_not_null($mboxByName{'Inbox'}); - $self->assert_not_null($mboxByName{'i1'}); - $self->assert_not_null($mboxByName{'foo'}); - $self->assert_null($mboxByName{i1}->{parentId}); - $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{foo}->{parentId}); + xlog $self, "Assert mailbox tree"; + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(3, scalar keys %mboxByName); + $self->assert_not_null($mboxByName{'Inbox'}); + $self->assert_not_null($mboxByName{'i1'}); + $self->assert_not_null($mboxByName{'foo'}); + $self->assert_null($mboxByName{i1}->{parentId}); + $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{foo}->{parentId}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_createchild b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_createchild index c3f0717b88..559fd7ea24 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_createchild +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_createchild @@ -2,51 +2,56 @@ use Cassandane::Tiny; sub test_mailbox_set_intermediary_createchild - :min_version_3_1 :max_version_3_4 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.i2.i3.foo") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.i2.i3.foo") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - 1 => { - name => 'bar', - parentId => $mboxByName{'i2'}->{id}, - }, - } - }, 'R1'] - ]); - $self->assert_not_null($res->[0][1]{created}{1}{id}); + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + 1 => { + name => 'bar', + parentId => $mboxByName{'i2'}->{id}, + }, + } + }, + 'R1' + ] ]); + $self->assert_not_null($res->[0][1]{created}{1}{id}); - xlog $self, "Assert mailbox tree"; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(6, scalar keys %mboxByName); - $self->assert_not_null($mboxByName{'Inbox'}); - $self->assert_not_null($mboxByName{'i1'}); - $self->assert_not_null($mboxByName{'i2'}); - $self->assert_not_null($mboxByName{'i3'}); - $self->assert_not_null($mboxByName{'foo'}); - $self->assert_not_null($mboxByName{'bar'}); - $self->assert_null($mboxByName{i1}->{parentId}); - $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i2}->{parentId}); - $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{i3}->{parentId}); - $self->assert_str_equals($mboxByName{i3}->{id}, $mboxByName{foo}->{parentId}); - $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{bar}->{parentId}); + xlog $self, "Assert mailbox tree"; + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(6, scalar keys %mboxByName); + $self->assert_not_null($mboxByName{'Inbox'}); + $self->assert_not_null($mboxByName{'i1'}); + $self->assert_not_null($mboxByName{'i2'}); + $self->assert_not_null($mboxByName{'i3'}); + $self->assert_not_null($mboxByName{'foo'}); + $self->assert_not_null($mboxByName{'bar'}); + $self->assert_null($mboxByName{i1}->{parentId}); + $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i2}->{parentId}); + $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{i3}->{parentId}); + $self->assert_str_equals($mboxByName{i3}->{id}, $mboxByName{foo}->{parentId}); + $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{bar}->{parentId}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_destroy b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_destroy index e30a22b3c7..413abf332a 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_destroy +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_destroy @@ -2,46 +2,55 @@ use Cassandane::Tiny; sub test_mailbox_set_intermediary_destroy - :min_version_3_1 :max_version_3_4 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.i2.foo") or die; - $imap->create("INBOX.i1.bar") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdFoo = $mboxByName{'foo'}->{id}; - my $mboxId2 = $mboxByName{'i2'}->{id}; + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.i2.foo") or die; + $imap->create("INBOX.i1.bar") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdFoo = $mboxByName{'foo'}->{id}; + my $mboxId2 = $mboxByName{'i2'}->{id}; - xlog $self, "Destroy intermediate"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [$mboxId2, $mboxIdFoo], - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{destroyed}}); + xlog $self, "Destroy intermediate"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + destroy => [ $mboxId2, $mboxIdFoo ], + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{destroyed} }); - xlog $self, "Assert mailbox tree and changes"; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"], - ]); + xlog $self, "Assert mailbox tree and changes"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ], + ]); - # Intermediaries with real children are kept. - %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(3, scalar keys %mboxByName); - $self->assert_not_null($mboxByName{'Inbox'}); - $self->assert_not_null($mboxByName{'i1'}); - $self->assert_not_null($mboxByName{'bar'}); - $self->assert_null($mboxByName{i1}->{parentId}); - $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{bar}->{parentId}); + # Intermediaries with real children are kept. + %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(3, scalar keys %mboxByName); + $self->assert_not_null($mboxByName{'Inbox'}); + $self->assert_not_null($mboxByName{'i1'}); + $self->assert_not_null($mboxByName{'bar'}); + $self->assert_null($mboxByName{i1}->{parentId}); + $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{bar}->{parentId}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_destroy_child b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_destroy_child index f2adddccc7..ef8ff846a4 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_destroy_child +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_destroy_child @@ -2,54 +2,67 @@ use Cassandane::Tiny; sub test_mailbox_set_intermediary_destroy_child - :min_version_3_1 :max_version_3_4 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.i2.foo") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdFoo = $mboxByName{'foo'}->{id}; - my $mboxId1 = $mboxByName{'i1'}->{id}; - my $mboxId2 = $mboxByName{'i2'}->{id}; - my $state = $res->[0][1]{state}; + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.i2.foo") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdFoo = $mboxByName{'foo'}->{id}; + my $mboxId1 = $mboxByName{'i1'}->{id}; + my $mboxId2 = $mboxByName{'i2'}->{id}; + my $state = $res->[0][1]{state}; - xlog $self, "Destroy child of intermediate"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [$mboxIdFoo], - }, 'R1'], - ]); - $self->assert_str_equals($mboxIdFoo, $res->[0][1]{destroyed}[0]); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $state = $res->[0][1]{newState}; + xlog $self, "Destroy child of intermediate"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + destroy => [$mboxIdFoo], + }, + 'R1' + ], + ]); + $self->assert_str_equals($mboxIdFoo, $res->[0][1]{destroyed}[0]); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $state = $res->[0][1]{newState}; - xlog $self, "Assert mailbox tree and changes"; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"], - ['Mailbox/changes', { - sinceState => $state, - }, 'R2'], - ]); + xlog $self, "Assert mailbox tree and changes"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ], + [ + 'Mailbox/changes', + { + sinceState => $state, + }, + 'R2' + ], + ]); - # All intermediaries without real children are gone. - %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(1, scalar keys %mboxByName); - $self->assert_not_null($mboxByName{'Inbox'}); + # All intermediaries without real children are gone. + %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(1, scalar keys %mboxByName); + $self->assert_not_null($mboxByName{'Inbox'}); - # But Mailbox/changes reports the implicitly destroyed mailboxes. - $self->assert_num_equals(2, scalar @{$res->[1][1]{destroyed}}); - my %destroyed = map { $_ => 1 } @{$res->[1][1]{destroyed}}; - $self->assert_not_null($destroyed{$mboxId1}); - $self->assert_not_null($destroyed{$mboxId2}); + # But Mailbox/changes reports the implicitly destroyed mailboxes. + $self->assert_num_equals(2, scalar @{ $res->[1][1]{destroyed} }); + my %destroyed = map { $_ => 1 } @{ $res->[1][1]{destroyed} }; + $self->assert_not_null($destroyed{$mboxId1}); + $self->assert_not_null($destroyed{$mboxId2}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_move_child b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_move_child index 7b9dc77a7c..2354fde616 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_move_child +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_move_child @@ -2,67 +2,80 @@ use Cassandane::Tiny; sub test_mailbox_set_intermediary_move_child - :min_version_3_1 :max_version_3_4 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.i2.foo") or die; - $imap->create("INBOX.i1.i3.bar") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxIdFoo = $mboxByName{'foo'}->{id}; - my $mboxId1 = $mboxByName{'i1'}->{id}; - my $mboxId2 = $mboxByName{'i2'}->{id}; - my $mboxId3 = $mboxByName{'i3'}->{id}; - my $mboxIdBar = $mboxByName{'bar'}->{id}; - my $state = $res->[0][1]{state}; + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.i2.foo") or die; + $imap->create("INBOX.i1.i3.bar") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxIdFoo = $mboxByName{'foo'}->{id}; + my $mboxId1 = $mboxByName{'i1'}->{id}; + my $mboxId2 = $mboxByName{'i2'}->{id}; + my $mboxId3 = $mboxByName{'i3'}->{id}; + my $mboxIdBar = $mboxByName{'bar'}->{id}; + my $state = $res->[0][1]{state}; - xlog $self, "Move child of intermediary to another intermediary"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxIdBar => { - parentId => $mboxId2, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$mboxIdBar}); - $self->assert_str_not_equals($state, $res->[0][1]{newState}); - $state = $res->[0][1]{newState}; + xlog $self, "Move child of intermediary to another intermediary"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxIdBar => { + parentId => $mboxId2, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$mboxIdBar}); + $self->assert_str_not_equals($state, $res->[0][1]{newState}); + $state = $res->[0][1]{newState}; - xlog $self, "Assert mailbox tree and changes"; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"], - ['Mailbox/changes', { - sinceState => $state, - }, 'R2'], - ]); + xlog $self, "Assert mailbox tree and changes"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ], + [ + 'Mailbox/changes', + { + sinceState => $state, + }, + 'R2' + ], + ]); - # All intermediaries without real children are gone. - %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(5, scalar keys %mboxByName); - $self->assert_not_null($mboxByName{'Inbox'}); - $self->assert_not_null($mboxByName{'i1'}); - $self->assert_not_null($mboxByName{'i2'}); - $self->assert_not_null($mboxByName{'foo'}); - $self->assert_not_null($mboxByName{'bar'}); - $self->assert_null($mboxByName{i1}->{parentId}); - $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i2}->{parentId}); - $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{foo}->{parentId}); - $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{bar}->{parentId}); + # All intermediaries without real children are gone. + %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(5, scalar keys %mboxByName); + $self->assert_not_null($mboxByName{'Inbox'}); + $self->assert_not_null($mboxByName{'i1'}); + $self->assert_not_null($mboxByName{'i2'}); + $self->assert_not_null($mboxByName{'foo'}); + $self->assert_not_null($mboxByName{'bar'}); + $self->assert_null($mboxByName{i1}->{parentId}); + $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i2}->{parentId}); + $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{foo}->{parentId}); + $self->assert_str_equals($mboxByName{i2}->{id}, $mboxByName{bar}->{parentId}); - # But Mailbox/changes reports the implicitly destroyed mailboxes. - $self->assert_num_equals(1, scalar @{$res->[1][1]{destroyed}}); - $self->assert_str_equals($mboxId3, $res->[1][1]{destroyed}[0]); + # But Mailbox/changes reports the implicitly destroyed mailboxes. + $self->assert_num_equals(1, scalar @{ $res->[1][1]{destroyed} }); + $self->assert_str_equals($mboxId3, $res->[1][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_rename b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_rename index e633bd3710..44ac1b4e7a 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_rename +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_intermediary_rename @@ -2,56 +2,67 @@ use Cassandane::Tiny; sub test_mailbox_set_intermediary_rename - :min_version_3_1 :max_version_3_4 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - xlog $self, "Create mailboxes"; - $imap->create("INBOX.i1.i2.foo") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - my %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $mboxId = $mboxByName{'i2'}->{id}; - my $mboxIdParent = $mboxByName{'i2'}->{parentId}; - $self->assert_not_null($mboxIdParent); + xlog $self, "Create mailboxes"; + $imap->create("INBOX.i1.i2.foo") or die; + my $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + my %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $mboxId = $mboxByName{'i2'}->{id}; + my $mboxIdParent = $mboxByName{'i2'}->{parentId}; + $self->assert_not_null($mboxIdParent); - xlog $self, "Rename intermediate"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxId => { - name => 'i3', - }, - } - }, 'R1'], - ['Mailbox/get', { - ids => [$mboxId], - properties => ['name', 'parentId'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$mboxId}); - $self->assert_str_equals('i3', $res->[1][1]{list}[0]{name}); - $self->assert_str_equals($mboxIdParent, $res->[1][1]{list}[0]{parentId}); + xlog $self, "Rename intermediate"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxId => { + name => 'i3', + }, + } + }, + 'R1' + ], + [ + 'Mailbox/get', + { + ids => [$mboxId], + properties => [ 'name', 'parentId' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$mboxId}); + $self->assert_str_equals('i3', $res->[1][1]{list}[0]{name}); + $self->assert_str_equals($mboxIdParent, $res->[1][1]{list}[0]{parentId}); - xlog $self, "Assert mailbox tree"; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['name', 'parentId'], - }, "R1"] - ]); - %mboxByName = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_num_equals(4, scalar keys %mboxByName); - $self->assert_not_null($mboxByName{'Inbox'}); - $self->assert_not_null($mboxByName{'i1'}); - $self->assert_not_null($mboxByName{'i3'}); - $self->assert_not_null($mboxByName{'foo'}); - $self->assert_null($mboxByName{i1}->{parentId}); - $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i3}->{parentId}); - $self->assert_str_equals($mboxByName{i3}->{id}, $mboxByName{foo}->{parentId}); + xlog $self, "Assert mailbox tree"; + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + properties => [ 'name', 'parentId' ], + }, + "R1" + ] ]); + %mboxByName = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_num_equals(4, scalar keys %mboxByName); + $self->assert_not_null($mboxByName{'Inbox'}); + $self->assert_not_null($mboxByName{'i1'}); + $self->assert_not_null($mboxByName{'i3'}); + $self->assert_not_null($mboxByName{'foo'}); + $self->assert_null($mboxByName{i1}->{parentId}); + $self->assert_str_equals($mboxByName{i1}->{id}, $mboxByName{i3}->{parentId}); + $self->assert_str_equals($mboxByName{i3}->{id}, $mboxByName{foo}->{parentId}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_issubscribed b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_issubscribed index ff3ef4bbc7..9107c243b9 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_issubscribed +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_issubscribed @@ -2,60 +2,67 @@ use Cassandane::Tiny; sub test_mailbox_set_issubscribed - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - "A" => { - name => "A", - }, - "B" => { - name => "B", - isSubscribed => JSON::true, - } - } - }, "R1"] - ]); - $self->assert_equals(JSON::false, $res->[0][1]{created}{A}{isSubscribed}); - $self->assert(not exists $res->[0][1]{created}{B}{isSubscribed}); - my $mboxIdA = $res->[0][1]{created}{A}{id}; - my $mboxIdB = $res->[0][1]{created}{B}{id}; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "A" => { + name => "A", + }, + "B" => { + name => "B", + isSubscribed => JSON::true, + } + } + }, + "R1" + ] ]); + $self->assert_equals(JSON::false, $res->[0][1]{created}{A}{isSubscribed}); + $self->assert(not exists $res->[0][1]{created}{B}{isSubscribed}); + my $mboxIdA = $res->[0][1]{created}{A}{id}; + my $mboxIdB = $res->[0][1]{created}{B}{id}; - $res = $jmap->CallMethods([ - ['Mailbox/get', { - ids => [$mboxIdA, $mboxIdB], - properties => ['isSubscribed'], - }, 'R1'] - ]); - $self->assert_equals($mboxIdA, $res->[0][1]{list}[0]{id}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isSubscribed}); - $self->assert_equals($mboxIdB, $res->[0][1]{list}[1]{id}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[1]{isSubscribed}); + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + ids => [ $mboxIdA, $mboxIdB ], + properties => ['isSubscribed'], + }, + 'R1' + ] ]); + $self->assert_equals($mboxIdA, $res->[0][1]{list}[0]{id}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[0]{isSubscribed}); + $self->assert_equals($mboxIdB, $res->[0][1]{list}[1]{id}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[1]{isSubscribed}); - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxIdA => { - isSubscribed => JSON::true, - }, - $mboxIdB => { - isSubscribed => JSON::false, - }, - } - }, "R1"] - ]); - $res = $jmap->CallMethods([ - ['Mailbox/get', { - ids => [$mboxIdA, $mboxIdB], - properties => ['isSubscribed'], - }, 'R1'] - ]); - $self->assert_equals($mboxIdA, $res->[0][1]{list}[0]{id}); - $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isSubscribed}); - $self->assert_equals($mboxIdB, $res->[0][1]{list}[1]{id}); - $self->assert_equals(JSON::false, $res->[0][1]{list}[1]{isSubscribed}); + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $mboxIdA => { + isSubscribed => JSON::true, + }, + $mboxIdB => { + isSubscribed => JSON::false, + }, + } + }, + "R1" + ] ]); + $res = $jmap->CallMethods([ [ + 'Mailbox/get', + { + ids => [ $mboxIdA, $mboxIdB ], + properties => ['isSubscribed'], + }, + 'R1' + ] ]); + $self->assert_equals($mboxIdA, $res->[0][1]{list}[0]{id}); + $self->assert_equals(JSON::true, $res->[0][1]{list}[0]{isSubscribed}); + $self->assert_equals($mboxIdB, $res->[0][1]{list}[1]{id}); + $self->assert_equals(JSON::false, $res->[0][1]{list}[1]{isSubscribed}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_issue2377 b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_issue2377 index e662b96d86..b180c152ac 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_issue2377 +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_issue2377 @@ -2,26 +2,31 @@ use Cassandane::Tiny; sub test_mailbox_set_issue2377 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get inbox"; - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("Inbox", $inbox->{name}); + xlog $self, "get inbox"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("Inbox", $inbox->{name}); - my $state = $res->[0][1]{state}; + my $state = $res->[0][1]{state}; - xlog $self, "create mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "foo", - parentId => "#", - role => undef - }}}, "R1"] - ]); - $self->assert_not_null($res->[0][1]{notCreated}{'1'}); + xlog $self, "create mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + parentId => "#", + role => undef + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{notCreated}{'1'}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_collision b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_collision index fdc9a4504a..a3b570e8fd 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_collision +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_collision @@ -2,81 +2,97 @@ use Cassandane::Tiny; sub test_mailbox_set_name_collision - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get inbox"; - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("Inbox", $inbox->{name}); + xlog $self, "get inbox"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("Inbox", $inbox->{name}); - my $state = $res->[0][1]{state}; + my $state = $res->[0][1]{state}; - xlog $self, "create three mailboxes named foo (two will fail)"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { - "1" => { - name => "foo", - parentId => $inbox->{id}, - role => undef - }, - "2" => { - name => "foo", - parentId => $inbox->{id}, - role => undef - }, - "3" => { - name => "foo", - parentId => $inbox->{id}, - role => undef - } - }}, "R1"] - ]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{created}}); - $self->assert_num_equals(2, scalar keys %{$res->[0][1]{notCreated}}); + xlog $self, "create three mailboxes named foo (two will fail)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + parentId => $inbox->{id}, + role => undef + }, + "2" => { + name => "foo", + parentId => $inbox->{id}, + role => undef + }, + "3" => { + name => "foo", + parentId => $inbox->{id}, + role => undef + } + } + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{created} }); + $self->assert_num_equals(2, scalar keys %{ $res->[0][1]{notCreated} }); - my $fooid = $res->[0][1]{created}{(keys %{$res->[0][1]{created}})[0]}{id}; - $self->assert_not_null($fooid); + my $fooid = $res->[0][1]{created}{ (keys %{ $res->[0][1]{created} })[0] }{id}; + $self->assert_not_null($fooid); - xlog $self, "create mailbox bar"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { - "1" => { - name => "bar", - parentId => $inbox->{id}, - role => undef - } - }}, 'R1'], - ]); - my $barid = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($barid); + xlog $self, "create mailbox bar"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + "1" => { + name => "bar", + parentId => $inbox->{id}, + role => undef + } + } + }, + 'R1' + ], + ]); + my $barid = $res->[0][1]{created}{"1"}{id}; + $self->assert_not_null($barid); - # This MUST work per spec, but Cyrus /set does not support - # invalid interim states... - xlog $self, "rename bar to foo and foo to bar"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { update => { - $fooid => { - name => "bar", - }, - $barid => { - name => "foo", - }, - }}, 'R1'], - ]); - $self->assert_num_equals(2, scalar keys %{$res->[0][1]{updated}}); + # This MUST work per spec, but Cyrus /set does not support + # invalid interim states... + xlog $self, "rename bar to foo and foo to bar"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $fooid => { + name => "bar", + }, + $barid => { + name => "foo", + }, + } + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar keys %{ $res->[0][1]{updated} }); - xlog $self, "get mailboxes"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$fooid, $barid] }, "R1"]]); + xlog $self, "get mailboxes"; + $res = $jmap->CallMethods( + [ [ 'Mailbox/get', { ids => [ $fooid, $barid ] }, "R1" ] ]); - # foo is bar - $self->assert_str_equals($fooid, $res->[0][1]{list}[0]->{id}); - $self->assert_str_equals("bar", $res->[0][1]{list}[0]->{name}); + # foo is bar + $self->assert_str_equals($fooid, $res->[0][1]{list}[0]->{id}); + $self->assert_str_equals("bar", $res->[0][1]{list}[0]->{name}); - # and bar is foo - $self->assert_str_equals($barid, $res->[0][1]{list}[1]->{id}); - $self->assert_str_equals("foo", $res->[0][1]{list}[1]->{name}); + # and bar is foo + $self->assert_str_equals($barid, $res->[0][1]{list}[1]->{id}); + $self->assert_str_equals("foo", $res->[0][1]{list}[1]->{name}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_interop b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_interop index e1472e0215..8f306ea1f9 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_interop +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_interop @@ -2,57 +2,55 @@ use Cassandane::Tiny; sub test_mailbox_set_name_interop - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); - - xlog $self, "create mailbox via IMAP"; - $imaptalk->create("INBOX.foo") - or die "Cannot create mailbox INBOX.foo: $@"; - - xlog $self, "get foo mailbox"; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my $foo = $m{"foo"}; - my $id = $foo->{id}; - $self->assert_str_equals("foo", $foo->{name}); - - xlog $self, "rename mailbox foo to oof via JMAP"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { update => { $id => { name => "oof" }}}, "R1"] - ]); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "get mailbox via IMAP"; - my $data = $imaptalk->list("INBOX.oof", "%"); - $self->assert_num_equals(1, scalar @{$data}); - - xlog $self, "rename mailbox oof to bar via IMAP"; - $imaptalk->rename("INBOX.oof", "INBOX.bar") - or die "Cannot rename mailbox: $@"; - - xlog $self, "get mailbox $id"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id] }, "R1"]]); - $self->assert_str_equals("bar", $res->[0][1]{list}[0]->{name}); - - xlog $self, "rename mailbox bar to baz via JMAP"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { update => { $id => { name => "baz" }}}, "R1"] - ]); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "get mailbox via IMAP"; - $data = $imaptalk->list("INBOX.baz", "%"); - $self->assert_num_equals(1, scalar @{$data}); - - xlog $self, "rename mailbox baz to IFeel\N{WHITE SMILING FACE} via IMAP"; - $imaptalk->rename("INBOX.baz", "INBOX.IFeel\N{WHITE SMILING FACE}") - or die "Cannot rename mailbox: $@"; - - xlog $self, "get mailbox $id"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id] }, "R1"]]); - $self->assert_str_equals("IFeel\N{WHITE SMILING FACE}", $res->[0][1]{list}[0]->{name}); + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); + + xlog $self, "create mailbox via IMAP"; + $imaptalk->create("INBOX.foo") + or die "Cannot create mailbox INBOX.foo: $@"; + + xlog $self, "get foo mailbox"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my $foo = $m{"foo"}; + my $id = $foo->{id}; + $self->assert_str_equals("foo", $foo->{name}); + + xlog $self, "rename mailbox foo to oof via JMAP"; + $res = $jmap->CallMethods([ + [ 'Mailbox/set', { update => { $id => { name => "oof" } } }, "R1" ] ]); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "get mailbox via IMAP"; + my $data = $imaptalk->list("INBOX.oof", "%"); + $self->assert_num_equals(1, scalar @{$data}); + + xlog $self, "rename mailbox oof to bar via IMAP"; + $imaptalk->rename("INBOX.oof", "INBOX.bar") + or die "Cannot rename mailbox: $@"; + + xlog $self, "get mailbox $id"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals("bar", $res->[0][1]{list}[0]->{name}); + + xlog $self, "rename mailbox bar to baz via JMAP"; + $res = $jmap->CallMethods([ + [ 'Mailbox/set', { update => { $id => { name => "baz" } } }, "R1" ] ]); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "get mailbox via IMAP"; + $data = $imaptalk->list("INBOX.baz", "%"); + $self->assert_num_equals(1, scalar @{$data}); + + xlog $self, "rename mailbox baz to IFeel\N{WHITE SMILING FACE} via IMAP"; + $imaptalk->rename("INBOX.baz", "INBOX.IFeel\N{WHITE SMILING FACE}") + or die "Cannot rename mailbox: $@"; + + xlog $self, "get mailbox $id"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals("IFeel\N{WHITE SMILING FACE}", + $res->[0][1]{list}[0]->{name}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_missing b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_missing index c10081b55f..0e72a4630a 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_missing +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_missing @@ -2,22 +2,27 @@ use Cassandane::Tiny; sub test_mailbox_set_name_missing - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "create mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { - "1" => { role => undef }, - "2" => { role => undef, name => "\t " }, - }}, "R1"] - ]); - $self->assert_str_equals('Mailbox/set', $res->[0][0]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notCreated}{1}{type}); - $self->assert_str_equals('name', $res->[0][1]{notCreated}{1}{properties}[0]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notCreated}{2}{type}); - $self->assert_str_equals('name', $res->[0][1]{notCreated}{2}{properties}[0]); + xlog $self, "create mailbox"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { role => undef }, + "2" => { role => undef, name => "\t " }, + } + }, + "R1" + ] ]); + $self->assert_str_equals('Mailbox/set', $res->[0][0]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{1}{type}); + $self->assert_str_equals('name', $res->[0][1]{notCreated}{1}{properties}[0]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notCreated}{2}{type}); + $self->assert_str_equals('name', $res->[0][1]{notCreated}{2}{properties}[0]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_swap b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_swap index 592fe440c2..39eeeed5d2 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_swap +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_swap @@ -2,33 +2,44 @@ use Cassandane::Tiny; sub test_mailbox_set_name_swap - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - A => { - name => 'A', parentId => undef, role => undef, - }, - B => { - name => 'B', parentId => undef, role => undef, - }, + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + A => { + name => 'A', + parentId => undef, + role => undef, }, - }, "R1"]]); - my $idA =$res->[0][1]{created}{A}{id}; - my $idB =$res->[0][1]{created}{B}{id}; - $self->assert_not_null($idA); - $self->assert_not_null($idB); - - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $idA => { name => 'B' }, - $idB => { name => 'A' }, + B => { + name => 'B', + parentId => undef, + role => undef, }, - }, "R1"]]); - $self->assert(exists $res->[0][1]{updated}{$idA}); - $self->assert(exists $res->[0][1]{updated}{$idB}); + }, + }, + "R1" + ] ]); + my $idA = $res->[0][1]{created}{A}{id}; + my $idB = $res->[0][1]{created}{B}{id}; + $self->assert_not_null($idA); + $self->assert_not_null($idB); + + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $idA => { name => 'B' }, + $idB => { name => 'A' }, + }, + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{updated}{$idA}); + $self->assert(exists $res->[0][1]{updated}{$idB}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_unicode_nfc b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_unicode_nfc index 8669222253..d983d0dccb 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_unicode_nfc +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_name_unicode_nfc @@ -2,32 +2,41 @@ use Cassandane::Tiny; sub test_mailbox_set_name_unicode_nfc - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "get inbox"; - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("Inbox", $inbox->{name}); + xlog $self, "get inbox"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("Inbox", $inbox->{name}); - my $state = $res->[0][1]{state}; + my $state = $res->[0][1]{state}; - my $name = "\N{ANGSTROM SIGN}ngstr\N{LATIN SMALL LETTER O WITH DIAERESIS}m"; - my $want = "\N{LATIN CAPITAL LETTER A WITH RING ABOVE}ngstr\N{LATIN SMALL LETTER O WITH DIAERESIS}m"; + my $name = "\N{ANGSTROM SIGN}ngstr\N{LATIN SMALL LETTER O WITH DIAERESIS}m"; + my $want + = "\N{LATIN CAPITAL LETTER A WITH RING ABOVE}ngstr\N{LATIN SMALL LETTER O WITH DIAERESIS}m"; - xlog $self, "create mailboxes with name not conforming to Net Unicode (NFC)"; - $res = $jmap->CallMethods([['Mailbox/set', { create => { "1" => { - name => "\N{ANGSTROM SIGN}ngstr\N{LATIN SMALL LETTER O WITH DIAERESIS}m", - parentId => $inbox->{id}, - role => undef - }}}, "R1"]]); - $self->assert_not_null($res->[0][1]{created}{1}); - my $id = $res->[0][1]{created}{1}{id}; + xlog $self, "create mailboxes with name not conforming to Net Unicode (NFC)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => + "\N{ANGSTROM SIGN}ngstr\N{LATIN SMALL LETTER O WITH DIAERESIS}m", + parentId => $inbox->{id}, + role => undef + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}{1}); + my $id = $res->[0][1]{created}{1}{id}; - xlog $self, "get mailbox $id"; - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id] }, "R1"]]); - $self->assert_str_equals($want, $res->[0][1]{list}[0]->{name}); + xlog $self, "get mailbox $id"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id] }, "R1" ] ]); + $self->assert_str_equals($want, $res->[0][1]{list}[0]->{name}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_nameclash b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_nameclash index 486412115e..68a4778a7c 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_nameclash +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_nameclash @@ -2,39 +2,54 @@ use Cassandane::Tiny; sub test_mailbox_set_nameclash - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Test name-clash at top-level - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - A1 => { - name => 'A', parentId => undef, role => undef, - }, - A2 => { - name => 'A', parentId => undef, role => undef, - }, + # Test name-clash at top-level + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + A1 => { + name => 'A', + parentId => undef, + role => undef, }, - }, "R1"]]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{notCreated}}); + A2 => { + name => 'A', + parentId => undef, + role => undef, + }, + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{notCreated} }); - # Test name-clash at lower lever - my $parentA = (values %{$res->[0][1]{created}})[0]{id}; - $self->assert_not_null($parentA); - $res = $jmap->CallMethods([['Mailbox/set', { - create => { - B1 => { - name => 'B', parentId => $parentA, role => undef, - }, - B2 => { - name => 'B', parentId => $parentA, role => undef, - }, + # Test name-clash at lower lever + my $parentA = (values %{ $res->[0][1]{created} })[0]{id}; + $self->assert_not_null($parentA); + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + B1 => { + name => 'B', + parentId => $parentA, + role => undef, + }, + B2 => { + name => 'B', + parentId => $parentA, + role => undef, }, - }, "R1"]]); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{created}}); - $self->assert_num_equals(1, scalar keys %{$res->[0][1]{notCreated}}); + }, + }, + "R1" + ] ]); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{created} }); + $self->assert_num_equals(1, scalar keys %{ $res->[0][1]{notCreated} }); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_no_outbox_role b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_no_outbox_role index 52fdc63b46..5f254d2b85 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_no_outbox_role +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_no_outbox_role @@ -2,18 +2,21 @@ use Cassandane::Tiny; sub test_mailbox_set_no_outbox_role - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; - # Regression test to make sure the non-standard 'outbox' - # role is rejected for mailboxes. + # Regression test to make sure the non-standard 'outbox' + # role is rejected for mailboxes. - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { - "1" => { name => "foo", parentId => undef, role => "outbox" }, - }}, "R1"] - ]); - $self->assert_str_equals("role", $res->[0][1]{notCreated}{1}{properties}[0]); + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { name => "foo", parentId => undef, role => "outbox" }, + } + }, + "R1" + ] ]); + $self->assert_str_equals("role", $res->[0][1]{notCreated}{1}{properties}[0]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_order b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_order index 70bbd8c379..e371e77e6d 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_order +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_order @@ -2,20 +2,19 @@ use Cassandane::Tiny; sub test_mailbox_set_order - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Assert mailboxes are created in the right order. - my $RawRequest = { - headers => { - 'Authorization' => $jmap->auth_header(), - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - }, - content => '{ + # Assert mailboxes are created in the right order. + my $RawRequest = { + headers => { + 'Authorization' => $jmap->auth_header(), + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + }, + content => '{ "using" : ["urn:ietf:params:jmap:mail"], "methodCalls" : [["Mailbox/set", { "create" : { @@ -31,27 +30,30 @@ sub test_mailbox_set_order } }, "R1"]] }', - }; - my $RawResponse = $jmap->ua->post($jmap->uri(), $RawRequest); - if ($ENV{DEBUGJMAP}) { - warn "JMAP " . Dumper($RawRequest, $RawResponse); - } - $self->assert($RawResponse->{success}); + }; + my $RawResponse = $jmap->ua->post($jmap->uri(), $RawRequest); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($RawRequest, $RawResponse); + } + $self->assert($RawResponse->{success}); - my $res = eval { decode_json($RawResponse->{content}) }; - $res = $res->{methodResponses}; - $self->assert_not_null($res->[0][1]{created}{A}); - $self->assert_not_null($res->[0][1]{created}{B}); - $self->assert_not_null($res->[0][1]{created}{C}); + my $res = eval { decode_json($RawResponse->{content}) }; + $res = $res->{methodResponses}; + $self->assert_not_null($res->[0][1]{created}{A}); + $self->assert_not_null($res->[0][1]{created}{B}); + $self->assert_not_null($res->[0][1]{created}{C}); - # Assert mailboxes are destroyed in the right order. - $res = $jmap->CallMethods([['Mailbox/set', { - destroy => [ - $res->[0][1]{created}{A}{id}, - $res->[0][1]{created}{B}{id}, - $res->[0][1]{created}{C}{id}, - ] - }, "R1"]]); - $self->assert_num_equals(3, scalar @{$res->[0][1]{destroyed}}); - $self->assert_null($res->[0][1]{notDestroyed}); + # Assert mailboxes are destroyed in the right order. + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + destroy => [ + $res->[0][1]{created}{A}{id}, $res->[0][1]{created}{B}{id}, + $res->[0][1]{created}{C}{id}, + ] + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{destroyed} }); + $self->assert_null($res->[0][1]{notDestroyed}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_order2 b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_order2 index 2e765e5a43..2ee5d8eb46 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_order2 +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_order2 @@ -2,44 +2,45 @@ use Cassandane::Tiny; sub test_mailbox_set_order2 - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imaptalk = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imaptalk = $self->{store}->get_client(); - # Create and get mailbox tree. - $imaptalk->create("INBOX.A") or die; - $imaptalk->create("INBOX.A.B") or die; - my $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - my ($idA, $idB) = ($m{"A"}{id}, $m{"B"}{id}); + # Create and get mailbox tree. + $imaptalk->create("INBOX.A") or die; + $imaptalk->create("INBOX.A.B") or die; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + my ($idA, $idB) = ($m{"A"}{id}, $m{"B"}{id}); - # Use a non-trivial, but correct operations order: this - # asserts that name clashes and mailboxHasChild conflicts - # are resolved appropriately: the create depends on the - # deletion of current mailbox A, which depends on the - # update to move away the child from A, which requires - # the create to set the parentId. Fun times. - $res = $jmap->CallMethods([['Mailbox/set', { - create => { - Anew => { - name => 'A', - parentId => undef, - role => undef, - }, + # Use a non-trivial, but correct operations order: this + # asserts that name clashes and mailboxHasChild conflicts + # are resolved appropriately: the create depends on the + # deletion of current mailbox A, which depends on the + # update to move away the child from A, which requires + # the create to set the parentId. Fun times. + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + Anew => { + name => 'A', + parentId => undef, + role => undef, }, - update => { - $idB => { - parentId => '#Anew', - }, + }, + update => { + $idB => { + parentId => '#Anew', }, - destroy => [ - $idA, - ] - }, "R1"]]); - $self->assert(exists $res->[0][1]{created}{'Anew'}); - $self->assert(exists $res->[0][1]{updated}{$idB}); - $self->assert_str_equals($idA, $res->[0][1]{destroyed}[0]); + }, + destroy => [ $idA, ] + }, + "R1" + ] ]); + $self->assert(exists $res->[0][1]{created}{'Anew'}); + $self->assert(exists $res->[0][1]{updated}{$idB}); + $self->assert_str_equals($idA, $res->[0][1]{destroyed}[0]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_parent b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_parent index 06a64990f2..3b05d78bee 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_parent +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_parent @@ -2,123 +2,161 @@ use Cassandane::Tiny; sub test_mailbox_set_parent - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - # Create mailboxes - xlog $self, "create mailbox foo"; - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - "1" => { - name => "foo", - parentId => undef, - role => undef } + # Create mailboxes + xlog $self, "create mailbox foo"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + parentId => undef, + role => undef } - }, "R1"]]); - my $id1 = $res->[0][1]{created}{"1"}{id}; - xlog $self, "create mailbox foo.bar"; - $res = $jmap->CallMethods([['Mailbox/set', { - create => { - "2" => { - name => "bar", - parentId => $id1, - role => undef } + } + }, + "R1" + ] ]); + my $id1 = $res->[0][1]{created}{"1"}{id}; + xlog $self, "create mailbox foo.bar"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "2" => { + name => "bar", + parentId => $id1, + role => undef } - }, "R1"]]); - my $id2 = $res->[0][1]{created}{"2"}{id}; - xlog $self, "create mailbox foo.bar.baz"; - $res = $jmap->CallMethods([['Mailbox/set', { - create => { - "3" => { - name => "baz", - parentId => $id2, - role => undef - } + } + }, + "R1" + ] ]); + my $id2 = $res->[0][1]{created}{"2"}{id}; + xlog $self, "create mailbox foo.bar.baz"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "3" => { + name => "baz", + parentId => $id2, + role => undef } - }, "R1"]]); - my $id3 = $res->[0][1]{created}{"3"}{id}; + } + }, + "R1" + ] ]); + my $id3 = $res->[0][1]{created}{"3"}{id}; - # All set up? - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id1] }, "R1"]]); - $self->assert_null($res->[0][1]{list}[0]->{parentId}); - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id2] }, "R1"]]); - $self->assert_str_equals($id1, $res->[0][1]{list}[0]->{parentId}); - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id3] }, "R1"]]); - $self->assert_str_equals($id2, $res->[0][1]{list}[0]->{parentId}); + # All set up? + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id1] }, "R1" ] ]); + $self->assert_null($res->[0][1]{list}[0]->{parentId}); + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id2] }, "R1" ] ]); + $self->assert_str_equals($id1, $res->[0][1]{list}[0]->{parentId}); + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id3] }, "R1" ] ]); + $self->assert_str_equals($id2, $res->[0][1]{list}[0]->{parentId}); - xlog $self, "move foo.bar to bar"; - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $id2 => { - name => "bar", - parentId => undef, - role => undef } + xlog $self, "move foo.bar to bar"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $id2 => { + name => "bar", + parentId => undef, + role => undef } - }, "R1"]]); - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id2] }, "R1"]]); - $self->assert_null($res->[0][1]{list}[0]->{parentId}); + } + }, + "R1" + ] ]); + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id2] }, "R1" ] ]); + $self->assert_null($res->[0][1]{list}[0]->{parentId}); - xlog $self, "move bar.baz to foo.baz"; - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $id3 => { - name => "baz", - parentId => $id1, - role => undef - } + xlog $self, "move bar.baz to foo.baz"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $id3 => { + name => "baz", + parentId => $id1, + role => undef } - }, "R1"]]); - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id3] }, "R1"]]); - $self->assert_str_equals($id1, $res->[0][1]{list}[0]->{parentId}); + } + }, + "R1" + ] ]); + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id3] }, "R1" ] ]); + $self->assert_str_equals($id1, $res->[0][1]{list}[0]->{parentId}); - xlog $self, "move foo to bar.foo"; - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $id1 => { - name => "foo", - parentId => $id2, - role => undef - } + xlog $self, "move foo to bar.foo"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $id1 => { + name => "foo", + parentId => $id2, + role => undef } - }, "R1"]]); - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id1] }, "R1"]]); - $self->assert_str_equals($id2, $res->[0][1]{list}[0]->{parentId}); + } + }, + "R1" + ] ]); + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id1] }, "R1" ] ]); + $self->assert_str_equals($id2, $res->[0][1]{list}[0]->{parentId}); - xlog $self, "move foo to non-existent parent"; - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $id1 => { - name => "foo", - parentId => "nope", - role => undef - } + xlog $self, "move foo to non-existent parent"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $id1 => { + name => "foo", + parentId => "nope", + role => undef } - }, "R1"]]); - my $errType = $res->[0][1]{notUpdated}{$id1}{type}; - my $errProp = $res->[0][1]{notUpdated}{$id1}{properties}; - $self->assert_str_equals("invalidProperties", $errType); - $self->assert_deep_equals([ "parentId" ], $errProp); - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id1] }, "R1"]]); - $self->assert_str_equals($id2, $res->[0][1]{list}[0]->{parentId}); + } + }, + "R1" + ] ]); + my $errType = $res->[0][1]{notUpdated}{$id1}{type}; + my $errProp = $res->[0][1]{notUpdated}{$id1}{properties}; + $self->assert_str_equals("invalidProperties", $errType); + $self->assert_deep_equals(["parentId"], $errProp); + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id1] }, "R1" ] ]); + $self->assert_str_equals($id2, $res->[0][1]{list}[0]->{parentId}); - xlog $self, "attempt to destroy bar (which has child foo)"; - $res = $jmap->CallMethods([['Mailbox/set', { - destroy => [$id2] - }, "R1"]]); - $errType = $res->[0][1]{notDestroyed}{$id2}{type}; - $self->assert_str_equals("mailboxHasChild", $errType); - $res = $jmap->CallMethods([['Mailbox/get', { ids => [$id2] }, "R1"]]); - $self->assert_null($res->[0][1]{list}[0]->{parentId}); + xlog $self, "attempt to destroy bar (which has child foo)"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + destroy => [$id2] + }, + "R1" + ] ]); + $errType = $res->[0][1]{notDestroyed}{$id2}{type}; + $self->assert_str_equals("mailboxHasChild", $errType); + $res = $jmap->CallMethods([ [ 'Mailbox/get', { ids => [$id2] }, "R1" ] ]); + $self->assert_null($res->[0][1]{list}[0]->{parentId}); - xlog $self, "destroy all"; - $res = $jmap->CallMethods([['Mailbox/set', { - destroy => [$id3, $id1, $id2] - }, "R1"]]); - $self->assert_num_equals(3, scalar @{$res->[0][1]{destroyed}}); - $self->assert(grep {$_ eq $id1} @{$res->[0][1]{destroyed}}); - $self->assert(grep {$_ eq $id2} @{$res->[0][1]{destroyed}}); - $self->assert(grep {$_ eq $id3} @{$res->[0][1]{destroyed}}); + xlog $self, "destroy all"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + destroy => [ $id3, $id1, $id2 ] + }, + "R1" + ] ]); + $self->assert_num_equals(3, scalar @{ $res->[0][1]{destroyed} }); + $self->assert(grep { $_ eq $id1 } @{ $res->[0][1]{destroyed} }); + $self->assert(grep { $_ eq $id2 } @{ $res->[0][1]{destroyed} }); + $self->assert(grep { $_ eq $id3 } @{ $res->[0][1]{destroyed} }); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_parent_acl b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_parent_acl index f7549a5b50..662aeffdda 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_parent_acl +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_parent_acl @@ -2,33 +2,38 @@ use Cassandane::Tiny; sub test_mailbox_set_parent_acl - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $admintalk = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "get inbox"; - my $res = $jmap->CallMethods([['Mailbox/get', { }, "R1"]]); - my $inbox = $res->[0][1]{list}[0]; - $self->assert_str_equals("Inbox", $inbox->{name}); + xlog $self, "get inbox"; + my $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my $inbox = $res->[0][1]{list}[0]; + $self->assert_str_equals("Inbox", $inbox->{name}); - xlog $self, "get inbox ACL"; - my $parentacl = $admintalk->getacl("user.cassandane"); + xlog $self, "get inbox ACL"; + my $parentacl = $admintalk->getacl("user.cassandane"); - xlog $self, "create mailbox"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "foo", - role => undef - }}}, "R1"] - ]); - $self->assert_not_null($res->[0][1]{created}); + xlog $self, "create mailbox"; + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + "1" => { + name => "foo", + role => undef + } + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{created}); - xlog $self, "get new mailbox ACL"; - my $myacl = $admintalk->getacl("user.cassandane.foo"); + xlog $self, "get new mailbox ACL"; + my $myacl = $admintalk->getacl("user.cassandane.foo"); - xlog $self, "assert ACL matches parent ACL"; - $self->assert_deep_equals($parentacl, $myacl); + xlog $self, "assert ACL matches parent ACL"; + $self->assert_deep_equals($parentacl, $myacl); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_protected_move_parent b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_protected_move_parent index d1268c0ddd..906d0dfa75 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_protected_move_parent +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_protected_move_parent @@ -2,71 +2,93 @@ use Cassandane::Tiny; sub test_mailbox_set_protected_move_parent - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "create protected and unprotected roles"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - role => 'drafts', - }, - mboxB => { - name => 'B', - role => 'xspecialuse', - }, - mboxC => { - name => 'C', - }, - }, - }, "R2"], - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R3'], - ]); - my $mboxA = $res->[0][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[0][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - my $mboxC = $res->[0][1]{created}{mboxC}{id}; - $self->assert_not_null($mboxC); - xlog "move protected and unprotected roles in one method"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxA => { - parentId => $mboxC, - }, - $mboxB => { - parentId => $mboxC, - }, - }, - }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notUpdated}{$mboxA}{type}); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notUpdated}{$mboxB}{type}); + xlog "create protected and unprotected roles"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + role => 'drafts', + }, + mboxB => { + name => 'B', + role => 'xspecialuse', + }, + mboxC => { + name => 'C', + }, + }, + }, + "R2" + ], + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R3' + ], + ]); + my $mboxA = $res->[0][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[0][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + my $mboxC = $res->[0][1]{created}{mboxC}{id}; + $self->assert_not_null($mboxC); + xlog "move protected and unprotected roles in one method"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxA => { + parentId => $mboxC, + }, + $mboxB => { + parentId => $mboxC, + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$mboxA}{type}); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$mboxB}{type}); - xlog "move protected and unprotected roles in separate method"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxA => { - parentId => $mboxC, - }, - }, - }, 'R1'], - ['Mailbox/set', { - update => { - $mboxB => { - parentId => $mboxC, - }, - }, - }, 'R2'], - ]); - $self->assert_str_equals('invalidProperties', $res->[0][1]{notUpdated}{$mboxA}{type}); - $self->assert(exists $res->[1][1]{updated}{$mboxB}); + xlog "move protected and unprotected roles in separate method"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxA => { + parentId => $mboxC, + }, + }, + }, + 'R1' + ], + [ + 'Mailbox/set', + { + update => { + $mboxB => { + parentId => $mboxC, + }, + }, + }, + 'R2' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$mboxA}{type}); + $self->assert(exists $res->[1][1]{updated}{$mboxB}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_create b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_create index c65843aa45..bf526ced50 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_create +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_create @@ -2,42 +2,56 @@ use Cassandane::Tiny; sub test_mailbox_set_role_create - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "create mailboxes with roles"; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R1'], - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - role => 'trash', - }, - mboxB => { - name => 'B', - role => 'junk', - }, - }, - }, "R2"], - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R3'], - ]); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); - my $mboxA = $res->[1][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[1][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - my %roleByMbox = map { $_->{id} => $_->{role} } @{$res->[2][1]{list}}; - $self->assert_deep_equals({ - $inbox => 'inbox', - $mboxA => 'trash', - $mboxB => 'junk', - }, \%roleByMbox); + xlog "create mailboxes with roles"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + role => 'trash', + }, + mboxB => { + name => 'B', + role => 'junk', + }, + }, + }, + "R2" + ], + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R3' + ], + ]); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); + my $mboxA = $res->[1][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[1][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + my %roleByMbox = map { $_->{id} => $_->{role} } @{ $res->[2][1]{list} }; + $self->assert_deep_equals( + { + $inbox => 'inbox', + $mboxA => 'trash', + $mboxB => 'junk', + }, + \%roleByMbox + ); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_dups_createrole b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_dups_createrole index d7a048c310..2030fad0d4 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_dups_createrole +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_dups_createrole @@ -2,39 +2,55 @@ use Cassandane::Tiny; sub test_mailbox_set_role_dups_createrole - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "Can't create two mailboxes with the same role"; + xlog "Can't create two mailboxes with the same role"; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R1'], - ['Mailbox/set', { - create => { - mboxNope1 => { - name => 'nope1', - role => 'drafts', - }, - mboxNope2=> { - name => 'nope2', - role => 'drafts', - }, - }, - }, "R2"], - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R3'], - ]); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); - $self->assert_deep_equals(['role'], $res->[1][1]{notCreated}{'mboxNope1'}{properties}); - $self->assert_deep_equals(['role'], $res->[1][1]{notCreated}{'mboxNope2'}{properties}); - my %roleByMbox = map { $_->{id} => $_->{role} } @{$res->[2][1]{list}}; - $self->assert_deep_equals({ - $inbox => 'inbox', - }, \%roleByMbox); + my $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + create => { + mboxNope1 => { + name => 'nope1', + role => 'drafts', + }, + mboxNope2 => { + name => 'nope2', + role => 'drafts', + }, + }, + }, + "R2" + ], + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R3' + ], + ]); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); + $self->assert_deep_equals(['role'], + $res->[1][1]{notCreated}{'mboxNope1'}{properties}); + $self->assert_deep_equals(['role'], + $res->[1][1]{notCreated}{'mboxNope2'}{properties}); + my %roleByMbox = map { $_->{id} => $_->{role} } @{ $res->[2][1]{list} }; + $self->assert_deep_equals( + { + $inbox => 'inbox', + }, + \%roleByMbox + ); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_dups_existingrole b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_dups_existingrole index 27694a8bc7..081c0ccfec 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_dups_existingrole +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_dups_existingrole @@ -2,74 +2,102 @@ use Cassandane::Tiny; sub test_mailbox_set_role_dups_existingrole - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R1'], - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - role => 'junk', - }, - }, - }, "R2"], - ]); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); - my $mboxA = $res->[1][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); + my $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + role => 'junk', + }, + }, + }, + "R2" + ], + ]); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); + my $mboxA = $res->[1][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); - xlog "Can't create a mailbox with a duplicate role"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxB => { - name => 'B', - role => 'junk', - }, - }, - }, "R1"], - ]); - $self->assert_deep_equals(['role'], $res->[0][1]{notCreated}{'mboxB'}{properties}); + xlog "Can't create a mailbox with a duplicate role"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxB => { + name => 'B', + role => 'junk', + }, + }, + }, + "R1" + ], + ]); + $self->assert_deep_equals(['role'], + $res->[0][1]{notCreated}{'mboxB'}{properties}); - xlog "Can't update a mailbox with a duplicate role"; - # create it first - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxB => { - name => 'B', - }, - }, - }, "R1"], - ]); - my $mboxB = $res->[0][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - # now update - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxB => { - name => 'B', - role => 'junk', - }, - }, - }, "R1"], - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R2'], - ]); - $self->assert_deep_equals(['role'], $res->[0][1]{notUpdated}{$mboxB}{properties}); - my %roleByMbox = map { $_->{id} => $_->{role} } @{$res->[1][1]{list}}; - $self->assert_deep_equals({ - $inbox => 'inbox', - $mboxA => 'junk', - $mboxB => undef, - }, \%roleByMbox); + xlog "Can't update a mailbox with a duplicate role"; + # create it first + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxB => { + name => 'B', + }, + }, + }, + "R1" + ], + ]); + my $mboxB = $res->[0][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + # now update + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxB => { + name => 'B', + role => 'junk', + }, + }, + }, + "R1" + ], + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R2' + ], + ]); + $self->assert_deep_equals(['role'], + $res->[0][1]{notUpdated}{$mboxB}{properties}); + my %roleByMbox = map { $_->{id} => $_->{role} } @{ $res->[1][1]{list} }; + $self->assert_deep_equals( + { + $inbox => 'inbox', + $mboxA => 'junk', + $mboxB => undef, + }, + \%roleByMbox + ); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_move_destroy b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_move_destroy index 8431e673a1..c5bef77668 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_move_destroy +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_move_destroy @@ -2,51 +2,69 @@ use Cassandane::Tiny; sub test_mailbox_set_role_move_destroy - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "move role by destroy"; + xlog "move role by destroy"; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R1'], - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - role => 'trash', - }, - }, - }, "R2"], - ]); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); - my $mboxA = $res->[1][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); + my $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + role => 'trash', + }, + }, + }, + "R2" + ], + ]); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); + my $mboxA = $res->[1][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); - $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxB => { - name => 'B', - role => 'trash', - }, - }, - destroy => [$mboxA], - }, "R1"], - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R2'], - ]); - $self->assert_deep_equals([$mboxA], $res->[0][1]{destroyed}); - my $mboxB = $res->[0][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - my %roleByMbox = map { $_->{id} => $_->{role} } @{$res->[1][1]{list}}; - $self->assert_deep_equals({ - $inbox => 'inbox', - $mboxB => 'trash', - }, \%roleByMbox); + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxB => { + name => 'B', + role => 'trash', + }, + }, + destroy => [$mboxA], + }, + "R1" + ], + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R2' + ], + ]); + $self->assert_deep_equals([$mboxA], $res->[0][1]{destroyed}); + my $mboxB = $res->[0][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + my %roleByMbox = map { $_->{id} => $_->{role} } @{ $res->[1][1]{list} }; + $self->assert_deep_equals( + { + $inbox => 'inbox', + $mboxB => 'trash', + }, + \%roleByMbox + ); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_move_update b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_move_update index e47351778f..701d4bcc80 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_move_update +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_move_update @@ -2,56 +2,74 @@ use Cassandane::Tiny; sub test_mailbox_set_role_move_update - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R1'], - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - role => 'trash', - }, - mboxB => { - name => 'B', - }, - }, - }, "R2"], - ]); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); - my $mboxA = $res->[1][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[1][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); + my $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + role => 'trash', + }, + mboxB => { + name => 'B', + }, + }, + }, + "R2" + ], + ]); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); + my $mboxA = $res->[1][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[1][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); - xlog "move trash role by update"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxA => { - role => undef, - }, - $mboxB => { - role => 'trash', - }, - }, - }, "R1"], - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R2'], - ]); - $self->assert(exists $res->[0][1]{updated}{$mboxA}); - $self->assert(exists $res->[0][1]{updated}{$mboxB}); - my %roleByMbox = map { $_->{id} => $_->{role} } @{$res->[1][1]{list}}; - $self->assert_deep_equals({ - $inbox => 'inbox', - $mboxA => undef, - $mboxB => 'trash', - }, \%roleByMbox); + xlog "move trash role by update"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxA => { + role => undef, + }, + $mboxB => { + role => 'trash', + }, + }, + }, + "R1" + ], + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R2' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$mboxA}); + $self->assert(exists $res->[0][1]{updated}{$mboxB}); + my %roleByMbox = map { $_->{id} => $_->{role} } @{ $res->[1][1]{list} }; + $self->assert_deep_equals( + { + $inbox => 'inbox', + $mboxA => undef, + $mboxB => 'trash', + }, + \%roleByMbox + ); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_protected_destroy b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_protected_destroy index 023ede3101..6411ccbfa0 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_protected_destroy +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_role_protected_destroy @@ -2,71 +2,107 @@ use Cassandane::Tiny; sub test_mailbox_set_role_protected_destroy - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; - xlog "create protected and unprotected roles"; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R1'], - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - role => 'drafts', - }, - mboxB => { - name => 'B', - role => 'xspecialuse', - }, - }, - }, "R2"], - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R3'], - ]); - my $inbox = $res->[0][1]{list}[0]{id}; - $self->assert_not_null($inbox); - my $mboxA = $res->[1][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxA); - my $mboxB = $res->[1][1]{created}{mboxB}{id}; - $self->assert_not_null($mboxB); - my %roleByMbox = map { $_->{id} => $_->{role} } @{$res->[2][1]{list}}; - $self->assert_deep_equals({ - $inbox => 'inbox', - $mboxA => 'drafts', - $mboxB => 'xspecialuse', - }, \%roleByMbox); + xlog "create protected and unprotected roles"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + role => 'drafts', + }, + mboxB => { + name => 'B', + role => 'xspecialuse', + }, + }, + }, + "R2" + ], + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R3' + ], + ]); + my $inbox = $res->[0][1]{list}[0]{id}; + $self->assert_not_null($inbox); + my $mboxA = $res->[1][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxA); + my $mboxB = $res->[1][1]{created}{mboxB}{id}; + $self->assert_not_null($mboxB); + my %roleByMbox = map { $_->{id} => $_->{role} } @{ $res->[2][1]{list} }; + $self->assert_deep_equals( + { + $inbox => 'inbox', + $mboxA => 'drafts', + $mboxB => 'xspecialuse', + }, + \%roleByMbox + ); - xlog "destroy protected and unprotected roles in one method"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [$mboxA, $mboxB], - }, 'R1'], - ]); - $self->assert_str_equals('serverFail', $res->[0][1]{notDestroyed}{$mboxA}{type}); - $self->assert_str_equals('serverFail', $res->[0][1]{notDestroyed}{$mboxB}{type}); + xlog "destroy protected and unprotected roles in one method"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + destroy => [ $mboxA, $mboxB ], + }, + 'R1' + ], + ]); + $self->assert_str_equals('serverFail', + $res->[0][1]{notDestroyed}{$mboxA}{type}); + $self->assert_str_equals('serverFail', + $res->[0][1]{notDestroyed}{$mboxB}{type}); - xlog "destroy protected and unprotected roles in separate method"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - destroy => [$mboxA], - }, 'R1'], - ['Mailbox/set', { - destroy => [$mboxB], - }, 'R2'], - ['Mailbox/get', { - properties => ['role', 'name'], - }, 'R3'], - ]); - $self->assert_str_equals('serverFail', $res->[0][1]{notDestroyed}{$mboxA}{type}); - $self->assert_deep_equals([$mboxB], $res->[1][1]{destroyed}); - %roleByMbox = map { $_->{id} => $_->{role} } @{$res->[2][1]{list}}; - $self->assert_deep_equals({ - $inbox => 'inbox', - $mboxA => 'drafts', - }, \%roleByMbox); + xlog "destroy protected and unprotected roles in separate method"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + destroy => [$mboxA], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + destroy => [$mboxB], + }, + 'R2' + ], + [ + 'Mailbox/get', + { + properties => [ 'role', 'name' ], + }, + 'R3' + ], + ]); + $self->assert_str_equals('serverFail', + $res->[0][1]{notDestroyed}{$mboxA}{type}); + $self->assert_deep_equals([$mboxB], $res->[1][1]{destroyed}); + %roleByMbox = map { $_->{id} => $_->{role} } @{ $res->[2][1]{list} }; + $self->assert_deep_equals( + { + $inbox => 'inbox', + $mboxA => 'drafts', + }, + \%roleByMbox + ); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_shared b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_shared index b7874bd6b2..9ab6c7dc77 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_shared +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_shared @@ -2,78 +2,90 @@ use Cassandane::Tiny; sub test_mailbox_set_shared - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - # Create account - $self->{instance}->create_user("foo"); - - # Share inbox but do not allow to create subfolders - $admintalk->setacl("user.foo", "cassandane", "lr") or die; - - xlog $self, "get mailboxes for foo account"; - my $res = $jmap->CallMethods([['Mailbox/get', { accountId => "foo" }, "R1"]]); - my $inboxId = $res->[0][1]{list}[0]{id}; - - my $update = ['Mailbox/set', { - accountId => "foo", - update => { - $inboxId => { - name => "UpdatedInbox", - } + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; + my $jmap = $self->{jmap}; + + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + # Create account + $self->{instance}->create_user("foo"); + + # Share inbox but do not allow to create subfolders + $admintalk->setacl("user.foo", "cassandane", "lr") or die; + + xlog $self, "get mailboxes for foo account"; + my $res + = $jmap->CallMethods([ [ 'Mailbox/get', { accountId => "foo" }, "R1" ] ]); + my $inboxId = $res->[0][1]{list}[0]{id}; + + my $update = [ + 'Mailbox/set', + { + accountId => "foo", + update => { + $inboxId => { + name => "UpdatedInbox", } - }, "R1"]; - - xlog $self, "update shared INBOX (should fail)"; - $res = $jmap->CallMethods([ $update ]); - $self->assert(exists $res->[0][1]{notUpdated}{$inboxId}); - - xlog $self, "Add update ACL rights to shared INBOX"; - $admintalk->setacl("user.foo", "cassandane", "lrw") or die; - - xlog $self, "update shared INBOX (should succeed)"; - $res = $jmap->CallMethods([ $update ]); - $self->assert(exists $res->[0][1]{updated}{$inboxId}); - - my $create = ['Mailbox/set', { - accountId => "foo", - create => { - "1" => { - name => "x", - } + } + }, + "R1" + ]; + + xlog $self, "update shared INBOX (should fail)"; + $res = $jmap->CallMethods([$update]); + $self->assert(exists $res->[0][1]{notUpdated}{$inboxId}); + + xlog $self, "Add update ACL rights to shared INBOX"; + $admintalk->setacl("user.foo", "cassandane", "lrw") or die; + + xlog $self, "update shared INBOX (should succeed)"; + $res = $jmap->CallMethods([$update]); + $self->assert(exists $res->[0][1]{updated}{$inboxId}); + + my $create = [ + 'Mailbox/set', + { + accountId => "foo", + create => { + "1" => { + name => "x", } - }, "R1"]; - - xlog $self, "create mailbox child (should fail)"; - $res = $jmap->CallMethods([ $create ]); - $self->assert_not_null($res->[0][1]{notCreated}{1}); - - xlog $self, "Add update ACL rights to shared INBOX"; - $admintalk->setacl("user.foo", "cassandane", "lrwk") or die; - - xlog $self, "create mailbox child (should succeed)"; - $res = $jmap->CallMethods([ $create ]); - $self->assert_not_null($res->[0][1]{created}{1}); - my $childId = $res->[0][1]{created}{1}{id}; - - my $destroy = ['Mailbox/set', { - accountId => "foo", - destroy => [ $childId ], - }, 'R1' ]; - - xlog $self, "destroy shared mailbox child (should fail)"; - $res = $jmap->CallMethods([ $destroy ]); - $self->assert(exists $res->[0][1]{notDestroyed}{$childId}); - - xlog $self, "Add delete ACL rights"; - $admintalk->setacl("user.foo.x", "cassandane", "lrwkx") or die; - - xlog $self, "destroy shared mailbox child (should succeed)"; - $res = $jmap->CallMethods([ $destroy ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{destroyed}}); + } + }, + "R1" + ]; + + xlog $self, "create mailbox child (should fail)"; + $res = $jmap->CallMethods([$create]); + $self->assert_not_null($res->[0][1]{notCreated}{1}); + + xlog $self, "Add update ACL rights to shared INBOX"; + $admintalk->setacl("user.foo", "cassandane", "lrwk") or die; + + xlog $self, "create mailbox child (should succeed)"; + $res = $jmap->CallMethods([$create]); + $self->assert_not_null($res->[0][1]{created}{1}); + my $childId = $res->[0][1]{created}{1}{id}; + + my $destroy = [ + 'Mailbox/set', + { + accountId => "foo", + destroy => [$childId], + }, + 'R1' + ]; + + xlog $self, "destroy shared mailbox child (should fail)"; + $res = $jmap->CallMethods([$destroy]); + $self->assert(exists $res->[0][1]{notDestroyed}{$childId}); + + xlog $self, "Add delete ACL rights"; + $admintalk->setacl("user.foo.x", "cassandane", "lrwkx") or die; + + xlog $self, "destroy shared mailbox child (should succeed)"; + $res = $jmap->CallMethods([$destroy]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{destroyed} }); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_sharewith b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_sharewith index 57d5fb30c6..72a7555d0d 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_sharewith +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_sharewith @@ -2,98 +2,135 @@ use Cassandane::Tiny; sub test_mailbox_set_sharewith - :min_version_3_3 :needs_component_jmap :NoAltNameSpace :JMAPExtensions -{ - my ($self) = @_; + : min_version_3_3 : needs_component_jmap : NoAltNameSpace : JMAPExtensions { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $admin = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $admin = $self->{adminstore}->get_client(); - my $using = [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', - 'https://cyrusimap.org/ns/jmap/mail', - ]; + my $using = [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', + 'https://cyrusimap.org/ns/jmap/mail', + ]; - my $inboxId = $self->getinbox()->{id}; - $self->assert_not_null($inboxId); + my $inboxId = $self->getinbox()->{id}; + $self->assert_not_null($inboxId); - $self->{instance}->create_user("sharee"); + $self->{instance}->create_user("sharee"); - xlog $self, "Overwrite shareWith"; - my $res = $jmap->CallMethods([ - ['Mailbox/get', { - ids => [$inboxId], - properties => ['shareWith'], - }, 'R1'], - ['Mailbox/set', { - update => { - $inboxId => { - shareWith => { - sharee => { - mayRead => JSON::true, - }, - }, + xlog $self, "Overwrite shareWith"; + my $res = $jmap->CallMethods( + [ + [ + 'Mailbox/get', + { + ids => [$inboxId], + properties => ['shareWith'], + }, + 'R1' + ], + [ + 'Mailbox/set', + { + update => { + $inboxId => { + shareWith => { + sharee => { + mayRead => JSON::true, }, + }, }, - }, 'R2'], - ['Mailbox/get', { - ids => [$inboxId], - properties => ['shareWith'], - }, 'R3'], - ], $using); - - $self->assert_null($res->[0][1]{list}[0]{shareWith}); - $self->assert_deep_equals({ - sharee => { - mayRead => JSON::true, - mayWrite => JSON::false, - mayAdmin => JSON::false, + }, + }, + 'R2' + ], + [ + 'Mailbox/get', + { + ids => [$inboxId], + properties => ['shareWith'], }, - }, $res->[2][1]{list}[0]{shareWith}); - my $acl = $admin->getacl("user.cassandane"); - my %map = @$acl; - $self->assert_str_equals('lr', $map{sharee}); + 'R3' + ], + ], + $using + ); - xlog $self, "Patch shareWith"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $inboxId => { - 'shareWith/sharee/mayWrite' => JSON::true, - }, - }, - }, 'R1'], - ['Mailbox/get', { - ids => [$inboxId], - properties => ['shareWith'], - }, 'R2'], - ], $using); + $self->assert_null($res->[0][1]{list}[0]{shareWith}); + $self->assert_deep_equals( + { + sharee => { + mayRead => JSON::true, + mayWrite => JSON::false, + mayAdmin => JSON::false, + }, + }, + $res->[2][1]{list}[0]{shareWith} + ); + my $acl = $admin->getacl("user.cassandane"); + my %map = @$acl; + $self->assert_str_equals('lr', $map{sharee}); - $self->assert_deep_equals({ - sharee => { - mayRead => JSON::true, - mayWrite => JSON::true, - mayAdmin => JSON::false, + xlog $self, "Patch shareWith"; + $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + update => { + $inboxId => { + 'shareWith/sharee/mayWrite' => JSON::true, + }, + }, + }, + 'R1' + ], + [ + 'Mailbox/get', + { + ids => [$inboxId], + properties => ['shareWith'], }, - }, $res->[1][1]{list}[0]{shareWith}); - $acl = $admin->getacl("user.cassandane"); - %map = @$acl; - $self->assert_str_equals('lrswitedn', $map{sharee}); + 'R2' + ], + ], + $using + ); - xlog $self, "Patch shareWith with unknown right"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $inboxId => { - 'shareWith/sharee/unknownRight' => JSON::true, - }, + $self->assert_deep_equals( + { + sharee => { + mayRead => JSON::true, + mayWrite => JSON::true, + mayAdmin => JSON::false, + }, + }, + $res->[1][1]{list}[0]{shareWith} + ); + $acl = $admin->getacl("user.cassandane"); + %map = @$acl; + $self->assert_str_equals('lrswitedn', $map{sharee}); + + xlog $self, "Patch shareWith with unknown right"; + $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + update => { + $inboxId => { + 'shareWith/sharee/unknownRight' => JSON::true, }, - }, 'R1'], - ], $using); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{$inboxId}{type}); - $self->assert_deep_equals(['shareWith/sharee/unknownRight'], - $res->[0][1]{notUpdated}{$inboxId}{properties}); + }, + }, + 'R1' + ], + ], + $using + ); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$inboxId}{type}); + $self->assert_deep_equals(['shareWith/sharee/unknownRight'], + $res->[0][1]{notUpdated}{$inboxId}{properties}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_sharewith_acl b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_sharewith_acl index 16dcbf2fdb..e566f3d0e5 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_sharewith_acl +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_sharewith_acl @@ -2,78 +2,98 @@ use Cassandane::Tiny; sub test_mailbox_set_sharewith_acl - :min_version_3_5 :needs_component_jmap :JMAPExtensions -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $admin = $self->{adminstore}->get_client(); - my $imap = $self->{store}->get_client(); + : min_version_3_5 : needs_component_jmap : JMAPExtensions { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $admin = $self->{adminstore}->get_client(); + my $imap = $self->{store}->get_client(); - $imap->create("A") or die; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { - filter => { - name => 'A', - }, - }, 'R1'], - ]); - my $mboxId = $res->[0][1]{ids}[0]; - $self->assert_not_null($mboxId); + $imap->create("A") or die; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/query', + { + filter => { + name => 'A', + }, + }, + 'R1' + ], + ]); + my $mboxId = $res->[0][1]{ids}[0]; + $self->assert_not_null($mboxId); - $admin->create("user.sharee"); + $admin->create("user.sharee"); - my @testCases = ({ - rights => { - mayAdmin => JSON::true, - }, - acl => 'kxca', - }, { - rights => { - mayWrite => JSON::true, - }, - acl => 'switedn', - }, { - rights => { - mayRead => JSON::true, - }, - acl => 'lr', - }); + my @testCases = ( + { + rights => { + mayAdmin => JSON::true, + }, + acl => 'kxca', + }, + { + rights => { + mayWrite => JSON::true, + }, + acl => 'switedn', + }, + { + rights => { + mayRead => JSON::true, + }, + acl => 'lr', + } + ); - foreach(@testCases) { + foreach (@testCases) { - xlog "Run test for acl $_->{acl}"; + xlog "Run test for acl $_->{acl}"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxId => { - shareWith => { - sharee => $_->{rights}, - }, - }, + $res = $jmap->CallMethods( + [ + [ + 'Mailbox/set', + { + update => { + $mboxId => { + shareWith => { + sharee => $_->{rights}, }, - }, 'R1'], - ['Mailbox/get', { - ids => [$mboxId], - properties => ['shareWith'], - }, 'R2'], - ], [ - 'urn:ietf:params:jmap:core', - 'urn:ietf:params:jmap:mail', + }, + }, + }, + 'R1' + ], + [ + 'Mailbox/get', + { + ids => [$mboxId], + properties => ['shareWith'], + }, + 'R2' + ], + ], + [ + 'urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail', 'https://cyrusimap.org/ns/jmap/mail' - ]) ; + ] + ); - $_->{wantRights} ||= $_->{rights}; + $_->{wantRights} ||= $_->{rights}; - my %mergedrights = (( - mayAdmin => JSON::false, - mayWrite => JSON::false, - mayRead => JSON::false, - ), %{$_->{wantRights}}); + my %mergedrights = ( + ( + mayAdmin => JSON::false, + mayWrite => JSON::false, + mayRead => JSON::false, + ), + %{ $_->{wantRights} } + ); - $self->assert_deep_equals(\%mergedrights, - $res->[1][1]{list}[0]{shareWith}{sharee}); - my %acl = @{$admin->getacl("user.cassandane.A")}; - $self->assert_str_equals($_->{acl}, $acl{sharee}); - } + $self->assert_deep_equals(\%mergedrights, + $res->[1][1]{list}[0]{shareWith}{sharee}); + my %acl = @{ $admin->getacl("user.cassandane.A") }; + $self->assert_str_equals($_->{acl}, $acl{sharee}); + } } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_destroy b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_destroy index 671e1ba948..b2c1437d66 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_destroy +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_destroy @@ -2,37 +2,46 @@ use Cassandane::Tiny; sub test_mailbox_set_subscriptions_destroy - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - A => { - name => 'A', parentId => undef, role => undef, - }, + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + A => { + name => 'A', + parentId => undef, + role => undef, }, - }, "R1"]]); - my $idA =$res->[0][1]{created}{A}{id}; - $self->assert_not_null($idA); - - my $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_num_equals(0, scalar @{$subdata}); - - $imap->subscribe("INBOX.A") || die; - - $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_num_equals(1, scalar @{$subdata}); - $self->assert_str_equals('INBOX.A', $subdata->[0][2]); - - $res = $jmap->CallMethods([['Mailbox/set', { - destroy => [$idA], - }, "R1"]]); - $self->assert_str_equals($idA, $res->[0][1]{destroyed}[0]); - - $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_num_equals(0, scalar @{$subdata}); + }, + }, + "R1" + ] ]); + my $idA = $res->[0][1]{created}{A}{id}; + $self->assert_not_null($idA); + + my $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); + $self->assert_num_equals(0, scalar @{$subdata}); + + $imap->subscribe("INBOX.A") || die; + + $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); + $self->assert_num_equals(1, scalar @{$subdata}); + $self->assert_str_equals('INBOX.A', $subdata->[0][2]); + + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + destroy => [$idA], + }, + "R1" + ] ]); + $self->assert_str_equals($idA, $res->[0][1]{destroyed}[0]); + + $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); + $self->assert_num_equals(0, scalar @{$subdata}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_rename b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_rename index 3bd7f36912..a1cb39580a 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_rename +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_rename @@ -2,36 +2,45 @@ use Cassandane::Tiny; sub test_mailbox_set_subscriptions_rename - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - A => { - name => 'A', parentId => undef, role => undef, - }, + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + A => { + name => 'A', + parentId => undef, + role => undef, }, - }, "R1"]]); - my $idA =$res->[0][1]{created}{A}{id}; - $self->assert_not_null($idA); - $imap->subscribe("INBOX.A") || die; + }, + }, + "R1" + ] ]); + my $idA = $res->[0][1]{created}{A}{id}; + $self->assert_not_null($idA); + $imap->subscribe("INBOX.A") || die; - my $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_num_equals(1, scalar @{$subdata}); - $self->assert_str_equals('INBOX.A', $subdata->[0][2]); + my $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); + $self->assert_num_equals(1, scalar @{$subdata}); + $self->assert_str_equals('INBOX.A', $subdata->[0][2]); - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $idA => { - name => 'B', - }, + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $idA => { + name => 'B', }, - }, "R1"]]); - $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_num_equals(1, scalar @{$subdata}); - $self->assert_str_equals('INBOX.B', $subdata->[0][2]); + }, + }, + "R1" + ] ]); + $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); + $self->assert_num_equals(1, scalar @{$subdata}); + $self->assert_str_equals('INBOX.B', $subdata->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_rename_children b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_rename_children index 9ae945a8d3..9dc42d3f55 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_rename_children +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_subscriptions_rename_children @@ -2,39 +2,50 @@ use Cassandane::Tiny; sub test_mailbox_set_subscriptions_rename_children - :min_version_3_1 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - A => { - name => 'A', parentId => undef, role => undef, - }, - C => { - name => 'C', parentId => '#A', role => undef, - }, + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + A => { + name => 'A', + parentId => undef, + role => undef, }, - }, "R1"]]); - my $idA =$res->[0][1]{created}{A}{id}; - $self->assert_not_null($idA); - $imap->subscribe("INBOX.A.C") || die; + C => { + name => 'C', + parentId => '#A', + role => undef, + }, + }, + }, + "R1" + ] ]); + my $idA = $res->[0][1]{created}{A}{id}; + $self->assert_not_null($idA); + $imap->subscribe("INBOX.A.C") || die; - my $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_num_equals(1, scalar @{$subdata}); - $self->assert_str_equals('INBOX.A.C', $subdata->[0][2]); + my $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); + $self->assert_num_equals(1, scalar @{$subdata}); + $self->assert_str_equals('INBOX.A.C', $subdata->[0][2]); - $res = $jmap->CallMethods([['Mailbox/set', { - update => { - $idA => { - name => 'B', - }, + $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + update => { + $idA => { + name => 'B', }, - }, "R1"]]); - $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_num_equals(1, scalar @{$subdata}); - $self->assert_str_equals('INBOX.B.C', $subdata->[0][2]); + }, + }, + "R1" + ] ]); + $subdata = $imap->list([qw(SUBSCRIBED)], "", "*"); + $self->assert_num_equals(1, scalar @{$subdata}); + $self->assert_str_equals('INBOX.B.C', $subdata->[0][2]); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_update_serverset_props b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_update_serverset_props index 80211fc952..832ea11a55 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_set_update_serverset_props +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_set_update_serverset_props @@ -2,110 +2,129 @@ use Cassandane::Tiny; sub test_mailbox_set_update_serverset_props - :min_version_3_1 :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_1 : needs_component_jmap { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog "create mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { - create => { - mboxA => { - name => 'A', - role => undef, - }, - }, - }, 'R1'], - ['Mailbox/get', { - ids => ['#mboxA'], - }, 'R2'], - ]); - my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; - $self->assert_not_null($mboxIdA); - my $mboxA = $res->[1][1]{list}[0]; - $self->assert_not_null($mboxA); + xlog "create mailbox"; + my $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + create => { + mboxA => { + name => 'A', + role => undef, + }, + }, + }, + 'R1' + ], + [ + 'Mailbox/get', + { + ids => ['#mboxA'], + }, + 'R2' + ], + ]); + my $mboxIdA = $res->[0][1]{created}{mboxA}{id}; + $self->assert_not_null($mboxIdA); + my $mboxA = $res->[1][1]{list}[0]; + $self->assert_not_null($mboxA); - xlog "update with matching server-set properties"; - $mboxA->{name} = 'XA'; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxIdA => $mboxA, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$mboxIdA}); + xlog "update with matching server-set properties"; + $mboxA->{name} = 'XA'; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxIdA => $mboxA, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{$mboxIdA}); - xlog "update with matching server-set properties"; - # Assert default values before we change them. - $self->assert_num_equals(0, $mboxA->{totalEmails}); - $self->assert_num_equals(0, $mboxA->{unreadEmails}); - $self->assert_num_equals(0, $mboxA->{totalThreads}); - $self->assert_num_equals(0, $mboxA->{unreadThreads}); - $self->assert_deep_equals({ - mayReadItems => JSON::true, - mayAddItems => JSON::true, - mayRemoveItems => JSON::true, - mayCreateChild => JSON::true, - mayDelete => JSON::true, - maySubmit => JSON::true, - maySetSeen => JSON::true, - maySetKeywords => JSON::true, - mayAdmin => JSON::true, - mayRename => JSON::true, - }, $mboxA->{myRights}); - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxIdA => { - totalEmails => 1, - unreadEmails => 1, - totalThreads => 1, - unreadThreads => 1, - myRights => { - mayReadItems => JSON::false, - mayAddItems => JSON::false, - mayRemoveItems => JSON::false, - mayCreateChild => JSON::false, - mayDelete => JSON::false, - maySubmit => JSON::false, - maySetSeen => JSON::false, - maySetKeywords => JSON::false, - mayAdmin => JSON::false, - mayRename => JSON::false, - }, - }, + xlog "update with matching server-set properties"; + # Assert default values before we change them. + $self->assert_num_equals(0, $mboxA->{totalEmails}); + $self->assert_num_equals(0, $mboxA->{unreadEmails}); + $self->assert_num_equals(0, $mboxA->{totalThreads}); + $self->assert_num_equals(0, $mboxA->{unreadThreads}); + $self->assert_deep_equals( + { + mayReadItems => JSON::true, + mayAddItems => JSON::true, + mayRemoveItems => JSON::true, + mayCreateChild => JSON::true, + mayDelete => JSON::true, + maySubmit => JSON::true, + maySetSeen => JSON::true, + maySetKeywords => JSON::true, + mayAdmin => JSON::true, + mayRename => JSON::true, + }, + $mboxA->{myRights} + ); + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxIdA => { + totalEmails => 1, + unreadEmails => 1, + totalThreads => 1, + unreadThreads => 1, + myRights => { + mayReadItems => JSON::false, + mayAddItems => JSON::false, + mayRemoveItems => JSON::false, + mayCreateChild => JSON::false, + mayDelete => JSON::false, + maySubmit => JSON::false, + maySetSeen => JSON::false, + maySetKeywords => JSON::false, + mayAdmin => JSON::false, + mayRename => JSON::false, }, - }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{$mboxIdA}{type}); - my @wantInvalidProps = ( - 'myRights', - 'totalEmails', - 'unreadEmails', - 'totalThreads', - 'unreadThreads', - ); - my @gotInvalidProps = @{$res->[0][1]{notUpdated}{$mboxIdA}{properties}}; - @wantInvalidProps = sort @wantInvalidProps; - @gotInvalidProps = sort @gotInvalidProps; - $self->assert_deep_equals(\@wantInvalidProps, \@gotInvalidProps); + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$mboxIdA}{type}); + my @wantInvalidProps = ( + 'myRights', 'totalEmails', 'unreadEmails', 'totalThreads', + 'unreadThreads', + ); + my @gotInvalidProps = @{ $res->[0][1]{notUpdated}{$mboxIdA}{properties} }; + @wantInvalidProps = sort @wantInvalidProps; + @gotInvalidProps = sort @gotInvalidProps; + $self->assert_deep_equals(\@wantInvalidProps, \@gotInvalidProps); - xlog "update with unknown mailbox right"; - $res = $jmap->CallMethods([ - ['Mailbox/set', { - update => { - $mboxIdA => { - 'myRights/mayXxx' => JSON::false, - }, - }, - }, 'R1'], - ]); - $self->assert_str_equals('invalidProperties', - $res->[0][1]{notUpdated}{$mboxIdA}{type}); - $self->assert_deep_equals(['myRights'], - $res->[0][1]{notUpdated}{$mboxIdA}{properties}) + xlog "update with unknown mailbox right"; + $res = $jmap->CallMethods([ + [ + 'Mailbox/set', + { + update => { + $mboxIdA => { + 'myRights/mayXxx' => JSON::false, + }, + }, + }, + 'R1' + ], + ]); + $self->assert_str_equals('invalidProperties', + $res->[0][1]{notUpdated}{$mboxIdA}{type}); + $self->assert_deep_equals(['myRights'], + $res->[0][1]{notUpdated}{$mboxIdA}{properties}); } diff --git a/cassandane/tiny-tests/JMAPMailbox/mailbox_trash_counts_ondelete b/cassandane/tiny-tests/JMAPMailbox/mailbox_trash_counts_ondelete index 3cb15e8c8d..b22a7ba76b 100644 --- a/cassandane/tiny-tests/JMAPMailbox/mailbox_trash_counts_ondelete +++ b/cassandane/tiny-tests/JMAPMailbox/mailbox_trash_counts_ondelete @@ -2,30 +2,33 @@ use Cassandane::Tiny; sub test_mailbox_trash_counts_ondelete - :min_version_3_3 :needs_component_jmap :NoAltNameSpace -{ - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - $imap->uid(1); + : min_version_3_3 : needs_component_jmap : NoAltNameSpace { + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + $imap->uid(1); - xlog "Set up mailboxes"; - my $res = $jmap->CallMethods([ - ['Mailbox/query', { }, 'R1'], - ['Mailbox/set', { - create => { - "a" => { name => "a", parentId => undef }, - "b" => { name => "b", parentId => undef }, - "trash" => { name => "Trash", parentId => undef, role => "trash" }, - }, - }, 'R2'], - ]); - my %ids = map { $_ => $res->[1][1]{created}{$_}{id} } - keys %{$res->[1][1]{created}}; + xlog "Set up mailboxes"; + my $res = $jmap->CallMethods([ + [ 'Mailbox/query', {}, 'R1' ], + [ + 'Mailbox/set', + { + create => { + "a" => { name => "a", parentId => undef }, + "b" => { name => "b", parentId => undef }, + "trash" => { name => "Trash", parentId => undef, role => "trash" }, + }, + }, + 'R2' + ], + ]); + my %ids = map { $_ => $res->[1][1]{created}{$_}{id} } + keys %{ $res->[1][1]{created} }; - xlog "Set up messages"; - my %raw = ( - A => <<"EOF", + xlog "Set up messages"; + my %raw = ( + A => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -36,7 +39,7 @@ Content-Type: text/plain\r \r test A\r EOF - B => <<"EOF", + B => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -49,7 +52,7 @@ In-Reply-To: \r \r test B\r EOF - C => <<"EOF", + C => <<"EOF", From: \r To: to\@local\r Subject: test\r @@ -62,7 +65,7 @@ In-Reply-To: \r \r test C\r EOF - D => <<"EOF", + D => <<"EOF", From: \r To: to\@local\r Subject: test2\r @@ -73,42 +76,45 @@ Content-Type: text/plain\r \r test D\r EOF - ); + ); - # threads: - # T1: A B C - # T2: D + # threads: + # T1: A B C + # T2: D - xlog $self, "Set up all the emails in all the folders"; - $imap->append('INBOX.a', "(\\Seen)", $raw{A}) || die $@; - $imap->append('INBOX.a', "()", $raw{B}) || die $@; - $imap->append('INBOX.a', "(\\Seen)", $raw{C}) || die $@; - $imap->append('INBOX.a', "()", $raw{D}) || die $@; + xlog $self, "Set up all the emails in all the folders"; + $imap->append('INBOX.a', "(\\Seen)", $raw{A}) || die $@; + $imap->append('INBOX.a', "()", $raw{B}) || die $@; + $imap->append('INBOX.a', "(\\Seen)", $raw{C}) || die $@; + $imap->append('INBOX.a', "()", $raw{D}) || die $@; - $self->_check_counts('Initial Test', - a => [ 4, 2, 2, 2 ], - b => [ 0, 0, 0, 0 ], - Trash => [ 0, 0, 0, 0 ], - ); + $self->_check_counts( + 'Initial Test', + a => [ 4, 2, 2, 2 ], + b => [ 0, 0, 0, 0 ], + Trash => [ 0, 0, 0, 0 ], + ); - xlog $self, "Move everything to trash"; - $imap->select("INBOX.a"); - $imap->move("1:*", "INBOX.Trash"); - $self->_check_counts('After move all to Trash', - a => [ 0, 0, 0, 0 ], - b => [ 0, 0, 0, 0 ], - Trash => [ 4, 2, 2, 2 ], - ); + xlog $self, "Move everything to trash"; + $imap->select("INBOX.a"); + $imap->move("1:*", "INBOX.Trash"); + $self->_check_counts( + 'After move all to Trash', + a => [ 0, 0, 0, 0 ], + b => [ 0, 0, 0, 0 ], + Trash => [ 4, 2, 2, 2 ], + ); - xlog $self, "Destroy everything via JMAP"; + xlog $self, "Destroy everything via JMAP"; - $res = $jmap->CallMethods([['Email/query', {}, "R1"]]); - my $ids = $res->[0][1]->{ids}; - $res = $jmap->CallMethods([['Email/set', { destroy => $ids }, "R1"]]); + $res = $jmap->CallMethods([ [ 'Email/query', {}, "R1" ] ]); + my $ids = $res->[0][1]->{ids}; + $res = $jmap->CallMethods([ [ 'Email/set', { destroy => $ids }, "R1" ] ]); - $self->_check_counts('After Destroy Everything', - a => [ 0, 0, 0, 0 ], - b => [ 0, 0, 0, 0 ], - Trash => [ 0, 0, 0, 0 ], - ); + $self->_check_counts( + 'After Destroy Everything', + a => [ 0, 0, 0, 0 ], + b => [ 0, 0, 0, 0 ], + Trash => [ 0, 0, 0, 0 ], + ); } diff --git a/cassandane/tiny-tests/List/crossdomains b/cassandane/tiny-tests/List/crossdomains index e3e76e4b04..2fe03c5040 100644 --- a/cassandane/tiny-tests/List/crossdomains +++ b/cassandane/tiny-tests/List/crossdomains @@ -2,25 +2,29 @@ use Cassandane::Tiny; sub test_crossdomains - :UnixHierarchySep :VirtDomains :CrossDomains :min_version_3_0 :NoAltNameSpace -{ - my ($self) = @_; + : UnixHierarchySep : VirtDomains : CrossDomains : min_version_3_0 : + NoAltNameSpace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/foo\@example.com"); - $admintalk->create("user/bar\@example.net"); - $admintalk->create("user/bar/Shared\@example.net"); # yay bogus domaining + $admintalk->create("user/foo\@example.com"); + $admintalk->create("user/bar\@example.net"); + $admintalk->create("user/bar/Shared\@example.net"); # yay bogus domaining - $admintalk->setacl("user/foo\@example.com", 'cassandane' => 'lrswipkxtecd'); - $admintalk->setacl("user/bar/Shared\@example.net", 'cassandane' => 'lrswipkxtecd'); + $admintalk->setacl("user/foo\@example.com", 'cassandane' => 'lrswipkxtecd'); + $admintalk->setacl("user/bar/Shared\@example.net", + 'cassandane' => 'lrswipkxtecd'); - my $data = $imaptalk->list("", "*"); + my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasNoChildren', - 'user/foo@example.com' => '\\HasNoChildren', - 'user/bar@example.net/Shared' => '\\HasNoChildren', - }); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasNoChildren', + 'user/foo@example.com' => '\\HasNoChildren', + 'user/bar@example.net/Shared' => '\\HasNoChildren', + } + ); } diff --git a/cassandane/tiny-tests/List/crossdomains_alt b/cassandane/tiny-tests/List/crossdomains_alt index 54fbe1dae5..6700d8740c 100644 --- a/cassandane/tiny-tests/List/crossdomains_alt +++ b/cassandane/tiny-tests/List/crossdomains_alt @@ -2,25 +2,29 @@ use Cassandane::Tiny; sub test_crossdomains_alt - :UnixHierarchySep :VirtDomains :CrossDomains :AltNamespace :min_version_3_0 -{ - my ($self) = @_; + : UnixHierarchySep : VirtDomains : CrossDomains : AltNamespace : + min_version_3_0 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/foo\@example.com"); - $admintalk->create("user/bar\@example.net"); - $admintalk->create("user/bar/Shared\@example.net"); # yay bogus domaining + $admintalk->create("user/foo\@example.com"); + $admintalk->create("user/bar\@example.net"); + $admintalk->create("user/bar/Shared\@example.net"); # yay bogus domaining - $admintalk->setacl("user/foo\@example.com", 'cassandane' => 'lrswipkxtecd'); - $admintalk->setacl("user/bar/Shared\@example.net", 'cassandane' => 'lrswipkxtecd'); + $admintalk->setacl("user/foo\@example.com", 'cassandane' => 'lrswipkxtecd'); + $admintalk->setacl("user/bar/Shared\@example.net", + 'cassandane' => 'lrswipkxtecd'); - my $data = $imaptalk->list("", "*"); + my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasNoChildren', - 'Other Users/foo@example.com' => '\\HasNoChildren', - 'Other Users/bar@example.net/Shared' => '\\HasNoChildren', - }); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasNoChildren', + 'Other Users/foo@example.com' => '\\HasNoChildren', + 'Other Users/bar@example.net/Shared' => '\\HasNoChildren', + } + ); } diff --git a/cassandane/tiny-tests/List/delete_nounsubscribe b/cassandane/tiny-tests/List/delete_nounsubscribe index bb778c685b..f4de52aaa0 100644 --- a/cassandane/tiny-tests/List/delete_nounsubscribe +++ b/cassandane/tiny-tests/List/delete_nounsubscribe @@ -2,24 +2,29 @@ use Cassandane::Tiny; sub test_delete_nounsubscribe - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( deltest deltest/sub1 deltest/sub2 )] ], - [ 'subscribe' => [qw( deltest deltest/sub2 )] ], - [ 'delete' => 'deltest/sub2' ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( deltest deltest/sub1 deltest/sub2 )] ], + [ 'subscribe' => [qw( deltest deltest/sub2 )] ], + [ 'delete' => 'deltest/sub2' ], + ] + ); - my $subdata = $imaptalk->list([qw(SUBSCRIBED)], "", "*"); + my $subdata = $imaptalk->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_mailbox_structure($subdata, '/', { - 'INBOX' => '\\Subscribed', - 'deltest' => [qw( \\Subscribed )], - 'deltest/sub2' => [qw( \\NonExistent \\Subscribed )], - }); + $self->assert_mailbox_structure( + $subdata, '/', + { + 'INBOX' => '\\Subscribed', + 'deltest' => [qw( \\Subscribed )], + 'deltest/sub2' => [qw( \\NonExistent \\Subscribed )], + } + ); } diff --git a/cassandane/tiny-tests/List/delete_unsubscribe b/cassandane/tiny-tests/List/delete_unsubscribe index edcfaa47fb..72ec51a217 100644 --- a/cassandane/tiny-tests/List/delete_unsubscribe +++ b/cassandane/tiny-tests/List/delete_unsubscribe @@ -2,26 +2,31 @@ use Cassandane::Tiny; sub test_delete_unsubscribe - :UnixHierarchySep :AltNamespace :NoStartInstances :min_version_3_0 -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace : NoStartInstances : min_version_3_0 { + my ($self) = @_; - $self->{instance}->{config}->set('delete_unsubscribe' => 'yes'); - $self->_start_instances(); + $self->{instance}->{config}->set('delete_unsubscribe' => 'yes'); + $self->_start_instances(); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( deltest deltest/sub1 deltest/sub2 )] ], - [ 'subscribe' => [qw( deltest deltest/sub2 )] ], - [ 'delete' => 'deltest/sub2' ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( deltest deltest/sub1 deltest/sub2 )] ], + [ 'subscribe' => [qw( deltest deltest/sub2 )] ], + [ 'delete' => 'deltest/sub2' ], + ] + ); - my $subdata = $imaptalk->list([qw(SUBSCRIBED)], "", "*"); + my $subdata = $imaptalk->list([qw(SUBSCRIBED)], "", "*"); - $self->assert_mailbox_structure($subdata, '/', { - 'INBOX' => '\\Subscribed', - 'deltest' => '\\Subscribed', - }); + $self->assert_mailbox_structure( + $subdata, '/', + { + 'INBOX' => '\\Subscribed', + 'deltest' => '\\Subscribed', + } + ); } diff --git a/cassandane/tiny-tests/List/dotuser_gh1875_novirt b/cassandane/tiny-tests/List/dotuser_gh1875_novirt index 691a97eee6..e6cebc6dce 100644 --- a/cassandane/tiny-tests/List/dotuser_gh1875_novirt +++ b/cassandane/tiny-tests/List/dotuser_gh1875_novirt @@ -2,28 +2,30 @@ use Cassandane::Tiny; sub test_dotuser_gh1875_novirt - :UnixHierarchySep -{ - my ($self) = @_; + : UnixHierarchySep { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/foo.bar"); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user/foo.bar"); - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo.bar"); - my $footalk = $foostore->get_client(); + my $foostore = $self->{instance}->get_service('imap') + ->create_store(username => "foo.bar"); + my $footalk = $foostore->get_client(); - $footalk->create("INBOX/Drafts"); - $footalk->create("INBOX/Sent"); - $footalk->create("INBOX/Trash"); + $footalk->create("INBOX/Drafts"); + $footalk->create("INBOX/Sent"); + $footalk->create("INBOX/Trash"); - my $data = $footalk->list("", "*"); + my $data = $footalk->list("", "*"); - xlog $self, Dumper $data; - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX/Sent' => [qw( \\HasNoChildren )], - 'INBOX/Drafts' => [qw( \\HasNoChildren )], - 'INBOX/Trash' => [qw( \\HasNoChildren )], - }); + xlog $self, Dumper $data; + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX/Sent' => [qw( \\HasNoChildren )], + 'INBOX/Drafts' => [qw( \\HasNoChildren )], + 'INBOX/Trash' => [qw( \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/dotuser_gh1875_novirt_altns b/cassandane/tiny-tests/List/dotuser_gh1875_novirt_altns index 5f2738a9ae..fb8f5f34c9 100644 --- a/cassandane/tiny-tests/List/dotuser_gh1875_novirt_altns +++ b/cassandane/tiny-tests/List/dotuser_gh1875_novirt_altns @@ -2,28 +2,30 @@ use Cassandane::Tiny; sub test_dotuser_gh1875_novirt_altns - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/foo.bar"); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user/foo.bar"); - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo.bar"); - my $footalk = $foostore->get_client(); + my $foostore = $self->{instance}->get_service('imap') + ->create_store(username => "foo.bar"); + my $footalk = $foostore->get_client(); - $footalk->create("Drafts"); - $footalk->create("Sent"); - $footalk->create("Trash"); + $footalk->create("Drafts"); + $footalk->create("Sent"); + $footalk->create("Trash"); - my $data = $footalk->list("", "*"); + my $data = $footalk->list("", "*"); - xlog $self, Dumper $data; - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Sent' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren )], - 'Trash' => [qw( \\HasNoChildren )], - }); + xlog $self, Dumper $data; + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Sent' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren )], + 'Trash' => [qw( \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/dotuser_gh1875_virt b/cassandane/tiny-tests/List/dotuser_gh1875_virt index c32aba6fc9..3703785ee8 100644 --- a/cassandane/tiny-tests/List/dotuser_gh1875_virt +++ b/cassandane/tiny-tests/List/dotuser_gh1875_virt @@ -2,28 +2,30 @@ use Cassandane::Tiny; sub test_dotuser_gh1875_virt - :VirtDomains :UnixHierarchySep -{ - my ($self) = @_; + : VirtDomains : UnixHierarchySep { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/foo.bar\@example.com"); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user/foo.bar\@example.com"); - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo.bar\@example.com"); - my $footalk = $foostore->get_client(); + my $foostore = $self->{instance}->get_service('imap') + ->create_store(username => "foo.bar\@example.com"); + my $footalk = $foostore->get_client(); - $footalk->create("INBOX/Drafts"); - $footalk->create("INBOX/Sent"); - $footalk->create("INBOX/Trash"); + $footalk->create("INBOX/Drafts"); + $footalk->create("INBOX/Sent"); + $footalk->create("INBOX/Trash"); - my $data = $footalk->list("", "*"); + my $data = $footalk->list("", "*"); - xlog $self, Dumper $data; - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX/Sent' => [qw( \\HasNoChildren )], - 'INBOX/Drafts' => [qw( \\HasNoChildren )], - 'INBOX/Trash' => [qw( \\HasNoChildren )], - }); + xlog $self, Dumper $data; + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX/Sent' => [qw( \\HasNoChildren )], + 'INBOX/Drafts' => [qw( \\HasNoChildren )], + 'INBOX/Trash' => [qw( \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/dotuser_gh1875_virt_altns b/cassandane/tiny-tests/List/dotuser_gh1875_virt_altns index 7b70a3ae3e..ad24c92c4f 100644 --- a/cassandane/tiny-tests/List/dotuser_gh1875_virt_altns +++ b/cassandane/tiny-tests/List/dotuser_gh1875_virt_altns @@ -2,28 +2,30 @@ use Cassandane::Tiny; sub test_dotuser_gh1875_virt_altns - :VirtDomains :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : VirtDomains : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/foo.bar\@example.com"); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user/foo.bar\@example.com"); - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo.bar\@example.com"); - my $footalk = $foostore->get_client(); + my $foostore = $self->{instance}->get_service('imap') + ->create_store(username => "foo.bar\@example.com"); + my $footalk = $foostore->get_client(); - $footalk->create("Drafts"); - $footalk->create("Sent"); - $footalk->create("Trash"); + $footalk->create("Drafts"); + $footalk->create("Sent"); + $footalk->create("Trash"); - my $data = $footalk->list("", "*"); + my $data = $footalk->list("", "*"); - xlog $self, Dumper $data; - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Sent' => [qw( \\HasNoChildren )], - 'Drafts' => [qw( \\HasNoChildren )], - 'Trash' => [qw( \\HasNoChildren )], - }); + xlog $self, Dumper $data; + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Sent' => [qw( \\HasNoChildren )], + 'Drafts' => [qw( \\HasNoChildren )], + 'Trash' => [qw( \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/empty_mailbox b/cassandane/tiny-tests/List/empty_mailbox index 3ea5919155..d323e96df3 100644 --- a/cassandane/tiny-tests/List/empty_mailbox +++ b/cassandane/tiny-tests/List/empty_mailbox @@ -2,15 +2,17 @@ use Cassandane::Tiny; sub test_empty_mailbox - :UnixHierarchySep -{ - my ($self) = @_; + : UnixHierarchySep { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", ""); + my $data = $imaptalk->list("", ""); - $self->assert_mailbox_structure($data, '/', { - '' => [ '\\Noselect' ], - }); + $self->assert_mailbox_structure( + $data, '/', + { + '' => ['\\Noselect'], + } + ); } diff --git a/cassandane/tiny-tests/List/folder_at_novirtdomains b/cassandane/tiny-tests/List/folder_at_novirtdomains index 8f3e5e7077..e8a9587ab1 100644 --- a/cassandane/tiny-tests/List/folder_at_novirtdomains +++ b/cassandane/tiny-tests/List/folder_at_novirtdomains @@ -2,20 +2,21 @@ use Cassandane::Tiny; sub test_folder_at_novirtdomains - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( foo@bar )] ], - ]); + $self->setup_mailbox_structure($imaptalk, + [ [ 'create' => [qw( foo@bar )] ], ]); - my $data = $imaptalk->list("", "%", "RETURN", [qw( CHILDREN )]); + my $data = $imaptalk->list("", "%", "RETURN", [qw( CHILDREN )]); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasNoChildren', - 'foo@bar' => '\\HasNoChildren', - }); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasNoChildren', + 'foo@bar' => '\\HasNoChildren', + } + ); } diff --git a/cassandane/tiny-tests/List/inbox_altnamespace b/cassandane/tiny-tests/List/inbox_altnamespace index 5f4a1de24e..c10d162290 100644 --- a/cassandane/tiny-tests/List/inbox_altnamespace +++ b/cassandane/tiny-tests/List/inbox_altnamespace @@ -2,54 +2,66 @@ use Cassandane::Tiny; sub test_inbox_altnamespace - :UnixHierarchySep :VirtDomains :CrossDomains :AltNamespace :min_version_3_0 :max_version_3_4 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - foreach my $Folder ("user/cassandane/INBOX/sub", "user/cassandane/AEARLY", - "user/cassandane/sub2", "user/cassandane/sub2/achild", - "user/cassandane/INBOX/very/deep/one", - "user/cassandane/not/so/deep", - # stuff you can't see - "user/cassandane/INBOX", - "user/cassandane/inbox", - "user/cassandane/inbox/subnobody") { - $admintalk->create($Folder); - $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); + : UnixHierarchySep : VirtDomains : CrossDomains : AltNamespace : + min_version_3_0 : max_version_3_4 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + foreach my $Folder ( + "user/cassandane/INBOX/sub", "user/cassandane/AEARLY", + "user/cassandane/sub2", "user/cassandane/sub2/achild", + "user/cassandane/INBOX/very/deep/one", + "user/cassandane/not/so/deep", + # stuff you can't see + "user/cassandane/INBOX", + "user/cassandane/inbox", + "user/cassandane/inbox/subnobody" + ) + { + $admintalk->create($Folder); + $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); + } + + my $data = $imaptalk->list("", "*"); + + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasChildren', + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + 'AEARLY' => '\\HasNoChildren', + 'not/so/deep' => '\\HasNoChildren', + 'sub2' => '\\HasChildren', + 'sub2/achild' => '\\HasNoChildren', + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody' => '\\HasNoChildren', } + ); + + my $data2 = $imaptalk->list("", "%"); - my $data = $imaptalk->list("", "*"); - - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasChildren', - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - 'AEARLY' => '\\HasNoChildren', - 'not/so/deep' => '\\HasNoChildren', - 'sub2' => '\\HasChildren', - 'sub2/achild' => '\\HasNoChildren', - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody' => '\\HasNoChildren', - }); - - my $data2 = $imaptalk->list("", "%"); - - $self->assert_mailbox_structure($data2, '/', { - 'INBOX' => '\\HasChildren', - 'AEARLY' => '\\HasNoChildren', - 'not' => '\\HasChildren \\Noselect', - 'sub2' => '\\HasChildren', - 'Alt Folders' => '\\HasChildren \\Noselect', - }); - - my $data3 = $imaptalk->list("", "INBOX/%"); - - $self->assert_mailbox_structure($data3, '/', { - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\HasChildren \\Noselect', - }); + $self->assert_mailbox_structure( + $data2, '/', + { + 'INBOX' => '\\HasChildren', + 'AEARLY' => '\\HasNoChildren', + 'not' => '\\HasChildren \\Noselect', + 'sub2' => '\\HasChildren', + 'Alt Folders' => '\\HasChildren \\Noselect', + } + ); + + my $data3 = $imaptalk->list("", "INBOX/%"); + + $self->assert_mailbox_structure( + $data3, '/', + { + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\HasChildren \\Noselect', + } + ); } diff --git a/cassandane/tiny-tests/List/inbox_altnamespace_no_intermediates b/cassandane/tiny-tests/List/inbox_altnamespace_no_intermediates index 107a2fbc6b..296be5ce71 100644 --- a/cassandane/tiny-tests/List/inbox_altnamespace_no_intermediates +++ b/cassandane/tiny-tests/List/inbox_altnamespace_no_intermediates @@ -2,58 +2,70 @@ use Cassandane::Tiny; sub test_inbox_altnamespace_no_intermediates - :UnixHierarchySep :VirtDomains :CrossDomains :AltNamespace :min_version_3_5 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - foreach my $Folder ("user/cassandane/INBOX/sub", "user/cassandane/AEARLY", - "user/cassandane/sub2", "user/cassandane/sub2/achild", - "user/cassandane/INBOX/very/deep/one", - "user/cassandane/not/so/deep", - # stuff you can't see - "user/cassandane/INBOX", - "user/cassandane/inbox", - "user/cassandane/inbox/subnobody") { - $admintalk->create($Folder); - $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); + : UnixHierarchySep : VirtDomains : CrossDomains : AltNamespace : + min_version_3_5 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + foreach my $Folder ( + "user/cassandane/INBOX/sub", "user/cassandane/AEARLY", + "user/cassandane/sub2", "user/cassandane/sub2/achild", + "user/cassandane/INBOX/very/deep/one", + "user/cassandane/not/so/deep", + # stuff you can't see + "user/cassandane/INBOX", + "user/cassandane/inbox", + "user/cassandane/inbox/subnobody" + ) + { + $admintalk->create($Folder); + $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); + } + + my $data = $imaptalk->list("", "*"); + + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasChildren', + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\HasChildren', + 'INBOX/very/deep' => '\\HasChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + 'AEARLY' => '\\HasNoChildren', + 'not' => '\\HasChildren', + 'not/so' => '\\HasChildren', + 'not/so/deep' => '\\HasNoChildren', + 'sub2' => '\\HasChildren', + 'sub2/achild' => '\\HasNoChildren', + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody' => '\\HasNoChildren', } + ); + + my $data2 = $imaptalk->list("", "%"); - my $data = $imaptalk->list("", "*"); - - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasChildren', - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\HasChildren', - 'INBOX/very/deep' => '\\HasChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - 'AEARLY' => '\\HasNoChildren', - 'not' => '\\HasChildren', - 'not/so' => '\\HasChildren', - 'not/so/deep' => '\\HasNoChildren', - 'sub2' => '\\HasChildren', - 'sub2/achild' => '\\HasNoChildren', - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody' => '\\HasNoChildren', - }); - - my $data2 = $imaptalk->list("", "%"); - - $self->assert_mailbox_structure($data2, '/', { - 'INBOX' => '\\HasChildren', - 'AEARLY' => '\\HasNoChildren', - 'not' => '\\HasChildren', - 'sub2' => '\\HasChildren', - 'Alt Folders' => '\\HasChildren \\Noselect', - }); - - my $data3 = $imaptalk->list("", "INBOX/%"); - - $self->assert_mailbox_structure($data3, '/', { - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\HasChildren', - }); + $self->assert_mailbox_structure( + $data2, '/', + { + 'INBOX' => '\\HasChildren', + 'AEARLY' => '\\HasNoChildren', + 'not' => '\\HasChildren', + 'sub2' => '\\HasChildren', + 'Alt Folders' => '\\HasChildren \\Noselect', + } + ); + + my $data3 = $imaptalk->list("", "INBOX/%"); + + $self->assert_mailbox_structure( + $data3, '/', + { + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\HasChildren', + } + ); } diff --git a/cassandane/tiny-tests/List/list_non_extended_trash_nochildren b/cassandane/tiny-tests/List/list_non_extended_trash_nochildren index 1e8bd3f8f8..8e5eeff46d 100644 --- a/cassandane/tiny-tests/List/list_non_extended_trash_nochildren +++ b/cassandane/tiny-tests/List/list_non_extended_trash_nochildren @@ -2,40 +2,48 @@ use Cassandane::Tiny; sub test_list_non_extended_trash_nochildren - :UnixHierarchySep :AltNamespace :NoStartInstances :min_version_3_7 -{ - my ($self) = @_; - - $self->{instance}->{config}->set('specialuse_nochildren' => '\\Trash'); - $self->_start_instances(); - - my $imaptalk = $self->{store}->get_client(); - - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash Snoozed) ] ], - ]); - - $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("Snoozed", "/private/specialuse", "\\Snoozed"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - my $alldata = $imaptalk->list("", "%"); - - $self->assert_mailbox_structure($alldata, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'ToDo' => [qw( \\HasNoChildren )], - 'Projects' => [qw( \\HasChildren )], - 'SentMail' => [qw( \\Sent \\HasNoChildren )], - 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], - 'Trash' => [qw( \\Trash \\HasNoChildren \\Noinferiors )], - 'Snoozed' => [qw( \\Snoozed \\HasNoChildren )], - }); + : UnixHierarchySep : AltNamespace : NoStartInstances : min_version_3_7 { + my ($self) = @_; + + $self->{instance}->{config}->set('specialuse_nochildren' => '\\Trash'); + $self->_start_instances(); + + my $imaptalk = $self->{store}->get_client(); + + $self->setup_mailbox_structure( + $imaptalk, + [ + [ + 'create' => + [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash Snoozed)] + ], + ] + ); + + $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("Snoozed", "/private/specialuse", "\\Snoozed"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + my $alldata = $imaptalk->list("", "%"); + + $self->assert_mailbox_structure( + $alldata, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'ToDo' => [qw( \\HasNoChildren )], + 'Projects' => [qw( \\HasChildren )], + 'SentMail' => [qw( \\Sent \\HasNoChildren )], + 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], + 'Trash' => [qw( \\Trash \\HasNoChildren \\Noinferiors )], + 'Snoozed' => [qw( \\Snoozed \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/list_return_subscribed b/cassandane/tiny-tests/List/list_return_subscribed index 7fca5703a6..3549b39f99 100644 --- a/cassandane/tiny-tests/List/list_return_subscribed +++ b/cassandane/tiny-tests/List/list_return_subscribed @@ -2,32 +2,37 @@ use Cassandane::Tiny; sub test_list_return_subscribed - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], - [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], - [ 'delete' => 'Fruit/Peach' ], - [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], - [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], + [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], + [ 'delete' => 'Fruit/Peach' ], + [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], + [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], + ] + ); - my $subdata = $imaptalk->list([qw()], "", "*", 'RETURN', [qw(SUBSCRIBED)]); + my $subdata = $imaptalk->list([qw()], "", "*", 'RETURN', [qw(SUBSCRIBED)]); - xlog(Dumper $subdata); - $self->assert_mailbox_structure($subdata, '/', { - 'INBOX' => [qw( \\Subscribed \\HasNoChildren )], - 'Fruit' => [qw( \\HasChildren )], - 'Fruit/Apple' => [qw( \\HasNoChildren )], - 'Fruit/Banana' => [qw( \\Subscribed \\HasNoChildren )], - 'Tofu' => [qw( \\HasNoChildren )], - 'Vegetable' => [qw( \\Subscribed \\HasChildren )], - 'Vegetable/Broccoli' => [qw( \\Subscribed \\HasNoChildren )], - 'Vegetable/Corn' => [qw( \\HasNoChildren )], - }); + xlog(Dumper $subdata); + $self->assert_mailbox_structure( + $subdata, '/', + { + 'INBOX' => [qw( \\Subscribed \\HasNoChildren )], + 'Fruit' => [qw( \\HasChildren )], + 'Fruit/Apple' => [qw( \\HasNoChildren )], + 'Fruit/Banana' => [qw( \\Subscribed \\HasNoChildren )], + 'Tofu' => [qw( \\HasNoChildren )], + 'Vegetable' => [qw( \\Subscribed \\HasChildren )], + 'Vegetable/Broccoli' => [qw( \\Subscribed \\HasNoChildren )], + 'Vegetable/Corn' => [qw( \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/list_return_subscribed_trash_nochildren b/cassandane/tiny-tests/List/list_return_subscribed_trash_nochildren index 857592afa7..9b3e9b0997 100644 --- a/cassandane/tiny-tests/List/list_return_subscribed_trash_nochildren +++ b/cassandane/tiny-tests/List/list_return_subscribed_trash_nochildren @@ -2,40 +2,48 @@ use Cassandane::Tiny; sub test_list_return_subscribed_trash_nochildren - :UnixHierarchySep :AltNamespace :NoStartInstances :min_version_3_7 -{ - my ($self) = @_; - - $self->{instance}->{config}->set('specialuse_nochildren' => '\\Trash'); - $self->_start_instances(); - - my $imaptalk = $self->{store}->get_client(); - - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash Snoozed) ] ], - [ 'subscribe' => [qw( SentMail Trash) ] ], - ]); - - $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("Snoozed", "/private/specialuse", "\\Snoozed"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - my $alldata = $imaptalk->list([qw( SPECIAL-USE )], "", "*", - 'RETURN', [qw(SUBSCRIBED)]); - - xlog $self, Dumper $alldata; - $self->assert_mailbox_structure($alldata, '/', { - 'SentMail' => [qw( \\Sent \\HasNoChildren \\Subscribed )], - 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], - 'Trash' => [qw( \\Trash \\Noinferiors \\Subscribed )], - 'Snoozed' => [qw( \\Snoozed \\HasNoChildren )], - }); + : UnixHierarchySep : AltNamespace : NoStartInstances : min_version_3_7 { + my ($self) = @_; + + $self->{instance}->{config}->set('specialuse_nochildren' => '\\Trash'); + $self->_start_instances(); + + my $imaptalk = $self->{store}->get_client(); + + $self->setup_mailbox_structure( + $imaptalk, + [ + [ + 'create' => + [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash Snoozed)] + ], + [ 'subscribe' => [qw( SentMail Trash)] ], + ] + ); + + $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("Snoozed", "/private/specialuse", "\\Snoozed"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + my $alldata + = $imaptalk->list([qw( SPECIAL-USE )], "", "*", 'RETURN', [qw(SUBSCRIBED)]); + + xlog $self, Dumper $alldata; + $self->assert_mailbox_structure( + $alldata, '/', + { + 'SentMail' => [qw( \\Sent \\HasNoChildren \\Subscribed )], + 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], + 'Trash' => [qw( \\Trash \\Noinferiors \\Subscribed )], + 'Snoozed' => [qw( \\Snoozed \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/list_special_use_return_subscribed b/cassandane/tiny-tests/List/list_special_use_return_subscribed index 09aa6548d5..06fe419812 100644 --- a/cassandane/tiny-tests/List/list_special_use_return_subscribed +++ b/cassandane/tiny-tests/List/list_special_use_return_subscribed @@ -2,34 +2,39 @@ use Cassandane::Tiny; sub test_list_special_use_return_subscribed - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash) ] ], - [ 'subscribe' => [qw( SentMail Trash) ] ], - ]); - - $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - my $alldata = $imaptalk->list([qw( SPECIAL-USE )], "", "*", - 'RETURN', [qw(SUBSCRIBED)]); - - xlog $self, Dumper $alldata; - $self->assert_mailbox_structure($alldata, '/', { - 'SentMail' => [qw( \\Sent \\HasNoChildren \\Subscribed )], - 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], - 'Trash' => [qw( \\Trash \\HasNoChildren \\Subscribed )], - }); + : UnixHierarchySep : AltNamespace { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash)] ], + [ 'subscribe' => [qw( SentMail Trash)] ], + ] + ); + + $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + my $alldata + = $imaptalk->list([qw( SPECIAL-USE )], "", "*", 'RETURN', [qw(SUBSCRIBED)]); + + xlog $self, Dumper $alldata; + $self->assert_mailbox_structure( + $alldata, '/', + { + 'SentMail' => [qw( \\Sent \\HasNoChildren \\Subscribed )], + 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], + 'Trash' => [qw( \\Trash \\HasNoChildren \\Subscribed )], + } + ); } diff --git a/cassandane/tiny-tests/List/list_subscribed_return_children b/cassandane/tiny-tests/List/list_subscribed_return_children index 00f73f3227..f34239faba 100644 --- a/cassandane/tiny-tests/List/list_subscribed_return_children +++ b/cassandane/tiny-tests/List/list_subscribed_return_children @@ -2,29 +2,36 @@ use Cassandane::Tiny; sub test_list_subscribed_return_children - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], - [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], - [ 'delete' => 'Fruit/Peach' ], - [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], - [ 'subscribe' => [qw( Vegetable )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], + [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], + [ 'delete' => 'Fruit/Peach' ], + [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], + [ 'subscribe' => [qw( Vegetable )] ], + ] + ); - xlog $self, "listing..."; - my $subdata = $imaptalk->list([qw(SUBSCRIBED)], "", "*", "RETURN", [qw(CHILDREN)]); + xlog $self, "listing..."; + my $subdata + = $imaptalk->list([qw(SUBSCRIBED)], "", "*", "RETURN", [qw(CHILDREN)]); - xlog $self, "subscribed to: " . Dumper $subdata; - $self->assert_mailbox_structure($subdata, '/', { - 'INBOX' => [qw( \\Subscribed \\HasNoChildren )], - 'Fruit/Banana' => [qw( \\Subscribed \\HasNoChildren )], - 'Fruit/Peach' => [qw( \\NonExistent \\Subscribed \\HasNoChildren )], - 'Vegetable' => [qw( \\Subscribed \\HasChildren )], - }, 'strict'); + xlog $self, "subscribed to: " . Dumper $subdata; + $self->assert_mailbox_structure( + $subdata, '/', + { + 'INBOX' => [qw( \\Subscribed \\HasNoChildren )], + 'Fruit/Banana' => [qw( \\Subscribed \\HasNoChildren )], + 'Fruit/Peach' => [qw( \\NonExistent \\Subscribed \\HasNoChildren )], + 'Vegetable' => [qw( \\Subscribed \\HasChildren )], + }, + 'strict' + ); } diff --git a/cassandane/tiny-tests/List/list_subscribed_return_children_noaltns b/cassandane/tiny-tests/List/list_subscribed_return_children_noaltns index f1a69f08a6..f562ad88ca 100644 --- a/cassandane/tiny-tests/List/list_subscribed_return_children_noaltns +++ b/cassandane/tiny-tests/List/list_subscribed_return_children_noaltns @@ -2,31 +2,42 @@ use Cassandane::Tiny; sub test_list_subscribed_return_children_noaltns - :UnixHierarchySep -{ - my ($self) = @_; + : UnixHierarchySep { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( INBOX/Fruit INBOX/Fruit/Apple INBOX/Fruit/Banana - INBOX/Fruit/Peach )] ], - [ 'subscribe' => [qw( INBOX/Fruit/Banana INBOX/Fruit/Peach )] ], - [ 'delete' => 'INBOX/Fruit/Peach' ], - [ 'create' => [qw( INBOX/Tofu INBOX/Vegetable INBOX/Vegetable/Broccoli - INBOX/Vegetable/Corn )] ], - [ 'subscribe' => [qw( INBOX/Vegetable )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ + 'create' => [ qw( INBOX/Fruit INBOX/Fruit/Apple INBOX/Fruit/Banana + INBOX/Fruit/Peach ) ] + ], + [ 'subscribe' => [qw( INBOX/Fruit/Banana INBOX/Fruit/Peach )] ], + [ 'delete' => 'INBOX/Fruit/Peach' ], + [ + 'create' => [ qw( INBOX/Tofu INBOX/Vegetable INBOX/Vegetable/Broccoli + INBOX/Vegetable/Corn ) ] + ], + [ 'subscribe' => [qw( INBOX/Vegetable )] ], + ] + ); - xlog $self, "listing..."; - my $subdata = $imaptalk->list([qw(SUBSCRIBED)], "", "*", "RETURN", [qw(CHILDREN)]); + xlog $self, "listing..."; + my $subdata + = $imaptalk->list([qw(SUBSCRIBED)], "", "*", "RETURN", [qw(CHILDREN)]); - xlog $self, "subscribed to: " . Dumper $subdata; - $self->assert_mailbox_structure($subdata, '/', { - 'INBOX' => [qw( \\Subscribed \\HasChildren )], - 'INBOX/Fruit/Banana' => [qw( \\Subscribed \\HasNoChildren )], - 'INBOX/Fruit/Peach' => [qw( \\NonExistent \\Subscribed \\HasNoChildren )], - 'INBOX/Vegetable' => [qw( \\Subscribed \\HasChildren )], - }, 'strict'); + xlog $self, "subscribed to: " . Dumper $subdata; + $self->assert_mailbox_structure( + $subdata, '/', + { + 'INBOX' => [qw( \\Subscribed \\HasChildren )], + 'INBOX/Fruit/Banana' => [qw( \\Subscribed \\HasNoChildren )], + 'INBOX/Fruit/Peach' => [qw( \\NonExistent \\Subscribed \\HasNoChildren )], + 'INBOX/Vegetable' => [qw( \\Subscribed \\HasChildren )], + }, + 'strict' + ); } diff --git a/cassandane/tiny-tests/List/lookup_only_otheruser b/cassandane/tiny-tests/List/lookup_only_otheruser index 35ccc2d79a..f3ec48e979 100644 --- a/cassandane/tiny-tests/List/lookup_only_otheruser +++ b/cassandane/tiny-tests/List/lookup_only_otheruser @@ -2,31 +2,29 @@ use Cassandane::Tiny; sub test_lookup_only_otheruser - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - $self->{instance}->create_user("other"); + $self->{instance}->create_user("other"); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user/other/foo'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl('user/other/foo', - 'cassandane' => 'l'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user/other/foo'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('user/other/foo', 'cassandane' => 'l'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Other Users/other/foo' => [qw( \\HasNoChildren )], - }); + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Other Users/other/foo' => [qw( \\HasNoChildren )], + } + ); - # only "l" permission, should be able to list, but not select! - $imaptalk->select('Other Users/other/foo'); - $self->assert_str_equals('no', - $imaptalk->get_last_completion_response()); + # only "l" permission, should be able to list, but not select! + $imaptalk->select('Other Users/other/foo'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/List/lookup_only_otheruser_noaltns b/cassandane/tiny-tests/List/lookup_only_otheruser_noaltns index 6614e6cc00..e79ae97b6e 100644 --- a/cassandane/tiny-tests/List/lookup_only_otheruser_noaltns +++ b/cassandane/tiny-tests/List/lookup_only_otheruser_noaltns @@ -2,31 +2,29 @@ use Cassandane::Tiny; sub test_lookup_only_otheruser_noaltns - :UnixHierarchySep :NoAltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : NoAltNamespace { + my ($self) = @_; - $self->{instance}->create_user("other"); + $self->{instance}->create_user("other"); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user/other/foo'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl('user/other/foo', - 'cassandane' => 'l'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user/other/foo'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('user/other/foo', 'cassandane' => 'l'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'user/other/foo' => [qw( \\HasNoChildren )], - }); + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'user/other/foo' => [qw( \\HasNoChildren )], + } + ); - # only "l" permission, should be able to list, but not select! - $imaptalk->select('user/other/foo'); - $self->assert_str_equals('no', - $imaptalk->get_last_completion_response()); + # only "l" permission, should be able to list, but not select! + $imaptalk->select('user/other/foo'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/List/lookup_only_otheruser_noaltns_racl b/cassandane/tiny-tests/List/lookup_only_otheruser_noaltns_racl index d7802b6263..3b61d5b24a 100644 --- a/cassandane/tiny-tests/List/lookup_only_otheruser_noaltns_racl +++ b/cassandane/tiny-tests/List/lookup_only_otheruser_noaltns_racl @@ -2,31 +2,29 @@ use Cassandane::Tiny; sub test_lookup_only_otheruser_noaltns_racl - :UnixHierarchySep :NoAltNamespace :ReverseACLs -{ - my ($self) = @_; + : UnixHierarchySep : NoAltNamespace : ReverseACLs { + my ($self) = @_; - $self->{instance}->create_user("other"); + $self->{instance}->create_user("other"); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user/other/foo'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl('user/other/foo', - 'cassandane' => 'l'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user/other/foo'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('user/other/foo', 'cassandane' => 'l'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'user/other/foo' => [qw( \\HasNoChildren )], - }); + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'user/other/foo' => [qw( \\HasNoChildren )], + } + ); - # only "l" permission, should be able to list, but not select! - $imaptalk->select('user/other/foo'); - $self->assert_str_equals('no', - $imaptalk->get_last_completion_response()); + # only "l" permission, should be able to list, but not select! + $imaptalk->select('user/other/foo'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/List/lookup_only_otheruser_racl b/cassandane/tiny-tests/List/lookup_only_otheruser_racl index 51895ff2a2..869bef830b 100644 --- a/cassandane/tiny-tests/List/lookup_only_otheruser_racl +++ b/cassandane/tiny-tests/List/lookup_only_otheruser_racl @@ -2,31 +2,29 @@ use Cassandane::Tiny; sub test_lookup_only_otheruser_racl - :UnixHierarchySep :AltNamespace :ReverseACLs -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace : ReverseACLs { + my ($self) = @_; - $self->{instance}->create_user("other"); + $self->{instance}->create_user("other"); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user/other/foo'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl('user/other/foo', - 'cassandane' => 'l'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user/other/foo'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('user/other/foo', 'cassandane' => 'l'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Other Users/other/foo' => [qw( \\HasNoChildren )], - }); + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Other Users/other/foo' => [qw( \\HasNoChildren )], + } + ); - # only "l" permission, should be able to list, but not select! - $imaptalk->select('Other Users/other/foo'); - $self->assert_str_equals('no', - $imaptalk->get_last_completion_response()); + # only "l" permission, should be able to list, but not select! + $imaptalk->select('Other Users/other/foo'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/List/lookup_only_own b/cassandane/tiny-tests/List/lookup_only_own index f4f89e5e9e..b5a670df3b 100644 --- a/cassandane/tiny-tests/List/lookup_only_own +++ b/cassandane/tiny-tests/List/lookup_only_own @@ -2,31 +2,29 @@ use Cassandane::Tiny; sub test_lookup_only_own - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - $self->{instance}->create_user("other"); + $self->{instance}->create_user("other"); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user/cassandane/foo'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl('user/cassandane/foo', - 'cassandane' => 'l'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user/cassandane/foo'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('user/cassandane/foo', 'cassandane' => 'l'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'foo' => [qw( \\HasNoChildren )], - }); + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'foo' => [qw( \\HasNoChildren )], + } + ); - # only "l" permission, should be able to list, but not select! - $imaptalk->select('foo'); - $self->assert_str_equals('no', - $imaptalk->get_last_completion_response()); + # only "l" permission, should be able to list, but not select! + $imaptalk->select('foo'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/List/lookup_only_own_racl b/cassandane/tiny-tests/List/lookup_only_own_racl index e09e7d23cb..ae5b96a554 100644 --- a/cassandane/tiny-tests/List/lookup_only_own_racl +++ b/cassandane/tiny-tests/List/lookup_only_own_racl @@ -2,31 +2,29 @@ use Cassandane::Tiny; sub test_lookup_only_own_racl - :UnixHierarchySep :AltNamespace :ReverseACLs -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace : ReverseACLs { + my ($self) = @_; - $self->{instance}->create_user("other"); + $self->{instance}->create_user("other"); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('user/cassandane/foo'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl('user/cassandane/foo', - 'cassandane' => 'l'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('user/cassandane/foo'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('user/cassandane/foo', 'cassandane' => 'l'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'foo' => [qw( \\HasNoChildren )], - }); + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'foo' => [qw( \\HasNoChildren )], + } + ); - # only "l" permission, should be able to list, but not select! - $imaptalk->select('foo'); - $self->assert_str_equals('no', - $imaptalk->get_last_completion_response()); + # only "l" permission, should be able to list, but not select! + $imaptalk->select('foo'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/List/lookup_only_shared b/cassandane/tiny-tests/List/lookup_only_shared index 48e15d9622..9cf95bdd6f 100644 --- a/cassandane/tiny-tests/List/lookup_only_shared +++ b/cassandane/tiny-tests/List/lookup_only_shared @@ -2,31 +2,29 @@ use Cassandane::Tiny; sub test_lookup_only_shared - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('shared'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl('shared', - 'cassandane' => 'l'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('shared'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('shared', 'cassandane' => 'l'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Shared Folders/shared' => [qw( \\HasNoChildren )], - }); + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Shared Folders/shared' => [qw( \\HasNoChildren )], + } + ); - # implicit "anyone:r" on shared mailboxes means that the - # cassandane user can also select this, despite only having - # "l" of their own - $imaptalk->select('Shared Folders/shared'); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); + # implicit "anyone:r" on shared mailboxes means that the + # cassandane user can also select this, despite only having + # "l" of their own + $imaptalk->select('Shared Folders/shared'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/List/lookup_only_shared_racl b/cassandane/tiny-tests/List/lookup_only_shared_racl index 3cd45898c4..4d64065898 100644 --- a/cassandane/tiny-tests/List/lookup_only_shared_racl +++ b/cassandane/tiny-tests/List/lookup_only_shared_racl @@ -2,31 +2,29 @@ use Cassandane::Tiny; sub test_lookup_only_shared_racl - :UnixHierarchySep :AltNamespace :ReverseACLs -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace : ReverseACLs { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create('shared'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl('shared', - 'cassandane' => 'l'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create('shared'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('shared', 'cassandane' => 'l'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Shared Folders/shared' => [qw( \\HasNoChildren )], - }); + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Shared Folders/shared' => [qw( \\HasNoChildren )], + } + ); - # implicit "anyone:r" on shared mailboxes means that the - # cassandane user can also select this, despite only having - # "l" of their own - $imaptalk->select('Shared Folders/shared'); - $self->assert_str_equals('ok', - $imaptalk->get_last_completion_response()); + # implicit "anyone:r" on shared mailboxes means that the + # cassandane user can also select this, despite only having + # "l" of their own + $imaptalk->select('Shared Folders/shared'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/List/no_inbox_tombstone b/cassandane/tiny-tests/List/no_inbox_tombstone index 3fcf1c9a0b..1a4f2d072d 100644 --- a/cassandane/tiny-tests/List/no_inbox_tombstone +++ b/cassandane/tiny-tests/List/no_inbox_tombstone @@ -2,43 +2,42 @@ use Cassandane::Tiny; sub test_no_inbox_tombstone - :UnixHierarchySep :ReverseACLs :AllowMoves -{ - my ($self) = @_; + : UnixHierarchySep : ReverseACLs : AllowMoves { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - $admintalk->rename("user/cassandane", "user/cassandane-old"); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->rename("user/cassandane", "user/cassandane-old"); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); - my $tombstone_name = 'user.cassandane'; + my $tombstone_name = 'user.cassandane'; - my $mailboxesdb = $self->{instance}->read_mailboxes_db(); - $self->assert_matches(qr{d}, $mailboxesdb->{$tombstone_name}->{mbtype}); + my $mailboxesdb = $self->{instance}->read_mailboxes_db(); + $self->assert_matches(qr{d}, $mailboxesdb->{$tombstone_name}->{mbtype}); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - # basic list - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', {}); + # basic list + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure($data, '/', {}); - # basic xlist - $data = $imaptalk->xlist("", "*"); - $self->assert_str_equals('ok', $data); # no mailboxes listed + # basic xlist + $data = $imaptalk->xlist("", "*"); + $self->assert_str_equals('ok', $data); # no mailboxes listed - # partial match list - $data = $imaptalk->list("", "INB*"); - $self->assert_mailbox_structure($data, '/', {}); + # partial match list + $data = $imaptalk->list("", "INB*"); + $self->assert_mailbox_structure($data, '/', {}); - # partial match xlist - $data = $imaptalk->xlist("", "INB*"); - $self->assert_str_equals('ok', $data); # no mailboxes listed + # partial match xlist + $data = $imaptalk->xlist("", "INB*"); + $self->assert_str_equals('ok', $data); # no mailboxes listed - # direct list - $data = $imaptalk->list("", "INBOX"); - $self->assert_mailbox_structure($data, '/', {}); + # direct list + $data = $imaptalk->list("", "INBOX"); + $self->assert_mailbox_structure($data, '/', {}); - # direct xlist - $data = $imaptalk->xlist("", "INBOX"); - $self->assert_str_equals('ok', $data); # no mailboxes listed + # direct xlist + $data = $imaptalk->xlist("", "INBOX"); + $self->assert_str_equals('ok', $data); # no mailboxes listed } diff --git a/cassandane/tiny-tests/List/no_tombstones b/cassandane/tiny-tests/List/no_tombstones index f6f0508b6d..15b82ba1b3 100644 --- a/cassandane/tiny-tests/List/no_tombstones +++ b/cassandane/tiny-tests/List/no_tombstones @@ -2,53 +2,67 @@ use Cassandane::Tiny; sub test_no_tombstones - :UnixHierarchySep :AltNamespace :ReverseACLs -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( INBOX/Tombstone )] ], - [ 'subscribe' => [qw( INBOX/Tombstone )] ], - [ 'delete' => 'INBOX/Tombstone' ], - ]); - - my $tombstone_name = 'user.cassandane.INBOX.Tombstone'; - - my $mailboxesdb = $self->{instance}->read_mailboxes_db(); - $self->assert_matches(qr{d}, $mailboxesdb->{$tombstone_name}->{mbtype}); - - # basic list - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - }); - - # basic xlist - $data = $imaptalk->xlist("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - }); - - # partial match list - $data = $imaptalk->list("", "INB*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - }); - - # partial match xlist - $data = $imaptalk->xlist("", "INB*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - }); - - # direct list - $data = $imaptalk->list("", "INBOX/Tombstone"); - $self->assert_mailbox_structure($data, '/', {}); - - # direct xlist - $data = $imaptalk->xlist("", "INBOX/Tombstone"); - $self->assert_str_equals('ok', $data); # no mailboxes listed + : UnixHierarchySep : AltNamespace : ReverseACLs { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( INBOX/Tombstone )] ], + [ 'subscribe' => [qw( INBOX/Tombstone )] ], + [ 'delete' => 'INBOX/Tombstone' ], + ] + ); + + my $tombstone_name = 'user.cassandane.INBOX.Tombstone'; + + my $mailboxesdb = $self->{instance}->read_mailboxes_db(); + $self->assert_matches(qr{d}, $mailboxesdb->{$tombstone_name}->{mbtype}); + + # basic list + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + } + ); + + # basic xlist + $data = $imaptalk->xlist("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + } + ); + + # partial match list + $data = $imaptalk->list("", "INB*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + } + ); + + # partial match xlist + $data = $imaptalk->xlist("", "INB*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + } + ); + + # direct list + $data = $imaptalk->list("", "INBOX/Tombstone"); + $self->assert_mailbox_structure($data, '/', {}); + + # direct xlist + $data = $imaptalk->xlist("", "INBOX/Tombstone"); + $self->assert_str_equals('ok', $data); # no mailboxes listed } diff --git a/cassandane/tiny-tests/List/otherusers_pattern b/cassandane/tiny-tests/List/otherusers_pattern index 7774e31fbf..2f70d2a2fd 100644 --- a/cassandane/tiny-tests/List/otherusers_pattern +++ b/cassandane/tiny-tests/List/otherusers_pattern @@ -2,49 +2,52 @@ use Cassandane::Tiny; sub test_otherusers_pattern - :NoAltNameSpace -{ - my ($self) = @_; - $self->{instance}->create_user("foo"); - - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo"); - my $footalk = $foostore->get_client(); - - $footalk->create('INBOX.mytest'); - $self->assert_str_equals('ok', $footalk->get_last_completion_response()); - $footalk->create('INBOX.mytest.mysubtest'); - $self->assert_str_equals('ok', $footalk->get_last_completion_response()); - - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->setacl("user.foo", - 'cassandane' => 'lrswipkxtecd'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl("user.foo.mytest", - 'cassandane' => 'lrswipkxtecd'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl("user.foo.mytest.mysubtest", - 'cassandane' => 'lrswipkxtecd'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - my $casstalk = $self->{store}->get_client(); - my $data; - - $data = $casstalk->list("", "user.%"); - $self->assert_mailbox_structure($data, '.', { - 'user.foo' => [qw( \\HasChildren )], - }); - - $data = $casstalk->list("", "user.foo.%"); - $self->assert_mailbox_structure($data, '.', { - 'user.foo.mytest' => [qw( \\HasChildren )], - }); - - $data = $casstalk->list("", "user.foo.mytest.%"); - $self->assert_mailbox_structure($data, '.', { - 'user.foo.mytest.mysubtest' => [qw( \\HasNoChildren )], - }); + : NoAltNameSpace { + my ($self) = @_; + $self->{instance}->create_user("foo"); + + my $foostore + = $self->{instance}->get_service('imap')->create_store(username => "foo"); + my $footalk = $foostore->get_client(); + + $footalk->create('INBOX.mytest'); + $self->assert_str_equals('ok', $footalk->get_last_completion_response()); + $footalk->create('INBOX.mytest.mysubtest'); + $self->assert_str_equals('ok', $footalk->get_last_completion_response()); + + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->setacl("user.foo", 'cassandane' => 'lrswipkxtecd'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user.foo.mytest", 'cassandane' => 'lrswipkxtecd'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user.foo.mytest.mysubtest", + 'cassandane' => 'lrswipkxtecd'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + my $casstalk = $self->{store}->get_client(); + my $data; + + $data = $casstalk->list("", "user.%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'user.foo' => [qw( \\HasChildren )], + } + ); + + $data = $casstalk->list("", "user.foo.%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'user.foo.mytest' => [qw( \\HasChildren )], + } + ); + + $data = $casstalk->list("", "user.foo.mytest.%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'user.foo.mytest.mysubtest' => [qw( \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/otherusers_pattern_unixhs b/cassandane/tiny-tests/List/otherusers_pattern_unixhs index 646221fa8e..caf99e8133 100644 --- a/cassandane/tiny-tests/List/otherusers_pattern_unixhs +++ b/cassandane/tiny-tests/List/otherusers_pattern_unixhs @@ -2,49 +2,52 @@ use Cassandane::Tiny; sub test_otherusers_pattern_unixhs - :UnixHierarchySep :NoAltNameSpace -{ - my ($self) = @_; - $self->{instance}->create_user("foo"); - - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo"); - my $footalk = $foostore->get_client(); - - $footalk->create('INBOX/mytest'); - $self->assert_str_equals('ok', $footalk->get_last_completion_response()); - $footalk->create('INBOX/mytest/mysubtest'); - $self->assert_str_equals('ok', $footalk->get_last_completion_response()); - - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->setacl("user/foo", - 'cassandane' => 'lrswipkxtecd'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl("user/foo/mytest", - 'cassandane' => 'lrswipkxtecd'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl("user/foo/mytest/mysubtest", - 'cassandane' => 'lrswipkxtecd'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - - my $casstalk = $self->{store}->get_client(); - my $data; - - $data = $casstalk->list("", "user/%"); - $self->assert_mailbox_structure($data, '/', { - 'user/foo' => [qw( \\HasChildren )], - }); - - $data = $casstalk->list("", "user/foo/%"); - $self->assert_mailbox_structure($data, '/', { - 'user/foo/mytest' => [qw( \\HasChildren )], - }); - - $data = $casstalk->list("", "user/foo/mytest/%"); - $self->assert_mailbox_structure($data, '/', { - 'user/foo/mytest/mysubtest' => [qw( \\HasNoChildren )], - }); + : UnixHierarchySep : NoAltNameSpace { + my ($self) = @_; + $self->{instance}->create_user("foo"); + + my $foostore + = $self->{instance}->get_service('imap')->create_store(username => "foo"); + my $footalk = $foostore->get_client(); + + $footalk->create('INBOX/mytest'); + $self->assert_str_equals('ok', $footalk->get_last_completion_response()); + $footalk->create('INBOX/mytest/mysubtest'); + $self->assert_str_equals('ok', $footalk->get_last_completion_response()); + + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->setacl("user/foo", 'cassandane' => 'lrswipkxtecd'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user/foo/mytest", 'cassandane' => 'lrswipkxtecd'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user/foo/mytest/mysubtest", + 'cassandane' => 'lrswipkxtecd'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + + my $casstalk = $self->{store}->get_client(); + my $data; + + $data = $casstalk->list("", "user/%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'user/foo' => [qw( \\HasChildren )], + } + ); + + $data = $casstalk->list("", "user/foo/%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'user/foo/mytest' => [qw( \\HasChildren )], + } + ); + + $data = $casstalk->list("", "user/foo/mytest/%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'user/foo/mytest/mysubtest' => [qw( \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/outlook_compatible_xlist_empty_mailbox b/cassandane/tiny-tests/List/outlook_compatible_xlist_empty_mailbox index ffe3a0d15a..3a90645a67 100644 --- a/cassandane/tiny-tests/List/outlook_compatible_xlist_empty_mailbox +++ b/cassandane/tiny-tests/List/outlook_compatible_xlist_empty_mailbox @@ -2,17 +2,19 @@ use Cassandane::Tiny; sub test_outlook_compatible_xlist_empty_mailbox - :UnixHierarchySep -{ - my ($self) = @_; + : UnixHierarchySep { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $data = $imaptalk->xlist("", ""); + my $data = $imaptalk->xlist("", ""); - $self->assert(ref $data, "expected list response, got scalar: $data"); + $self->assert(ref $data, "expected list response, got scalar: $data"); - $self->assert_mailbox_structure($data, '/', { - '' => [ '\\Noselect' ], - }); + $self->assert_mailbox_structure( + $data, '/', + { + '' => ['\\Noselect'], + } + ); } diff --git a/cassandane/tiny-tests/List/percent b/cassandane/tiny-tests/List/percent index 30dfb9a5cb..50c3105f12 100644 --- a/cassandane/tiny-tests/List/percent +++ b/cassandane/tiny-tests/List/percent @@ -6,165 +6,188 @@ use Cassandane::Tiny; # mailbox name argument, matching levels of hierarchy # are also returned. sub test_percent - :NoAltNameSpace :max_version_3_4 -{ - my ($self) = @_; + : NoAltNameSpace : max_version_3_4 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # INBOX needs to exist even if we can't see it - $admintalk->create('user.bar'); + # INBOX needs to exist even if we can't see it + $admintalk->create('user.bar'); - foreach my $Folder ("user.cassandane.INBOX.sub", "user.cassandane.AEARLY", - "user.cassandane.sub2", "user.cassandane.sub2.achild", - "user.cassandane.INBOX.very.deep.one", - "user.cassandane.not.so.deep", - # stuff you can't see - "user.cassandane.INBOX", - "user.cassandane.inbox", - "user.cassandane.inbox.subnobody.deep", - "user.cassandane.Inbox.subnobody.deep", - # other users - "user.bar.Trash", - "user.foo", - "user.foo.really.deep", - # shared - "shared stuff.something") { - $admintalk->create($Folder); - $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); - } + foreach my $Folder ( + "user.cassandane.INBOX.sub", "user.cassandane.AEARLY", + "user.cassandane.sub2", "user.cassandane.sub2.achild", + "user.cassandane.INBOX.very.deep.one", + "user.cassandane.not.so.deep", + # stuff you can't see + "user.cassandane.INBOX", + "user.cassandane.inbox", + "user.cassandane.inbox.subnobody.deep", + "user.cassandane.Inbox.subnobody.deep", + # other users + "user.bar.Trash", + "user.foo", + "user.foo.really.deep", + # shared + "shared stuff.something" + ) + { + $admintalk->create($Folder); + $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); + } - xlog $self, "List *"; - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => '\\HasChildren', - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.INBOX.sub' => '\\HasNoChildren', - 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', - 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.not.so.deep' => '\\HasNoChildren', - 'INBOX.sub2' => '\\HasChildren', - 'INBOX.sub2.achild' => '\\HasNoChildren', - 'user.bar.Trash' => '\\HasNoChildren', - 'user.foo' => '\\HasChildren', - 'user.foo.really.deep' => '\\HasNoChildren', - 'shared stuff.something' => '\\HasNoChildren', - }); + xlog $self, "List *"; + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => '\\HasChildren', + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.INBOX.sub' => '\\HasNoChildren', + 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', + 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.not.so.deep' => '\\HasNoChildren', + 'INBOX.sub2' => '\\HasChildren', + 'INBOX.sub2.achild' => '\\HasNoChildren', + 'user.bar.Trash' => '\\HasNoChildren', + 'user.foo' => '\\HasChildren', + 'user.foo.really.deep' => '\\HasNoChildren', + 'shared stuff.something' => '\\HasNoChildren', + } + ); - #xlog $self, "LIST %"; - #$data = $imaptalk->list("", "%"); - #$self->assert_mailbox_structure($data, '.', { - #'INBOX' => '\\HasChildren', - #'user' => '\\Noselect \\HasChildren', - #'shared stuff' => '\\Noselect \\HasChildren', - #}); + #xlog $self, "LIST %"; + #$data = $imaptalk->list("", "%"); + #$self->assert_mailbox_structure($data, '.', { + #'INBOX' => '\\HasChildren', + #'user' => '\\Noselect \\HasChildren', + #'shared stuff' => '\\Noselect \\HasChildren', + #}); - xlog $self, "List *%"; - $data = $imaptalk->list("", "*%"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => '\\HasChildren', - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.INBOX.sub' => '\\HasNoChildren', - 'INBOX.INBOX.very' => '\\Noselect \\HasChildren', - 'INBOX.INBOX.very.deep' => '\\Noselect \\HasChildren', - 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', - 'INBOX.Inbox' => '\\Noselect \\HasChildren', - 'INBOX.Inbox.subnobody' => '\\Noselect \\HasChildren', - 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.inbox.subnobody' => '\\Noselect \\HasChildren', - 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.not' => '\\Noselect \\HasChildren', - 'INBOX.not.so' => '\\Noselect \\HasChildren', - 'INBOX.not.so.deep' => '\\HasNoChildren', - 'INBOX.sub2' => '\\HasChildren', - 'INBOX.sub2.achild' => '\\HasNoChildren', - 'user' => '\\Noselect \\HasChildren', - 'user.bar' => '\\Noselect \\HasChildren', - 'user.bar.Trash' => '\\HasNoChildren', - 'user.foo' => '\\HasChildren', - 'user.foo.really' => '\\Noselect \\HasChildren', - 'user.foo.really.deep' => '\\HasNoChildren', - 'shared stuff' => '\\Noselect \\HasChildren', - 'shared stuff.something' => '\\HasNoChildren', - }); + xlog $self, "List *%"; + $data = $imaptalk->list("", "*%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => '\\HasChildren', + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.INBOX.sub' => '\\HasNoChildren', + 'INBOX.INBOX.very' => '\\Noselect \\HasChildren', + 'INBOX.INBOX.very.deep' => '\\Noselect \\HasChildren', + 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', + 'INBOX.Inbox' => '\\Noselect \\HasChildren', + 'INBOX.Inbox.subnobody' => '\\Noselect \\HasChildren', + 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.inbox.subnobody' => '\\Noselect \\HasChildren', + 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.not' => '\\Noselect \\HasChildren', + 'INBOX.not.so' => '\\Noselect \\HasChildren', + 'INBOX.not.so.deep' => '\\HasNoChildren', + 'INBOX.sub2' => '\\HasChildren', + 'INBOX.sub2.achild' => '\\HasNoChildren', + 'user' => '\\Noselect \\HasChildren', + 'user.bar' => '\\Noselect \\HasChildren', + 'user.bar.Trash' => '\\HasNoChildren', + 'user.foo' => '\\HasChildren', + 'user.foo.really' => '\\Noselect \\HasChildren', + 'user.foo.really.deep' => '\\HasNoChildren', + 'shared stuff' => '\\Noselect \\HasChildren', + 'shared stuff.something' => '\\HasNoChildren', + } + ); - xlog $self, "LIST INBOX.*"; - $data = $imaptalk->list("INBOX.", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.INBOX.sub' => '\\HasNoChildren', - 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', - 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.not.so.deep' => '\\HasNoChildren', - 'INBOX.sub2' => '\\HasChildren', - 'INBOX.sub2.achild' => '\\HasNoChildren', - }); + xlog $self, "LIST INBOX.*"; + $data = $imaptalk->list("INBOX.", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.INBOX.sub' => '\\HasNoChildren', + 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', + 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.not.so.deep' => '\\HasNoChildren', + 'INBOX.sub2' => '\\HasChildren', + 'INBOX.sub2.achild' => '\\HasNoChildren', + } + ); - xlog $self, "LIST INBOX.*%"; - $data = $imaptalk->list("INBOX.", "*%"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.INBOX.sub' => '\\HasNoChildren', - 'INBOX.INBOX.very' => '\\Noselect \\HasChildren', - 'INBOX.INBOX.very.deep' => '\\Noselect \\HasChildren', - 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', - 'INBOX.Inbox' => '\\Noselect \\HasChildren', - 'INBOX.Inbox.subnobody' => '\\Noselect \\HasChildren', - 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.inbox.subnobody' => '\\Noselect \\HasChildren', - 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.not' => '\\Noselect \\HasChildren', - 'INBOX.not.so' => '\\Noselect \\HasChildren', - 'INBOX.not.so.deep' => '\\HasNoChildren', - 'INBOX.sub2' => '\\HasChildren', - 'INBOX.sub2.achild' => '\\HasNoChildren', - }); + xlog $self, "LIST INBOX.*%"; + $data = $imaptalk->list("INBOX.", "*%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.INBOX.sub' => '\\HasNoChildren', + 'INBOX.INBOX.very' => '\\Noselect \\HasChildren', + 'INBOX.INBOX.very.deep' => '\\Noselect \\HasChildren', + 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', + 'INBOX.Inbox' => '\\Noselect \\HasChildren', + 'INBOX.Inbox.subnobody' => '\\Noselect \\HasChildren', + 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.inbox.subnobody' => '\\Noselect \\HasChildren', + 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.not' => '\\Noselect \\HasChildren', + 'INBOX.not.so' => '\\Noselect \\HasChildren', + 'INBOX.not.so.deep' => '\\HasNoChildren', + 'INBOX.sub2' => '\\HasChildren', + 'INBOX.sub2.achild' => '\\HasNoChildren', + } + ); - xlog $self, "LIST INBOX.%"; - $data = $imaptalk->list("INBOX.", "%"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.Inbox' => '\\Noselect \\HasChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.not' => '\\Noselect \\HasChildren', - 'INBOX.sub2' => '\\HasChildren', - }); + xlog $self, "LIST INBOX.%"; + $data = $imaptalk->list("INBOX.", "%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.Inbox' => '\\Noselect \\HasChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.not' => '\\Noselect \\HasChildren', + 'INBOX.sub2' => '\\HasChildren', + } + ); - xlog $self, "List user.*"; - $data = $imaptalk->list("user.", "*"); - $self->assert_mailbox_structure($data, '.', { - 'user.bar.Trash' => '\\HasNoChildren', - 'user.foo' => '\\HasChildren', - 'user.foo.really.deep' => '\\HasNoChildren', - }); + xlog $self, "List user.*"; + $data = $imaptalk->list("user.", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'user.bar.Trash' => '\\HasNoChildren', + 'user.foo' => '\\HasChildren', + 'user.foo.really.deep' => '\\HasNoChildren', + } + ); - xlog $self, "List user.*%"; - $data = $imaptalk->list("user.", "*%"); - $self->assert_mailbox_structure($data, '.', { - 'user.bar' => '\\Noselect \\HasChildren', - 'user.bar.Trash' => '\\HasNoChildren', - 'user.foo' => '\\HasChildren', - 'user.foo.really' => '\\Noselect \\HasChildren', - 'user.foo.really.deep' => '\\HasNoChildren', - }); + xlog $self, "List user.*%"; + $data = $imaptalk->list("user.", "*%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'user.bar' => '\\Noselect \\HasChildren', + 'user.bar.Trash' => '\\HasNoChildren', + 'user.foo' => '\\HasChildren', + 'user.foo.really' => '\\Noselect \\HasChildren', + 'user.foo.really.deep' => '\\HasNoChildren', + } + ); - #xlog $self, "List user.%"; - #$data = $imaptalk->list("user.", "%"); - #$self->assert_mailbox_structure($data, '.', { - # 'user.bar' => '\\Noselect \\HasChildren', - # 'user.foo' => '\\HasChildren', - #}); + #xlog $self, "List user.%"; + #$data = $imaptalk->list("user.", "%"); + #$self->assert_mailbox_structure($data, '.', { + # 'user.bar' => '\\Noselect \\HasChildren', + # 'user.foo' => '\\HasChildren', + #}); } diff --git a/cassandane/tiny-tests/List/percent_altns b/cassandane/tiny-tests/List/percent_altns index 69ac7e2a56..7691570b44 100644 --- a/cassandane/tiny-tests/List/percent_altns +++ b/cassandane/tiny-tests/List/percent_altns @@ -6,213 +6,264 @@ use Cassandane::Tiny; # mailbox name argument, matching levels of hierarchy # are also returned. sub test_percent_altns - :UnixHierarchySep :VirtDomains :CrossDomains :AltNamespace :max_version_3_4 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - # INBOX needs to exist even if we can't see it - $admintalk->create('user/bar'); - - foreach my $Folder ("user/cassandane/INBOX/sub", "user/cassandane/AEARLY", - "user/cassandane/sub2", "user/cassandane/sub2/achild", - "user/cassandane/INBOX/very/deep/one", - "user/cassandane/not/so/deep", - # stuff you can't see - "user/cassandane/INBOX", - "user/cassandane/inbox", - "user/cassandane/inbox/subnobody/deep", - "user/cassandane/Inbox/subnobody/deep", - # other users - "user/bar/Trash", - "user/foo", - "user/foo/really/deep", - # shared - "shared stuff/something") { - $admintalk->create($Folder); - $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); - } - - xlog $self, "List *"; - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasChildren', - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - 'AEARLY' => '\\HasNoChildren', - 'not/so/deep' => '\\HasNoChildren', - 'sub2' => '\\HasChildren', - 'sub2/achild' => '\\HasNoChildren', - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', - 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', - 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', - 'Shared Folders/shared stuff@defdomain/something' => '\\HasNoChildren', - }); - - xlog $self, "List *%"; - $data = $imaptalk->list("", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasChildren', - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\Noselect \\HasChildren', - 'INBOX/very/deep' => '\\Noselect \\HasChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - 'AEARLY' => '\\HasNoChildren', - 'not' => '\\Noselect \\HasChildren', - 'not/so' => '\\Noselect \\HasChildren', - 'not/so/deep' => '\\HasNoChildren', - 'sub2' => '\\HasChildren', - 'sub2/achild' => '\\HasNoChildren', - 'Alt Folders' => '\\Noselect \\HasChildren', - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody' => '\\Noselect \\HasChildren', - 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', - 'Alt Folders/Inbox' => '\\Noselect \\HasChildren', - 'Alt Folders/Inbox/subnobody' => '\\Noselect \\HasChildren', - 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', - 'Other Users' => '\\Noselect \\HasChildren', - 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', - 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - 'Other Users/foo@defdomain/really' => '\\Noselect \\HasChildren', - 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', - 'Shared Folders' => '\\Noselect \\HasChildren', - 'Shared Folders/shared stuff@defdomain' => '\\Noselect \\HasChildren', - 'Shared Folders/shared stuff@defdomain/something' => '\\HasNoChildren', - }); - - xlog $self, "List %"; - $data = $imaptalk->list("", "%"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasChildren', - 'AEARLY' => '\\HasNoChildren', - 'not' => '\\Noselect \\HasChildren', - 'sub2' => '\\HasChildren', - 'Alt Folders' => '\\Noselect \\HasChildren', - 'Other Users' => '\\Noselect \\HasChildren', - 'Shared Folders' => '\\Noselect \\HasChildren', - }); - - # check some partials - - xlog $self, "List INBOX/*"; - $data = $imaptalk->list("INBOX/", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - }); - - xlog $self, "List INBOX/*%"; - $data = $imaptalk->list("INBOX/", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\Noselect \\HasChildren', - 'INBOX/very/deep' => '\\Noselect \\HasChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - }); - - xlog $self, "List INBOX/%"; - $data = $imaptalk->list("INBOX/", "%"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\Noselect \\HasChildren', - }); - - xlog $self, "List AEARLY/*"; - $data = $imaptalk->list("AEARLY/", "*"); - $self->assert_mailbox_structure($data, '/', {}); - - xlog $self, "List AEARLY/*%"; - $data = $imaptalk->list("AEARLY/", "*%"); - $self->assert_mailbox_structure($data, '/', {}); - - xlog $self, "List AEARLY/%"; - $data = $imaptalk->list("AEARLY/", "%"); - $self->assert_mailbox_structure($data, '/', {}); - - xlog $self, "List sub2/*"; - $data = $imaptalk->list("sub2/", "*"); - $self->assert_mailbox_structure($data, '/', { - 'sub2/achild' => '\\HasNoChildren', - }); - - xlog $self, "List sub2/*%"; - $data = $imaptalk->list("sub2/", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'sub2/achild' => '\\HasNoChildren', - }); - - xlog $self, "List sub2/%"; - $data = $imaptalk->list("sub2/", "%"); - $self->assert_mailbox_structure($data, '/', { - 'sub2/achild' => '\\HasNoChildren', - }); - - xlog $self, "List Alt Folders/*"; - $data = $imaptalk->list("Alt Folders/", "*"); - $self->assert_mailbox_structure($data, '/', { - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', - 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', - }); - - xlog $self, "List Alt Folders/*%"; - $data = $imaptalk->list("Alt Folders/", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody' => '\\Noselect \\HasChildren', - 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', - 'Alt Folders/Inbox' => '\\Noselect \\HasChildren', - 'Alt Folders/Inbox/subnobody' => '\\Noselect \\HasChildren', - 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', - }); - - xlog $self, "List Alt Folders/%"; - $data = $imaptalk->list("Alt Folders/", "%"); - $self->assert_mailbox_structure($data, '/', { - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/Inbox' => '\\Noselect \\HasChildren', - }); - - xlog $self, "List Other Users"; - $data = $imaptalk->list("", "Other Users"); - $self->assert_mailbox_structure($data, '/', { - 'Other Users' => '\\Noselect \\HasChildren', - }); - - xlog $self, "List Other Users/*"; - $data = $imaptalk->list("Other Users/", "*"); - $self->assert_mailbox_structure($data, '/', { - 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', - }); - - xlog $self, "List Other Users/*%"; - $data = $imaptalk->list("Other Users/", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', - 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - 'Other Users/foo@defdomain/really' => '\\Noselect \\HasChildren', - 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', - }); - - xlog $self, "List Other Users/%"; - $data = $imaptalk->list("Other Users/", "%"); - $self->assert_mailbox_structure($data, '/', { - 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - }); + : UnixHierarchySep : VirtDomains : CrossDomains : AltNamespace : + max_version_3_4 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + # INBOX needs to exist even if we can't see it + $admintalk->create('user/bar'); + + foreach my $Folder ( + "user/cassandane/INBOX/sub", "user/cassandane/AEARLY", + "user/cassandane/sub2", "user/cassandane/sub2/achild", + "user/cassandane/INBOX/very/deep/one", + "user/cassandane/not/so/deep", + # stuff you can't see + "user/cassandane/INBOX", + "user/cassandane/inbox", + "user/cassandane/inbox/subnobody/deep", + "user/cassandane/Inbox/subnobody/deep", + # other users + "user/bar/Trash", + "user/foo", + "user/foo/really/deep", + # shared + "shared stuff/something" + ) + { + $admintalk->create($Folder); + $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); + } + + xlog $self, "List *"; + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasChildren', + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + 'AEARLY' => '\\HasNoChildren', + 'not/so/deep' => '\\HasNoChildren', + 'sub2' => '\\HasChildren', + 'sub2/achild' => '\\HasNoChildren', + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', + 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', + 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', + 'Shared Folders/shared stuff@defdomain/something' => '\\HasNoChildren', + } + ); + + xlog $self, "List *%"; + $data = $imaptalk->list("", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasChildren', + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\Noselect \\HasChildren', + 'INBOX/very/deep' => '\\Noselect \\HasChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + 'AEARLY' => '\\HasNoChildren', + 'not' => '\\Noselect \\HasChildren', + 'not/so' => '\\Noselect \\HasChildren', + 'not/so/deep' => '\\HasNoChildren', + 'sub2' => '\\HasChildren', + 'sub2/achild' => '\\HasNoChildren', + 'Alt Folders' => '\\Noselect \\HasChildren', + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody' => '\\Noselect \\HasChildren', + 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', + 'Alt Folders/Inbox' => '\\Noselect \\HasChildren', + 'Alt Folders/Inbox/subnobody' => '\\Noselect \\HasChildren', + 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', + 'Other Users' => '\\Noselect \\HasChildren', + 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', + 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + 'Other Users/foo@defdomain/really' => '\\Noselect \\HasChildren', + 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', + 'Shared Folders' => '\\Noselect \\HasChildren', + 'Shared Folders/shared stuff@defdomain' => '\\Noselect \\HasChildren', + 'Shared Folders/shared stuff@defdomain/something' => '\\HasNoChildren', + } + ); + + xlog $self, "List %"; + $data = $imaptalk->list("", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasChildren', + 'AEARLY' => '\\HasNoChildren', + 'not' => '\\Noselect \\HasChildren', + 'sub2' => '\\HasChildren', + 'Alt Folders' => '\\Noselect \\HasChildren', + 'Other Users' => '\\Noselect \\HasChildren', + 'Shared Folders' => '\\Noselect \\HasChildren', + } + ); + + # check some partials + + xlog $self, "List INBOX/*"; + $data = $imaptalk->list("INBOX/", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + } + ); + + xlog $self, "List INBOX/*%"; + $data = $imaptalk->list("INBOX/", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\Noselect \\HasChildren', + 'INBOX/very/deep' => '\\Noselect \\HasChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + } + ); + + xlog $self, "List INBOX/%"; + $data = $imaptalk->list("INBOX/", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\Noselect \\HasChildren', + } + ); + + xlog $self, "List AEARLY/*"; + $data = $imaptalk->list("AEARLY/", "*"); + $self->assert_mailbox_structure($data, '/', {}); + + xlog $self, "List AEARLY/*%"; + $data = $imaptalk->list("AEARLY/", "*%"); + $self->assert_mailbox_structure($data, '/', {}); + + xlog $self, "List AEARLY/%"; + $data = $imaptalk->list("AEARLY/", "%"); + $self->assert_mailbox_structure($data, '/', {}); + + xlog $self, "List sub2/*"; + $data = $imaptalk->list("sub2/", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'sub2/achild' => '\\HasNoChildren', + } + ); + + xlog $self, "List sub2/*%"; + $data = $imaptalk->list("sub2/", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'sub2/achild' => '\\HasNoChildren', + } + ); + + xlog $self, "List sub2/%"; + $data = $imaptalk->list("sub2/", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'sub2/achild' => '\\HasNoChildren', + } + ); + + xlog $self, "List Alt Folders/*"; + $data = $imaptalk->list("Alt Folders/", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', + 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', + } + ); + + xlog $self, "List Alt Folders/*%"; + $data = $imaptalk->list("Alt Folders/", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody' => '\\Noselect \\HasChildren', + 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', + 'Alt Folders/Inbox' => '\\Noselect \\HasChildren', + 'Alt Folders/Inbox/subnobody' => '\\Noselect \\HasChildren', + 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', + } + ); + + xlog $self, "List Alt Folders/%"; + $data = $imaptalk->list("Alt Folders/", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/Inbox' => '\\Noselect \\HasChildren', + } + ); + + xlog $self, "List Other Users"; + $data = $imaptalk->list("", "Other Users"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Other Users' => '\\Noselect \\HasChildren', + } + ); + + xlog $self, "List Other Users/*"; + $data = $imaptalk->list("Other Users/", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', + } + ); + + xlog $self, "List Other Users/*%"; + $data = $imaptalk->list("Other Users/", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', + 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + 'Other Users/foo@defdomain/really' => '\\Noselect \\HasChildren', + 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', + } + ); + + xlog $self, "List Other Users/%"; + $data = $imaptalk->list("Other Users/", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + } + ); } diff --git a/cassandane/tiny-tests/List/percent_altns_no_intermediates b/cassandane/tiny-tests/List/percent_altns_no_intermediates index 1cc3651622..1fcf0cc7b1 100644 --- a/cassandane/tiny-tests/List/percent_altns_no_intermediates +++ b/cassandane/tiny-tests/List/percent_altns_no_intermediates @@ -2,228 +2,279 @@ use Cassandane::Tiny; sub test_percent_altns_no_intermediates - :UnixHierarchySep :VirtDomains :CrossDomains :AltNamespace :min_version_3_5 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - # INBOX needs to exist even if we can't see it - $admintalk->create('user/bar'); - - foreach my $Folder ("user/cassandane/INBOX/sub", "user/cassandane/AEARLY", - "user/cassandane/sub2", "user/cassandane/sub2/achild", - "user/cassandane/INBOX/very/deep/one", - "user/cassandane/not/so/deep", - # stuff you can't see - "user/cassandane/INBOX", - "user/cassandane/inbox", - "user/cassandane/inbox/subnobody/deep", - "user/cassandane/Inbox/subnobody/deep", - # other users - "user/bar/Trash", - "user/foo", - "user/foo/really/deep", - # shared - "shared stuff/something") { - $admintalk->create($Folder); - $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); - } - - xlog $self, "List *"; - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasChildren', - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\HasChildren', - 'INBOX/very/deep' => '\\HasChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - 'AEARLY' => '\\HasNoChildren', - 'not' => '\\HasChildren', - 'not/so' => '\\HasChildren', - 'not/so/deep' => '\\HasNoChildren', - 'sub2' => '\\HasChildren', - 'sub2/achild' => '\\HasNoChildren', - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/Inbox' => '\\HasChildren', - 'Alt Folders/Inbox/subnobody' => '\\HasChildren', - 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody' => '\\HasChildren', - 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', - 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - 'Other Users/foo@defdomain/really' => '\\HasChildren', - 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', - 'Shared Folders/shared stuff@defdomain' => '\\HasChildren', - 'Shared Folders/shared stuff@defdomain/something' => '\\HasNoChildren', - }); - - xlog $self, "List *%"; - $data = $imaptalk->list("", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasChildren', - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\HasChildren', - 'INBOX/very/deep' => '\\HasChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - 'AEARLY' => '\\HasNoChildren', - 'not' => '\\HasChildren', - 'not/so' => '\\HasChildren', - 'not/so/deep' => '\\HasNoChildren', - 'sub2' => '\\HasChildren', - 'sub2/achild' => '\\HasNoChildren', - 'Alt Folders' => '\\Noselect \\HasChildren', - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody' => '\\HasChildren', - 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', - 'Alt Folders/Inbox' => '\\HasChildren', - 'Alt Folders/Inbox/subnobody' => '\\HasChildren', - 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', - 'Other Users' => '\\Noselect \\HasChildren', - 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', - 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - 'Other Users/foo@defdomain/really' => '\\HasChildren', - 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', - 'Shared Folders' => '\\Noselect \\HasChildren', - 'Shared Folders/shared stuff@defdomain' => '\\HasChildren', - 'Shared Folders/shared stuff@defdomain/something' => '\\HasNoChildren', - }); - - xlog $self, "List %"; - $data = $imaptalk->list("", "%"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasChildren', - 'AEARLY' => '\\HasNoChildren', - 'not' => '\\HasChildren', - 'sub2' => '\\HasChildren', - 'Alt Folders' => '\\Noselect \\HasChildren', - 'Other Users' => '\\Noselect \\HasChildren', - 'Shared Folders' => '\\Noselect \\HasChildren', - }); - - # check some partials - - xlog $self, "List INBOX/*"; - $data = $imaptalk->list("INBOX/", "*"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\HasChildren', - 'INBOX/very/deep' => '\\HasChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - }); - - xlog $self, "List INBOX/*%"; - $data = $imaptalk->list("INBOX/", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\HasChildren', - 'INBOX/very/deep' => '\\HasChildren', - 'INBOX/very/deep/one' => '\\HasNoChildren', - }); - - xlog $self, "List INBOX/%"; - $data = $imaptalk->list("INBOX/", "%"); - $self->assert_mailbox_structure($data, '/', { - 'INBOX/sub' => '\\HasNoChildren', - 'INBOX/very' => '\\HasChildren', - }); - - xlog $self, "List AEARLY/*"; - $data = $imaptalk->list("AEARLY/", "*"); - $self->assert_mailbox_structure($data, '/', {}); - - xlog $self, "List AEARLY/*%"; - $data = $imaptalk->list("AEARLY/", "*%"); - $self->assert_mailbox_structure($data, '/', {}); - - xlog $self, "List AEARLY/%"; - $data = $imaptalk->list("AEARLY/", "%"); - $self->assert_mailbox_structure($data, '/', {}); - - xlog $self, "List sub2/*"; - $data = $imaptalk->list("sub2/", "*"); - $self->assert_mailbox_structure($data, '/', { - 'sub2/achild' => '\\HasNoChildren', - }); - - xlog $self, "List sub2/*%"; - $data = $imaptalk->list("sub2/", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'sub2/achild' => '\\HasNoChildren', - }); - - xlog $self, "List sub2/%"; - $data = $imaptalk->list("sub2/", "%"); - $self->assert_mailbox_structure($data, '/', { - 'sub2/achild' => '\\HasNoChildren', - }); - - xlog $self, "List Alt Folders/*"; - $data = $imaptalk->list("Alt Folders/", "*"); - $self->assert_mailbox_structure($data, '/', { - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody' => '\\HasChildren', - 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', - 'Alt Folders/Inbox' => '\\HasChildren', - 'Alt Folders/Inbox/subnobody' => '\\HasChildren', - 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', - }); - - xlog $self, "List Alt Folders/*%"; - $data = $imaptalk->list("Alt Folders/", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/inbox/subnobody' => '\\HasChildren', - 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', - 'Alt Folders/Inbox' => '\\HasChildren', - 'Alt Folders/Inbox/subnobody' => '\\HasChildren', - 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', - }); - - xlog $self, "List Alt Folders/%"; - $data = $imaptalk->list("Alt Folders/", "%"); - $self->assert_mailbox_structure($data, '/', { - 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', - 'Alt Folders/inbox' => '\\HasChildren', - 'Alt Folders/Inbox' => '\\HasChildren', - }); - - xlog $self, "List Other Users"; - $data = $imaptalk->list("", "Other Users"); - $self->assert_mailbox_structure($data, '/', { - 'Other Users' => '\\Noselect \\HasChildren', - }); - - xlog $self, "List Other Users/*"; - $data = $imaptalk->list("Other Users/", "*"); - $self->assert_mailbox_structure($data, '/', { - 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - 'Other Users/foo@defdomain/really' => '\\HasChildren', - 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', - }); - - xlog $self, "List Other Users/*%"; - $data = $imaptalk->list("Other Users/", "*%"); - $self->assert_mailbox_structure($data, '/', { - 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', - 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - 'Other Users/foo@defdomain/really' => '\\HasChildren', - 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', - }); - - xlog $self, "List Other Users/%"; - $data = $imaptalk->list("Other Users/", "%"); - $self->assert_mailbox_structure($data, '/', { - 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', - 'Other Users/foo@defdomain' => '\\HasChildren', - }); + : UnixHierarchySep : VirtDomains : CrossDomains : AltNamespace : + min_version_3_5 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + # INBOX needs to exist even if we can't see it + $admintalk->create('user/bar'); + + foreach my $Folder ( + "user/cassandane/INBOX/sub", "user/cassandane/AEARLY", + "user/cassandane/sub2", "user/cassandane/sub2/achild", + "user/cassandane/INBOX/very/deep/one", + "user/cassandane/not/so/deep", + # stuff you can't see + "user/cassandane/INBOX", + "user/cassandane/inbox", + "user/cassandane/inbox/subnobody/deep", + "user/cassandane/Inbox/subnobody/deep", + # other users + "user/bar/Trash", + "user/foo", + "user/foo/really/deep", + # shared + "shared stuff/something" + ) + { + $admintalk->create($Folder); + $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); + } + + xlog $self, "List *"; + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasChildren', + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\HasChildren', + 'INBOX/very/deep' => '\\HasChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + 'AEARLY' => '\\HasNoChildren', + 'not' => '\\HasChildren', + 'not/so' => '\\HasChildren', + 'not/so/deep' => '\\HasNoChildren', + 'sub2' => '\\HasChildren', + 'sub2/achild' => '\\HasNoChildren', + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/Inbox' => '\\HasChildren', + 'Alt Folders/Inbox/subnobody' => '\\HasChildren', + 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody' => '\\HasChildren', + 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', + 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + 'Other Users/foo@defdomain/really' => '\\HasChildren', + 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', + 'Shared Folders/shared stuff@defdomain' => '\\HasChildren', + 'Shared Folders/shared stuff@defdomain/something' => '\\HasNoChildren', + } + ); + + xlog $self, "List *%"; + $data = $imaptalk->list("", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasChildren', + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\HasChildren', + 'INBOX/very/deep' => '\\HasChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + 'AEARLY' => '\\HasNoChildren', + 'not' => '\\HasChildren', + 'not/so' => '\\HasChildren', + 'not/so/deep' => '\\HasNoChildren', + 'sub2' => '\\HasChildren', + 'sub2/achild' => '\\HasNoChildren', + 'Alt Folders' => '\\Noselect \\HasChildren', + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody' => '\\HasChildren', + 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', + 'Alt Folders/Inbox' => '\\HasChildren', + 'Alt Folders/Inbox/subnobody' => '\\HasChildren', + 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', + 'Other Users' => '\\Noselect \\HasChildren', + 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', + 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + 'Other Users/foo@defdomain/really' => '\\HasChildren', + 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', + 'Shared Folders' => '\\Noselect \\HasChildren', + 'Shared Folders/shared stuff@defdomain' => '\\HasChildren', + 'Shared Folders/shared stuff@defdomain/something' => '\\HasNoChildren', + } + ); + + xlog $self, "List %"; + $data = $imaptalk->list("", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasChildren', + 'AEARLY' => '\\HasNoChildren', + 'not' => '\\HasChildren', + 'sub2' => '\\HasChildren', + 'Alt Folders' => '\\Noselect \\HasChildren', + 'Other Users' => '\\Noselect \\HasChildren', + 'Shared Folders' => '\\Noselect \\HasChildren', + } + ); + + # check some partials + + xlog $self, "List INBOX/*"; + $data = $imaptalk->list("INBOX/", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\HasChildren', + 'INBOX/very/deep' => '\\HasChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + } + ); + + xlog $self, "List INBOX/*%"; + $data = $imaptalk->list("INBOX/", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\HasChildren', + 'INBOX/very/deep' => '\\HasChildren', + 'INBOX/very/deep/one' => '\\HasNoChildren', + } + ); + + xlog $self, "List INBOX/%"; + $data = $imaptalk->list("INBOX/", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX/sub' => '\\HasNoChildren', + 'INBOX/very' => '\\HasChildren', + } + ); + + xlog $self, "List AEARLY/*"; + $data = $imaptalk->list("AEARLY/", "*"); + $self->assert_mailbox_structure($data, '/', {}); + + xlog $self, "List AEARLY/*%"; + $data = $imaptalk->list("AEARLY/", "*%"); + $self->assert_mailbox_structure($data, '/', {}); + + xlog $self, "List AEARLY/%"; + $data = $imaptalk->list("AEARLY/", "%"); + $self->assert_mailbox_structure($data, '/', {}); + + xlog $self, "List sub2/*"; + $data = $imaptalk->list("sub2/", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'sub2/achild' => '\\HasNoChildren', + } + ); + + xlog $self, "List sub2/*%"; + $data = $imaptalk->list("sub2/", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'sub2/achild' => '\\HasNoChildren', + } + ); + + xlog $self, "List sub2/%"; + $data = $imaptalk->list("sub2/", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'sub2/achild' => '\\HasNoChildren', + } + ); + + xlog $self, "List Alt Folders/*"; + $data = $imaptalk->list("Alt Folders/", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody' => '\\HasChildren', + 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', + 'Alt Folders/Inbox' => '\\HasChildren', + 'Alt Folders/Inbox/subnobody' => '\\HasChildren', + 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', + } + ); + + xlog $self, "List Alt Folders/*%"; + $data = $imaptalk->list("Alt Folders/", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/inbox/subnobody' => '\\HasChildren', + 'Alt Folders/inbox/subnobody/deep' => '\\HasNoChildren', + 'Alt Folders/Inbox' => '\\HasChildren', + 'Alt Folders/Inbox/subnobody' => '\\HasChildren', + 'Alt Folders/Inbox/subnobody/deep' => '\\HasNoChildren', + } + ); + + xlog $self, "List Alt Folders/%"; + $data = $imaptalk->list("Alt Folders/", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Alt Folders/INBOX' => '\\HasNoChildren \\Noinferiors', + 'Alt Folders/inbox' => '\\HasChildren', + 'Alt Folders/Inbox' => '\\HasChildren', + } + ); + + xlog $self, "List Other Users"; + $data = $imaptalk->list("", "Other Users"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Other Users' => '\\Noselect \\HasChildren', + } + ); + + xlog $self, "List Other Users/*"; + $data = $imaptalk->list("Other Users/", "*"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + 'Other Users/foo@defdomain/really' => '\\HasChildren', + 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', + } + ); + + xlog $self, "List Other Users/*%"; + $data = $imaptalk->list("Other Users/", "*%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', + 'Other Users/bar@defdomain/Trash' => '\\HasNoChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + 'Other Users/foo@defdomain/really' => '\\HasChildren', + 'Other Users/foo@defdomain/really/deep' => '\\HasNoChildren', + } + ); + + xlog $self, "List Other Users/%"; + $data = $imaptalk->list("Other Users/", "%"); + $self->assert_mailbox_structure( + $data, '/', + { + 'Other Users/bar@defdomain' => '\\Noselect \\HasChildren', + 'Other Users/foo@defdomain' => '\\HasChildren', + } + ); } diff --git a/cassandane/tiny-tests/List/percent_no_intermediates b/cassandane/tiny-tests/List/percent_no_intermediates index 1a0005d791..ffc7070166 100644 --- a/cassandane/tiny-tests/List/percent_no_intermediates +++ b/cassandane/tiny-tests/List/percent_no_intermediates @@ -2,184 +2,207 @@ use Cassandane::Tiny; sub test_percent_no_intermediates - :NoAltNameSpace :min_version_3_5 -{ - my ($self) = @_; + : NoAltNameSpace : min_version_3_5 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $imaptalk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - # INBOX needs to exist even if we can't see it - $admintalk->create('user.bar'); + # INBOX needs to exist even if we can't see it + $admintalk->create('user.bar'); - foreach my $Folder ("user.cassandane.INBOX.sub", "user.cassandane.AEARLY", - "user.cassandane.sub2", "user.cassandane.sub2.achild", - "user.cassandane.INBOX.very.deep.one", - "user.cassandane.not.so.deep", - # stuff you can't see - "user.cassandane.INBOX", - "user.cassandane.inbox", - "user.cassandane.inbox.subnobody.deep", - "user.cassandane.Inbox.subnobody.deep", - # other users - "user.bar.Trash", - "user.foo", - "user.foo.really.deep", - # shared - "shared stuff.something") { - $admintalk->create($Folder); - $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); - } + foreach my $Folder ( + "user.cassandane.INBOX.sub", "user.cassandane.AEARLY", + "user.cassandane.sub2", "user.cassandane.sub2.achild", + "user.cassandane.INBOX.very.deep.one", + "user.cassandane.not.so.deep", + # stuff you can't see + "user.cassandane.INBOX", + "user.cassandane.inbox", + "user.cassandane.inbox.subnobody.deep", + "user.cassandane.Inbox.subnobody.deep", + # other users + "user.bar.Trash", + "user.foo", + "user.foo.really.deep", + # shared + "shared stuff.something" + ) + { + $admintalk->create($Folder); + $admintalk->setacl($Folder, 'cassandane' => 'lrswipkxtecd'); + } - xlog $self, "List *"; - my $data = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => '\\HasChildren', - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.INBOX.sub' => '\\HasNoChildren', - 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', - 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.not.so.deep' => '\\HasNoChildren', - 'INBOX.sub2' => '\\HasChildren', - 'INBOX.sub2.achild' => '\\HasNoChildren', - 'user.bar.Trash' => '\\HasNoChildren', - 'user.foo' => '\\HasChildren', - 'user.foo.really.deep' => '\\HasNoChildren', - 'shared stuff.something' => '\\HasNoChildren', + xlog $self, "List *"; + my $data = $imaptalk->list("", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => '\\HasChildren', + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.INBOX.sub' => '\\HasNoChildren', + 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', + 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.not.so.deep' => '\\HasNoChildren', + 'INBOX.sub2' => '\\HasChildren', + 'INBOX.sub2.achild' => '\\HasNoChildren', + 'user.bar.Trash' => '\\HasNoChildren', + 'user.foo' => '\\HasChildren', + 'user.foo.really.deep' => '\\HasNoChildren', + 'shared stuff.something' => '\\HasNoChildren', - 'INBOX.INBOX.very' => '\\HasChildren', - 'INBOX.INBOX.very.deep' => '\\HasChildren', - 'INBOX.inbox.subnobody' => '\\HasChildren', - 'INBOX.not' => '\\HasChildren', - 'INBOX.not.so' => '\\HasChildren', - 'INBOX.Inbox' => '\\HasChildren', - 'INBOX.Inbox.subnobody' => '\\HasChildren', - 'INBOX.inbox.subnobody' => '\\HasChildren', - 'user.foo.really' => '\\HasChildren', - 'shared stuff' => '\\HasChildren', - }); + 'INBOX.INBOX.very' => '\\HasChildren', + 'INBOX.INBOX.very.deep' => '\\HasChildren', + 'INBOX.inbox.subnobody' => '\\HasChildren', + 'INBOX.not' => '\\HasChildren', + 'INBOX.not.so' => '\\HasChildren', + 'INBOX.Inbox' => '\\HasChildren', + 'INBOX.Inbox.subnobody' => '\\HasChildren', + 'INBOX.inbox.subnobody' => '\\HasChildren', + 'user.foo.really' => '\\HasChildren', + 'shared stuff' => '\\HasChildren', + } + ); - #xlog $self, "LIST %"; - #$data = $imaptalk->list("", "%"); - #$self->assert_mailbox_structure($data, '.', { - #'INBOX' => '\\HasChildren', - #'user' => '\\Noselect \\HasChildren', - #'shared stuff' => '\\Noselect \\HasChildren', - #}); + #xlog $self, "LIST %"; + #$data = $imaptalk->list("", "%"); + #$self->assert_mailbox_structure($data, '.', { + #'INBOX' => '\\HasChildren', + #'user' => '\\Noselect \\HasChildren', + #'shared stuff' => '\\Noselect \\HasChildren', + #}); - xlog $self, "List *%"; - $data = $imaptalk->list("", "*%"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX' => '\\HasChildren', - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.INBOX.sub' => '\\HasNoChildren', - 'INBOX.INBOX.very' => '\\HasChildren', - 'INBOX.INBOX.very.deep' => '\\HasChildren', - 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', - 'INBOX.Inbox' => '\\HasChildren', - 'INBOX.Inbox.subnobody' => '\\HasChildren', - 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.inbox.subnobody' => '\\HasChildren', - 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.not' => '\\HasChildren', - 'INBOX.not.so' => '\\HasChildren', - 'INBOX.not.so.deep' => '\\HasNoChildren', - 'INBOX.sub2' => '\\HasChildren', - 'INBOX.sub2.achild' => '\\HasNoChildren', - 'user' => '\\Noselect \\HasChildren', - 'user.bar' => '\\Noselect \\HasChildren', - 'user.bar.Trash' => '\\HasNoChildren', - 'user.foo' => '\\HasChildren', - 'user.foo.really' => '\\HasChildren', - 'user.foo.really.deep' => '\\HasNoChildren', - 'shared stuff' => '\\HasChildren', - 'shared stuff.something' => '\\HasNoChildren', - }); + xlog $self, "List *%"; + $data = $imaptalk->list("", "*%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX' => '\\HasChildren', + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.INBOX.sub' => '\\HasNoChildren', + 'INBOX.INBOX.very' => '\\HasChildren', + 'INBOX.INBOX.very.deep' => '\\HasChildren', + 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', + 'INBOX.Inbox' => '\\HasChildren', + 'INBOX.Inbox.subnobody' => '\\HasChildren', + 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.inbox.subnobody' => '\\HasChildren', + 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.not' => '\\HasChildren', + 'INBOX.not.so' => '\\HasChildren', + 'INBOX.not.so.deep' => '\\HasNoChildren', + 'INBOX.sub2' => '\\HasChildren', + 'INBOX.sub2.achild' => '\\HasNoChildren', + 'user' => '\\Noselect \\HasChildren', + 'user.bar' => '\\Noselect \\HasChildren', + 'user.bar.Trash' => '\\HasNoChildren', + 'user.foo' => '\\HasChildren', + 'user.foo.really' => '\\HasChildren', + 'user.foo.really.deep' => '\\HasNoChildren', + 'shared stuff' => '\\HasChildren', + 'shared stuff.something' => '\\HasNoChildren', + } + ); - xlog $self, "LIST INBOX.*"; - $data = $imaptalk->list("INBOX.", "*"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.INBOX.sub' => '\\HasNoChildren', - 'INBOX.INBOX.very' => '\\HasChildren', - 'INBOX.INBOX.very.deep' => '\\HasChildren', - 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', - 'INBOX.Inbox' => '\\HasChildren', - 'INBOX.Inbox.subnobody' => '\\HasChildren', - 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.inbox.subnobody' => '\\HasChildren', - 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.not' => '\\HasChildren', - 'INBOX.not.so' => '\\HasChildren', - 'INBOX.not.so.deep' => '\\HasNoChildren', - 'INBOX.sub2' => '\\HasChildren', - 'INBOX.sub2.achild' => '\\HasNoChildren', - }); + xlog $self, "LIST INBOX.*"; + $data = $imaptalk->list("INBOX.", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.INBOX.sub' => '\\HasNoChildren', + 'INBOX.INBOX.very' => '\\HasChildren', + 'INBOX.INBOX.very.deep' => '\\HasChildren', + 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', + 'INBOX.Inbox' => '\\HasChildren', + 'INBOX.Inbox.subnobody' => '\\HasChildren', + 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.inbox.subnobody' => '\\HasChildren', + 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.not' => '\\HasChildren', + 'INBOX.not.so' => '\\HasChildren', + 'INBOX.not.so.deep' => '\\HasNoChildren', + 'INBOX.sub2' => '\\HasChildren', + 'INBOX.sub2.achild' => '\\HasNoChildren', + } + ); - xlog $self, "LIST INBOX.*%"; - $data = $imaptalk->list("INBOX.", "*%"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.INBOX.sub' => '\\HasNoChildren', - 'INBOX.INBOX.very' => '\\HasChildren', - 'INBOX.INBOX.very.deep' => '\\HasChildren', - 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', - 'INBOX.Inbox' => '\\HasChildren', - 'INBOX.Inbox.subnobody' => '\\HasChildren', - 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.inbox.subnobody' => '\\HasChildren', - 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', - 'INBOX.not' => '\\HasChildren', - 'INBOX.not.so' => '\\HasChildren', - 'INBOX.not.so.deep' => '\\HasNoChildren', - 'INBOX.sub2' => '\\HasChildren', - 'INBOX.sub2.achild' => '\\HasNoChildren', - }); + xlog $self, "LIST INBOX.*%"; + $data = $imaptalk->list("INBOX.", "*%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.INBOX.sub' => '\\HasNoChildren', + 'INBOX.INBOX.very' => '\\HasChildren', + 'INBOX.INBOX.very.deep' => '\\HasChildren', + 'INBOX.INBOX.very.deep.one' => '\\HasNoChildren', + 'INBOX.Inbox' => '\\HasChildren', + 'INBOX.Inbox.subnobody' => '\\HasChildren', + 'INBOX.Inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.inbox.subnobody' => '\\HasChildren', + 'INBOX.inbox.subnobody.deep' => '\\HasNoChildren', + 'INBOX.not' => '\\HasChildren', + 'INBOX.not.so' => '\\HasChildren', + 'INBOX.not.so.deep' => '\\HasNoChildren', + 'INBOX.sub2' => '\\HasChildren', + 'INBOX.sub2.achild' => '\\HasNoChildren', + } + ); - xlog $self, "LIST INBOX.%"; - $data = $imaptalk->list("INBOX.", "%"); - $self->assert_mailbox_structure($data, '.', { - 'INBOX.AEARLY' => '\\HasNoChildren', - 'INBOX.INBOX' => '\\HasChildren', - 'INBOX.Inbox' => '\\HasChildren', - 'INBOX.inbox' => '\\HasChildren', - 'INBOX.not' => '\\HasChildren', - 'INBOX.sub2' => '\\HasChildren', - }); + xlog $self, "LIST INBOX.%"; + $data = $imaptalk->list("INBOX.", "%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'INBOX.AEARLY' => '\\HasNoChildren', + 'INBOX.INBOX' => '\\HasChildren', + 'INBOX.Inbox' => '\\HasChildren', + 'INBOX.inbox' => '\\HasChildren', + 'INBOX.not' => '\\HasChildren', + 'INBOX.sub2' => '\\HasChildren', + } + ); - xlog $self, "List user.*"; - $data = $imaptalk->list("user.", "*"); - $self->assert_mailbox_structure($data, '.', { - 'user.bar.Trash' => '\\HasNoChildren', - 'user.foo' => '\\HasChildren', - 'user.foo.really' => '\\HasChildren', - 'user.foo.really.deep' => '\\HasNoChildren', - }); + xlog $self, "List user.*"; + $data = $imaptalk->list("user.", "*"); + $self->assert_mailbox_structure( + $data, '.', + { + 'user.bar.Trash' => '\\HasNoChildren', + 'user.foo' => '\\HasChildren', + 'user.foo.really' => '\\HasChildren', + 'user.foo.really.deep' => '\\HasNoChildren', + } + ); - xlog $self, "List user.*%"; - $data = $imaptalk->list("user.", "*%"); - $self->assert_mailbox_structure($data, '.', { - 'user.bar' => '\\HasChildren', - 'user.bar.Trash' => '\\HasNoChildren', - 'user.foo' => '\\HasChildren', - 'user.foo.really' => '\\HasChildren', - 'user.foo.really.deep' => '\\HasNoChildren', - }); + xlog $self, "List user.*%"; + $data = $imaptalk->list("user.", "*%"); + $self->assert_mailbox_structure( + $data, '.', + { + 'user.bar' => '\\HasChildren', + 'user.bar.Trash' => '\\HasNoChildren', + 'user.foo' => '\\HasChildren', + 'user.foo.really' => '\\HasChildren', + 'user.foo.really.deep' => '\\HasNoChildren', + } + ); - #xlog $self, "List user.%"; - #$data = $imaptalk->list("user.", "%"); - #$self->assert_mailbox_structure($data, '.', { - # 'user.bar' => '\\Noselect \\HasChildren', - # 'user.foo' => '\\HasChildren', - #}); + #xlog $self, "List user.%"; + #$data = $imaptalk->list("user.", "%"); + #$self->assert_mailbox_structure($data, '.', { + # 'user.bar' => '\\Noselect \\HasChildren', + # 'user.foo' => '\\HasChildren', + #}); } diff --git a/cassandane/tiny-tests/List/recursivematch b/cassandane/tiny-tests/List/recursivematch index 890754b4d0..26da4337cd 100644 --- a/cassandane/tiny-tests/List/recursivematch +++ b/cassandane/tiny-tests/List/recursivematch @@ -2,29 +2,35 @@ use Cassandane::Tiny; sub test_recursivematch - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], - [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], - [ 'delete' => 'Fruit/Peach' ], - [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], - [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], + [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], + [ 'delete' => 'Fruit/Peach' ], + [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], + [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], + ] + ); - my $subdata = $imaptalk->list([qw(SUBSCRIBED RECURSIVEMATCH)], "", "*"); + my $subdata = $imaptalk->list([qw(SUBSCRIBED RECURSIVEMATCH)], "", "*"); - xlog(Dumper $subdata); - $self->assert_mailbox_structure($subdata, '/', { - 'INBOX' => '\\Subscribed', - 'Fruit/Banana' => '\\Subscribed', - 'Fruit/Peach' => [qw( \\NonExistent \\Subscribed )], - 'Vegetable' => [qw( \\Subscribed \\HasChildren )], # HasChildren not required by spec, but cyrus tells us - 'Vegetable/Broccoli' => '\\Subscribed', - }); + xlog(Dumper $subdata); + $self->assert_mailbox_structure( + $subdata, '/', + { + 'INBOX' => '\\Subscribed', + 'Fruit/Banana' => '\\Subscribed', + 'Fruit/Peach' => [qw( \\NonExistent \\Subscribed )], + 'Vegetable' => [qw( \\Subscribed \\HasChildren )] + , # HasChildren not required by spec, but cyrus tells us + 'Vegetable/Broccoli' => '\\Subscribed', + } + ); } diff --git a/cassandane/tiny-tests/List/recursivematch_percent b/cassandane/tiny-tests/List/recursivematch_percent index 831ae1151d..e9bec7418e 100644 --- a/cassandane/tiny-tests/List/recursivematch_percent +++ b/cassandane/tiny-tests/List/recursivematch_percent @@ -2,27 +2,33 @@ use Cassandane::Tiny; sub test_recursivematch_percent - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], - [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], - [ 'delete' => 'Fruit/Peach' ], - [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], - [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], + [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], + [ 'delete' => 'Fruit/Peach' ], + [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], + [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], + ] + ); - my $subdata = $imaptalk->list([qw(SUBSCRIBED RECURSIVEMATCH)], "", "%"); + my $subdata = $imaptalk->list([qw(SUBSCRIBED RECURSIVEMATCH)], "", "%"); - xlog(Dumper $subdata); - $self->assert_mailbox_structure($subdata, '/', { - 'INBOX' => [qw( \\Subscribed )], - 'Fruit' => [qw( \\NonExistent \\HasChildren )], - 'Vegetable' => [qw( \\Subscribed \\HasChildren )], # HasChildren not required by spec, but cyrus tells us - }); + xlog(Dumper $subdata); + $self->assert_mailbox_structure( + $subdata, '/', + { + 'INBOX' => [qw( \\Subscribed )], + 'Fruit' => [qw( \\NonExistent \\HasChildren )], + 'Vegetable' => [qw( \\Subscribed \\HasChildren )] + , # HasChildren not required by spec, but cyrus tells us + } + ); } diff --git a/cassandane/tiny-tests/List/rfc5258_ex01_list_all b/cassandane/tiny-tests/List/rfc5258_ex01_list_all index e17c5e2371..33d9a88ea8 100644 --- a/cassandane/tiny-tests/List/rfc5258_ex01_list_all +++ b/cassandane/tiny-tests/List/rfc5258_ex01_list_all @@ -2,31 +2,36 @@ use Cassandane::Tiny; sub test_rfc5258_ex01_list_all - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], - [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], - [ 'delete' => 'Fruit/Peach' ], - [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], - [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], + [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], + [ 'delete' => 'Fruit/Peach' ], + [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], + [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], + ] + ); - my $alldata = $imaptalk->list("", "*"); + my $alldata = $imaptalk->list("", "*"); - $self->assert_mailbox_structure($alldata, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'Fruit' => [qw( \\HasChildren )], - 'Fruit/Apple' => [qw( \\HasNoChildren )], - 'Fruit/Banana' => [qw( \\HasNoChildren )], - 'Tofu' => [qw( \\HasNoChildren )], - 'Vegetable' => [qw( \\HasChildren )], - 'Vegetable/Broccoli' => [qw( \\HasNoChildren )], - 'Vegetable/Corn' => [qw( \\HasNoChildren )], - }); + $self->assert_mailbox_structure( + $alldata, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'Fruit' => [qw( \\HasChildren )], + 'Fruit/Apple' => [qw( \\HasNoChildren )], + 'Fruit/Banana' => [qw( \\HasNoChildren )], + 'Tofu' => [qw( \\HasNoChildren )], + 'Vegetable' => [qw( \\HasChildren )], + 'Vegetable/Broccoli' => [qw( \\HasNoChildren )], + 'Vegetable/Corn' => [qw( \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/rfc5258_ex02_list_subscribed b/cassandane/tiny-tests/List/rfc5258_ex02_list_subscribed index 8c2cd876e4..a054a4bd52 100644 --- a/cassandane/tiny-tests/List/rfc5258_ex02_list_subscribed +++ b/cassandane/tiny-tests/List/rfc5258_ex02_list_subscribed @@ -2,29 +2,35 @@ use Cassandane::Tiny; sub test_rfc5258_ex02_list_subscribed - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], - [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], - [ 'delete' => 'Fruit/Peach' ], - [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], - [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], + [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], + [ 'delete' => 'Fruit/Peach' ], + [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], + [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], + ] + ); - my $subdata = $imaptalk->list([qw(SUBSCRIBED)], "", "*"); + my $subdata = $imaptalk->list([qw(SUBSCRIBED)], "", "*"); - xlog(Dumper $subdata); - $self->assert_mailbox_structure($subdata, '/', { - 'INBOX' => '\\Subscribed', - 'Fruit/Banana' => '\\Subscribed', - 'Fruit/Peach' => [qw( \\NonExistent \\Subscribed )], - 'Vegetable' => [qw( \\Subscribed \\HasChildren )], # HasChildren not required by spec, but cyrus tells us - 'Vegetable/Broccoli' => '\\Subscribed', - }); + xlog(Dumper $subdata); + $self->assert_mailbox_structure( + $subdata, '/', + { + 'INBOX' => '\\Subscribed', + 'Fruit/Banana' => '\\Subscribed', + 'Fruit/Peach' => [qw( \\NonExistent \\Subscribed )], + 'Vegetable' => [qw( \\Subscribed \\HasChildren )] + , # HasChildren not required by spec, but cyrus tells us + 'Vegetable/Broccoli' => '\\Subscribed', + } + ); } diff --git a/cassandane/tiny-tests/List/rfc5258_ex03_children b/cassandane/tiny-tests/List/rfc5258_ex03_children index 682db11ebb..8f080d45dd 100644 --- a/cassandane/tiny-tests/List/rfc5258_ex03_children +++ b/cassandane/tiny-tests/List/rfc5258_ex03_children @@ -2,29 +2,32 @@ use Cassandane::Tiny; sub test_rfc5258_ex03_children - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'subscribe' => 'INBOX' ], - [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], - [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], - [ 'delete' => 'Fruit/Peach' ], - [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], - [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'subscribe' => 'INBOX' ], + [ 'create' => [qw( Fruit Fruit/Apple Fruit/Banana Fruit/Peach)] ], + [ 'subscribe' => [qw( Fruit/Banana Fruit/Peach )] ], + [ 'delete' => 'Fruit/Peach' ], + [ 'create' => [qw( Tofu Vegetable Vegetable/Broccoli Vegetable/Corn )] ], + [ 'subscribe' => [qw( Vegetable Vegetable/Broccoli )] ], + ] + ); - my $data = $imaptalk->list( - [qw()], "", "%", 'RETURN', [qw(CHILDREN)], - ); + my $data = $imaptalk->list([qw()], "", "%", 'RETURN', [qw(CHILDREN)],); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [ '\\HasNoChildren' ], - 'Fruit' => [ '\\HasChildren' ], - 'Tofu' => [ '\\HasNoChildren' ], - 'Vegetable' => [ '\\HasChildren' ], - }); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => ['\\HasNoChildren'], + 'Fruit' => ['\\HasChildren'], + 'Tofu' => ['\\HasNoChildren'], + 'Vegetable' => ['\\HasChildren'], + } + ); } diff --git a/cassandane/tiny-tests/List/rfc5258_ex07_multiple_mailbox_patterns b/cassandane/tiny-tests/List/rfc5258_ex07_multiple_mailbox_patterns index 30251c1654..7a4723533c 100644 --- a/cassandane/tiny-tests/List/rfc5258_ex07_multiple_mailbox_patterns +++ b/cassandane/tiny-tests/List/rfc5258_ex07_multiple_mailbox_patterns @@ -2,27 +2,34 @@ use Cassandane::Tiny; sub test_rfc5258_ex07_multiple_mailbox_patterns - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => 'Drafts' ], - [ 'create' => [qw( - Sent Sent/March2004 Sent/December2003 Sent/August2004 - )] ], - [ 'create' => [qw( Unlisted Unlisted/Foo )] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'create' => 'Drafts' ], + [ + 'create' => [ qw( + Sent Sent/March2004 Sent/December2003 Sent/August2004 + ) ] + ], + [ 'create' => [qw( Unlisted Unlisted/Foo )] ], + ] + ); - my $data = $imaptalk->list("", [qw( INBOX Drafts Sent/% )]); + my $data = $imaptalk->list("", [qw( INBOX Drafts Sent/% )]); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => [ '\\HasNoChildren' ], - 'Drafts' => [ '\\HasNoChildren' ], - 'Sent/August2004' => [ '\\HasNoChildren' ], - 'Sent/December2003' => [ '\\HasNoChildren' ], - 'Sent/March2004' => [ '\\HasNoChildren' ], - }); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => ['\\HasNoChildren'], + 'Drafts' => ['\\HasNoChildren'], + 'Sent/August2004' => ['\\HasNoChildren'], + 'Sent/December2003' => ['\\HasNoChildren'], + 'Sent/March2004' => ['\\HasNoChildren'], + } + ); } diff --git a/cassandane/tiny-tests/List/rfc5258_ex08_haschildren_childinfo b/cassandane/tiny-tests/List/rfc5258_ex08_haschildren_childinfo index b44f9f6ebb..8bbee63b5a 100644 --- a/cassandane/tiny-tests/List/rfc5258_ex08_haschildren_childinfo +++ b/cassandane/tiny-tests/List/rfc5258_ex08_haschildren_childinfo @@ -2,24 +2,25 @@ use Cassandane::Tiny; sub test_rfc5258_ex08_haschildren_childinfo - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( Foo Foo/Bar Foo/Baz Moo )] ], - ]); + $self->setup_mailbox_structure($imaptalk, + [ [ 'create' => [qw( Foo Foo/Bar Foo/Baz Moo )] ], ]); - my $data = $imaptalk->list("", "%", "RETURN", [qw( CHILDREN )]); + my $data = $imaptalk->list("", "%", "RETURN", [qw( CHILDREN )]); - $self->assert_mailbox_structure($data, '/', { - 'INBOX' => '\\HasNoChildren', - 'Foo' => '\\HasChildren', - 'Moo' => '\\HasNoChildren', - }); + $self->assert_mailbox_structure( + $data, '/', + { + 'INBOX' => '\\HasNoChildren', + 'Foo' => '\\HasChildren', + 'Moo' => '\\HasNoChildren', + } + ); - # TODO probably break the rest of this test out into 8a, 8b etc - xlog('FIXME much more to test here...'); + # TODO probably break the rest of this test out into 8a, 8b etc + xlog('FIXME much more to test here...'); } diff --git a/cassandane/tiny-tests/List/rfc6154_ex02a_list_return_special_use b/cassandane/tiny-tests/List/rfc6154_ex02a_list_return_special_use index e159f0e5a5..ad5c6f5486 100644 --- a/cassandane/tiny-tests/List/rfc6154_ex02a_list_return_special_use +++ b/cassandane/tiny-tests/List/rfc6154_ex02a_list_return_special_use @@ -2,33 +2,38 @@ use Cassandane::Tiny; sub test_rfc6154_ex02a_list_return_special_use - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash) ] ], - ]); - - $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - - my $alldata = $imaptalk->list("", "%", 'RETURN', [qw( SPECIAL-USE )]); - - $self->assert_mailbox_structure($alldata, '/', { - 'INBOX' => [qw( \\HasNoChildren )], - 'ToDo' => [qw( \\HasNoChildren )], - 'Projects' => [qw( \\HasChildren )], - 'SentMail' => [qw( \\Sent \\HasNoChildren )], - 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], - 'Trash' => [qw( \\Trash \\HasNoChildren )], - }); + : UnixHierarchySep : AltNamespace { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash)] ], + ] + ); + + $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + + my $alldata = $imaptalk->list("", "%", 'RETURN', [qw( SPECIAL-USE )]); + + $self->assert_mailbox_structure( + $alldata, '/', + { + 'INBOX' => [qw( \\HasNoChildren )], + 'ToDo' => [qw( \\HasNoChildren )], + 'Projects' => [qw( \\HasChildren )], + 'SentMail' => [qw( \\Sent \\HasNoChildren )], + 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], + 'Trash' => [qw( \\Trash \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/rfc6154_ex02b_list_special_use b/cassandane/tiny-tests/List/rfc6154_ex02b_list_special_use index b1226e3f1c..d4d6d665b3 100644 --- a/cassandane/tiny-tests/List/rfc6154_ex02b_list_special_use +++ b/cassandane/tiny-tests/List/rfc6154_ex02b_list_special_use @@ -2,30 +2,35 @@ use Cassandane::Tiny; sub test_rfc6154_ex02b_list_special_use - :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash) ] ], - ]); + $self->setup_mailbox_structure( + $imaptalk, + [ + [ 'create' => [qw( ToDo Projects Projects/Foo SentMail MyDrafts Trash)] ], + ] + ); - $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->setmetadata("SentMail", "/private/specialuse", "\\Sent"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->setmetadata("MyDrafts", "/private/specialuse", "\\Drafts"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->setmetadata("Trash", "/private/specialuse", "\\Trash"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - my $alldata = $imaptalk->list([qw( SPECIAL-USE )], "", "%"); + my $alldata = $imaptalk->list([qw( SPECIAL-USE )], "", "%"); - $self->assert_mailbox_structure($alldata, '/', { - 'SentMail' => [qw( \\Sent \\HasNoChildren )], - 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], - 'Trash' => [qw( \\Trash \\HasNoChildren )], - }); + $self->assert_mailbox_structure( + $alldata, '/', + { + 'SentMail' => [qw( \\Sent \\HasNoChildren )], + 'MyDrafts' => [qw( \\Drafts \\HasNoChildren )], + 'Trash' => [qw( \\Trash \\HasNoChildren )], + } + ); } diff --git a/cassandane/tiny-tests/List/scan b/cassandane/tiny-tests/List/scan index 2eec964a68..2d095b73cd 100644 --- a/cassandane/tiny-tests/List/scan +++ b/cassandane/tiny-tests/List/scan @@ -2,51 +2,61 @@ use Cassandane::Tiny; sub test_scan - :min_version_3_9 -{ - my ($self) = @_; - - my $store = $self->{store}; - my $imaptalk = $store->get_client(); - - $self->setup_mailbox_structure($imaptalk, [ - [ 'create' => [qw( INBOX.a INBOX.b INBOX.c )] ], - ]); - - xlog $self, "listing..."; - my $res = $imaptalk->list("", "*"); - - $self->assert_mailbox_structure($res, '.', { - 'INBOX' => [qw( \\HasChildren )], - 'INBOX.a' => [qw( \\HasNoChildren )], - 'INBOX.b' => [qw( \\HasNoChildren )], - 'INBOX.c' => [qw( \\HasNoChildren )], - }, 'strict'); - - xlog $self, "Generate some messages"; - $store->set_folder('INBOX.a'); - $self->make_message("foo"); - - $store->set_folder('INBOX.b'); - $self->make_message("bar"); - - $store->set_folder('INBOX.c'); - $self->make_message("baz", body => 'foo\r\n'); - - my @results = {}; - xlog $self, "scan with a selected mailbox"; - $res = $imaptalk->_imap_cmd('SCAN', 0, "list", "", "*", "foo"); - - $self->assert_mailbox_structure($res, '.', { - 'INBOX.a' => [qw( \\HasNoChildren )], - 'INBOX.c' => [qw( \\HasNoChildren )], - }, 'strict'); - - xlog $self, "scan with NO selected mailbox"; - $res = $imaptalk->unselect(); - $res = $imaptalk->_imap_cmd('SCAN', 0, "list", "", "*", "bar"); - - $self->assert_mailbox_structure($res, '.', { - 'INBOX.b' => [qw( \\HasNoChildren )], - }, 'strict'); + : min_version_3_9 { + my ($self) = @_; + + my $store = $self->{store}; + my $imaptalk = $store->get_client(); + + $self->setup_mailbox_structure($imaptalk, + [ [ 'create' => [qw( INBOX.a INBOX.b INBOX.c )] ], ]); + + xlog $self, "listing..."; + my $res = $imaptalk->list("", "*"); + + $self->assert_mailbox_structure( + $res, '.', + { + 'INBOX' => [qw( \\HasChildren )], + 'INBOX.a' => [qw( \\HasNoChildren )], + 'INBOX.b' => [qw( \\HasNoChildren )], + 'INBOX.c' => [qw( \\HasNoChildren )], + }, + 'strict' + ); + + xlog $self, "Generate some messages"; + $store->set_folder('INBOX.a'); + $self->make_message("foo"); + + $store->set_folder('INBOX.b'); + $self->make_message("bar"); + + $store->set_folder('INBOX.c'); + $self->make_message("baz", body => 'foo\r\n'); + + my @results = {}; + xlog $self, "scan with a selected mailbox"; + $res = $imaptalk->_imap_cmd('SCAN', 0, "list", "", "*", "foo"); + + $self->assert_mailbox_structure( + $res, '.', + { + 'INBOX.a' => [qw( \\HasNoChildren )], + 'INBOX.c' => [qw( \\HasNoChildren )], + }, + 'strict' + ); + + xlog $self, "scan with NO selected mailbox"; + $res = $imaptalk->unselect(); + $res = $imaptalk->_imap_cmd('SCAN', 0, "list", "", "*", "bar"); + + $self->assert_mailbox_structure( + $res, '.', + { + 'INBOX.b' => [qw( \\HasNoChildren )], + }, + 'strict' + ); } diff --git a/cassandane/tiny-tests/List/virtdomains_return_subscribed_altns b/cassandane/tiny-tests/List/virtdomains_return_subscribed_altns index 06ff923a0c..cc928c9b1b 100644 --- a/cassandane/tiny-tests/List/virtdomains_return_subscribed_altns +++ b/cassandane/tiny-tests/List/virtdomains_return_subscribed_altns @@ -2,71 +2,80 @@ use Cassandane::Tiny; sub test_virtdomains_return_subscribed_altns - :VirtDomains :UnixHierarchySep :AltNamespace -{ - my ($self) = @_; + : VirtDomains : UnixHierarchySep : AltNamespace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/foo\@example.com"); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user/foo\@example.com"); - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo\@example.com"); - my $footalk = $foostore->get_client(); + my $foostore = $self->{instance}->get_service('imap') + ->create_store(username => "foo\@example.com"); + my $footalk = $foostore->get_client(); - $footalk->create("Drafts"); - $footalk->create("Sent"); - $footalk->create("Trash"); + $footalk->create("Drafts"); + $footalk->create("Sent"); + $footalk->create("Trash"); - $footalk->subscribe("INBOX"); - $footalk->subscribe("Drafts"); - $footalk->subscribe("Sent"); - $footalk->subscribe("Trash"); + $footalk->subscribe("INBOX"); + $footalk->subscribe("Drafts"); + $footalk->subscribe("Sent"); + $footalk->subscribe("Trash"); - $footalk->setmetadata("Drafts", "/private/specialuse", "\\Drafts"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->setmetadata("Drafts", "/private/specialuse", "\\Drafts"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - $footalk->setmetadata("Sent", "/private/specialuse", "\\Sent"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->setmetadata("Sent", "/private/specialuse", "\\Sent"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - my $specialuse = $footalk->list([qw( SPECIAL-USE )], "", "*", - 'RETURN', [qw(SUBSCRIBED)]); + my $specialuse + = $footalk->list([qw( SPECIAL-USE )], "", "*", 'RETURN', [qw(SUBSCRIBED)]); - xlog $self, Dumper $specialuse; - $self->assert_mailbox_structure($specialuse, '/', { - 'Sent' => [qw( \\Sent \\HasNoChildren \\Subscribed )], - 'Drafts' => [qw( \\Drafts \\HasNoChildren \\Subscribed )], - }); + xlog $self, Dumper $specialuse; + $self->assert_mailbox_structure( + $specialuse, + '/', + { + 'Sent' => [qw( \\Sent \\HasNoChildren \\Subscribed )], + 'Drafts' => [qw( \\Drafts \\HasNoChildren \\Subscribed )], + } + ); - $admintalk->create("user/bar\@example.com"); - $admintalk->create("user/bar/shared-folder\@example.com"); # yay bogus domaining - $admintalk->setacl("user/bar/shared-folder\@example.com", - 'foo@example.com' => 'lrswipkxtecd'); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create("user/bar\@example.com"); + $admintalk->create("user/bar/shared-folder\@example.com") + ; # yay bogus domaining + $admintalk->setacl( + "user/bar/shared-folder\@example.com", + 'foo@example.com' => 'lrswipkxtecd' + ); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); - $footalk->subscribe("Other Users/bar/shared-folder"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->subscribe("Other Users/bar/shared-folder"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - $admintalk->create("another-namespace\@example.com"); - $admintalk->create("another-namespace/folder\@example.com"); - $admintalk->setacl("another-namespace/folder\@example.com", - 'foo@example.com' => 'lrswipkxtecd'); + $admintalk->create("another-namespace\@example.com"); + $admintalk->create("another-namespace/folder\@example.com"); + $admintalk->setacl( + "another-namespace/folder\@example.com", + 'foo@example.com' => 'lrswipkxtecd' + ); - $footalk->subscribe("Shared Folders/another-namespace/folder"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->subscribe("Shared Folders/another-namespace/folder"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - my $alldata = $footalk->list("", "*", 'RETURN', [qw(SUBSCRIBED)]); + my $alldata = $footalk->list("", "*", 'RETURN', [qw(SUBSCRIBED)]); - xlog $self, Dumper $alldata; - $self->assert_mailbox_structure($alldata, '/', { - 'INBOX' => [qw( \\HasNoChildren \\Subscribed )], - 'Drafts' => [qw( \\HasNoChildren \\Subscribed )], - 'Sent' => [qw( \\HasNoChildren \\Subscribed )], - 'Trash' => [qw( \\HasNoChildren \\Subscribed )], - 'Other Users/bar/shared-folder' - => [qw( \\HasNoChildren \\Subscribed )], - 'Shared Folders/another-namespace' - => [qw( \\HasChildren )], - 'Shared Folders/another-namespace/folder' - => [qw( \\HasNoChildren \\Subscribed )], - }); + xlog $self, Dumper $alldata; + $self->assert_mailbox_structure( + $alldata, '/', + { + 'INBOX' => [qw( \\HasNoChildren \\Subscribed )], + 'Drafts' => [qw( \\HasNoChildren \\Subscribed )], + 'Sent' => [qw( \\HasNoChildren \\Subscribed )], + 'Trash' => [qw( \\HasNoChildren \\Subscribed )], + 'Other Users/bar/shared-folder' => [qw( \\HasNoChildren \\Subscribed )], + 'Shared Folders/another-namespace' => [qw( \\HasChildren )], + 'Shared Folders/another-namespace/folder' => + [qw( \\HasNoChildren \\Subscribed )], + } + ); } diff --git a/cassandane/tiny-tests/List/virtdomains_return_subscribed_noaltns b/cassandane/tiny-tests/List/virtdomains_return_subscribed_noaltns index 9f2a646113..0413bb3159 100644 --- a/cassandane/tiny-tests/List/virtdomains_return_subscribed_noaltns +++ b/cassandane/tiny-tests/List/virtdomains_return_subscribed_noaltns @@ -2,69 +2,80 @@ use Cassandane::Tiny; sub test_virtdomains_return_subscribed_noaltns - :VirtDomains :UnixHierarchySep :NoAltNameSpace -{ - my ($self) = @_; + : VirtDomains : UnixHierarchySep : NoAltNameSpace { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/foo\@example.com"); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user/foo\@example.com"); - my $foostore = $self->{instance}->get_service('imap')->create_store( - username => "foo\@example.com"); - my $footalk = $foostore->get_client(); + my $foostore = $self->{instance}->get_service('imap') + ->create_store(username => "foo\@example.com"); + my $footalk = $foostore->get_client(); - $footalk->create("INBOX/Drafts"); - $footalk->create("INBOX/Sent"); - $footalk->create("INBOX/Trash"); + $footalk->create("INBOX/Drafts"); + $footalk->create("INBOX/Sent"); + $footalk->create("INBOX/Trash"); - $footalk->subscribe("INBOX"); - $footalk->subscribe("INBOX/Drafts"); - $footalk->subscribe("INBOX/Sent"); - $footalk->subscribe("INBOX/Trash"); + $footalk->subscribe("INBOX"); + $footalk->subscribe("INBOX/Drafts"); + $footalk->subscribe("INBOX/Sent"); + $footalk->subscribe("INBOX/Trash"); - $footalk->setmetadata("INBOX/Drafts", "/private/specialuse", "\\Drafts"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->setmetadata("INBOX/Drafts", "/private/specialuse", "\\Drafts"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - $footalk->setmetadata("INBOX/Sent", "/private/specialuse", "\\Sent"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->setmetadata("INBOX/Sent", "/private/specialuse", "\\Sent"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - my $specialuse = $footalk->list([qw( SPECIAL-USE )], "", "*", - 'RETURN', [qw(SUBSCRIBED)]); + my $specialuse + = $footalk->list([qw( SPECIAL-USE )], "", "*", 'RETURN', [qw(SUBSCRIBED)]); - xlog $self, Dumper $specialuse; - $self->assert_mailbox_structure($specialuse, '/', { - 'INBOX/Sent' => [qw( \\Sent \\HasNoChildren \\Subscribed )], - 'INBOX/Drafts' => [qw( \\Drafts \\HasNoChildren \\Subscribed )], - }); + xlog $self, Dumper $specialuse; + $self->assert_mailbox_structure( + $specialuse, + '/', + { + 'INBOX/Sent' => [qw( \\Sent \\HasNoChildren \\Subscribed )], + 'INBOX/Drafts' => [qw( \\Drafts \\HasNoChildren \\Subscribed )], + } + ); - $admintalk->create("user/bar\@example.com"); - $admintalk->create("user/bar/shared-folder\@example.com"); # yay bogus domaining - $admintalk->setacl("user/bar/shared-folder\@example.com", - 'foo@example.com' => 'lrswipkxtecd'); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create("user/bar\@example.com"); + $admintalk->create("user/bar/shared-folder\@example.com") + ; # yay bogus domaining + $admintalk->setacl( + "user/bar/shared-folder\@example.com", + 'foo@example.com' => 'lrswipkxtecd' + ); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); - $footalk->subscribe("user/bar/shared-folder"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->subscribe("user/bar/shared-folder"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - $admintalk->create("another-namespace\@example.com"); - $admintalk->create("another-namespace/folder\@example.com"); - $admintalk->setacl("another-namespace/folder\@example.com", - 'foo@example.com' => 'lrswipkxtecd'); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create("another-namespace\@example.com"); + $admintalk->create("another-namespace/folder\@example.com"); + $admintalk->setacl( + "another-namespace/folder\@example.com", + 'foo@example.com' => 'lrswipkxtecd' + ); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); - $footalk->subscribe("another-namespace/folder"); - $self->assert_equals('ok', $footalk->get_last_completion_response()); + $footalk->subscribe("another-namespace/folder"); + $self->assert_equals('ok', $footalk->get_last_completion_response()); - my $alldata = $footalk->list("", "*", 'RETURN', [qw(SUBSCRIBED)]); + my $alldata = $footalk->list("", "*", 'RETURN', [qw(SUBSCRIBED)]); - xlog $self, Dumper $alldata; - $self->assert_mailbox_structure($alldata, '/', { - 'INBOX' => [qw( \\HasChildren \\Subscribed )], - 'INBOX/Drafts' => [qw( \\HasNoChildren \\Subscribed )], - 'INBOX/Sent' => [qw( \\HasNoChildren \\Subscribed )], - 'INBOX/Trash' => [qw( \\HasNoChildren \\Subscribed )], - 'user/bar/shared-folder' => [qw( \\HasNoChildren \\Subscribed )], - 'another-namespace' => [qw( \\HasChildren ) ], - 'another-namespace/folder' => [qw( \\HasNoChildren \\Subscribed )], - }); + xlog $self, Dumper $alldata; + $self->assert_mailbox_structure( + $alldata, '/', + { + 'INBOX' => [qw( \\HasChildren \\Subscribed )], + 'INBOX/Drafts' => [qw( \\HasNoChildren \\Subscribed )], + 'INBOX/Sent' => [qw( \\HasNoChildren \\Subscribed )], + 'INBOX/Trash' => [qw( \\HasNoChildren \\Subscribed )], + 'user/bar/shared-folder' => [qw( \\HasNoChildren \\Subscribed )], + 'another-namespace' => [qw( \\HasChildren )], + 'another-namespace/folder' => [qw( \\HasNoChildren \\Subscribed )], + } + ); } diff --git a/cassandane/tiny-tests/Metadata/capabilities b/cassandane/tiny-tests/Metadata/capabilities index 4aadd85ef6..c0c96dfabf 100644 --- a/cassandane/tiny-tests/Metadata/capabilities +++ b/cassandane/tiny-tests/Metadata/capabilities @@ -4,14 +4,13 @@ use Cassandane::Tiny; # # Test the capabilities # -sub test_capabilities -{ - my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); +sub test_capabilities { + my ($self) = @_; + my $imaptalk = $self->{store}->get_client(); - my $caps = $imaptalk->capability(); - xlog $self, "RFC5257 defines capability ANNOTATE-EXPERIMENT-1"; - $self->assert_not_null($caps->{"annotate-experiment-1"}); - xlog $self, "RFC5464 defines capability METADATA"; - $self->assert_not_null($caps->{"metadata"}); + my $caps = $imaptalk->capability(); + xlog $self, "RFC5257 defines capability ANNOTATE-EXPERIMENT-1"; + $self->assert_not_null($caps->{"annotate-experiment-1"}); + xlog $self, "RFC5464 defines capability METADATA"; + $self->assert_not_null($caps->{"metadata"}); } diff --git a/cassandane/tiny-tests/Metadata/comment b/cassandane/tiny-tests/Metadata/comment index 283d5fe700..d590959a1a 100644 --- a/cassandane/tiny-tests/Metadata/comment +++ b/cassandane/tiny-tests/Metadata/comment @@ -1,65 +1,61 @@ #!perl use Cassandane::Tiny; -sub test_comment -{ - my ($self) = @_; - - xlog $self, "testing /shared/comment"; - - my $imaptalk = $self->{store}->get_client(); - my $res; - my $entry = '/shared/comment'; - my $value1 = "Hello World this is a value"; - - xlog $self, "initial value is NIL"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); - - xlog $self, "cannot set the value as ordinary user"; - $imaptalk->setmetadata("", $entry, $value1); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert($imaptalk->get_last_error() =~ m/permission denied/i); - - xlog $self, "can set the value as admin"; - $imaptalk = $self->{adminstore}->get_client(); - $imaptalk->setmetadata("", $entry, $value1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "can get the set value back"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "the annot gives the same value in a new connection"; - $self->{adminstore}->disconnect(); - $imaptalk = $self->{adminstore}->get_client(); - $self->assert($imaptalk->state() == Mail::IMAPTalk::Authenticated); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "can delete value"; - $imaptalk->setmetadata("", $entry, undef); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => undef } - }; - $self->assert_deep_equals($expected, $res); +sub test_comment { + my ($self) = @_; + + xlog $self, "testing /shared/comment"; + + my $imaptalk = $self->{store}->get_client(); + my $res; + my $entry = '/shared/comment'; + my $value1 = "Hello World this is a value"; + + xlog $self, "initial value is NIL"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); + + xlog $self, "cannot set the value as ordinary user"; + $imaptalk->setmetadata("", $entry, $value1); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert($imaptalk->get_last_error() =~ m/permission denied/i); + + xlog $self, "can set the value as admin"; + $imaptalk = $self->{adminstore}->get_client(); + $imaptalk->setmetadata("", $entry, $value1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "can get the set value back"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "the annot gives the same value in a new connection"; + $self->{adminstore}->disconnect(); + $imaptalk = $self->{adminstore}->get_client(); + $self->assert($imaptalk->state() == Mail::IMAPTalk::Authenticated); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "can delete value"; + $imaptalk->setmetadata("", $entry, undef); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => undef } }; + $self->assert_deep_equals($expected, $res); } diff --git a/cassandane/tiny-tests/Metadata/comment_repl b/cassandane/tiny-tests/Metadata/comment_repl index 0a40fb1ba7..148a940916 100644 --- a/cassandane/tiny-tests/Metadata/comment_repl +++ b/cassandane/tiny-tests/Metadata/comment_repl @@ -2,81 +2,85 @@ use Cassandane::Tiny; sub test_comment_repl - :Replication :SyncLog :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing /shared/comment"; - - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - - $self->assert_not_null($self->{replica}); - - my $imaptalk = $self->{master_store}->get_client(); - - my $res; - my $entry = '/shared/comment'; - my $value1 = "Hello World this is a value"; - - xlog $self, "initial value is NIL"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); - - xlog $self, "can set the value as admin"; - $imaptalk = $self->{adminstore}->get_client(); - $imaptalk->setmetadata("", $entry, $value1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "replica value is NIL"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); - - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); - - xlog $self, "the annot gives the same value on the replica"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "can delete value"; - $imaptalk = $self->{adminstore}->get_client(); - $imaptalk->setmetadata("", $entry, undef); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => undef } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "run replication to clear annot"; - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); - - xlog $self, "replica value is NIL"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); + : Replication : SyncLog : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing /shared/comment"; + + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + + $self->assert_not_null($self->{replica}); + + my $imaptalk = $self->{master_store}->get_client(); + + my $res; + my $entry = '/shared/comment'; + my $value1 = "Hello World this is a value"; + + xlog $self, "initial value is NIL"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); + + xlog $self, "can set the value as admin"; + $imaptalk = $self->{adminstore}->get_client(); + $imaptalk->setmetadata("", $entry, $value1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "replica value is NIL"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); + + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); + + xlog $self, "the annot gives the same value on the replica"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "can delete value"; + $imaptalk = $self->{adminstore}->get_client(); + $imaptalk->setmetadata("", $entry, undef); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => undef } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "run replication to clear annot"; + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); + + xlog $self, "replica value is NIL"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/copy_messages b/cassandane/tiny-tests/Metadata/copy_messages index 435e09740d..f5569bbb1d 100644 --- a/cassandane/tiny-tests/Metadata/copy_messages +++ b/cassandane/tiny-tests/Metadata/copy_messages @@ -1,84 +1,82 @@ #!perl use Cassandane::Tiny; -sub test_copy_messages -{ - my ($self) = @_; - - xlog $self, "testing COPY with message scope annotations (BZ3528)"; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $from_folder = 'INBOX.from'; - my $to_folder = 'INBOX.to'; - - $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Create subfolders to copy from and to"; - my $store = $self->{store}; - my $talk = $store->get_client(); - $talk->create($from_folder) - or die "Cannot create mailbox $from_folder: $@"; - $talk->create($to_folder) - or die "Cannot create mailbox $to_folder: $@"; - - $store->set_folder($from_folder); - - my @data_by_uid = ( - undef, - # data thanks to hipsteripsum.me - "american apparel", - "mixtape aesthetic", - "organic quinoa" - ); - - xlog $self, "Append some messages and store annotations"; - my %exp; - my $uid = 1; - while (defined $data_by_uid[$uid]) - { - my $data = $data_by_uid[$uid]; - my $msg = $self->make_message("Message $uid"); - $msg->set_attribute('uid', $uid); - $msg->set_annotation($entry, $attrib, $data); - $exp{$uid} = $msg; - $self->set_msg_annotation(undef, $uid, $entry, $attrib, $data); - $uid++; - } - - xlog $self, "Check the annotations are there"; - $self->check_messages(\%exp); - - xlog $self, "COPY the messages"; - $talk = $store->get_client(); - $talk->copy('1:*', $to_folder); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Messages are now in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Messages are still in the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Delete the messages from the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $talk = $store->get_client(); - $talk->store('1:*', '+flags', '(\\Deleted)'); - $talk->expunge(); - - xlog $self, "Messages are gone from the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $self->check_messages({}); - - xlog $self, "Messages are still in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); +sub test_copy_messages { + my ($self) = @_; + + xlog $self, "testing COPY with message scope annotations (BZ3528)"; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $from_folder = 'INBOX.from'; + my $to_folder = 'INBOX.to'; + + $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Create subfolders to copy from and to"; + my $store = $self->{store}; + my $talk = $store->get_client(); + $talk->create($from_folder) + or die "Cannot create mailbox $from_folder: $@"; + $talk->create($to_folder) + or die "Cannot create mailbox $to_folder: $@"; + + $store->set_folder($from_folder); + + my @data_by_uid = ( + undef, + # data thanks to hipsteripsum.me + "american apparel", + "mixtape aesthetic", + "organic quinoa" + ); + + xlog $self, "Append some messages and store annotations"; + my %exp; + my $uid = 1; + while (defined $data_by_uid[$uid]) { + my $data = $data_by_uid[$uid]; + my $msg = $self->make_message("Message $uid"); + $msg->set_attribute('uid', $uid); + $msg->set_annotation($entry, $attrib, $data); + $exp{$uid} = $msg; + $self->set_msg_annotation(undef, $uid, $entry, $attrib, $data); + $uid++; + } + + xlog $self, "Check the annotations are there"; + $self->check_messages(\%exp); + + xlog $self, "COPY the messages"; + $talk = $store->get_client(); + $talk->copy('1:*', $to_folder); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Messages are now in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Messages are still in the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Delete the messages from the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $talk = $store->get_client(); + $talk->store('1:*', '+flags', '(\\Deleted)'); + $talk->expunge(); + + xlog $self, "Messages are gone from the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $self->check_messages({}); + + xlog $self, "Messages are still in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); } diff --git a/cassandane/tiny-tests/Metadata/createspecialuse b/cassandane/tiny-tests/Metadata/createspecialuse index 6a8ddddcd8..e1320eea54 100644 --- a/cassandane/tiny-tests/Metadata/createspecialuse +++ b/cassandane/tiny-tests/Metadata/createspecialuse @@ -2,25 +2,27 @@ use Cassandane::Tiny; sub test_createspecialuse - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - xlog $self, "testing create specialuse"; + xlog $self, "testing create specialuse"; - my $imaptalk = $self->{store}->get_client(); - my $res; - my $entry = '/private/specialuse'; - my $folder = "INBOX.Archive"; - my $use = "\\Archive"; - $imaptalk->create($folder, "(USE ($use))") - or die "Cannot create mailbox $folder with special-use $use: $@"; + my $imaptalk = $self->{store}->get_client(); + my $res; + my $entry = '/private/specialuse'; + my $folder = "INBOX.Archive"; + my $use = "\\Archive"; + $imaptalk->create($folder, "(USE ($use))") + or die "Cannot create mailbox $folder with special-use $use: $@"; - xlog $self, "initial value for $folder is $use"; - $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - $folder => { $entry => $use } - }, $res); + xlog $self, "initial value for $folder is $use"; + $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + $folder => { $entry => $use } + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/cvt_cyrusdb b/cassandane/tiny-tests/Metadata/cvt_cyrusdb index 7ba9d1b12b..21addac9f7 100644 --- a/cassandane/tiny-tests/Metadata/cvt_cyrusdb +++ b/cassandane/tiny-tests/Metadata/cvt_cyrusdb @@ -1,121 +1,118 @@ #!perl use Cassandane::Tiny; -sub test_cvt_cyrusdb -{ - my ($self) = @_; - - xlog $self, "test cvt_cyrusdb between annotation db and flat files (BZ2686)"; - - my $folder = 'INBOX'; - my $fentry = '/private/comment'; - my $mentry = '/comment'; - my $mattrib = 'value.priv'; - my $evilchars = " \t\r\n\0\001"; - - my $store = $self->{store}; - $store->set_fetch_attributes('uid', "annotation ($mentry $mattrib)"); - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - xlog $self, "store annotations"; - my $data = $self->make_random_data(2, maxreps => 20, separators => $evilchars); - $talk->setmetadata($folder, $fentry, $data); +sub test_cvt_cyrusdb { + my ($self) = @_; + + xlog $self, "test cvt_cyrusdb between annotation db and flat files (BZ2686)"; + + my $folder = 'INBOX'; + my $fentry = '/private/comment'; + my $mentry = '/comment'; + my $mattrib = 'value.priv'; + my $evilchars = " \t\r\n\0\001"; + + my $store = $self->{store}; + $store->set_fetch_attributes('uid', "annotation ($mentry $mattrib)"); + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + xlog $self, "store annotations"; + my $data + = $self->make_random_data(2, maxreps => 20, separators => $evilchars); + $talk->setmetadata($folder, $fentry, $data); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "add some messages"; + my $uid = 1; + my %exp; + for (1 .. 10) { + my $msg = $self->make_message("Message $_"); + $exp{$uid} = $msg; + $msg->set_attribute('uid', $uid); + my $data + = $self->make_random_data(7, maxreps => 20, separators => $evilchars); + $msg->set_annotation($mentry, $mattrib, $data); + $talk->store('' . $uid, 'annotation', [ $mentry, [ $mattrib, $data ] ]); $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uid++; + } - xlog $self, "add some messages"; - my $uid = 1; - my %exp; - for (1..10) + xlog $self, "Check the messages are all there"; + $self->check_messages(\%exp); + + xlog $self, "Check the mailbox annotation is still there"; + my $res = $talk->getmetadata($folder, $fentry); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( { - my $msg = $self->make_message("Message $_"); - $exp{$uid} = $msg; - $msg->set_attribute('uid', $uid); - my $data = $self->make_random_data(7, maxreps => 20, separators => $evilchars); - $msg->set_annotation($mentry, $mattrib, $data); - $talk->store('' . $uid, 'annotation', - [$mentry, [$mattrib, $data]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $uid++; - } - - xlog $self, "Check the messages are all there"; - $self->check_messages(\%exp); - - xlog $self, "Check the mailbox annotation is still there"; - my $res = $talk->getmetadata($folder, $fentry); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => $data } - }, $res); - - xlog $self, "Shut down the instance"; - $self->{store}->disconnect(); - $self->{adminstore}->disconnect(); - $talk = undef; - $admintalk = undef; - $self->{instance}->stop(); - $self->{instance}->{re_use_dir} = 1; - - xlog $self, "Convert the global annotation db to flat"; - my $basedir = $self->{instance}->{basedir}; - my $global_db = "$basedir/conf/annotations.db"; - my $global_flat = "$basedir/xann.txt"; - my $format = $self->{instance}->{config}->get('annotation_db'); - $format = $format // 'twoskip'; - - $self->assert_not_file_test($global_flat, '-f'); - $self->{instance}->run_command({ cyrus => 1 }, - 'cvt_cyrusdb', - $global_db, $format, - $global_flat, 'flat'); - $self->assert_file_test($global_flat, '-f'); - - xlog $self, "Convert the mailbox annotation db to flat"; - my $datapath = $self->{instance}->folder_to_directory('user.cassandane'); - my $mailbox_db = "$datapath/cyrus.annotations"; - my $mailbox_flat = "$basedir/xcassann.txt"; - - $self->assert_not_file_test($mailbox_flat, '-f'); - $self->{instance}->run_command({ cyrus => 1 }, - 'cvt_cyrusdb', - $mailbox_db, $format, - $mailbox_flat, 'flat'); - $self->assert_file_test($mailbox_flat, '-f'); - - xlog $self, "Move aside the original annotation dbs"; - rename($global_db, "$global_db.NOT") - or die "Cannot rename $global_db to $global_db.NOT: $!"; - rename($mailbox_db, "$mailbox_db.NOT") - or die "Cannot rename $mailbox_db to $mailbox_db.NOT: $!"; - $self->assert_not_file_test($global_db, '-f'); - $self->assert_not_file_test($mailbox_db, '-f'); - - xlog $self, "restore the global annotation db from flat"; - $self->{instance}->run_command({ cyrus => 1 }, - 'cvt_cyrusdb', - $global_flat, 'flat', - $global_db, $format); - $self->assert_file_test($global_db, '-f'); - - xlog $self, "restore the mailbox annotation db from flat"; - $self->{instance}->run_command({ cyrus => 1 }, - 'cvt_cyrusdb', - $mailbox_flat, 'flat', - $mailbox_db, $format); - $self->assert_file_test($mailbox_db, '-f'); - - xlog $self, "Start the instance up again and reconnect"; - $self->{instance}->start(); - $talk = $store->get_client(); - - xlog $self, "Check the messages are still all there"; - $self->check_messages(\%exp); - - xlog $self, "Check the mailbox annotation is still there"; - $res = $talk->getmetadata($folder, $fentry); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => $data } - }, $res); + $folder => { $fentry => $data } + }, + $res + ); + + xlog $self, "Shut down the instance"; + $self->{store}->disconnect(); + $self->{adminstore}->disconnect(); + $talk = undef; + $admintalk = undef; + $self->{instance}->stop(); + $self->{instance}->{re_use_dir} = 1; + + xlog $self, "Convert the global annotation db to flat"; + my $basedir = $self->{instance}->{basedir}; + my $global_db = "$basedir/conf/annotations.db"; + my $global_flat = "$basedir/xann.txt"; + my $format = $self->{instance}->{config}->get('annotation_db'); + $format = $format // 'twoskip'; + + $self->assert_not_file_test($global_flat, '-f'); + $self->{instance}->run_command({ cyrus => 1 }, + 'cvt_cyrusdb', $global_db, $format, $global_flat, 'flat'); + $self->assert_file_test($global_flat, '-f'); + + xlog $self, "Convert the mailbox annotation db to flat"; + my $datapath = $self->{instance}->folder_to_directory('user.cassandane'); + my $mailbox_db = "$datapath/cyrus.annotations"; + my $mailbox_flat = "$basedir/xcassann.txt"; + + $self->assert_not_file_test($mailbox_flat, '-f'); + $self->{instance}->run_command({ cyrus => 1 }, + 'cvt_cyrusdb', $mailbox_db, $format, $mailbox_flat, 'flat'); + $self->assert_file_test($mailbox_flat, '-f'); + + xlog $self, "Move aside the original annotation dbs"; + rename($global_db, "$global_db.NOT") + or die "Cannot rename $global_db to $global_db.NOT: $!"; + rename($mailbox_db, "$mailbox_db.NOT") + or die "Cannot rename $mailbox_db to $mailbox_db.NOT: $!"; + $self->assert_not_file_test($global_db, '-f'); + $self->assert_not_file_test($mailbox_db, '-f'); + + xlog $self, "restore the global annotation db from flat"; + $self->{instance}->run_command({ cyrus => 1 }, + 'cvt_cyrusdb', $global_flat, 'flat', $global_db, $format); + $self->assert_file_test($global_db, '-f'); + + xlog $self, "restore the mailbox annotation db from flat"; + $self->{instance}->run_command({ cyrus => 1 }, + 'cvt_cyrusdb', $mailbox_flat, 'flat', $mailbox_db, $format); + $self->assert_file_test($mailbox_db, '-f'); + + xlog $self, "Start the instance up again and reconnect"; + $self->{instance}->start(); + $talk = $store->get_client(); + + xlog $self, "Check the messages are still all there"; + $self->check_messages(\%exp); + + xlog $self, "Check the mailbox annotation is still there"; + $res = $talk->getmetadata($folder, $fentry); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( + { + $folder => { $fentry => $data } + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/embedded_nuls b/cassandane/tiny-tests/Metadata/embedded_nuls index 8ae6fafaa3..ce09e83c29 100644 --- a/cassandane/tiny-tests/Metadata/embedded_nuls +++ b/cassandane/tiny-tests/Metadata/embedded_nuls @@ -1,44 +1,43 @@ #!perl use Cassandane::Tiny; -sub test_embedded_nuls -{ - my ($self) = @_; - - xlog $self, "testing getting and setting embedded NULs"; - - my $imaptalk = $self->{store}->get_client(); - my $folder = 'INBOX.test_embedded_nuls'; - my $entry = '/private/comment'; - my $binary = "Hello\0World"; - - xlog $self, "create a temporary mailbox"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; - - xlog $self, "initially, NIL is reported"; - my $res = $imaptalk->getmetadata($folder, $entry) - or die "Cannot get metadata: $@"; - $self->assert_num_equals(1, scalar keys %$res); - $self->assert_null($res->{$folder}{$entry}); - - xlog $self, "set and then get the same back again"; - $imaptalk->setmetadata($folder, $entry, $binary); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals($binary, $res->{$folder}{$entry}); - - xlog $self, "remove it again"; - $imaptalk->setmetadata($folder, $entry, undef); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "check it's gone now"; - $res = $imaptalk->getmetadata($folder, $entry) - or die "Cannot get metadata: $@"; - $self->assert_num_equals(1, scalar keys %$res); - $self->assert_null($res->{$folder}{$entry}); - - xlog $self, "clean up temporary mailbox"; - $imaptalk->delete($folder) - or die "Cannot delete mailbox $folder: $@"; +sub test_embedded_nuls { + my ($self) = @_; + + xlog $self, "testing getting and setting embedded NULs"; + + my $imaptalk = $self->{store}->get_client(); + my $folder = 'INBOX.test_embedded_nuls'; + my $entry = '/private/comment'; + my $binary = "Hello\0World"; + + xlog $self, "create a temporary mailbox"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; + + xlog $self, "initially, NIL is reported"; + my $res = $imaptalk->getmetadata($folder, $entry) + or die "Cannot get metadata: $@"; + $self->assert_num_equals(1, scalar keys %$res); + $self->assert_null($res->{$folder}{$entry}); + + xlog $self, "set and then get the same back again"; + $imaptalk->setmetadata($folder, $entry, $binary); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals($binary, $res->{$folder}{$entry}); + + xlog $self, "remove it again"; + $imaptalk->setmetadata($folder, $entry, undef); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "check it's gone now"; + $res = $imaptalk->getmetadata($folder, $entry) + or die "Cannot get metadata: $@"; + $self->assert_num_equals(1, scalar keys %$res); + $self->assert_null($res->{$folder}{$entry}); + + xlog $self, "clean up temporary mailbox"; + $imaptalk->delete($folder) + or die "Cannot delete mailbox $folder: $@"; } diff --git a/cassandane/tiny-tests/Metadata/expunge_messages b/cassandane/tiny-tests/Metadata/expunge_messages index 7f779cd721..afbd5d6701 100644 --- a/cassandane/tiny-tests/Metadata/expunge_messages +++ b/cassandane/tiny-tests/Metadata/expunge_messages @@ -1,112 +1,115 @@ #!perl use Cassandane::Tiny; -sub test_expunge_messages -{ - my ($self) = @_; +sub test_expunge_messages { + my ($self) = @_; - xlog $self, "testing expunge of messages with message scope"; - xlog $self, "annotations [IRIS-1553]"; + xlog $self, "testing expunge of messages with message scope"; + xlog $self, "annotations [IRIS-1553]"; - my $entry = '/comment'; - my $attrib = 'value.priv'; + my $entry = '/comment'; + my $attrib = 'value.priv'; - $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - my $talk = $self->{store}->get_client(); - $talk->uid(1); + $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + my $talk = $self->{store}->get_client(); + $talk->uid(1); - my @data_by_uid = ( - undef, - # data thanks to hipsteripsum.me - "polaroid seitan", - "bicycle rights", - "bushwick gastropub" - ); + my @data_by_uid = ( + undef, + # data thanks to hipsteripsum.me + "polaroid seitan", + "bicycle rights", + "bushwick gastropub" + ); - xlog $self, "Append some messages and store annotations"; - my %exp; - my $uid = 1; - while (defined $data_by_uid[$uid]) - { - my $data = $data_by_uid[$uid]; - my $msg = $self->make_message("Message $uid"); - $msg->set_annotation($entry, $attrib, $data); - $exp{$uid} = $msg; - $self->set_msg_annotation(undef, $uid, $entry, $attrib, $data); - $uid++; - } + xlog $self, "Append some messages and store annotations"; + my %exp; + my $uid = 1; + while (defined $data_by_uid[$uid]) { + my $data = $data_by_uid[$uid]; + my $msg = $self->make_message("Message $uid"); + $msg->set_annotation($entry, $attrib, $data); + $exp{$uid} = $msg; + $self->set_msg_annotation(undef, $uid, $entry, $attrib, $data); + $uid++; + } - xlog $self, "Check the annotations are there"; - $self->check_messages(\%exp, keyed_on => 'uid'); + xlog $self, "Check the annotations are there"; + $self->check_messages(\%exp, keyed_on => 'uid'); - xlog $self, "Check the annotations are in the DB too"; - my $r = $self->list_annotations(scope => 'message'); - $self->assert_deep_equals([ - { - mboxname => 'user.cassandane', - uid => 1, - entry => $entry, - userid => 'cassandane', - data => $data_by_uid[1] - }, - { - mboxname => 'user.cassandane', - uid => 2, - entry => $entry, - userid => 'cassandane', - data => $data_by_uid[2] - }, - { - mboxname => 'user.cassandane', - uid => 3, - entry => $entry, - userid => 'cassandane', - data => $data_by_uid[3] - } - ], $r); + xlog $self, "Check the annotations are in the DB too"; + my $r = $self->list_annotations(scope => 'message'); + $self->assert_deep_equals( + [ + { + mboxname => 'user.cassandane', + uid => 1, + entry => $entry, + userid => 'cassandane', + data => $data_by_uid[1] + }, + { + mboxname => 'user.cassandane', + uid => 2, + entry => $entry, + userid => 'cassandane', + data => $data_by_uid[2] + }, + { + mboxname => 'user.cassandane', + uid => 3, + entry => $entry, + userid => 'cassandane', + data => $data_by_uid[3] + } + ], + $r + ); - $uid = 1; - while (defined $data_by_uid[$uid]) - { - xlog $self, "Delete message $uid"; - $talk->store($uid, '+flags', '(\\Deleted)'); - $talk->expunge(); + $uid = 1; + while (defined $data_by_uid[$uid]) { + xlog $self, "Delete message $uid"; + $talk->store($uid, '+flags', '(\\Deleted)'); + $talk->expunge(); - xlog $self, "Check the annotation is gone"; - delete $exp{$uid}; - $self->check_messages(\%exp); - $uid++; - } + xlog $self, "Check the annotation is gone"; + delete $exp{$uid}; + $self->check_messages(\%exp); + $uid++; + } - xlog $self, "Check the annotations are still in the DB"; - $r = $self->list_annotations(scope => 'message'); - $self->assert_deep_equals([ - { - mboxname => 'user.cassandane', - uid => 1, - entry => $entry, - userid => 'cassandane', - data => $data_by_uid[1] - }, - { - mboxname => 'user.cassandane', - uid => 2, - entry => $entry, - userid => 'cassandane', - data => $data_by_uid[2] - }, - { - mboxname => 'user.cassandane', - uid => 3, - entry => $entry, - userid => 'cassandane', - data => $data_by_uid[3] - } - ], $r); + xlog $self, "Check the annotations are still in the DB"; + $r = $self->list_annotations(scope => 'message'); + $self->assert_deep_equals( + [ + { + mboxname => 'user.cassandane', + uid => 1, + entry => $entry, + userid => 'cassandane', + data => $data_by_uid[1] + }, + { + mboxname => 'user.cassandane', + uid => 2, + entry => $entry, + userid => 'cassandane', + data => $data_by_uid[2] + }, + { + mboxname => 'user.cassandane', + uid => 3, + entry => $entry, + userid => 'cassandane', + data => $data_by_uid[3] + } + ], + $r + ); - $self->run_delayed_expunge(); + $self->run_delayed_expunge(); - xlog $self, "Check the annotations are gone from the DB"; - $r = $self->list_annotations(scope => 'message'); - $self->assert_deep_equals([], $r); + xlog $self, "Check the annotations are gone from the DB"; + $r = $self->list_annotations(scope => 'message'); + $self->assert_deep_equals([], $r); } diff --git a/cassandane/tiny-tests/Metadata/folder_delete_mboxa_dmdel b/cassandane/tiny-tests/Metadata/folder_delete_mboxa_dmdel index f3b0befe09..52a4be52d2 100644 --- a/cassandane/tiny-tests/Metadata/folder_delete_mboxa_dmdel +++ b/cassandane/tiny-tests/Metadata/folder_delete_mboxa_dmdel @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_folder_delete_mboxa_dmdel - :DelayedDelete -{ - my ($self) = @_; + : DelayedDelete { + my ($self) = @_; - xlog $self, "test that per-mailbox GETMETADATA annotations are"; - xlog $self, "deleted with the mailbox; delete_mode = delayed (BZ2685)"; + xlog $self, "test that per-mailbox GETMETADATA annotations are"; + xlog $self, "deleted with the mailbox; delete_mode = delayed (BZ2685)"; - $self->assert_str_equals('delayed', - $self->{instance}->{config}->get('delete_mode')); + $self->assert_str_equals('delayed', + $self->{instance}->{config}->get('delete_mode')); - $self->folder_delete_mboxa_common(); + $self->folder_delete_mboxa_common(); } diff --git a/cassandane/tiny-tests/Metadata/folder_delete_mboxa_dmimm b/cassandane/tiny-tests/Metadata/folder_delete_mboxa_dmimm index 5e09327d77..3b3d5fbb8c 100644 --- a/cassandane/tiny-tests/Metadata/folder_delete_mboxa_dmimm +++ b/cassandane/tiny-tests/Metadata/folder_delete_mboxa_dmimm @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_folder_delete_mboxa_dmimm - :ImmediateDelete -{ - my ($self) = @_; + : ImmediateDelete { + my ($self) = @_; - xlog $self, "test that per-mailbox GETMETADATA annotations are"; - xlog $self, "deleted with the mailbox; delete_mode = immediate (BZ2685)"; + xlog $self, "test that per-mailbox GETMETADATA annotations are"; + xlog $self, "deleted with the mailbox; delete_mode = immediate (BZ2685)"; - $self->assert_str_equals('immediate', - $self->{instance}->{config}->get('delete_mode')); + $self->assert_str_equals('immediate', + $self->{instance}->{config}->get('delete_mode')); - $self->folder_delete_mboxa_common(); + $self->folder_delete_mboxa_common(); } diff --git a/cassandane/tiny-tests/Metadata/folder_delete_mboxm_dmdel b/cassandane/tiny-tests/Metadata/folder_delete_mboxm_dmdel index 64e14f4545..c37c34b6cf 100644 --- a/cassandane/tiny-tests/Metadata/folder_delete_mboxm_dmdel +++ b/cassandane/tiny-tests/Metadata/folder_delete_mboxm_dmdel @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_folder_delete_mboxm_dmdel - :DelayedDelete -{ - my ($self) = @_; + : DelayedDelete { + my ($self) = @_; - xlog $self, "test that per-mailbox GETMETADATA annotations are"; - xlog $self, "deleted with the mailbox; delete_mode = delayed (BZ2685)"; + xlog $self, "test that per-mailbox GETMETADATA annotations are"; + xlog $self, "deleted with the mailbox; delete_mode = delayed (BZ2685)"; - $self->assert_str_equals('delayed', - $self->{instance}->{config}->get('delete_mode')); + $self->assert_str_equals('delayed', + $self->{instance}->{config}->get('delete_mode')); - $self->folder_delete_mboxm_common(); + $self->folder_delete_mboxm_common(); } diff --git a/cassandane/tiny-tests/Metadata/folder_delete_mboxm_dmimm b/cassandane/tiny-tests/Metadata/folder_delete_mboxm_dmimm index 381900bc70..92cd73adaf 100644 --- a/cassandane/tiny-tests/Metadata/folder_delete_mboxm_dmimm +++ b/cassandane/tiny-tests/Metadata/folder_delete_mboxm_dmimm @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_folder_delete_mboxm_dmimm - :ImmediateDelete -{ - my ($self) = @_; + : ImmediateDelete { + my ($self) = @_; - xlog $self, "test that per-mailbox GETMETADATA annotations are"; - xlog $self, "deleted with the mailbox; delete_mode = immediate (BZ2685)"; + xlog $self, "test that per-mailbox GETMETADATA annotations are"; + xlog $self, "deleted with the mailbox; delete_mode = immediate (BZ2685)"; - $self->assert_str_equals('immediate', - $self->{instance}->{config}->get('delete_mode')); + $self->assert_str_equals('immediate', + $self->{instance}->{config}->get('delete_mode')); - $self->folder_delete_mboxm_common(); + $self->folder_delete_mboxm_common(); } diff --git a/cassandane/tiny-tests/Metadata/folder_delete_msg_dmdel b/cassandane/tiny-tests/Metadata/folder_delete_msg_dmdel index 7503b20640..bad68d2c02 100644 --- a/cassandane/tiny-tests/Metadata/folder_delete_msg_dmdel +++ b/cassandane/tiny-tests/Metadata/folder_delete_msg_dmdel @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_folder_delete_msg_dmdel - :DelayedDelete -{ - my ($self) = @_; + : DelayedDelete { + my ($self) = @_; - xlog $self, "test that per-message annotations are"; - xlog $self, "deleted with the mailbox; delete_mode = delayed (BZ2685)"; + xlog $self, "test that per-message annotations are"; + xlog $self, "deleted with the mailbox; delete_mode = delayed (BZ2685)"; - $self->assert_str_equals('delayed', - $self->{instance}->{config}->get('delete_mode')); + $self->assert_str_equals('delayed', + $self->{instance}->{config}->get('delete_mode')); - $self->folder_delete_msg_common(); + $self->folder_delete_msg_common(); } diff --git a/cassandane/tiny-tests/Metadata/folder_delete_msg_dmimm b/cassandane/tiny-tests/Metadata/folder_delete_msg_dmimm index 77b984bbec..ee2a80af5f 100644 --- a/cassandane/tiny-tests/Metadata/folder_delete_msg_dmimm +++ b/cassandane/tiny-tests/Metadata/folder_delete_msg_dmimm @@ -2,15 +2,14 @@ use Cassandane::Tiny; sub test_folder_delete_msg_dmimm - :ImmediateDelete -{ - my ($self) = @_; + : ImmediateDelete { + my ($self) = @_; - xlog $self, "test that per-message annotations are"; - xlog $self, "deleted with the mailbox; delete_mode = immediate (BZ2685)"; + xlog $self, "test that per-message annotations are"; + xlog $self, "deleted with the mailbox; delete_mode = immediate (BZ2685)"; - $self->assert_str_equals('immediate', - $self->{instance}->{config}->get('delete_mode')); + $self->assert_str_equals('immediate', + $self->{instance}->{config}->get('delete_mode')); - $self->folder_delete_msg_common(); + $self->folder_delete_msg_common(); } diff --git a/cassandane/tiny-tests/Metadata/folder_move_partition b/cassandane/tiny-tests/Metadata/folder_move_partition index a4cc449f6d..529ef209ac 100644 --- a/cassandane/tiny-tests/Metadata/folder_move_partition +++ b/cassandane/tiny-tests/Metadata/folder_move_partition @@ -2,45 +2,53 @@ use Cassandane::Tiny; sub test_folder_move_partition - :Partition2 -{ - my ($self) = @_; - - my $admintalk = $self->{adminstore}->get_client(); - - my $folder = 'user.asd'; - my $entry = '/shared/vendor/cmu/cyrus-imapd/expire'; - my $value = '13'; - - $admintalk->create($folder); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); - - # annotation should be set yet - my $res = $admintalk->getmetadata($folder, $entry); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals({ - $entry => undef, - }, $res->{$folder}); - - # set an annotation - $admintalk->setmetadata($folder, $entry, $value); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); - - # annotation should be set now - $res = $admintalk->getmetadata($folder, $entry); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals({ - $entry => $value, - }, $res->{$folder}); - - # move the mailbox to the other partition - $admintalk->rename($folder, $folder, 'p2'); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); - - # annotation should still be set - $res = $admintalk->getmetadata($folder, $entry); - $self->assert_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals({ - $entry => $value, - }, $res->{$folder}); + : Partition2 { + my ($self) = @_; + + my $admintalk = $self->{adminstore}->get_client(); + + my $folder = 'user.asd'; + my $entry = '/shared/vendor/cmu/cyrus-imapd/expire'; + my $value = '13'; + + $admintalk->create($folder); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); + + # annotation should be set yet + my $res = $admintalk->getmetadata($folder, $entry); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $entry => undef, + }, + $res->{$folder} + ); + + # set an annotation + $admintalk->setmetadata($folder, $entry, $value); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); + + # annotation should be set now + $res = $admintalk->getmetadata($folder, $entry); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $entry => $value, + }, + $res->{$folder} + ); + + # move the mailbox to the other partition + $admintalk->rename($folder, $folder, 'p2'); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); + + # annotation should still be set + $res = $admintalk->getmetadata($folder, $entry); + $self->assert_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $entry => $value, + }, + $res->{$folder} + ); } diff --git a/cassandane/tiny-tests/Metadata/getmetadata_depth b/cassandane/tiny-tests/Metadata/getmetadata_depth index 24964a2dcd..7fe61a67fd 100644 --- a/cassandane/tiny-tests/Metadata/getmetadata_depth +++ b/cassandane/tiny-tests/Metadata/getmetadata_depth @@ -2,77 +2,81 @@ use Cassandane::Tiny; sub test_getmetadata_depth - :AnnotationAllowUndefined -{ - my ($self) = @_; + : AnnotationAllowUndefined { + my ($self) = @_; - xlog $self, "test the GETMETADATA command with DEPTH option"; + xlog $self, "test the GETMETADATA command with DEPTH option"; - my $imaptalk = $self->{store}->get_client(); - # data thanks to hipsteripsum.me - my $folder = 'INBOX.denim'; - my %entries = ( - '/shared/selvage' => 'locavore', - '/shared/selvage/portland' => 'ennui', - '/shared/selvage/leggings' => 'scenester', - '/shared/selvage/portland/mustache' => 'terry richardson', - '/shared/selvage/portland/mustache/american' => 'messenger bag', - '/shared/selvage/portland/mustache/american/apparel' => 'street art', - ); - my $rootentry = '/shared/selvage'; - my $res; + my $imaptalk = $self->{store}->get_client(); + # data thanks to hipsteripsum.me + my $folder = 'INBOX.denim'; + my %entries = ( + '/shared/selvage' => 'locavore', + '/shared/selvage/portland' => 'ennui', + '/shared/selvage/leggings' => 'scenester', + '/shared/selvage/portland/mustache' => 'terry richardson', + '/shared/selvage/portland/mustache/american' => 'messenger bag', + '/shared/selvage/portland/mustache/american/apparel' => 'street art', + ); + my $rootentry = '/shared/selvage'; + my $res; - xlog $self, "Create folder"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; + xlog $self, "Create folder"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; - xlog $self, "Setup metadata"; - foreach my $entry (sort keys %entries) - { - $imaptalk->setmetadata($folder, $entry, $entries{$entry}) - or die "Cannot setmetadata: $@"; - } + xlog $self, "Setup metadata"; + foreach my $entry (sort keys %entries) { + $imaptalk->setmetadata($folder, $entry, $entries{$entry}) + or die "Cannot setmetadata: $@"; + } - xlog $self, "Getting metadata with no DEPTH"; - $res = getmetadata($imaptalk, $folder, $rootentry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { $rootentry => $entries{$rootentry} } } , $res); + xlog $self, "Getting metadata with no DEPTH"; + $res = getmetadata($imaptalk, $folder, $rootentry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { $folder => { $rootentry => $entries{$rootentry} } }, $res); - xlog $self, "Getting metadata with DEPTH 0 in the right place"; - $res = getmetadata($imaptalk, $folder, [ DEPTH => 0 ], $rootentry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { $rootentry => $entries{$rootentry} } } , $res); + xlog $self, "Getting metadata with DEPTH 0 in the right place"; + $res = getmetadata($imaptalk, $folder, [ DEPTH => 0 ], $rootentry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { $folder => { $rootentry => $entries{$rootentry} } }, $res); - xlog $self, "Getting metadata with DEPTH 1 in the right place"; - my @subset = ( qw(/shared/selvage /shared/selvage/portland /shared/selvage/leggings) ); - $res = getmetadata($imaptalk, $folder, [ DEPTH => 1 ], $rootentry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { map { $_ => $entries{$_} } @subset } }, $res); + xlog $self, "Getting metadata with DEPTH 1 in the right place"; + my @subset + = (qw(/shared/selvage /shared/selvage/portland /shared/selvage/leggings)); + $res = getmetadata($imaptalk, $folder, [ DEPTH => 1 ], $rootentry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { $folder => { map { $_ => $entries{$_} } @subset } }, $res); - xlog $self, "Getting metadata with DEPTH infinity in the right place"; - $res = getmetadata($imaptalk, $folder, [ DEPTH => 'infinity' ], $rootentry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { %entries } } , $res); + xlog $self, "Getting metadata with DEPTH infinity in the right place"; + $res = getmetadata($imaptalk, $folder, [ DEPTH => 'infinity' ], $rootentry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals({ $folder => {%entries} }, $res); - xlog $self, "Getting metadata with DEPTH 0 in the wrong place"; - $res = getmetadata($imaptalk, $folder, [ DEPTH => 0 ], $rootentry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { $rootentry => $entries{$rootentry} } } , $res); + xlog $self, "Getting metadata with DEPTH 0 in the wrong place"; + $res = getmetadata($imaptalk, $folder, [ DEPTH => 0 ], $rootentry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { $folder => { $rootentry => $entries{$rootentry} } }, $res); - xlog $self, "Getting metadata with DEPTH 1 in the wrong place"; - $res = getmetadata($imaptalk, $folder, [ DEPTH => 1 ], $rootentry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { map { $_ => $entries{$_} } @subset } }, $res); + xlog $self, "Getting metadata with DEPTH 1 in the wrong place"; + $res = getmetadata($imaptalk, $folder, [ DEPTH => 1 ], $rootentry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { $folder => { map { $_ => $entries{$_} } @subset } }, $res); - xlog $self, "Getting metadata with DEPTH infinity in the wrong place"; - $res = getmetadata($imaptalk, $folder, [ DEPTH => 'infinity' ], $rootentry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { %entries } } , $res); + xlog $self, "Getting metadata with DEPTH infinity in the wrong place"; + $res = getmetadata($imaptalk, $folder, [ DEPTH => 'infinity' ], $rootentry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals({ $folder => {%entries} }, $res); } diff --git a/cassandane/tiny-tests/Metadata/getmetadata_maxsize b/cassandane/tiny-tests/Metadata/getmetadata_maxsize index 722d93caa9..f4cfe47169 100644 --- a/cassandane/tiny-tests/Metadata/getmetadata_maxsize +++ b/cassandane/tiny-tests/Metadata/getmetadata_maxsize @@ -1,55 +1,54 @@ #!perl use Cassandane::Tiny; -sub test_getmetadata_maxsize -{ - my ($self) = @_; - - xlog $self, "test the GETMETADATA command with the MAXSIZE option"; - - my $imaptalk = $self->{store}->get_client(); - # data thanks to hipsteripsum.me - my $folder = 'INBOX.denim'; - my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - my $res; - - xlog $self, "Create folder"; - $imaptalk->create($folder) - or die "Cannot create mailbox $folder: $@"; - - $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - - my $uuid = $res->{$folder}{$entry}; - $self->assert_not_null($uuid); - $self->assert($uuid =~ m/^[0-9a-z-]+$/); - - xlog $self, "Getting metadata with no MAXSIZE"; - $res = getmetadata($imaptalk, $folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { $entry => $uuid } } , $res); - - xlog $self, "Getting metadata with a large MAXSIZE in the right place"; - $res = getmetadata($imaptalk, [ MAXSIZE => 2048 ], $folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { $entry => $uuid } } , $res); - - xlog $self, "Getting metadata with a small MAXSIZE in the right place"; - $res = getmetadata($imaptalk, [ MAXSIZE => 8 ], $folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ longentries => length($uuid) } , $res); - - xlog $self, "Getting metadata with a large MAXSIZE in the wrong place"; - $res = getmetadata($imaptalk, $folder, [ MAXSIZE => 2048 ], $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ $folder => { $entry => $uuid } } , $res); - - xlog $self, "Getting metadata with a small MAXSIZE in the wrong place"; - $res = getmetadata($imaptalk, $folder, [ MAXSIZE => 8 ], $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ longentries => length($uuid) } , $res); +sub test_getmetadata_maxsize { + my ($self) = @_; + + xlog $self, "test the GETMETADATA command with the MAXSIZE option"; + + my $imaptalk = $self->{store}->get_client(); + # data thanks to hipsteripsum.me + my $folder = 'INBOX.denim'; + my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + my $res; + + xlog $self, "Create folder"; + $imaptalk->create($folder) + or die "Cannot create mailbox $folder: $@"; + + $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + + my $uuid = $res->{$folder}{$entry}; + $self->assert_not_null($uuid); + $self->assert($uuid =~ m/^[0-9a-z-]+$/); + + xlog $self, "Getting metadata with no MAXSIZE"; + $res = getmetadata($imaptalk, $folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals({ $folder => { $entry => $uuid } }, $res); + + xlog $self, "Getting metadata with a large MAXSIZE in the right place"; + $res = getmetadata($imaptalk, [ MAXSIZE => 2048 ], $folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals({ $folder => { $entry => $uuid } }, $res); + + xlog $self, "Getting metadata with a small MAXSIZE in the right place"; + $res = getmetadata($imaptalk, [ MAXSIZE => 8 ], $folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals({ longentries => length($uuid) }, $res); + + xlog $self, "Getting metadata with a large MAXSIZE in the wrong place"; + $res = getmetadata($imaptalk, $folder, [ MAXSIZE => 2048 ], $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals({ $folder => { $entry => $uuid } }, $res); + + xlog $self, "Getting metadata with a small MAXSIZE in the wrong place"; + $res = getmetadata($imaptalk, $folder, [ MAXSIZE => 8 ], $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals({ longentries => length($uuid) }, $res); } diff --git a/cassandane/tiny-tests/Metadata/getmetadata_multiple_folders b/cassandane/tiny-tests/Metadata/getmetadata_multiple_folders index 53ced18cd1..e23c82e4c8 100644 --- a/cassandane/tiny-tests/Metadata/getmetadata_multiple_folders +++ b/cassandane/tiny-tests/Metadata/getmetadata_multiple_folders @@ -1,48 +1,45 @@ #!perl use Cassandane::Tiny; -sub test_getmetadata_multiple_folders -{ - my ($self) = @_; - - xlog $self, "test the Cyrus-specific extension to the GETMETADATA"; - xlog $self, "syntax which allows specifying a parenthesised list"; - xlog $self, "of folder names [IRIS-1109]"; - - my $imaptalk = $self->{store}->get_client(); - # data thanks to hipsteripsum.me - my @folders = ( qw(INBOX.denim INBOX.sustainable INBOX.biodiesel.vinyl) ); - my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - my %uuids; - - xlog $self, "Create folders"; - foreach my $f (@folders) - { - $imaptalk->create($f) - or die "Cannot create mailbox $f: $@"; - - my $res = $imaptalk->getmetadata($f, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - - my $uuid = $res->{$f}{$entry}; - $self->assert_not_null($uuid); - $self->assert($uuid =~ m/^[0-9a-z-]+$/); - $uuids{$f} = $uuid; - } - - xlog $self, "Getting metadata with a list of folder names"; - my @f2; - my %exp; - foreach my $f (@folders) - { - push(@f2, $f); - $exp{$f} = { $entry => $uuids{$f} }; - - my $res = $imaptalk->getmetadata(\@f2, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - - $self->assert_deep_equals(\%exp, $res); - } +sub test_getmetadata_multiple_folders { + my ($self) = @_; + + xlog $self, "test the Cyrus-specific extension to the GETMETADATA"; + xlog $self, "syntax which allows specifying a parenthesised list"; + xlog $self, "of folder names [IRIS-1109]"; + + my $imaptalk = $self->{store}->get_client(); + # data thanks to hipsteripsum.me + my @folders = (qw(INBOX.denim INBOX.sustainable INBOX.biodiesel.vinyl)); + my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + my %uuids; + + xlog $self, "Create folders"; + foreach my $f (@folders) { + $imaptalk->create($f) + or die "Cannot create mailbox $f: $@"; + + my $res = $imaptalk->getmetadata($f, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + + my $uuid = $res->{$f}{$entry}; + $self->assert_not_null($uuid); + $self->assert($uuid =~ m/^[0-9a-z-]+$/); + $uuids{$f} = $uuid; + } + + xlog $self, "Getting metadata with a list of folder names"; + my @f2; + my %exp; + foreach my $f (@folders) { + push(@f2, $f); + $exp{$f} = { $entry => $uuids{$f} }; + + my $res = $imaptalk->getmetadata(\@f2, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + + $self->assert_deep_equals(\%exp, $res); + } } diff --git a/cassandane/tiny-tests/Metadata/mbox_replication_new_mas b/cassandane/tiny-tests/Metadata/mbox_replication_new_mas index 1e8b725634..b75063c3b5 100644 --- a/cassandane/tiny-tests/Metadata/mbox_replication_new_mas +++ b/cassandane/tiny-tests/Metadata/mbox_replication_new_mas @@ -2,52 +2,51 @@ use Cassandane::Tiny; sub test_mbox_replication_new_mas - :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing replication of mailbox scope annotations"; - xlog $self, "case new_mas: new message appears, on master only"; - - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - my $master_talk = $master_store->get_client(); - my $replica_talk = $replica_store->get_client(); - - my $folder = 'INBOX'; - my $entry = '/private/comment'; - my $value1 = "Hello World"; - my $res; - - xlog $self, "store an annotation"; - $master_talk->setmetadata($folder, $entry, $value1); - $self->assert_str_equals('ok', $master_talk->get_last_completion_response()); - - xlog $self, "Before replication, annotation is present on the master"; - $res = $master_talk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $master_talk->get_last_completion_response()); - $self->assert_deep_equals({ $folder => { $entry => $value1 } }, $res); - - xlog $self, "Before replication, annotation is missing from the replica"; - $res = $replica_talk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $replica_talk->get_last_completion_response()); - $self->assert_deep_equals({ $folder => { $entry => undef } }, $res); - - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $master_talk = $master_store->get_client(); - $replica_talk = $replica_store->get_client(); - - xlog $self, "After replication, annotation is still present on the master"; - $res = $master_talk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $master_talk->get_last_completion_response()); - $self->assert_deep_equals({ $folder => { $entry => $value1 } }, $res); - - xlog $self, "After replication, annotation is now present on the replica"; - $res = $replica_talk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $replica_talk->get_last_completion_response()); - $self->assert_deep_equals({ $folder => { $entry => $value1 } }, $res); + : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing replication of mailbox scope annotations"; + xlog $self, "case new_mas: new message appears, on master only"; + + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + my $master_talk = $master_store->get_client(); + my $replica_talk = $replica_store->get_client(); + + my $folder = 'INBOX'; + my $entry = '/private/comment'; + my $value1 = "Hello World"; + my $res; + + xlog $self, "store an annotation"; + $master_talk->setmetadata($folder, $entry, $value1); + $self->assert_str_equals('ok', $master_talk->get_last_completion_response()); + + xlog $self, "Before replication, annotation is present on the master"; + $res = $master_talk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $master_talk->get_last_completion_response()); + $self->assert_deep_equals({ $folder => { $entry => $value1 } }, $res); + + xlog $self, "Before replication, annotation is missing from the replica"; + $res = $replica_talk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $replica_talk->get_last_completion_response()); + $self->assert_deep_equals({ $folder => { $entry => undef } }, $res); + + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $master_talk = $master_store->get_client(); + $replica_talk = $replica_store->get_client(); + + xlog $self, "After replication, annotation is still present on the master"; + $res = $master_talk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $master_talk->get_last_completion_response()); + $self->assert_deep_equals({ $folder => { $entry => $value1 } }, $res); + + xlog $self, "After replication, annotation is now present on the replica"; + $res = $replica_talk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $replica_talk->get_last_completion_response()); + $self->assert_deep_equals({ $folder => { $entry => $value1 } }, $res); } diff --git a/cassandane/tiny-tests/Metadata/modseq b/cassandane/tiny-tests/Metadata/modseq index 0e4c528039..fb2d822f70 100644 --- a/cassandane/tiny-tests/Metadata/modseq +++ b/cassandane/tiny-tests/Metadata/modseq @@ -8,103 +8,101 @@ use Cassandane::Tiny; # - deleting an annotation bumps the message's modseq etc # - modseq of other messages is never affected # -sub test_modseq -{ - my ($self) = @_; +sub test_modseq { + my ($self) = @_; - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid modseq)); + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid modseq)); - xlog $self, "Append 3 messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{B} = $self->make_message('Message B'); - $msg{C} = $self->make_message('Message C'); + xlog $self, "Append 3 messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{B} = $self->make_message('Message B'); + $msg{C} = $self->make_message('Message C'); - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; - xlog $self, "fetch an annotation - should be no values"; - my $hms0 = $self->get_highestmodseq(); - my $res = $talk->fetch('1:*', - ['modseq', 'annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { - modseq => [$hms0-2], - annotation => { $entry => { $attrib => undef } } - }, - 2 => { - modseq => [$hms0-1], - annotation => { $entry => { $attrib => undef } } - }, - 3 => { - modseq => [$hms0], - annotation => { $entry => { $attrib => undef } } - }, - }, - $res); + xlog $self, "fetch an annotation - should be no values"; + my $hms0 = $self->get_highestmodseq(); + my $res + = $talk->fetch('1:*', [ 'modseq', 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { + modseq => [ $hms0 - 2 ], + annotation => { $entry => { $attrib => undef } } + }, + 2 => { + modseq => [ $hms0 - 1 ], + annotation => { $entry => { $attrib => undef } } + }, + 3 => { + modseq => [$hms0], + annotation => { $entry => { $attrib => undef } } + }, + }, + $res + ); - xlog $self, "store an annotation"; - $talk->store('1', 'annotation', - [$entry, [$attrib, $value1]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "store an annotation"; + $talk->store('1', 'annotation', [ $entry, [ $attrib, $value1 ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "fetch an annotation - should be updated"; - my $hms1 = $self->get_highestmodseq(); - $self->assert($hms1 > $hms0); - $res = $talk->fetch('1:*', - ['modseq', 'annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { - modseq => [$hms1], - annotation => { $entry => { $attrib => $value1 } } - }, - 2 => { - modseq => [$hms0-1], - annotation => { $entry => { $attrib => undef } } - }, - 3 => { - modseq => [$hms0], - annotation => { $entry => { $attrib => undef } } - }, - }, - $res); + xlog $self, "fetch an annotation - should be updated"; + my $hms1 = $self->get_highestmodseq(); + $self->assert($hms1 > $hms0); + $res = $talk->fetch('1:*', [ 'modseq', 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { + modseq => [$hms1], + annotation => { $entry => { $attrib => $value1 } } + }, + 2 => { + modseq => [ $hms0 - 1 ], + annotation => { $entry => { $attrib => undef } } + }, + 3 => { + modseq => [$hms0], + annotation => { $entry => { $attrib => undef } } + }, + }, + $res + ); - xlog $self, "delete an annotation"; - $talk->store('1', 'annotation', - [$entry, [$attrib, undef]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "delete an annotation"; + $talk->store('1', 'annotation', [ $entry, [ $attrib, undef ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "fetch an annotation - should be updated"; - my $hms2 = $self->get_highestmodseq(); - $self->assert($hms2 > $hms1); - $res = $talk->fetch('1:*', - ['modseq', 'annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { - modseq => [$hms2], - annotation => { $entry => { $attrib => undef } } - }, - 2 => { - modseq => [$hms0-1], - annotation => { $entry => { $attrib => undef } } - }, - 3 => { - modseq => [$hms0], - annotation => { $entry => { $attrib => undef } } - }, - }, - $res); + xlog $self, "fetch an annotation - should be updated"; + my $hms2 = $self->get_highestmodseq(); + $self->assert($hms2 > $hms1); + $res = $talk->fetch('1:*', [ 'modseq', 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { + modseq => [$hms2], + annotation => { $entry => { $attrib => undef } } + }, + 2 => { + modseq => [ $hms0 - 1 ], + annotation => { $entry => { $attrib => undef } } + }, + 3 => { + modseq => [$hms0], + annotation => { $entry => { $attrib => undef } } + }, + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/motd b/cassandane/tiny-tests/Metadata/motd index c7fa4f81c7..faab1fab86 100644 --- a/cassandane/tiny-tests/Metadata/motd +++ b/cassandane/tiny-tests/Metadata/motd @@ -7,80 +7,76 @@ use Cassandane::Tiny; # Note: this needs the Mail::IMAPTalk install to have commit # "Alert reponse is remainder of line, put that in the response code" # -sub test_motd -{ - my ($self) = @_; +sub test_motd { + my ($self) = @_; - xlog $self, "testing /shared/motd"; + xlog $self, "testing /shared/motd"; - my $imaptalk = $self->{store}->get_client(); - my $res; - my $entry = '/shared/motd'; - my $value1 = "Hello World this is a value"; + my $imaptalk = $self->{store}->get_client(); + my $res; + my $entry = '/shared/motd'; + my $value1 = "Hello World this is a value"; - xlog $self, "No ALERT was received when we connected"; - $self->assert($imaptalk->state() == Mail::IMAPTalk::Authenticated); - $self->assert_null($imaptalk->get_response_code('alert')); + xlog $self, "No ALERT was received when we connected"; + $self->assert($imaptalk->state() == Mail::IMAPTalk::Authenticated); + $self->assert_null($imaptalk->get_response_code('alert')); - xlog $self, "initial value is NIL"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); + xlog $self, "initial value is NIL"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); - xlog $self, "cannot set the value as ordinary user"; - $imaptalk->setmetadata("", $entry, $value1); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert($imaptalk->get_last_error() =~ m/permission denied/i); + xlog $self, "cannot set the value as ordinary user"; + $imaptalk->setmetadata("", $entry, $value1); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert($imaptalk->get_last_error() =~ m/permission denied/i); - xlog $self, "can set the value as admin"; - $imaptalk = $self->{adminstore}->get_client(); - $imaptalk->setmetadata("", $entry, $value1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "can set the value as admin"; + $imaptalk = $self->{adminstore}->get_client(); + $imaptalk->setmetadata("", $entry, $value1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "can get the set value back"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); + xlog $self, "can get the set value back"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); - xlog $self, "a new connection will get an ALERT with the motd value"; - $self->{adminstore}->disconnect(); - $imaptalk = $self->{adminstore}->get_client(); - $self->assert($imaptalk->state() == Mail::IMAPTalk::Authenticated); - my $alert = $imaptalk->get_response_code('alert'); - $self->assert_not_null($alert); - $self->assert_str_equals($value1, $alert); + xlog $self, "a new connection will get an ALERT with the motd value"; + $self->{adminstore}->disconnect(); + $imaptalk = $self->{adminstore}->get_client(); + $self->assert($imaptalk->state() == Mail::IMAPTalk::Authenticated); + my $alert = $imaptalk->get_response_code('alert'); + $self->assert_not_null($alert); + $self->assert_str_equals($value1, $alert); - xlog $self, "the annot gives the same value in the new connection"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); + xlog $self, "the annot gives the same value in the new connection"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); - xlog $self, "can delete value"; - $imaptalk->setmetadata("", $entry, undef); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "can delete value"; + $imaptalk->setmetadata("", $entry, undef); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => undef } - }; - $self->assert_deep_equals($expected, $res); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => undef } }; + $self->assert_deep_equals($expected, $res); - xlog $self, "a new connection no longer gets an ALERT"; - $self->{adminstore}->disconnect(); - $imaptalk = $self->{adminstore}->get_client(); - $self->assert($imaptalk->state() == Mail::IMAPTalk::Authenticated); - $self->assert_null($imaptalk->get_response_code('alert')); + xlog $self, "a new connection no longer gets an ALERT"; + $self->{adminstore}->disconnect(); + $imaptalk = $self->{adminstore}->get_client(); + $self->assert($imaptalk->state() == Mail::IMAPTalk::Authenticated); + $self->assert_null($imaptalk->get_response_code('alert')); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_exp_bot b/cassandane/tiny-tests/Metadata/msg_replication_exp_bot index e857ee5faa..11ed6869e0 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_exp_bot +++ b/cassandane/tiny-tests/Metadata/msg_replication_exp_bot @@ -2,76 +2,79 @@ use Cassandane::Tiny; sub test_msg_replication_exp_bot - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case exp_bot: message is expunged, on both ends"; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case exp_bot: message is expunged, on both ends"; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message and store an annotation"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); - $master_exp{A}->set_attribute('uid', 1); - $master_exp{A}->set_annotation($entry, $attrib, $value1); + xlog $self, "Append a message and store an annotation"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); + $master_exp{A}->set_attribute('uid', 1); + $master_exp{A}->set_annotation($entry, $attrib, $value1); - xlog $self, "Before first replication, message is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before first replication, message is missing from the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before first replication, message is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before first replication, message is missing from the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the message"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the message"; + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After first replication, message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After first replication, message is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, "After first replication, message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After first replication, message is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Delete and expunge the message on the master"; - my $talk = $master_store->get_client(); - $master_store->_select(); - $talk->store('1', '+flags', '(\\Deleted)'); - $talk->expunge(); + xlog $self, "Delete and expunge the message on the master"; + my $talk = $master_store->get_client(); + $master_store->_select(); + $talk->store('1', '+flags', '(\\Deleted)'); + $talk->expunge(); - xlog $self, "Delete and expunge the message on the replica"; - $talk = $replica_store->get_client(); - $replica_store->_select(); - $talk->store('1', '+flags', '(\\Deleted)'); - $talk->expunge(); + xlog $self, "Delete and expunge the message on the replica"; + $talk = $replica_store->get_client(); + $replica_store->_select(); + $talk->store('1', '+flags', '(\\Deleted)'); + $talk->expunge(); - delete $master_exp{A}; - delete $replica_exp{A}; - xlog $self, "Before second replication, the message is now missing on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before second replication, the message is now missing on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + delete $master_exp{A}; + delete $replica_exp{A}; + xlog $self, + "Before second replication, the message is now missing on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "Before second replication, the message is now missing on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the expunge"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the expunge"; + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After second replication, the message is still missing on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After second replication, the message is still missing on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, + "After second replication, the message is still missing on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After second replication, the message is still missing on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_exp_mas b/cassandane/tiny-tests/Metadata/msg_replication_exp_mas index 59a1736986..22b7b40314 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_exp_mas +++ b/cassandane/tiny-tests/Metadata/msg_replication_exp_mas @@ -2,70 +2,73 @@ use Cassandane::Tiny; sub test_msg_replication_exp_mas - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case exp_mas: message is expunged, on master only"; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case exp_mas: message is expunged, on master only"; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message and store an annotation"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); - $master_exp{A}->set_attribute('uid', 1); - $master_exp{A}->set_annotation($entry, $attrib, $value1); + xlog $self, "Append a message and store an annotation"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); + $master_exp{A}->set_attribute('uid', 1); + $master_exp{A}->set_annotation($entry, $attrib, $value1); - xlog $self, "Before first replication, message is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before first replication, message is missing from the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before first replication, message is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before first replication, message is missing from the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the message"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the message"; + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After first replication, message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After first replication, message is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, "After first replication, message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After first replication, message is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Delete and expunge the message on the master"; - my $talk = $master_store->get_client(); - $master_store->_select(); - $talk->store('1', '+flags', '(\\Deleted)'); - $talk->expunge(); + xlog $self, "Delete and expunge the message on the master"; + my $talk = $master_store->get_client(); + $master_store->_select(); + $talk->store('1', '+flags', '(\\Deleted)'); + $talk->expunge(); - delete $master_exp{A}; - xlog $self, "Before second replication, the message is now missing on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before second replication, the message is still present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + delete $master_exp{A}; + xlog $self, + "Before second replication, the message is now missing on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "Before second replication, the message is still present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the expunge"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the expunge"; + $self->run_replication(); + $self->check_replication('cassandane'); - delete $replica_exp{A}; - xlog $self, "After second replication, the message is still missing on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After second replication, the message is now missing on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + delete $replica_exp{A}; + xlog $self, + "After second replication, the message is still missing on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After second replication, the message is now missing on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_exp_rep b/cassandane/tiny-tests/Metadata/msg_replication_exp_rep index a16a61e5ec..32441e6f7a 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_exp_rep +++ b/cassandane/tiny-tests/Metadata/msg_replication_exp_rep @@ -2,70 +2,73 @@ use Cassandane::Tiny; sub test_msg_replication_exp_rep - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case exp_rep: message is expunged, on replica only"; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case exp_rep: message is expunged, on replica only"; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message and store an annotation"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); - $master_exp{A}->set_attribute('uid', 1); - $master_exp{A}->set_annotation($entry, $attrib, $value1); + xlog $self, "Append a message and store an annotation"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); + $master_exp{A}->set_attribute('uid', 1); + $master_exp{A}->set_annotation($entry, $attrib, $value1); - xlog $self, "Before first replication, message is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before first replication, message is missing from the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before first replication, message is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before first replication, message is missing from the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the message"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the message"; + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After first replication, message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After first replication, message is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, "After first replication, message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After first replication, message is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Delete and expunge the message on the replica"; - my $talk = $replica_store->get_client(); - $replica_store->_select(); - $talk->store('1', '+flags', '(\\Deleted)'); - $talk->expunge(); + xlog $self, "Delete and expunge the message on the replica"; + my $talk = $replica_store->get_client(); + $replica_store->_select(); + $talk->store('1', '+flags', '(\\Deleted)'); + $talk->expunge(); - delete $replica_exp{A}; - xlog $self, "Before second replication, the message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before second replication, the message is now missing on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + delete $replica_exp{A}; + xlog $self, + "Before second replication, the message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "Before second replication, the message is now missing on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the expunge"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the expunge"; + $self->run_replication(); + $self->check_replication('cassandane'); - delete $master_exp{A}; - xlog $self, "After second replication, the message is now missing on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After second replication, the message is still missing on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + delete $master_exp{A}; + xlog $self, + "After second replication, the message is now missing on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After second replication, the message is still missing on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_mod_bot_msh b/cassandane/tiny-tests/Metadata/msg_replication_mod_bot_msh index 552d0e2cec..e0ead49113 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_mod_bot_msh +++ b/cassandane/tiny-tests/Metadata/msg_replication_mod_bot_msh @@ -2,71 +2,74 @@ use Cassandane::Tiny; sub test_msg_replication_mod_bot_msh - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case mod_bot_msh: message is modified, on both ends, " . - "modseq higher on master"; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case mod_bot_msh: message is modified, on both ends, " + . "modseq higher on master"; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $valueA1 = "Hello World"; - my $valueA2 = "and friends"; - my $valueB = "Jeepers"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $valueA1 = "Hello World"; + my $valueA2 = "and friends"; + my $valueB = "Jeepers"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $master_exp{A}->set_attribute('uid', 1); + xlog $self, "Append a message"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $master_exp{A}->set_attribute('uid', 1); - xlog $self, "Before first replication, message is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before first replication, message is missing from the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before first replication, message is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before first replication, message is missing from the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the message"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the message"; + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After first replication, message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After first replication, message is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, "After first replication, message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After first replication, message is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Set an annotation twice on the master, once on the replica"; - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA1); - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA2); - $master_exp{A}->set_annotation($entry, $attrib, $valueA2); - $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB); - $replica_exp{A}->set_annotation($entry, $attrib, $valueB); + xlog $self, "Set an annotation twice on the master, once on the replica"; + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA1); + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA2); + $master_exp{A}->set_annotation($entry, $attrib, $valueA2); + $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB); + $replica_exp{A}->set_annotation($entry, $attrib, $valueB); - xlog $self, "Before second replication, one message annotation is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before second replication, a different message annotation is present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, + "Before second replication, one message annotation is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "Before second replication, a different message annotation is present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the annotation change"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the annotation change"; + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A}->set_annotation($entry, $attrib, $valueA2); - xlog $self, "After second replication, the message annotation is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After second replication, the message annotation is updated on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A}->set_annotation($entry, $attrib, $valueA2); + xlog $self, + "After second replication, the message annotation is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After second replication, the message annotation is updated on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_mod_bot_msl b/cassandane/tiny-tests/Metadata/msg_replication_mod_bot_msl index f0d9617cc3..0a0fdf3f08 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_mod_bot_msl +++ b/cassandane/tiny-tests/Metadata/msg_replication_mod_bot_msl @@ -2,71 +2,74 @@ use Cassandane::Tiny; sub test_msg_replication_mod_bot_msl - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case mod_bot_msl: message is modified, on both ends, " . - "modseq lower on master"; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case mod_bot_msl: message is modified, on both ends, " + . "modseq lower on master"; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $valueA = "Hello World"; - my $valueB1 = "Jeepers"; - my $valueB2 = "Creepers"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $valueA = "Hello World"; + my $valueB1 = "Jeepers"; + my $valueB2 = "Creepers"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $master_exp{A}->set_attribute('uid', 1); + xlog $self, "Append a message"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $master_exp{A}->set_attribute('uid', 1); - xlog $self, "Before first replication, message is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before first replication, message is missing from the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before first replication, message is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before first replication, message is missing from the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the message"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the message"; + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After first replication, message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After first replication, message is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, "After first replication, message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After first replication, message is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Set an annotation once on the master, twice on the replica"; - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA); - $master_exp{A}->set_annotation($entry, $attrib, $valueA); - $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB1); - $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB2); - $replica_exp{A}->set_annotation($entry, $attrib, $valueB2); + xlog $self, "Set an annotation once on the master, twice on the replica"; + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA); + $master_exp{A}->set_annotation($entry, $attrib, $valueA); + $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB1); + $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB2); + $replica_exp{A}->set_annotation($entry, $attrib, $valueB2); - xlog $self, "Before second replication, one message annotation is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before second replication, a different message annotation is present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, + "Before second replication, one message annotation is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "Before second replication, a different message annotation is present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the annotation change"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the annotation change"; + $self->run_replication(); + $self->check_replication('cassandane'); - $master_exp{A}->set_annotation($entry, $attrib, $valueB2); - xlog $self, "After second replication, the message annotation is updated on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After second replication, the message annotation is still present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $master_exp{A}->set_annotation($entry, $attrib, $valueB2); + xlog $self, + "After second replication, the message annotation is updated on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After second replication, the message annotation is still present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_mod_mas b/cassandane/tiny-tests/Metadata/msg_replication_mod_mas index 5c963f6058..b162c74c5f 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_mod_mas +++ b/cassandane/tiny-tests/Metadata/msg_replication_mod_mas @@ -2,64 +2,67 @@ use Cassandane::Tiny; sub test_msg_replication_mod_mas - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case mod_mas: message is modified, on master only"; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case mod_mas: message is modified, on master only"; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $master_exp{A}->set_attribute('uid', 1); + xlog $self, "Append a message"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $master_exp{A}->set_attribute('uid', 1); - xlog $self, "Before first replication, message is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before first replication, message is missing from the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before first replication, message is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before first replication, message is missing from the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the message"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the message"; + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After first replication, message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After first replication, message is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, "After first replication, message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After first replication, message is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Set an annotation on the master"; - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); - $master_exp{A}->set_annotation($entry, $attrib, $value1); + xlog $self, "Set an annotation on the master"; + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); + $master_exp{A}->set_annotation($entry, $attrib, $value1); - xlog $self, "Before second replication, the message annotation is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before second replication, the message annotation is missing on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, + "Before second replication, the message annotation is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "Before second replication, the message annotation is missing on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After second replication, the message annotation is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After second replication, the message annotation is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, + "After second replication, the message annotation is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After second replication, the message annotation is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_mod_rep b/cassandane/tiny-tests/Metadata/msg_replication_mod_rep index 640154ea9a..fd37e5d4d1 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_mod_rep +++ b/cassandane/tiny-tests/Metadata/msg_replication_mod_rep @@ -2,64 +2,67 @@ use Cassandane::Tiny; sub test_msg_replication_mod_rep - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case mod_rep: message is modified, on replica only"; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case mod_rep: message is modified, on replica only"; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $master_exp{A}->set_attribute('uid', 1); + xlog $self, "Append a message"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $master_exp{A}->set_attribute('uid', 1); - xlog $self, "Before first replication, message is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before first replication, message is missing from the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before first replication, message is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before first replication, message is missing from the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Replicate the message"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "Replicate the message"; + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After first replication, message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After first replication, message is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, "After first replication, message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After first replication, message is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Set an annotation on the master"; - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); - $master_exp{A}->set_annotation($entry, $attrib, $value1); + xlog $self, "Set an annotation on the master"; + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); + $master_exp{A}->set_annotation($entry, $attrib, $value1); - xlog $self, "Before second replication, the message annotation is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before second replication, the message annotation is missing on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, + "Before second replication, the message annotation is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "Before second replication, the message annotation is missing on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After second replication, the message annotation is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After second replication, the message annotation is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, + "After second replication, the message annotation is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After second replication, the message annotation is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_new_bot_mse_guh b/cassandane/tiny-tests/Metadata/msg_replication_new_bot_mse_guh index a526c41827..c16894b47e 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_new_bot_mse_guh +++ b/cassandane/tiny-tests/Metadata/msg_replication_new_bot_mse_guh @@ -2,66 +2,67 @@ use Cassandane::Tiny; sub test_msg_replication_new_bot_mse_guh - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case new_bot_mse_guh: new messages appear, on both master " . - "and replica, with equal modseqs, higher GUID on master."; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case new_bot_mse_guh: new messages appear, on both master " + . "and replica, with equal modseqs, higher GUID on master."; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $valueA = "Hello World"; - my $valueB = "Hello Dolly"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $valueA = "Hello World"; + my $valueB = "Hello Dolly"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message and store an annotation to each store"; - my ($msgB, $msgA) = $self->make_message_pair($replica_store, $master_store); - my %master_exp = ( A => $msgA ); - my %replica_exp = ( B => $msgB ); - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA); - $master_exp{A}->set_attribute('uid', 1); - $master_exp{A}->set_annotation($entry, $attrib, $valueA); - $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB); - $replica_exp{B}->set_attribute('uid', 1); - $replica_exp{B}->set_annotation($entry, $attrib, $valueB); + xlog $self, "Append a message and store an annotation to each store"; + my ($msgB, $msgA) = $self->make_message_pair($replica_store, $master_store); + my %master_exp = (A => $msgA); + my %replica_exp = (B => $msgB); + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA); + $master_exp{A}->set_attribute('uid', 1); + $master_exp{A}->set_annotation($entry, $attrib, $valueA); + $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB); + $replica_exp{B}->set_attribute('uid', 1); + $replica_exp{B}->set_annotation($entry, $attrib, $valueB); - xlog $self, "Before replication, only message A is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before replication, only message B is present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before replication, only message A is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before replication, only message B is present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, both messages are now present and renumbered on the master"; - $master_exp{B} = $replica_exp{B}->clone(); - $master_exp{B}->set_attribute('uid', 2); - $master_exp{A}->set_attribute('uid', 3); - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After replication, both messages are now present and renumbered on the replica"; - $replica_exp{A} = $master_exp{A}->clone(); - $replica_exp{B}->set_attribute('uid', 2); - $replica_exp{A}->set_attribute('uid', 3); - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, + "After replication, both messages are now present and renumbered on the master"; + $master_exp{B} = $replica_exp{B}->clone(); + $master_exp{B}->set_attribute('uid', 2); + $master_exp{A}->set_attribute('uid', 3); + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After replication, both messages are now present and renumbered on the replica"; + $replica_exp{A} = $master_exp{A}->clone(); + $replica_exp{B}->set_attribute('uid', 2); + $replica_exp{A}->set_attribute('uid', 3); + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); - # We should have generated a SYNCERROR or two - my $pattern = qr{ + # We should have generated a SYNCERROR or two + my $pattern = qr{ \bSYNCERROR:\sguid\smismatch (?: \suser\.cassandane\s1\b | :\smailbox=\suid=<1> ) }x; - $self->assert_syslog_matches($self->{instance}, $pattern); + $self->assert_syslog_matches($self->{instance}, $pattern); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_new_bot_mse_gul b/cassandane/tiny-tests/Metadata/msg_replication_new_bot_mse_gul index 1e9e4f6b3e..537c2acc2c 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_new_bot_mse_gul +++ b/cassandane/tiny-tests/Metadata/msg_replication_new_bot_mse_gul @@ -2,66 +2,67 @@ use Cassandane::Tiny; sub test_msg_replication_new_bot_mse_gul - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case new_bot_mse_gul: new messages appear, on both master " . - "and replica, with equal modseqs, lower GUID on master."; + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case new_bot_mse_gul: new messages appear, on both master " + . "and replica, with equal modseqs, lower GUID on master."; - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $valueA = "Hello World"; - my $valueB = "Hello Dolly"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $valueA = "Hello World"; + my $valueB = "Hello Dolly"; - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - xlog $self, "Append a message and store an annotation to each store"; - my ($msgA, $msgB) = $self->make_message_pair($master_store, $replica_store); - my %master_exp = ( A => $msgA ); - my %replica_exp = ( B => $msgB ); - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA); - $master_exp{A}->set_attribute('uid', 1); - $master_exp{A}->set_annotation($entry, $attrib, $valueA); - $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB); - $replica_exp{B}->set_attribute('uid', 1); - $replica_exp{B}->set_annotation($entry, $attrib, $valueB); + xlog $self, "Append a message and store an annotation to each store"; + my ($msgA, $msgB) = $self->make_message_pair($master_store, $replica_store); + my %master_exp = (A => $msgA); + my %replica_exp = (B => $msgB); + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $valueA); + $master_exp{A}->set_attribute('uid', 1); + $master_exp{A}->set_annotation($entry, $attrib, $valueA); + $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $valueB); + $replica_exp{B}->set_attribute('uid', 1); + $replica_exp{B}->set_annotation($entry, $attrib, $valueB); - xlog $self, "Before replication, only message A is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before replication, only message B is present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, "Before replication, only message A is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before replication, only message B is present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, both messages are now present and renumbered on the master"; - $master_exp{B} = $replica_exp{B}->clone(); - $master_exp{A}->set_attribute('uid', 2); - $master_exp{B}->set_attribute('uid', 3); - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After replication, both messages are now present and renumbered on the replica"; - $replica_exp{A} = $master_exp{A}->clone(); - $replica_exp{A}->set_attribute('uid', 2); - $replica_exp{B}->set_attribute('uid', 3); - $self->check_messages(\%replica_exp, store => $replica_store); + xlog $self, + "After replication, both messages are now present and renumbered on the master"; + $master_exp{B} = $replica_exp{B}->clone(); + $master_exp{A}->set_attribute('uid', 2); + $master_exp{B}->set_attribute('uid', 3); + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, + "After replication, both messages are now present and renumbered on the replica"; + $replica_exp{A} = $master_exp{A}->clone(); + $replica_exp{A}->set_attribute('uid', 2); + $replica_exp{B}->set_attribute('uid', 3); + $self->check_messages(\%replica_exp, store => $replica_store); - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); - # We should have generated a SYNCERROR or two - my $pattern = qr{ + # We should have generated a SYNCERROR or two + my $pattern = qr{ \bSYNCERROR:\sguid\smismatch (?: \suser\.cassandane\s1\b | :\smailbox=\suid=<1> ) }x; - $self->assert_syslog_matches($self->{instance}, $pattern); + $self->assert_syslog_matches($self->{instance}, $pattern); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_new_mas b/cassandane/tiny-tests/Metadata/msg_replication_new_mas index 0d003e5eef..a90e4c0aef 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_new_mas +++ b/cassandane/tiny-tests/Metadata/msg_replication_new_mas @@ -2,47 +2,46 @@ use Cassandane::Tiny; sub test_msg_replication_new_mas - :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case new_mas: new message appears, on master only"; - - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; - - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Append a message and store an annotation"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); - $master_exp{A}->set_attribute('uid', 1); - $master_exp{A}->set_annotation($entry, $attrib, $value1); - - xlog $self, "Before replication, message is present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before replication, message is missing from the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); - - $self->run_replication(); - $self->check_replication('cassandane'); - - $replica_exp{A} = $master_exp{A}->clone(); - xlog $self, "After replication, message is still present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After replication, message is now present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); - - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case new_mas: new message appears, on master only"; + + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; + + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Append a message and store an annotation"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $self->set_msg_annotation($master_store, 1, $entry, $attrib, $value1); + $master_exp{A}->set_attribute('uid', 1); + $master_exp{A}->set_annotation($entry, $attrib, $value1); + + xlog $self, "Before replication, message is present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before replication, message is missing from the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); + + $self->run_replication(); + $self->check_replication('cassandane'); + + $replica_exp{A} = $master_exp{A}->clone(); + xlog $self, "After replication, message is still present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After replication, message is now present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); + + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_new_mas_partial_wwd b/cassandane/tiny-tests/Metadata/msg_replication_new_mas_partial_wwd index 5b66c776b6..9f16bcc091 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_new_mas_partial_wwd +++ b/cassandane/tiny-tests/Metadata/msg_replication_new_mas_partial_wwd @@ -2,49 +2,51 @@ use Cassandane::Tiny; sub test_msg_replication_new_mas_partial_wwd - :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing partial replication of message scope annotations"; - xlog $self, "case master to replica: write, write, delete"; - - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - - xlog $self, "Append a message"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $master_exp{A}->set_attribute('uid', 1); - - xlog $self, "Run replication"; - $self->run_replication(); - $self->check_msg_annotation_replication($master_store, $replica_store); - - xlog $self, "Write an annotation"; - $self->set_msg_annotation($master_store, 1, '/comment', 'value.priv', 'c1'); - - xlog $self, "Run replication"; - $self->run_replication(); - $self->check_msg_annotation_replication($master_store, $replica_store); - - xlog $self, "Write another few annotations"; - $self->set_msg_annotation($master_store, 1, '/altsubject', 'value.priv', 'a1'); - $self->set_msg_annotation($master_store, 1, '/comment', 'value.shared', 'cs'); - $self->set_msg_annotation($master_store, 1, '/altsubject', 'value.shared', 'as'); - - xlog $self, "Run replication"; - $self->run_replication(); - $self->check_msg_annotation_replication($master_store, $replica_store); - - xlog $self, "Delete the first annotation"; - $self->set_msg_annotation($master_store, 1, '/comment', 'value.priv', ''); - $self->set_msg_annotation($master_store, 1, '/altsubject', 'value.shared', ''); - - xlog $self, "Run replication"; - $self->run_replication(); - $self->check_msg_annotation_replication($master_store, $replica_store); + : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing partial replication of message scope annotations"; + xlog $self, "case master to replica: write, write, delete"; + + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + + xlog $self, "Append a message"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $master_exp{A}->set_attribute('uid', 1); + + xlog $self, "Run replication"; + $self->run_replication(); + $self->check_msg_annotation_replication($master_store, $replica_store); + + xlog $self, "Write an annotation"; + $self->set_msg_annotation($master_store, 1, '/comment', 'value.priv', 'c1'); + + xlog $self, "Run replication"; + $self->run_replication(); + $self->check_msg_annotation_replication($master_store, $replica_store); + + xlog $self, "Write another few annotations"; + $self->set_msg_annotation($master_store, 1, '/altsubject', 'value.priv', + 'a1'); + $self->set_msg_annotation($master_store, 1, '/comment', 'value.shared', 'cs'); + $self->set_msg_annotation($master_store, 1, '/altsubject', 'value.shared', + 'as'); + + xlog $self, "Run replication"; + $self->run_replication(); + $self->check_msg_annotation_replication($master_store, $replica_store); + + xlog $self, "Delete the first annotation"; + $self->set_msg_annotation($master_store, 1, '/comment', 'value.priv', ''); + $self->set_msg_annotation($master_store, 1, '/altsubject', 'value.shared', + ''); + + xlog $self, "Run replication"; + $self->run_replication(); + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_new_mas_partial_wwsw b/cassandane/tiny-tests/Metadata/msg_replication_new_mas_partial_wwsw index 6acbb261c7..ac531096dd 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_new_mas_partial_wwsw +++ b/cassandane/tiny-tests/Metadata/msg_replication_new_mas_partial_wwsw @@ -2,40 +2,40 @@ use Cassandane::Tiny; sub test_msg_replication_new_mas_partial_wwsw - :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing partial replication of message scope annotations"; - xlog $self, "case master to replica: write, write, sync, write"; - - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - - xlog $self, "Append a message"; - my %master_exp; - my %replica_exp; - $master_exp{A} = $self->make_message('Message A', store => $master_store); - $master_exp{A}->set_attribute('uid', 1); - - xlog $self, "Run replication"; - $self->run_replication(); - $self->check_msg_annotation_replication($master_store, $replica_store); - - xlog $self, "Write an annotation twice"; - $self->set_msg_annotation($master_store, 1, '/comment', 'value.priv', 'c1'); - $self->set_msg_annotation($master_store, 1, '/comment', 'value.priv', 'c2'); - - xlog $self, "Run replication"; - $self->run_replication(); - $self->check_msg_annotation_replication($master_store, $replica_store); - - xlog $self, "Write another annotation"; - $self->set_msg_annotation($master_store, 1, '/altsubject', 'value.priv', 'a1'); - - xlog $self, "Run replication"; - $self->run_replication(); - $self->check_msg_annotation_replication($master_store, $replica_store); + : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing partial replication of message scope annotations"; + xlog $self, "case master to replica: write, write, sync, write"; + + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + + xlog $self, "Append a message"; + my %master_exp; + my %replica_exp; + $master_exp{A} = $self->make_message('Message A', store => $master_store); + $master_exp{A}->set_attribute('uid', 1); + + xlog $self, "Run replication"; + $self->run_replication(); + $self->check_msg_annotation_replication($master_store, $replica_store); + + xlog $self, "Write an annotation twice"; + $self->set_msg_annotation($master_store, 1, '/comment', 'value.priv', 'c1'); + $self->set_msg_annotation($master_store, 1, '/comment', 'value.priv', 'c2'); + + xlog $self, "Run replication"; + $self->run_replication(); + $self->check_msg_annotation_replication($master_store, $replica_store); + + xlog $self, "Write another annotation"; + $self->set_msg_annotation($master_store, 1, '/altsubject', 'value.priv', + 'a1'); + + xlog $self, "Run replication"; + $self->run_replication(); + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_replication_new_rep b/cassandane/tiny-tests/Metadata/msg_replication_new_rep index f668633329..03404978b9 100644 --- a/cassandane/tiny-tests/Metadata/msg_replication_new_rep +++ b/cassandane/tiny-tests/Metadata/msg_replication_new_rep @@ -2,47 +2,46 @@ use Cassandane::Tiny; sub test_msg_replication_new_rep - :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing replication of message scope annotations"; - xlog $self, "case new_rep: new message appears, on replica only"; - - xlog $self, "need a master and replica pair"; - $self->assert_not_null($self->{replica}); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; - - $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Append a message and store an annotation"; - my %master_exp; - my %replica_exp; - $replica_exp{A} = $self->make_message('Message A', store => $replica_store); - $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $value1); - $replica_exp{A}->set_attribute('uid', 1); - $replica_exp{A}->set_annotation($entry, $attrib, $value1); - - xlog $self, "Before replication, message is missing from the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "Before replication, message is present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); - - $self->run_replication(); - $self->check_replication('cassandane'); - - $master_exp{A} = $replica_exp{A}->clone(); - xlog $self, "After replication, message is now present on the master"; - $self->check_messages(\%master_exp, store => $master_store); - xlog $self, "After replication, message is still present on the replica"; - $self->check_messages(\%replica_exp, store => $replica_store); - - xlog $self, "Check that annotations in the master and replica DB match"; - $self->check_msg_annotation_replication($master_store, $replica_store); + : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing replication of message scope annotations"; + xlog $self, "case new_rep: new message appears, on replica only"; + + xlog $self, "need a master and replica pair"; + $self->assert_not_null($self->{replica}); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; + + $master_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + $replica_store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Append a message and store an annotation"; + my %master_exp; + my %replica_exp; + $replica_exp{A} = $self->make_message('Message A', store => $replica_store); + $self->set_msg_annotation($replica_store, 1, $entry, $attrib, $value1); + $replica_exp{A}->set_attribute('uid', 1); + $replica_exp{A}->set_annotation($entry, $attrib, $value1); + + xlog $self, "Before replication, message is missing from the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "Before replication, message is present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); + + $self->run_replication(); + $self->check_replication('cassandane'); + + $master_exp{A} = $replica_exp{A}->clone(); + xlog $self, "After replication, message is now present on the master"; + $self->check_messages(\%master_exp, store => $master_store); + xlog $self, "After replication, message is still present on the replica"; + $self->check_messages(\%replica_exp, store => $replica_store); + + xlog $self, "Check that annotations in the master and replica DB match"; + $self->check_msg_annotation_replication($master_store, $replica_store); } diff --git a/cassandane/tiny-tests/Metadata/msg_sort_order b/cassandane/tiny-tests/Metadata/msg_sort_order index f5f339ceea..703513c573 100644 --- a/cassandane/tiny-tests/Metadata/msg_sort_order +++ b/cassandane/tiny-tests/Metadata/msg_sort_order @@ -1,44 +1,44 @@ #!perl use Cassandane::Tiny; -sub test_msg_sort_order -{ - my ($self) = @_; - - xlog $self, "testing RFC5257 SORT command ANNOTATION order criterion"; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - # 20 random dictionary words - my @values = ( qw(gradual flips tempe cud flaunt nina crackle congo), - qw(buttons coating byrd arise ayyubid badgers argosy), - qw(sutton dallied belled fondues mimi) ); - # the expected result of sorting those words alphabetically - my @exp_order = ( 15, 12, 13, 14, 18, 9, 11, 10, 8, - 7, 4, 17, 5, 2, 19, 1, 20, 6, 16, 3 ); - - $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Append some messages and store annotations"; - my %exp; - for (my $i = 0 ; $i < 20 ; $i++) - { - my $letter = chr(ord('A')+$i); - my $uid = $i+1; - my $value = $values[$i]; - - $exp{$letter} = $self->make_message("Message $letter"); - $self->set_msg_annotation(undef, $uid, $entry, $attrib, $value); - $exp{$letter}->set_attribute('uid', $uid); - $exp{$letter}->set_annotation($entry, $attrib, $value); - } - $self->check_messages(\%exp); - - my $talk = $self->{store}->get_client(); - - xlog $self, "run the SORT command with an ANNOTATION order criterion"; - my $res = $talk->sort("(ANNOTATION $entry $attrib)", 'utf-8', 'all'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals(\@exp_order, $res); +sub test_msg_sort_order { + my ($self) = @_; + + xlog $self, "testing RFC5257 SORT command ANNOTATION order criterion"; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + # 20 random dictionary words + my @values = ( + qw(gradual flips tempe cud flaunt nina crackle congo), + qw(buttons coating byrd arise ayyubid badgers argosy), + qw(sutton dallied belled fondues mimi) + ); + # the expected result of sorting those words alphabetically + my @exp_order + = (15, 12, 13, 14, 18, 9, 11, 10, 8, 7, 4, 17, 5, 2, 19, 1, 20, 6, 16, 3); + + $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Append some messages and store annotations"; + my %exp; + for (my $i = 0; $i < 20; $i++) { + my $letter = chr(ord('A') + $i); + my $uid = $i + 1; + my $value = $values[$i]; + + $exp{$letter} = $self->make_message("Message $letter"); + $self->set_msg_annotation(undef, $uid, $entry, $attrib, $value); + $exp{$letter}->set_attribute('uid', $uid); + $exp{$letter}->set_annotation($entry, $attrib, $value); + } + $self->check_messages(\%exp); + + my $talk = $self->{store}->get_client(); + + xlog $self, "run the SORT command with an ANNOTATION order criterion"; + my $res = $talk->sort("(ANNOTATION $entry $attrib)", 'utf-8', 'all'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals(\@exp_order, $res); } diff --git a/cassandane/tiny-tests/Metadata/msg_sort_search b/cassandane/tiny-tests/Metadata/msg_sort_search index a6f8b79350..c01b9f9455 100644 --- a/cassandane/tiny-tests/Metadata/msg_sort_search +++ b/cassandane/tiny-tests/Metadata/msg_sort_search @@ -1,56 +1,56 @@ #!perl use Cassandane::Tiny; -sub test_msg_sort_search -{ - my ($self) = @_; - - xlog $self, "testing RFC5257 SORT command ANNOTATION search criterion"; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - # 10 random dictionary words, and 10 carefully chosen ones - my @values = ( qw(deirdre agreed feedback cuspids breeds decreed greedily), - qw(gibbers eakins flash needful yules linseed equine hangman), - qw(hatters ragweed pureed cloaked heedless) ); - # the expected result of sorting the words with 'eed' alphabetically - my @exp_order = ( 2, 5, 6, 3, 7, 20, 13, 11, 18, 17 ); - # the expected result of search for words with 'eed' and uid order - my @exp_search = ( 2, 3, 5, 6, 7, 11, 13, 17, 18, 20 ); - - $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Append some messages and store annotations"; - my %exp; - my $now = DateTime->now->epoch; - for (my $i = 0 ; $i < 20 ; $i++) - { - my $letter = chr(ord('A')+$i); - my $uid = $i+1; - my $value = $values[$i]; - my $date = DateTime->from_epoch(epoch => $now - (20-$i)*60); - - $exp{$letter} = $self->make_message("Message $letter", - date => $date); - $self->set_msg_annotation(undef, $uid, $entry, $attrib, $value); - $exp{$letter}->set_attribute('uid', $uid); - $exp{$letter}->set_annotation($entry, $attrib, $value); - } - $self->check_messages(\%exp); - - my $talk = $self->{store}->get_client(); - - xlog $self, "run the SORT command with an ANNOTATION search criterion"; - my $res = $talk->sort("(DATE)", 'utf-8', - 'ANNOTATION', $entry, $attrib, { Quote => "eed" }); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals(\@exp_search, $res); - - xlog $self, "run the SORT command with both ANNOTATION search & order criteria"; - $res = $talk->sort("(ANNOTATION $entry $attrib)", 'utf-8', - 'ANNOTATION', $entry, $attrib, { Quote => "eed" }); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals(\@exp_order, $res); +sub test_msg_sort_search { + my ($self) = @_; + + xlog $self, "testing RFC5257 SORT command ANNOTATION search criterion"; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + # 10 random dictionary words, and 10 carefully chosen ones + my @values = ( + qw(deirdre agreed feedback cuspids breeds decreed greedily), + qw(gibbers eakins flash needful yules linseed equine hangman), + qw(hatters ragweed pureed cloaked heedless) + ); + # the expected result of sorting the words with 'eed' alphabetically + my @exp_order = (2, 5, 6, 3, 7, 20, 13, 11, 18, 17); + # the expected result of search for words with 'eed' and uid order + my @exp_search = (2, 3, 5, 6, 7, 11, 13, 17, 18, 20); + + $self->{store}->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Append some messages and store annotations"; + my %exp; + my $now = DateTime->now->epoch; + for (my $i = 0; $i < 20; $i++) { + my $letter = chr(ord('A') + $i); + my $uid = $i + 1; + my $value = $values[$i]; + my $date = DateTime->from_epoch(epoch => $now - (20 - $i) * 60); + + $exp{$letter} = $self->make_message("Message $letter", date => $date); + $self->set_msg_annotation(undef, $uid, $entry, $attrib, $value); + $exp{$letter}->set_attribute('uid', $uid); + $exp{$letter}->set_annotation($entry, $attrib, $value); + } + $self->check_messages(\%exp); + + my $talk = $self->{store}->get_client(); + + xlog $self, "run the SORT command with an ANNOTATION search criterion"; + my $res = $talk->sort("(DATE)", 'utf-8', + 'ANNOTATION', $entry, $attrib, { Quote => "eed" }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals(\@exp_search, $res); + + xlog $self, + "run the SORT command with both ANNOTATION search & order criteria"; + $res = $talk->sort("(ANNOTATION $entry $attrib)", + 'utf-8', 'ANNOTATION', $entry, $attrib, { Quote => "eed" }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals(\@exp_order, $res); } diff --git a/cassandane/tiny-tests/Metadata/nonexistant_mailbox b/cassandane/tiny-tests/Metadata/nonexistant_mailbox index c0c4caa44e..05da5533ac 100644 --- a/cassandane/tiny-tests/Metadata/nonexistant_mailbox +++ b/cassandane/tiny-tests/Metadata/nonexistant_mailbox @@ -1,22 +1,21 @@ #!perl use Cassandane::Tiny; -sub test_nonexistant_mailbox -{ - my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); - my $entry = '/shared/comment'; - my $folder = 'INBOX.nonesuch'; - # data thanks to hipsteripsum.me - my $value1 = "Farm-to-table"; +sub test_nonexistant_mailbox { + my ($self) = @_; + my $imaptalk = $self->{store}->get_client(); + my $entry = '/shared/comment'; + my $folder = 'INBOX.nonesuch'; + # data thanks to hipsteripsum.me + my $value1 = "Farm-to-table"; - my $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert($imaptalk->get_last_error() =~ m/does not exist/i); - $self->assert_null($res); + my $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert($imaptalk->get_last_error() =~ m/does not exist/i); + $self->assert_null($res); - $res = $imaptalk->setmetadata($folder, $entry, $value1); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert($imaptalk->get_last_error() =~ m/does not exist/i); - $self->assert_null($res); + $res = $imaptalk->setmetadata($folder, $entry, $value1); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert($imaptalk->get_last_error() =~ m/does not exist/i); + $self->assert_null($res); } diff --git a/cassandane/tiny-tests/Metadata/permessage_getset b/cassandane/tiny-tests/Metadata/permessage_getset index 01bd9b676b..34c3e47c1d 100644 --- a/cassandane/tiny-tests/Metadata/permessage_getset +++ b/cassandane/tiny-tests/Metadata/permessage_getset @@ -1,132 +1,127 @@ #!perl use Cassandane::Tiny; -sub test_permessage_getset -{ - my ($self) = @_; - - xlog $self, "testing getting and setting message scope annotations"; - - my $talk = $self->{store}->get_client(); - - xlog $self, "Append 3 messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{B} = $self->make_message('Message B'); - $msg{C} = $self->make_message('Message C'); - - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; - my $value2 = "Goodnight\0Irene"; - my $value3 = "Gump"; - - xlog $self, "fetch an annotation - should be no values"; - my $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => undef } } }, - 2 => { annotation => { $entry => { $attrib => undef } } }, - 3 => { annotation => { $entry => { $attrib => undef } } }, - }, - $res); - - xlog $self, "store an annotation"; - $talk->store('1', 'annotation', - [$entry, [$attrib, $value1]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "fetch the annotation again, should see changes"; - $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => $value1 } } }, - 2 => { annotation => { $entry => { $attrib => undef } } }, - 3 => { annotation => { $entry => { $attrib => undef } } }, - }, - $res); - - xlog $self, "store an annotation with an embedded NUL"; - $talk->store('3', 'annotation', - [$entry, [$attrib, $value2]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "fetch the annotation again, should see changes"; - $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => $value1 } } }, - 2 => { annotation => { $entry => { $attrib => undef } } }, - 3 => { annotation => { $entry => { $attrib => $value2 } } }, - }, - $res); - - xlog $self, "store multiple annotations"; - # Note $value3 has no whitespace so we have to - # convince Mail::IMAPTalk to quote it anyway - $talk->store('1:*', 'annotation', - [$entry, [$attrib, { Quote => $value3 }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "fetch the annotation again, should see changes"; - $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => $value3 } } }, - 2 => { annotation => { $entry => { $attrib => $value3 } } }, - 3 => { annotation => { $entry => { $attrib => $value3 } } }, - }, - $res); - - xlog $self, "delete an annotation"; - # Note $value3 has no whitespace so we have to - # convince Mail::IMAPTalk to quote it anyway - $talk->store('2', 'annotation', - [$entry, [$attrib, undef]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "fetch the annotation again, should see changes"; - $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => $value3 } } }, - 2 => { annotation => { $entry => { $attrib => undef } } }, - 3 => { annotation => { $entry => { $attrib => $value3 } } }, - }, - $res); - - xlog $self, "delete all annotations"; - # Note $value3 has no whitespace so we have to - # convince Mail::IMAPTalk to quote it anyway - $talk->store('1:*', 'annotation', - [$entry, [$attrib, undef]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "fetch the annotation again, should see changes"; - $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => undef } } }, - 2 => { annotation => { $entry => { $attrib => undef } } }, - 3 => { annotation => { $entry => { $attrib => undef } } }, - }, - $res); +sub test_permessage_getset { + my ($self) = @_; + + xlog $self, "testing getting and setting message scope annotations"; + + my $talk = $self->{store}->get_client(); + + xlog $self, "Append 3 messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{B} = $self->make_message('Message B'); + $msg{C} = $self->make_message('Message C'); + + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; + my $value2 = "Goodnight\0Irene"; + my $value3 = "Gump"; + + xlog $self, "fetch an annotation - should be no values"; + my $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => undef } } }, + 2 => { annotation => { $entry => { $attrib => undef } } }, + 3 => { annotation => { $entry => { $attrib => undef } } }, + }, + $res + ); + + xlog $self, "store an annotation"; + $talk->store('1', 'annotation', [ $entry, [ $attrib, $value1 ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "fetch the annotation again, should see changes"; + $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => $value1 } } }, + 2 => { annotation => { $entry => { $attrib => undef } } }, + 3 => { annotation => { $entry => { $attrib => undef } } }, + }, + $res + ); + + xlog $self, "store an annotation with an embedded NUL"; + $talk->store('3', 'annotation', [ $entry, [ $attrib, $value2 ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "fetch the annotation again, should see changes"; + $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => $value1 } } }, + 2 => { annotation => { $entry => { $attrib => undef } } }, + 3 => { annotation => { $entry => { $attrib => $value2 } } }, + }, + $res + ); + + xlog $self, "store multiple annotations"; + # Note $value3 has no whitespace so we have to + # convince Mail::IMAPTalk to quote it anyway + $talk->store('1:*', 'annotation', + [ $entry, [ $attrib, { Quote => $value3 } ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "fetch the annotation again, should see changes"; + $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => $value3 } } }, + 2 => { annotation => { $entry => { $attrib => $value3 } } }, + 3 => { annotation => { $entry => { $attrib => $value3 } } }, + }, + $res + ); + + xlog $self, "delete an annotation"; + # Note $value3 has no whitespace so we have to + # convince Mail::IMAPTalk to quote it anyway + $talk->store('2', 'annotation', [ $entry, [ $attrib, undef ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "fetch the annotation again, should see changes"; + $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => $value3 } } }, + 2 => { annotation => { $entry => { $attrib => undef } } }, + 3 => { annotation => { $entry => { $attrib => $value3 } } }, + }, + $res + ); + + xlog $self, "delete all annotations"; + # Note $value3 has no whitespace so we have to + # convince Mail::IMAPTalk to quote it anyway + $talk->store('1:*', 'annotation', [ $entry, [ $attrib, undef ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "fetch the annotation again, should see changes"; + $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => undef } } }, + 2 => { annotation => { $entry => { $attrib => undef } } }, + 3 => { annotation => { $entry => { $attrib => undef } } }, + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/permessage_unknown b/cassandane/tiny-tests/Metadata/permessage_unknown index 801d39480e..e65badf696 100644 --- a/cassandane/tiny-tests/Metadata/permessage_unknown +++ b/cassandane/tiny-tests/Metadata/permessage_unknown @@ -1,46 +1,44 @@ #!perl use Cassandane::Tiny; -sub test_permessage_unknown -{ - my ($self) = @_; +sub test_permessage_unknown { + my ($self) = @_; - xlog $self, "testing getting and setting unknown annotations on a message"; - xlog $self, "where this is forbidden by the default config"; + xlog $self, "testing getting and setting unknown annotations on a message"; + xlog $self, "where this is forbidden by the default config"; - xlog $self, "Append a message"; - my %msg; - $msg{A} = $self->make_message('Message A'); + xlog $self, "Append a message"; + my %msg; + $msg{A} = $self->make_message('Message A'); - my $entry = '/thisentryisnotdefined'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; + my $entry = '/thisentryisnotdefined'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; - xlog $self, "fetch annotation - should be no values"; - my $talk = $self->{store}->get_client(); - my $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => undef } } } - }, - $res); + xlog $self, "fetch annotation - should be no values"; + my $talk = $self->{store}->get_client(); + my $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => undef } } } + }, + $res + ); - xlog $self, "store annotation - should fail"; - $talk->store('1', 'annotation', - [$entry, [$attrib, $value1]]); - $self->assert_str_equals('no', $talk->get_last_completion_response()); + xlog $self, "store annotation - should fail"; + $talk->store('1', 'annotation', [ $entry, [ $attrib, $value1 ] ]); + $self->assert_str_equals('no', $talk->get_last_completion_response()); - xlog $self, "fetch the annotation again, should see nothing"; - $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => undef } } } - }, - $res); + xlog $self, "fetch the annotation again, should see nothing"; + $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => undef } } } + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/permessage_unknown_allowed b/cassandane/tiny-tests/Metadata/permessage_unknown_allowed index 0adbf1f8dc..1fc451be68 100644 --- a/cassandane/tiny-tests/Metadata/permessage_unknown_allowed +++ b/cassandane/tiny-tests/Metadata/permessage_unknown_allowed @@ -2,46 +2,44 @@ use Cassandane::Tiny; sub test_permessage_unknown_allowed - :AnnotationAllowUndefined -{ - my ($self) = @_; + : AnnotationAllowUndefined { + my ($self) = @_; - xlog $self, "testing getting and setting unknown annotations on a message"; - xlog $self, "with config allowing this"; + xlog $self, "testing getting and setting unknown annotations on a message"; + xlog $self, "with config allowing this"; - xlog $self, "Append a message"; - my %msg; - $msg{A} = $self->make_message('Message A'); + xlog $self, "Append a message"; + my %msg; + $msg{A} = $self->make_message('Message A'); - my $entry = '/thisentryisnotdefined'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; + my $entry = '/thisentryisnotdefined'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; - xlog $self, "fetch annotation - should be no values"; - my $talk = $self->{store}->get_client(); - my $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => undef } } } - }, - $res); + xlog $self, "fetch annotation - should be no values"; + my $talk = $self->{store}->get_client(); + my $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => undef } } } + }, + $res + ); - xlog $self, "store annotation - should succeed"; - $talk->store('1', 'annotation', - [$entry, [$attrib, $value1]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "store annotation - should succeed"; + $talk->store('1', 'annotation', [ $entry, [ $attrib, $value1 ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "fetch the annotation again, should see the value"; - $res = $talk->fetch('1:*', - ['annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { annotation => { $entry => { $attrib => $value1 } } } - }, - $res); + xlog $self, "fetch the annotation again, should see the value"; + $res = $talk->fetch('1:*', [ 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { annotation => { $entry => { $attrib => $value1 } } } + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/private b/cassandane/tiny-tests/Metadata/private index 10c8cec414..975d2e6e28 100644 --- a/cassandane/tiny-tests/Metadata/private +++ b/cassandane/tiny-tests/Metadata/private @@ -1,61 +1,63 @@ #!perl use Cassandane::Tiny; -sub test_private -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - xlog $self, "testing private metadata operations"; - - xlog $self, "testing specific entries"; - my $res = $imaptalk->getmetadata('INBOX', {depth => 'infinity'}, '/private'); - my $r = $res->{INBOX}; - $self->assert_not_null($r); - my %specific_entries = ( - '/private/vendor/cmu/cyrus-imapd/squat' => undef, - '/private/vendor/cmu/cyrus-imapd/sieve' => undef, - '/private/vendor/cmu/cyrus-imapd/news2mail' => undef, - '/private/vendor/cmu/cyrus-imapd/expire' => undef, - '/private/thread' => undef, - '/private/sort' => undef, - '/private/comment' => undef, - '/private/checkperiod' => undef, - '/private/check' => undef, - '/private/specialuse' => undef, - '/private' => undef, - ); - my ($maj, $min, $rev) = Cassandane::Instance->get_version(); - # We introduced vendor/cmu/cyrus-imapd/{archive,delete} in 3.1.0 - if ($maj > 3 or ($maj == 3 and $min >= 1)) { - $specific_entries{'/private/vendor/cmu/cyrus-imapd/archive'} = undef; - $specific_entries{'/private/vendor/cmu/cyrus-imapd/delete'} = undef; - } - # We introduced vendor/cmu/cyrus-imapd/sortorder in 3.1.3 - if ($maj > 3 or ($maj == 3 and ($min > 1 or ($min == 1 and $rev >= 3)))) { - $specific_entries{'/private/vendor/cmu/cyrus-imapd/sortorder'} = undef; - } - # We introduced vendor/cmu/cyrus-imapd/search-fuzzy-always in 3.3.0 - if ($maj > 3 or ($maj == 3 and $min >= 3)) { - $specific_entries{'/private/vendor/cmu/cyrus-imapd/search-fuzzy-always'} = undef; - } - - # We introduced vendor/cmu/cyrus-imapd/noexpire_until in 3.9.0 - if ($maj > 3 or ($maj == 3 and $min >= 9)) { - $specific_entries{'/private/vendor/cmu/cyrus-imapd/noexpire_until'} = undef; - } - - $self->assert_deep_equals(\%specific_entries, $r); - - $imaptalk->setmetadata('INBOX', "/private/comment", "This is a comment"); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - my $com = $imaptalk->getmetadata('INBOX', "/private/comment"); - $self->assert_str_equals("This is a comment", $com->{INBOX}{"/private/comment"}); - - # remove it again - $imaptalk->setmetadata('INBOX', "/private/comment", undef); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $com = $imaptalk->getmetadata('INBOX', "/private/comment"); - $self->assert_null($com->{INBOX}{"/private/comment"}); +sub test_private { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + xlog $self, "testing private metadata operations"; + + xlog $self, "testing specific entries"; + my $res + = $imaptalk->getmetadata('INBOX', { depth => 'infinity' }, '/private'); + my $r = $res->{INBOX}; + $self->assert_not_null($r); + my %specific_entries = ( + '/private/vendor/cmu/cyrus-imapd/squat' => undef, + '/private/vendor/cmu/cyrus-imapd/sieve' => undef, + '/private/vendor/cmu/cyrus-imapd/news2mail' => undef, + '/private/vendor/cmu/cyrus-imapd/expire' => undef, + '/private/thread' => undef, + '/private/sort' => undef, + '/private/comment' => undef, + '/private/checkperiod' => undef, + '/private/check' => undef, + '/private/specialuse' => undef, + '/private' => undef, + ); + my ($maj, $min, $rev) = Cassandane::Instance->get_version(); + # We introduced vendor/cmu/cyrus-imapd/{archive,delete} in 3.1.0 + if ($maj > 3 or ($maj == 3 and $min >= 1)) { + $specific_entries{'/private/vendor/cmu/cyrus-imapd/archive'} = undef; + $specific_entries{'/private/vendor/cmu/cyrus-imapd/delete'} = undef; + } + # We introduced vendor/cmu/cyrus-imapd/sortorder in 3.1.3 + if ($maj > 3 or ($maj == 3 and ($min > 1 or ($min == 1 and $rev >= 3)))) { + $specific_entries{'/private/vendor/cmu/cyrus-imapd/sortorder'} = undef; + } + # We introduced vendor/cmu/cyrus-imapd/search-fuzzy-always in 3.3.0 + if ($maj > 3 or ($maj == 3 and $min >= 3)) { + $specific_entries{'/private/vendor/cmu/cyrus-imapd/search-fuzzy-always'} + = undef; + } + + # We introduced vendor/cmu/cyrus-imapd/noexpire_until in 3.9.0 + if ($maj > 3 or ($maj == 3 and $min >= 9)) { + $specific_entries{'/private/vendor/cmu/cyrus-imapd/noexpire_until'} = undef; + } + + $self->assert_deep_equals(\%specific_entries, $r); + + $imaptalk->setmetadata('INBOX', "/private/comment", "This is a comment"); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + my $com = $imaptalk->getmetadata('INBOX', "/private/comment"); + $self->assert_str_equals("This is a comment", + $com->{INBOX}{"/private/comment"}); + + # remove it again + $imaptalk->setmetadata('INBOX', "/private/comment", undef); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $com = $imaptalk->getmetadata('INBOX', "/private/comment"); + $self->assert_null($com->{INBOX}{"/private/comment"}); } diff --git a/cassandane/tiny-tests/Metadata/private_global_annot_replication b/cassandane/tiny-tests/Metadata/private_global_annot_replication index 6259dc35a2..1c98fc7b77 100644 --- a/cassandane/tiny-tests/Metadata/private_global_annot_replication +++ b/cassandane/tiny-tests/Metadata/private_global_annot_replication @@ -5,101 +5,101 @@ use Cassandane::Tiny; # Test the /private/foobar server annotation replicates correctly # sub test_private_global_annot_replication - :Replication :SyncLog :AnnotationAllowUndefined - :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing /private/foobar"; - - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - - $self->assert_not_null($self->{replica}); - - my $imaptalk = $self->{master_store}->get_client(); - - my $res; - my $entry = '/private/foobar'; - my $value1 = "Hello World this is a value - with a random annot"; - - xlog $self, "initial value is NIL"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); - - xlog $self, "can set the value as ordinary user"; - $imaptalk->setmetadata("", $entry, $value1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "can get the set value back"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - $self->{master_store}->disconnect(); - $imaptalk = $self->{master_store}->get_client(); - - xlog $self, "the annot gives the same value in the new connection"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "replica value is NIL"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); - - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); - - xlog $self, "the annot gives the same value on the replica"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "can delete value"; - $imaptalk = $self->{master_store}->get_client(); - $imaptalk->setmetadata("", $entry, undef); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => undef } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "run replication to clear annot"; - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); - - xlog $self, "replica value is NIL"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); + : Replication : SyncLog : AnnotationAllowUndefined + : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing /private/foobar"; + + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + + $self->assert_not_null($self->{replica}); + + my $imaptalk = $self->{master_store}->get_client(); + + my $res; + my $entry = '/private/foobar'; + my $value1 = "Hello World this is a value - with a random annot"; + + xlog $self, "initial value is NIL"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); + + xlog $self, "can set the value as ordinary user"; + $imaptalk->setmetadata("", $entry, $value1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "can get the set value back"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + $self->{master_store}->disconnect(); + $imaptalk = $self->{master_store}->get_client(); + + xlog $self, "the annot gives the same value in the new connection"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "replica value is NIL"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); + + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); + + xlog $self, "the annot gives the same value on the replica"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "can delete value"; + $imaptalk = $self->{master_store}->get_client(); + $imaptalk->setmetadata("", $entry, undef); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => undef } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "run replication to clear annot"; + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); + + xlog $self, "replica value is NIL"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/set_specialuse_twice b/cassandane/tiny-tests/Metadata/set_specialuse_twice index 2d27b39221..92e46c46f0 100644 --- a/cassandane/tiny-tests/Metadata/set_specialuse_twice +++ b/cassandane/tiny-tests/Metadata/set_specialuse_twice @@ -1,79 +1,80 @@ #!perl use Cassandane::Tiny; -sub test_set_specialuse_twice -{ - my ($self) = @_; - - xlog $self, "testing if we could /private/specialuse twice on a folder"; - - my $imaptalk = $self->{store}->get_client(); - my $entry = '/private/specialuse'; - my $sentry = '/shared/specialuse'; - my $folder1 = 'INBOX.bar'; - my $folder2 = 'INBOX.foo'; - my $specialuse1 = '\Sent \Trash'; - my $specialuse2 = '\Sent \Trash \Junk'; - my $specialuse3 = '\Sent'; - my $specialuse4 = '\Drafts'; - my $specialuse5 = '\Junk \Archive'; - my $specialuse6 = '\Drafts \Archive'; - my $res; - - xlog $self, "Create a folder $folder1"; - $imaptalk->create($folder1) - or die "Cannot create mailbox $folder1: $@"; - - xlog $self, "Create a folder $folder2"; - $imaptalk->create($folder2) - or die "Cannot create mailbox $folder2: $@"; - - $res = $imaptalk->getmetadata($folder1, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - delete $res->{$sentry}; - $self->assert_deep_equals({ - $folder1 => { $entry => undef } - }, $res); - - - xlog $self, "Set $folder1 to be $specialuse1"; - $imaptalk->setmetadata($folder1, $entry, $specialuse1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Set $folder2 to be $specialuse4"; - $imaptalk->setmetadata($folder2, $entry, $specialuse4); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Set $folder1 to $specialuse2, and it should work."; - $imaptalk->setmetadata($folder1, $entry, $specialuse2); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Set $folder1 to $specialuse1, and it should work."; - $imaptalk->setmetadata($folder1, $entry, $specialuse1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Set $folder1 to $specialuse3, and it should work."; - $imaptalk->setmetadata($folder1, $entry, $specialuse2); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Set $folder1 to $specialuse1, and it should work."; - $imaptalk->setmetadata($folder1, $entry, $specialuse1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Set $folder1 to $specialuse4, and it should not work."; - $imaptalk->setmetadata($folder1, $entry, $specialuse4); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - - xlog $self, "Set $folder1 to $specialuse5, and it should work."; - $imaptalk->setmetadata($folder1, $entry, $specialuse5); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Set $folder2 to be $specialuse1"; - $imaptalk->setmetadata($folder2, $entry, $specialuse1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "Now set $folder1 to $specialuse6, and it should work."; - $imaptalk->setmetadata($folder1, $entry, $specialuse6); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); +sub test_set_specialuse_twice { + my ($self) = @_; + + xlog $self, "testing if we could /private/specialuse twice on a folder"; + + my $imaptalk = $self->{store}->get_client(); + my $entry = '/private/specialuse'; + my $sentry = '/shared/specialuse'; + my $folder1 = 'INBOX.bar'; + my $folder2 = 'INBOX.foo'; + my $specialuse1 = '\Sent \Trash'; + my $specialuse2 = '\Sent \Trash \Junk'; + my $specialuse3 = '\Sent'; + my $specialuse4 = '\Drafts'; + my $specialuse5 = '\Junk \Archive'; + my $specialuse6 = '\Drafts \Archive'; + my $res; + + xlog $self, "Create a folder $folder1"; + $imaptalk->create($folder1) + or die "Cannot create mailbox $folder1: $@"; + + xlog $self, "Create a folder $folder2"; + $imaptalk->create($folder2) + or die "Cannot create mailbox $folder2: $@"; + + $res = $imaptalk->getmetadata($folder1, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + delete $res->{$sentry}; + $self->assert_deep_equals( + { + $folder1 => { $entry => undef } + }, + $res + ); + + xlog $self, "Set $folder1 to be $specialuse1"; + $imaptalk->setmetadata($folder1, $entry, $specialuse1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Set $folder2 to be $specialuse4"; + $imaptalk->setmetadata($folder2, $entry, $specialuse4); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Set $folder1 to $specialuse2, and it should work."; + $imaptalk->setmetadata($folder1, $entry, $specialuse2); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Set $folder1 to $specialuse1, and it should work."; + $imaptalk->setmetadata($folder1, $entry, $specialuse1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Set $folder1 to $specialuse3, and it should work."; + $imaptalk->setmetadata($folder1, $entry, $specialuse2); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Set $folder1 to $specialuse1, and it should work."; + $imaptalk->setmetadata($folder1, $entry, $specialuse1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Set $folder1 to $specialuse4, and it should not work."; + $imaptalk->setmetadata($folder1, $entry, $specialuse4); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + + xlog $self, "Set $folder1 to $specialuse5, and it should work."; + $imaptalk->setmetadata($folder1, $entry, $specialuse5); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Set $folder2 to be $specialuse1"; + $imaptalk->setmetadata($folder2, $entry, $specialuse1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "Now set $folder1 to $specialuse6, and it should work."; + $imaptalk->setmetadata($folder1, $entry, $specialuse6); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/Metadata/shared b/cassandane/tiny-tests/Metadata/shared index 3904b7dc1c..1d96c6afb4 100644 --- a/cassandane/tiny-tests/Metadata/shared +++ b/cassandane/tiny-tests/Metadata/shared @@ -4,101 +4,106 @@ use Cassandane::Tiny; # # Test the cyrus annotations # -sub test_shared -{ - my ($self) = @_; +sub test_shared { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "reading read_only Cyrus annotations"; - my $res = $imaptalk->getmetadata('INBOX', {depth => 'infinity'}, '/shared'); - my $r = $res->{INBOX}; - $self->assert_not_null($r); + xlog $self, "reading read_only Cyrus annotations"; + my $res = $imaptalk->getmetadata('INBOX', { depth => 'infinity' }, '/shared'); + my $r = $res->{INBOX}; + $self->assert_not_null($r); - xlog $self, "checking specific entries"; - # Note: lastupdate will be a time string close within the - # last second, but I'm too lazy to check that properly - $self->assert_not_null($r->{'/shared/vendor/cmu/cyrus-imapd/lastupdate'}); - delete $r->{'/shared/vendor/cmu/cyrus-imapd/lastupdate'}; - # Note: uniqueid will be a hash of some information that - # we can't entirely predict - $self->assert_not_null($r->{'/shared/vendor/cmu/cyrus-imapd/uniqueid'}); - delete $r->{'/shared/vendor/cmu/cyrus-imapd/uniqueid'}; - my %specific_entries = ( - '/shared/vendor/cmu/cyrus-imapd/squat' => undef, - '/shared/vendor/cmu/cyrus-imapd/size' => '0', - '/shared/vendor/cmu/cyrus-imapd/sieve' => undef, - '/shared/vendor/cmu/cyrus-imapd/sharedseen' => 'false', - '/shared/vendor/cmu/cyrus-imapd/pop3showafter' => undef, - '/shared/vendor/cmu/cyrus-imapd/pop3newuidl' => 'true', - '/shared/vendor/cmu/cyrus-imapd/partition' => 'default', - '/shared/vendor/cmu/cyrus-imapd/news2mail' => undef, - '/shared/vendor/cmu/cyrus-imapd/lastpop' => undef, - '/shared/vendor/cmu/cyrus-imapd/expire' => undef, - '/shared/vendor/cmu/cyrus-imapd/duplicatedeliver' => 'false', - '/shared/vendor/cmu/cyrus-imapd/userrawquota' => undef, - '/shared/specialuse' => undef, - '/shared/thread' => undef, - '/shared/sort' => undef, - '/shared/specialuse' => undef, - '/shared/comment' => undef, - '/shared/checkperiod' => undef, - '/shared/check' => undef, - '/shared' => undef, - ); - # Note: annotsize/synccrcs new in 3.0 - my ($maj, $min, $rev) = Cassandane::Instance->get_version(); - if ($maj >= 3) { - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/annotsize'} = '0'; - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/synccrcs'} = '0 0'; - } - # We introduced vendor/cmu/cyrus-imapd/{archive,delete} in 3.1.0 - if ($maj > 3 or ($maj == 3 and $min >= 1)) { - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/archive'} = undef; - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/delete'} = undef; - } - # We introduced vendor/cmu/cyrus-imapd/sortorder in 3.1.3 - if ($maj > 3 or ($maj == 3 and ($min > 1 or ($min == 1 and $rev >= 3)))) { - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/sortorder'} = undef; - } - # synccrcs got a new default in 3.1.7, and hasalarms got added - # XXX Not sure how useful it is to keep subdividing our 3.1 tests, we - # XXX expect this unstable series to be a moving target. Once 3.2 forks - # XXX I think we def should collapse all these 3.1s into a single 3.1 - if ($maj > 3 or ($maj == 3 and ($min > 1 or ($min == 1 and $rev >= 7)))) { - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/synccrcs'} = - '0 12345678'; - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/hasalarms'} = 'false'; - } - # foldermodsseq was added in 3.2.0 - if ($maj > 3 or ($maj == 3 and ($min > 1))) { - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/foldermodseq'} = 4; - } - # We introduced vendor/cmu/cyrus-imapd/search-fuzzy-always in 3.3.0 - if ($maj > 3 or ($maj == 3 and $min >= 3)) { - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/search-fuzzy-always'} = undef; - } + xlog $self, "checking specific entries"; + # Note: lastupdate will be a time string close within the + # last second, but I'm too lazy to check that properly + $self->assert_not_null($r->{'/shared/vendor/cmu/cyrus-imapd/lastupdate'}); + delete $r->{'/shared/vendor/cmu/cyrus-imapd/lastupdate'}; + # Note: uniqueid will be a hash of some information that + # we can't entirely predict + $self->assert_not_null($r->{'/shared/vendor/cmu/cyrus-imapd/uniqueid'}); + delete $r->{'/shared/vendor/cmu/cyrus-imapd/uniqueid'}; + my %specific_entries = ( + '/shared/vendor/cmu/cyrus-imapd/squat' => undef, + '/shared/vendor/cmu/cyrus-imapd/size' => '0', + '/shared/vendor/cmu/cyrus-imapd/sieve' => undef, + '/shared/vendor/cmu/cyrus-imapd/sharedseen' => 'false', + '/shared/vendor/cmu/cyrus-imapd/pop3showafter' => undef, + '/shared/vendor/cmu/cyrus-imapd/pop3newuidl' => 'true', + '/shared/vendor/cmu/cyrus-imapd/partition' => 'default', + '/shared/vendor/cmu/cyrus-imapd/news2mail' => undef, + '/shared/vendor/cmu/cyrus-imapd/lastpop' => undef, + '/shared/vendor/cmu/cyrus-imapd/expire' => undef, + '/shared/vendor/cmu/cyrus-imapd/duplicatedeliver' => 'false', + '/shared/vendor/cmu/cyrus-imapd/userrawquota' => undef, + '/shared/specialuse' => undef, + '/shared/thread' => undef, + '/shared/sort' => undef, + '/shared/specialuse' => undef, + '/shared/comment' => undef, + '/shared/checkperiod' => undef, + '/shared/check' => undef, + '/shared' => undef, + ); + # Note: annotsize/synccrcs new in 3.0 + my ($maj, $min, $rev) = Cassandane::Instance->get_version(); + if ($maj >= 3) { + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/annotsize'} = '0'; + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/synccrcs'} = '0 0'; + } + # We introduced vendor/cmu/cyrus-imapd/{archive,delete} in 3.1.0 + if ($maj > 3 or ($maj == 3 and $min >= 1)) { + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/archive'} = undef; + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/delete'} = undef; + } + # We introduced vendor/cmu/cyrus-imapd/sortorder in 3.1.3 + if ($maj > 3 or ($maj == 3 and ($min > 1 or ($min == 1 and $rev >= 3)))) { + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/sortorder'} = undef; + } + # synccrcs got a new default in 3.1.7, and hasalarms got added + # XXX Not sure how useful it is to keep subdividing our 3.1 tests, we + # XXX expect this unstable series to be a moving target. Once 3.2 forks + # XXX I think we def should collapse all these 3.1s into a single 3.1 + if ($maj > 3 or ($maj == 3 and ($min > 1 or ($min == 1 and $rev >= 7)))) { + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/synccrcs'} = '0 12345678'; + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/hasalarms'} = 'false'; + } + # foldermodsseq was added in 3.2.0 + if ($maj > 3 or ($maj == 3 and ($min > 1))) { + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/foldermodseq'} = 4; + } + # We introduced vendor/cmu/cyrus-imapd/search-fuzzy-always in 3.3.0 + if ($maj > 3 or ($maj == 3 and $min >= 3)) { + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/search-fuzzy-always'} + = undef; + } - # We introduced vendor/cmu/cyrus-imapd/noexpire_until in 3.9.0 - if ($maj > 3 or ($maj == 3 and $min >= 9)) { - $specific_entries{'/shared/vendor/cmu/cyrus-imapd/noexpire_until'} = undef; - } + # We introduced vendor/cmu/cyrus-imapd/noexpire_until in 3.9.0 + if ($maj > 3 or ($maj == 3 and $min >= 9)) { + $specific_entries{'/shared/vendor/cmu/cyrus-imapd/noexpire_until'} = undef; + } - $self->assert_deep_equals(\%specific_entries, $r); + $self->assert_deep_equals(\%specific_entries, $r); - # individual item fetch: - my $part = $imaptalk->getmetadata('INBOX', "/shared/vendor/cmu/cyrus-imapd/partition"); - $self->assert_str_equals('default', $part->{INBOX}{"/shared/vendor/cmu/cyrus-imapd/partition"}); + # individual item fetch: + my $part = $imaptalk->getmetadata('INBOX', + "/shared/vendor/cmu/cyrus-imapd/partition"); + $self->assert_str_equals('default', + $part->{INBOX}{"/shared/vendor/cmu/cyrus-imapd/partition"}); - # duplicate deliver should be false - $self->assert_str_equals('false', $res->{INBOX}{"/shared/vendor/cmu/cyrus-imapd/duplicatedeliver"}); + # duplicate deliver should be false + $self->assert_str_equals('false', + $res->{INBOX}{"/shared/vendor/cmu/cyrus-imapd/duplicatedeliver"}); - # set duplicate deliver (as admin) - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->setmetadata('user.cassandane', "/shared/vendor/cmu/cyrus-imapd/duplicatedeliver", 'true'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + # set duplicate deliver (as admin) + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->setmetadata('user.cassandane', + "/shared/vendor/cmu/cyrus-imapd/duplicatedeliver", 'true'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - # and make sure the change sticks - my $dup = $imaptalk->getmetadata('INBOX', "/shared/vendor/cmu/cyrus-imapd/duplicatedeliver"); - $self->assert_str_equals('true', $dup->{INBOX}{"/shared/vendor/cmu/cyrus-imapd/duplicatedeliver"}); + # and make sure the change sticks + my $dup = $imaptalk->getmetadata('INBOX', + "/shared/vendor/cmu/cyrus-imapd/duplicatedeliver"); + $self->assert_str_equals('true', + $dup->{INBOX}{"/shared/vendor/cmu/cyrus-imapd/duplicatedeliver"}); } diff --git a/cassandane/tiny-tests/Metadata/shared_global_annot_replication b/cassandane/tiny-tests/Metadata/shared_global_annot_replication index f48b7a0c28..995bbc72cd 100644 --- a/cassandane/tiny-tests/Metadata/shared_global_annot_replication +++ b/cassandane/tiny-tests/Metadata/shared_global_annot_replication @@ -5,107 +5,107 @@ use Cassandane::Tiny; # Test the /shared/foobar server annotation replicates correctly # sub test_shared_global_annot_replication - :Replication :SyncLog :AnnotationAllowUndefined - :needs_component_idled -{ - my ($self) = @_; - - xlog $self, "testing /shared/foobar"; - - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - - $self->assert_not_null($self->{replica}); - - my $imaptalk = $self->{master_store}->get_client(); - - my $res; - my $entry = '/shared/foobar'; - my $value1 = "Hello World this is a value - with a random annot"; - - xlog $self, "initial value is NIL"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); - - xlog $self, "cannot set the value as ordinary user"; - $imaptalk->setmetadata("", $entry, $value1); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert($imaptalk->get_last_error() =~ m/permission denied/i); - - xlog $self, "can set the value as admin"; - $imaptalk = $self->{adminstore}->get_client(); - $imaptalk->setmetadata("", $entry, $value1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "can get the set value back"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - $self->{adminstore}->disconnect(); - $imaptalk = $self->{adminstore}->get_client(); - - xlog $self, "the annot gives the same value in the new connection"; - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "replica value is NIL"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); - - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); - - xlog $self, "the annot gives the same value on the replica"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => $value1 } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "can delete value"; - $imaptalk = $self->{adminstore}->get_client(); - $imaptalk->setmetadata("", $entry, undef); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $expected = { - "" => { $entry => undef } - }; - $self->assert_deep_equals($expected, $res); - - xlog $self, "run replication to clear annot"; - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); - - xlog $self, "replica value is NIL"; - $imaptalk = $self->{replica_store}->get_client(); - $res = $imaptalk->getmetadata("", $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals({ - "" => { $entry => undef } - }, $res); + : Replication : SyncLog : AnnotationAllowUndefined + : needs_component_idled { + my ($self) = @_; + + xlog $self, "testing /shared/foobar"; + + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + + $self->assert_not_null($self->{replica}); + + my $imaptalk = $self->{master_store}->get_client(); + + my $res; + my $entry = '/shared/foobar'; + my $value1 = "Hello World this is a value - with a random annot"; + + xlog $self, "initial value is NIL"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); + + xlog $self, "cannot set the value as ordinary user"; + $imaptalk->setmetadata("", $entry, $value1); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert($imaptalk->get_last_error() =~ m/permission denied/i); + + xlog $self, "can set the value as admin"; + $imaptalk = $self->{adminstore}->get_client(); + $imaptalk->setmetadata("", $entry, $value1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "can get the set value back"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + $self->{adminstore}->disconnect(); + $imaptalk = $self->{adminstore}->get_client(); + + xlog $self, "the annot gives the same value in the new connection"; + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "replica value is NIL"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); + + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); + + xlog $self, "the annot gives the same value on the replica"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => $value1 } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "can delete value"; + $imaptalk = $self->{adminstore}->get_client(); + $imaptalk->setmetadata("", $entry, undef); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $expected = { "" => { $entry => undef } }; + $self->assert_deep_equals($expected, $res); + + xlog $self, "run replication to clear annot"; + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); + + xlog $self, "replica value is NIL"; + $imaptalk = $self->{replica_store}->get_client(); + $res = $imaptalk->getmetadata("", $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + "" => { $entry => undef } + }, + $res + ); } diff --git a/cassandane/tiny-tests/Metadata/size b/cassandane/tiny-tests/Metadata/size index c512844fe3..22e86b08c5 100644 --- a/cassandane/tiny-tests/Metadata/size +++ b/cassandane/tiny-tests/Metadata/size @@ -6,58 +6,66 @@ use Cassandane::Tiny; # which reports the total byte count of the RFC822 message # sizes in the mailbox. # -sub test_size -{ - my ($self) = @_; - - xlog $self, "testing /shared/vendor/cmu/cyrus-imapd/size"; - - my $imaptalk = $self->{store}->get_client(); - my $res; - my $folder_cass = 'INBOX'; - my $folder_admin = 'user.cassandane'; - $self->{store}->set_folder($folder_cass); - my $entry = '/shared/vendor/cmu/cyrus-imapd/size'; - - xlog $self, "initial value is numeric zero"; - $res = $imaptalk->getmetadata($folder_cass, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder_cass => { $entry => "0" } - }, $res); - - xlog $self, "cannot set the value as ordinary user"; - $imaptalk->setmetadata($folder_cass, $entry, '123'); - $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); - $self->assert($imaptalk->get_last_error() =~ m/permission denied/i); - - xlog $self, "cannot set the value as admin either"; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->setmetadata($folder_admin, $entry, '123'); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert($admintalk->get_last_error() =~ m/permission denied/i); - - xlog $self, "adding a message bumps the value by the message's size"; - my $expected = 0; - my %msg; - $msg{A} = $self->make_message('Message A'); - $expected += length($msg{A}->as_string()); - - $res = $imaptalk->getmetadata($folder_cass, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder_cass => { $entry => "" . $expected } - }, $res); - - xlog $self, "adding a 2nd message bumps the value by the message's size"; - $msg{B} = $self->make_message('Message B'); - $expected += length($msg{B}->as_string()); - - $res = $imaptalk->getmetadata($folder_cass, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder_cass => { $entry => "" . $expected } - }, $res); - - # TODO: removing a message doesn't reduce the value until (possibly delayed) expunge +sub test_size { + my ($self) = @_; + + xlog $self, "testing /shared/vendor/cmu/cyrus-imapd/size"; + + my $imaptalk = $self->{store}->get_client(); + my $res; + my $folder_cass = 'INBOX'; + my $folder_admin = 'user.cassandane'; + $self->{store}->set_folder($folder_cass); + my $entry = '/shared/vendor/cmu/cyrus-imapd/size'; + + xlog $self, "initial value is numeric zero"; + $res = $imaptalk->getmetadata($folder_cass, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $folder_cass => { $entry => "0" } + }, + $res + ); + + xlog $self, "cannot set the value as ordinary user"; + $imaptalk->setmetadata($folder_cass, $entry, '123'); + $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); + $self->assert($imaptalk->get_last_error() =~ m/permission denied/i); + + xlog $self, "cannot set the value as admin either"; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->setmetadata($folder_admin, $entry, '123'); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert($admintalk->get_last_error() =~ m/permission denied/i); + + xlog $self, "adding a message bumps the value by the message's size"; + my $expected = 0; + my %msg; + $msg{A} = $self->make_message('Message A'); + $expected += length($msg{A}->as_string()); + + $res = $imaptalk->getmetadata($folder_cass, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $folder_cass => { $entry => "" . $expected } + }, + $res + ); + + xlog $self, "adding a 2nd message bumps the value by the message's size"; + $msg{B} = $self->make_message('Message B'); + $expected += length($msg{B}->as_string()); + + $res = $imaptalk->getmetadata($folder_cass, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_deep_equals( + { + $folder_cass => { $entry => "" . $expected } + }, + $res + ); + +# TODO: removing a message doesn't reduce the value until (possibly delayed) expunge } diff --git a/cassandane/tiny-tests/Metadata/specialuse b/cassandane/tiny-tests/Metadata/specialuse index 468750bad1..5b743c3407 100644 --- a/cassandane/tiny-tests/Metadata/specialuse +++ b/cassandane/tiny-tests/Metadata/specialuse @@ -4,132 +4,127 @@ use Cassandane::Tiny; # # Test the /private/specialuse annotation defined by RFC6154. # -sub test_specialuse -{ - my ($self) = @_; +sub test_specialuse { + my ($self) = @_; - xlog $self, "testing /private/specialuse"; + xlog $self, "testing /private/specialuse"; - my $imaptalk = $self->{store}->get_client(); - my $res; - my $entry = '/private/specialuse'; - my $sentry = '/shared/specialuse'; - my @testcases = ( - # Cyrus has no virtual folders, so cannot do \All - { - folder => 'a', - specialuse => '\All', - result => 'no' - }, - { - folder => 'b', - specialuse => '\Archive', - result => 'ok' - }, - { - folder => 'c', - specialuse => '\Drafts', - result => 'ok' - }, - # Cyrus has no virtual folders, so cannot do \Flagged - { - folder => 'd', - specialuse => '\Flagged', - result => 'no' - }, - { - folder => 'e', - specialuse => '\Junk', - result => 'ok' - }, - { - folder => 'f', - specialuse => '\Sent', - result => 'ok' - }, - { - folder => 'g', - specialuse => '\Trash', - result => 'ok' - }, - # Tokens not defined in the RFC are rejected - { - folder => 'h', - specialuse => '\Nonesuch', - result => 'no' - }, - ); - - xlog $self, "First create all the folders"; - foreach my $tc (@testcases) + my $imaptalk = $self->{store}->get_client(); + my $res; + my $entry = '/private/specialuse'; + my $sentry = '/shared/specialuse'; + my @testcases = ( + # Cyrus has no virtual folders, so cannot do \All { - $imaptalk->create("INBOX.$tc->{folder}") - or die "Cannot create mailbox INBOX.$tc->{folder}: $@"; - } - - foreach my $tc (@testcases) + folder => 'a', + specialuse => '\All', + result => 'no' + }, + { + folder => 'b', + specialuse => '\Archive', + result => 'ok' + }, + { + folder => 'c', + specialuse => '\Drafts', + result => 'ok' + }, + # Cyrus has no virtual folders, so cannot do \Flagged + { + folder => 'd', + specialuse => '\Flagged', + result => 'no' + }, + { + folder => 'e', + specialuse => '\Junk', + result => 'ok' + }, { - my $folder = "INBOX.$tc->{folder}"; + folder => 'f', + specialuse => '\Sent', + result => 'ok' + }, + { + folder => 'g', + specialuse => '\Trash', + result => 'ok' + }, + # Tokens not defined in the RFC are rejected + { + folder => 'h', + specialuse => '\Nonesuch', + result => 'no' + }, + ); - xlog $self, "initial value for $folder is NIL"; - $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - delete $res->{$sentry}; # may return a shared entry as well... - $self->assert_deep_equals({ - $folder => { $entry => undef } - }, $res); + xlog $self, "First create all the folders"; + foreach my $tc (@testcases) { + $imaptalk->create("INBOX.$tc->{folder}") + or die "Cannot create mailbox INBOX.$tc->{folder}: $@"; + } - xlog $self, "can set $folder to $tc->{specialuse}"; - $imaptalk->setmetadata($folder, $entry, $tc->{specialuse}); - $self->assert_str_equals($tc->{result}, $imaptalk->get_last_completion_response()); + foreach my $tc (@testcases) { + my $folder = "INBOX.$tc->{folder}"; - xlog $self, "can get the set value back"; - $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - delete $res->{$sentry}; # may return a shared entry as well... - my $expected = { - $folder => { $entry => ($tc->{result} eq 'ok' ? $tc->{specialuse} : undef) } - }; - $self->assert_deep_equals($expected, $res); - } + xlog $self, "initial value for $folder is NIL"; + $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + delete $res->{$sentry}; # may return a shared entry as well... + $self->assert_deep_equals( + { + $folder => { $entry => undef } + }, + $res + ); - xlog $self, "can get same values in a new connection"; - $self->{store}->disconnect(); - $imaptalk = $self->{store}->get_client(); + xlog $self, "can set $folder to $tc->{specialuse}"; + $imaptalk->setmetadata($folder, $entry, $tc->{specialuse}); + $self->assert_str_equals($tc->{result}, + $imaptalk->get_last_completion_response()); - foreach my $tc (@testcases) - { - my $folder = "INBOX.$tc->{folder}"; + xlog $self, "can get the set value back"; + $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + delete $res->{$sentry}; # may return a shared entry as well... + my $expected = { $folder => + { $entry => ($tc->{result} eq 'ok' ? $tc->{specialuse} : undef) } }; + $self->assert_deep_equals($expected, $res); + } - $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - delete $res->{$sentry}; # may return a shared entry as well... - my $expected = { - $folder => { $entry => ($tc->{result} eq 'ok' ? $tc->{specialuse} : undef) } - }; - $self->assert_deep_equals($expected, $res); - } + xlog $self, "can get same values in a new connection"; + $self->{store}->disconnect(); + $imaptalk = $self->{store}->get_client(); - xlog $self, "can delete values"; - foreach my $tc (@testcases) - { - next unless ($tc->{result} eq 'ok'); - my $folder = "INBOX.$tc->{folder}"; + foreach my $tc (@testcases) { + my $folder = "INBOX.$tc->{folder}"; + + $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + delete $res->{$sentry}; # may return a shared entry as well... + my $expected = { $folder => + { $entry => ($tc->{result} eq 'ok' ? $tc->{specialuse} : undef) } }; + $self->assert_deep_equals($expected, $res); + } + + xlog $self, "can delete values"; + foreach my $tc (@testcases) { + next unless ($tc->{result} eq 'ok'); + my $folder = "INBOX.$tc->{folder}"; - $imaptalk->setmetadata($folder, $entry, undef); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->setmetadata($folder, $entry, undef); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->getmetadata($folder, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - delete $res->{$sentry}; # may return a shared entry as well... - my $expected = { - $folder => { $entry => undef } - }; - $self->assert_deep_equals($expected, $res); - } + $res = $imaptalk->getmetadata($folder, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + delete $res->{$sentry}; # may return a shared entry as well... + my $expected = { $folder => { $entry => undef } }; + $self->assert_deep_equals($expected, $res); + } } diff --git a/cassandane/tiny-tests/Metadata/unchangedsince b/cassandane/tiny-tests/Metadata/unchangedsince index 31adb850da..d749788a40 100644 --- a/cassandane/tiny-tests/Metadata/unchangedsince +++ b/cassandane/tiny-tests/Metadata/unchangedsince @@ -20,155 +20,149 @@ use Cassandane::Tiny; # - returns an OK response # - but reports the UID in the MODIFIED response code # -sub test_unchangedsince -{ - my ($self) = @_; +sub test_unchangedsince { + my ($self) = @_; - my $talk = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $talk->uid()); - $self->{store}->set_fetch_attributes(qw(uid modseq)); + my $talk = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $talk->uid()); + $self->{store}->set_fetch_attributes(qw(uid modseq)); - xlog $self, "Append 3 messages"; - my %msg; - $msg{A} = $self->make_message('Message A'); - $msg{B} = $self->make_message('Message B'); - $msg{C} = $self->make_message('Message C'); - my $hms0 = $self->get_highestmodseq(); + xlog $self, "Append 3 messages"; + my %msg; + $msg{A} = $self->make_message('Message A'); + $msg{B} = $self->make_message('Message B'); + $msg{C} = $self->make_message('Message C'); + my $hms0 = $self->get_highestmodseq(); - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $value1 = "Hello World"; - my $value2 = "Janis Joplin"; - my $value3 = "Phantom of the Opera"; + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $value1 = "Hello World"; + my $value2 = "Janis Joplin"; + my $value3 = "Phantom of the Opera"; - my %fetched; - my $modified; - my %handlers = - ( - fetch => sub - { - my ($response, $rr, $id) = @_; + my %fetched; + my $modified; + my %handlers = ( + fetch => sub { + my ($response, $rr, $id) = @_; - # older versions of Mail::IMAPTalk don't have - # the 3rd argument. We can't test properly in - # those circumstances. - $self->assert_not_null($id); + # older versions of Mail::IMAPTalk don't have + # the 3rd argument. We can't test properly in + # those circumstances. + $self->assert_not_null($id); - $fetched{$id} = $rr; - }, - modified => sub - { - my ($response, $rr) = @_; - # we should not get more than one of these ever - $self->assert_null($modified); - $modified = $rr; - } - ); + $fetched{$id} = $rr; + }, + modified => sub { + my ($response, $rr) = @_; + # we should not get more than one of these ever + $self->assert_null($modified); + $modified = $rr; + } + ); - # Note: Mail::IMAPTalk::store() doesn't support modifiers - # so we have to resort to the lower level interface. + # Note: Mail::IMAPTalk::store() doesn't support modifiers + # so we have to resort to the lower level interface. - xlog $self, "setting an annotation with current modseq == UNCHANGEDSINCE"; - %fetched = (); - $modified = undef; - $talk->_imap_cmd('store', 1, \%handlers, - '1', ['unchangedsince', $hms0-2], - 'annotation', [$entry, [$attrib, $value1]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "setting an annotation with current modseq == UNCHANGEDSINCE"; + %fetched = (); + $modified = undef; + $talk->_imap_cmd('store', 1, \%handlers, '1', [ 'unchangedsince', $hms0 - 2 ], + 'annotation', [ $entry, [ $attrib, $value1 ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "fetch an annotation - should be updated"; - my $hms1 = $self->get_highestmodseq(); - $self->assert($hms1 > $hms0); - my $res = $talk->fetch('1:*', - ['modseq', 'annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { - modseq => [$hms1], - annotation => { $entry => { $attrib => $value1 } } - }, - 2 => { - modseq => [$hms0-1], - annotation => { $entry => { $attrib => undef } } - }, - 3 => { - modseq => [$hms0], - annotation => { $entry => { $attrib => undef } } - }, - }, - $res); + xlog $self, "fetch an annotation - should be updated"; + my $hms1 = $self->get_highestmodseq(); + $self->assert($hms1 > $hms0); + my $res + = $talk->fetch('1:*', [ 'modseq', 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { + modseq => [$hms1], + annotation => { $entry => { $attrib => $value1 } } + }, + 2 => { + modseq => [ $hms0 - 1 ], + annotation => { $entry => { $attrib => undef } } + }, + 3 => { + modseq => [$hms0], + annotation => { $entry => { $attrib => undef } } + }, + }, + $res + ); - xlog $self, "setting an annotation with current modseq < UNCHANGEDSINCE"; - %fetched = (); - $modified = undef; - $talk->_imap_cmd('store', 1, \%handlers, - '1', ['unchangedsince', $hms1+1], - 'annotation', [$entry, [$attrib, $value2]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "setting an annotation with current modseq < UNCHANGEDSINCE"; + %fetched = (); + $modified = undef; + $talk->_imap_cmd('store', 1, \%handlers, '1', [ 'unchangedsince', $hms1 + 1 ], + 'annotation', [ $entry, [ $attrib, $value2 ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "fetch an annotation - should be updated"; - my $hms2 = $self->get_highestmodseq(); - $self->assert($hms2 > $hms1); - $res = $talk->fetch('1:*', - ['modseq', 'annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { - modseq => [$hms2], - annotation => { $entry => { $attrib => $value2 } } - }, - 2 => { - modseq => [$hms0-1], - annotation => { $entry => { $attrib => undef } } - }, - 3 => { - modseq => [$hms0], - annotation => { $entry => { $attrib => undef } } - }, - }, - $res); + xlog $self, "fetch an annotation - should be updated"; + my $hms2 = $self->get_highestmodseq(); + $self->assert($hms2 > $hms1); + $res = $talk->fetch('1:*', [ 'modseq', 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { + modseq => [$hms2], + annotation => { $entry => { $attrib => $value2 } } + }, + 2 => { + modseq => [ $hms0 - 1 ], + annotation => { $entry => { $attrib => undef } } + }, + 3 => { + modseq => [$hms0], + annotation => { $entry => { $attrib => undef } } + }, + }, + $res + ); - xlog $self, "setting an annotation with current modseq > UNCHANGEDSINCE"; - %fetched = (); - $modified = undef; - $talk->_imap_cmd('store', 1, \%handlers, - '1', ['unchangedsince', $hms2-1], - 'annotation', [$entry, [$attrib, $value3]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); + xlog $self, "setting an annotation with current modseq > UNCHANGEDSINCE"; + %fetched = (); + $modified = undef; + $talk->_imap_cmd('store', 1, \%handlers, '1', [ 'unchangedsince', $hms2 - 1 ], + 'annotation', [ $entry, [ $attrib, $value3 ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); - xlog $self, "didn't update modseq?"; - my $hms3 = $self->get_highestmodseq(); - $self->assert($hms3 == $hms2); - xlog $self, "fetch an annotation - should not be updated"; - $res = $talk->fetch('1:*', - ['modseq', 'annotation', [$entry, $attrib]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_not_null($res); - $self->assert_deep_equals( - { - 1 => { - # unchanged - modseq => [$hms2], - annotation => { $entry => { $attrib => $value2 } } - }, - 2 => { - modseq => [$hms0-1], - annotation => { $entry => { $attrib => undef } } - }, - 3 => { - modseq => [$hms0], - annotation => { $entry => { $attrib => undef } } - }, - }, - $res); - xlog $self, "reports the UID in the MODIFIED response code?"; - $self->assert_not_null($modified); - $self->assert_deep_equals($modified, [1]); - xlog $self, "sent no FETCH untagged response?"; - $self->assert_num_equals(0, scalar keys %fetched); + xlog $self, "didn't update modseq?"; + my $hms3 = $self->get_highestmodseq(); + $self->assert($hms3 == $hms2); + xlog $self, "fetch an annotation - should not be updated"; + $res = $talk->fetch('1:*', [ 'modseq', 'annotation', [ $entry, $attrib ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_not_null($res); + $self->assert_deep_equals( + { + 1 => { + # unchanged + modseq => [$hms2], + annotation => { $entry => { $attrib => $value2 } } + }, + 2 => { + modseq => [ $hms0 - 1 ], + annotation => { $entry => { $attrib => undef } } + }, + 3 => { + modseq => [$hms0], + annotation => { $entry => { $attrib => undef } } + }, + }, + $res + ); + xlog $self, "reports the UID in the MODIFIED response code?"; + $self->assert_not_null($modified); + $self->assert_deep_equals($modified, [1]); + xlog $self, "sent no FETCH untagged response?"; + $self->assert_num_equals(0, scalar keys %fetched); } diff --git a/cassandane/tiny-tests/Metadata/uniqueid b/cassandane/tiny-tests/Metadata/uniqueid index 3591a7b4e8..370c56f7cc 100644 --- a/cassandane/tiny-tests/Metadata/uniqueid +++ b/cassandane/tiny-tests/Metadata/uniqueid @@ -2,45 +2,42 @@ use Cassandane::Tiny; sub test_uniqueid - :ImmediateDelete -{ - my ($self) = @_; + : ImmediateDelete { + my ($self) = @_; - xlog $self, "testing /shared/vendor/cmu/cyrus-imapd/uniqueid"; + xlog $self, "testing /shared/vendor/cmu/cyrus-imapd/uniqueid"; - my $imaptalk = $self->{store}->get_client(); - my $res; - # data thanks to hipsteripsum.me - my @folders = ( qw(INBOX.etsy INBOX.etsy - INBOX.sartorial - INBOX.dreamcatcher.keffiyeh) ); - my @uuids; - my %uuids_seen; - my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + my $imaptalk = $self->{store}->get_client(); + my $res; + # data thanks to hipsteripsum.me + my @folders = (qw(INBOX.etsy INBOX.etsy + INBOX.sartorial + INBOX.dreamcatcher.keffiyeh)); + my @uuids; + my %uuids_seen; + my $entry = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - xlog $self, "create the folders"; - foreach my $f (@folders) - { - $imaptalk->create($f) - or die "Cannot create mailbox $f: $@"; - $res = $imaptalk->getmetadata($f, $entry); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_not_null($res); - my $uuid = $res->{$f}{$entry}; - $self->assert_not_null($uuid); - $self->assert($uuid =~ m/^[0-9a-z-]+$/); - $imaptalk->delete($f) - or die "Cannot delete mailbox $f: $@"; - push(@uuids, $uuid); - # all the uniqueids must be unique (duh) - $self->assert(!defined $uuids_seen{$uuid}); - $uuids_seen{$uuid} = 1; - } + xlog $self, "create the folders"; + foreach my $f (@folders) { + $imaptalk->create($f) + or die "Cannot create mailbox $f: $@"; + $res = $imaptalk->getmetadata($f, $entry); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_not_null($res); + my $uuid = $res->{$f}{$entry}; + $self->assert_not_null($uuid); + $self->assert($uuid =~ m/^[0-9a-z-]+$/); + $imaptalk->delete($f) + or die "Cannot delete mailbox $f: $@"; + push(@uuids, $uuid); + # all the uniqueids must be unique (duh) + $self->assert(!defined $uuids_seen{$uuid}); + $uuids_seen{$uuid} = 1; + } - # Do the logging in a 2nd pass in the hope of maximising - # our chances of getting all the creates in one second - for (my $i = 0 ; $i < scalar(@folders) ; $i++) - { - xlog $self, "uniqueid of " . $folders[$i] . " was \"" . $uuids[$i] . "\""; - } + # Do the logging in a 2nd pass in the hope of maximising + # our chances of getting all the creates in one second + for (my $i = 0; $i < scalar(@folders); $i++) { + xlog $self, "uniqueid of " . $folders[$i] . " was \"" . $uuids[$i] . "\""; + } } diff --git a/cassandane/tiny-tests/Quota/bug3735 b/cassandane/tiny-tests/Quota/bug3735 index 5016be2372..8b4f9317c2 100644 --- a/cassandane/tiny-tests/Quota/bug3735 +++ b/cassandane/tiny-tests/Quota/bug3735 @@ -2,27 +2,29 @@ use Cassandane::Tiny; sub test_bug3735 - :Bug3735 -{ - my ($self) = @_; - $self->{instance}->create_user("a"); - $self->{instance}->create_user("ab"); - $self->_set_quotaroot('user.a'); - $self->_set_limits(storage => 12345); - $self->_set_quotaroot('user.ab'); - $self->_set_limits(storage => 12345); + : Bug3735 { + my ($self) = @_; + $self->{instance}->create_user("a"); + $self->{instance}->create_user("ab"); + $self->_set_quotaroot('user.a'); + $self->_set_limits(storage => 12345); + $self->_set_quotaroot('user.ab'); + $self->_set_limits(storage => 12345); - my $filename = $self->{instance}->{basedir} . "/bug3735.out"; + my $filename = $self->{instance}->{basedir} . "/bug3735.out"; - $self->{instance}->run_command({ - cyrus => 1, - redirects => { stdout => $filename }, - }, 'quota', "user.a"); + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { stdout => $filename }, + }, + 'quota', "user.a" + ); - open RESULTS, '<', $filename - or die "Cannot open $filename for reading: $!"; - my @res = ; - close RESULTS; + open RESULTS, '<', $filename + or die "Cannot open $filename for reading: $!"; + my @res = ; + close RESULTS; - $self->assert(grep { m/user\.ab/ } @res); + $self->assert(grep { m/user\.ab/ } @res); } diff --git a/cassandane/tiny-tests/Quota/bz3529 b/cassandane/tiny-tests/Quota/bz3529 index daed0a286a..ddfe6073c7 100644 --- a/cassandane/tiny-tests/Quota/bz3529 +++ b/cassandane/tiny-tests/Quota/bz3529 @@ -1,46 +1,45 @@ #!perl use Cassandane::Tiny; -sub test_bz3529 -{ - my ($self) = @_; - - xlog $self, "testing annot storage quota when setting annots on multiple"; - xlog $self, "messages in a single STORE command, using quotalegacy backend."; - - # double check that some other part of Cassandane didn't - # accidentally futz with the expected quota db backend - my $backend = $self->{instance}->{config}->get('quota_db'); - $self->assert_str_equals('quotalegacy', $backend) - if defined $backend; # the default value is also ok - - $self->_set_quotaroot('user.cassandane'); - my $talk = $self->{store}->get_client(); - - xlog $self, "set ourselves a basic limit"; - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => 0); - - xlog $self, "make some messages to hang annotations on"; -# $self->{store}->set_folder($folder); - my $uid = 1; - my %msgs; - for (1..20) - { - $msgs{$uid} = $self->make_message("Message $uid"); - $msgs{$uid}->set_attribute('uid', $uid); - $uid++; - } - - my $data = $self->make_random_data(30); - $talk->store('1:*', 'annotation', ['/comment', ['value.priv', { Quote => $data }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - my $expected = ($uid-1) * length($data); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - # delete annotations - $talk->store('1:*', 'annotation', ['/comment', ['value.priv', undef]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->_check_usages($self->res_annot_storage => 0); +sub test_bz3529 { + my ($self) = @_; + + xlog $self, "testing annot storage quota when setting annots on multiple"; + xlog $self, "messages in a single STORE command, using quotalegacy backend."; + + # double check that some other part of Cassandane didn't + # accidentally futz with the expected quota db backend + my $backend = $self->{instance}->{config}->get('quota_db'); + $self->assert_str_equals('quotalegacy', $backend) + if defined $backend; # the default value is also ok + + $self->_set_quotaroot('user.cassandane'); + my $talk = $self->{store}->get_client(); + + xlog $self, "set ourselves a basic limit"; + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => 0); + + xlog $self, "make some messages to hang annotations on"; + # $self->{store}->set_folder($folder); + my $uid = 1; + my %msgs; + for (1 .. 20) { + $msgs{$uid} = $self->make_message("Message $uid"); + $msgs{$uid}->set_attribute('uid', $uid); + $uid++; + } + + my $data = $self->make_random_data(30); + $talk->store('1:*', 'annotation', + [ '/comment', [ 'value.priv', { Quote => $data } ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + my $expected = ($uid - 1) * length($data); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + # delete annotations + $talk->store('1:*', 'annotation', [ '/comment', [ 'value.priv', undef ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->_check_usages($self->res_annot_storage => 0); } diff --git a/cassandane/tiny-tests/Quota/deleted_storage b/cassandane/tiny-tests/Quota/deleted_storage index c46bbe77f8..976fb5f41f 100644 --- a/cassandane/tiny-tests/Quota/deleted_storage +++ b/cassandane/tiny-tests/Quota/deleted_storage @@ -1,50 +1,47 @@ #!perl use Cassandane::Tiny; -sub test_deleted_storage -{ - my ($self) = @_; - - xlog $self, "test DELETED and DELETED-STORAGE STATUS items"; - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits(storage => 100000); - $self->_check_usages(storage => 0); - my $talk = $self->{store}->get_client(); - - # append some messages - my $expected = 0; - for (1..10) - { - my $msg = $self->make_message("Message $_", - extra_lines => 10 + rand(5000)); - my $len = length($msg->as_string()); - $expected += $len; - xlog $self, "added $len bytes of message"; - $self->_check_usages(storage => int($expected/1024)); - } - - # delete messages - $talk->select("INBOX"); - $talk->store('1:*', '+flags.silent', '(\\deleted)'); - - # check deleted[-storage] status items - my $res = $talk->status('INBOX', '(messages size deleted deleted-storage)'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_num_equals(10, $res->{'messages'}); - $self->assert_num_equals(10, $res->{'deleted'}); - $self->assert_num_equals($expected, $res->{'size'}); - $self->assert_num_equals($expected, $res->{'deleted-storage'}); - - $talk->close(); - - # check deleted[-storage] status items - $res = $talk->status('INBOX', '(messages size deleted deleted-storage)'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_num_equals(0, $res->{'messages'}); - $self->assert_num_equals(0, $res->{'deleted'}); - $self->assert_num_equals(0, $res->{'size'}); - $self->assert_num_equals(0, $res->{'deleted-storage'}); - - $self->_check_usages(storage => 0); +sub test_deleted_storage { + my ($self) = @_; + + xlog $self, "test DELETED and DELETED-STORAGE STATUS items"; + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits(storage => 100000); + $self->_check_usages(storage => 0); + my $talk = $self->{store}->get_client(); + + # append some messages + my $expected = 0; + for (1 .. 10) { + my $msg = $self->make_message("Message $_", extra_lines => 10 + rand(5000)); + my $len = length($msg->as_string()); + $expected += $len; + xlog $self, "added $len bytes of message"; + $self->_check_usages(storage => int($expected / 1024)); + } + + # delete messages + $talk->select("INBOX"); + $talk->store('1:*', '+flags.silent', '(\\deleted)'); + + # check deleted[-storage] status items + my $res = $talk->status('INBOX', '(messages size deleted deleted-storage)'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_num_equals(10, $res->{'messages'}); + $self->assert_num_equals(10, $res->{'deleted'}); + $self->assert_num_equals($expected, $res->{'size'}); + $self->assert_num_equals($expected, $res->{'deleted-storage'}); + + $talk->close(); + + # check deleted[-storage] status items + $res = $talk->status('INBOX', '(messages size deleted deleted-storage)'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_num_equals(0, $res->{'messages'}); + $self->assert_num_equals(0, $res->{'deleted'}); + $self->assert_num_equals(0, $res->{'size'}); + $self->assert_num_equals(0, $res->{'deleted-storage'}); + + $self->_check_usages(storage => 0); } diff --git a/cassandane/tiny-tests/Quota/exceeding_message b/cassandane/tiny-tests/Quota/exceeding_message index bc1a6460a3..10be746203 100644 --- a/cassandane/tiny-tests/Quota/exceeding_message +++ b/cassandane/tiny-tests/Quota/exceeding_message @@ -1,46 +1,43 @@ #!perl use Cassandane::Tiny; -sub test_exceeding_message -{ - my ($self) = @_; +sub test_exceeding_message { + my ($self) = @_; - xlog $self, "test exceeding the MESSAGE quota limit"; + xlog $self, "test exceeding the MESSAGE quota limit"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - xlog $self, "set a low limit"; - $self->_set_quotaroot('user.cassandane'); - $self->_set_limits(message => 10); - $self->_check_usages(message => 0); + xlog $self, "set a low limit"; + $self->_set_quotaroot('user.cassandane'); + $self->_set_limits(message => 10); + $self->_check_usages(message => 0); - xlog $self, "adding messages to get just below the limit"; - my %msgs; - for (1..10) - { - $msgs{$_} = $self->make_message("Message $_"); - } - xlog $self, "check that the messages are all in the mailbox"; - $self->check_messages(\%msgs); - xlog $self, "check that the usage is just below the limit"; - $self->_check_usages(message => 10); + xlog $self, "adding messages to get just below the limit"; + my %msgs; + for (1 .. 10) { + $msgs{$_} = $self->make_message("Message $_"); + } + xlog $self, "check that the messages are all in the mailbox"; + $self->check_messages(\%msgs); + xlog $self, "check that the usage is just below the limit"; + $self->_check_usages(message => 10); - xlog $self, "add a message that exceeds the limit"; - my $overmsg = eval { $self->make_message("Message 11") }; - # As opposed to storage checking, which is currently done after receiving t - # (LITERAL) mail, message count checking is performed right away. This earl - # NO response while writing the LITERAL triggered a die in early versions - # of IMAPTalk, leaving the completion response undefined. - my $ex = $@; - if ($ex) { - $self->assert($ex =~ m/over quota/i); - } - else { - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/over quota/i); - } + xlog $self, "add a message that exceeds the limit"; + my $overmsg = eval { $self->make_message("Message 11") }; + # As opposed to storage checking, which is currently done after receiving t + # (LITERAL) mail, message count checking is performed right away. This earl + # NO response while writing the LITERAL triggered a die in early versions + # of IMAPTalk, leaving the completion response undefined. + my $ex = $@; + if ($ex) { + $self->assert($ex =~ m/over quota/i); + } else { + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/over quota/i); + } - xlog $self, "check that the exceeding message is not in the mailbox"; - $self->_check_usages(message => 10); - $self->check_messages(\%msgs); + xlog $self, "check that the exceeding message is not in the mailbox"; + $self->_check_usages(message => 10); + $self->check_messages(\%msgs); } diff --git a/cassandane/tiny-tests/Quota/exceeding_storage b/cassandane/tiny-tests/Quota/exceeding_storage index 1bd5893dba..36532820e2 100644 --- a/cassandane/tiny-tests/Quota/exceeding_storage +++ b/cassandane/tiny-tests/Quota/exceeding_storage @@ -1,57 +1,55 @@ #!perl use Cassandane::Tiny; -sub test_exceeding_storage -{ - my ($self) = @_; - - xlog $self, "test exceeding the STORAGE quota limit"; - - my $talk = $self->{store}->get_client(); - - xlog $self, "set a low limit"; - $self->_set_quotaroot('user.cassandane'); - $self->_set_limits(storage => 210); - $self->_check_usages(storage => 0); - - xlog $self, "adding messages to get just below the limit"; - my %msgs; - my $slack = 200 * 1024; - my $n = 1; - my $expected = 0; - while ($slack > 1000) - { - my $nlines = int(($slack - 640) / 23); - $nlines = 1000 if ($nlines > 1000); - - my $msg = $self->make_message("Message $n", - extra_lines => $nlines); - my $len = length($msg->as_string()); - $slack -= $len; - $expected += $len; - xlog $self, "added $len bytes of message"; - $msgs{$n} = $msg; - $n++; - } - xlog $self, "check that the messages are all in the mailbox"; - $self->check_messages(\%msgs); - xlog $self, "check that the usage is just below the limit"; - $self->_check_usages(storage => int($expected/1024)); - $self->_check_smmap('cassandane', 'OK'); - - xlog $self, "add a message that exceeds the limit"; - my $nlines = int(($slack - 640) / 23) * 2; - $nlines = 500 if ($nlines < 500); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - my $overmsg = eval { $self->make_message("Message $n", extra_lines => $nlines) }; - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/over quota/i); - - xlog $self, "check that the exceeding message is not in the mailbox"; - $self->check_messages(\%msgs); - - xlog $self, "check that the quota usage is still the same"; - $self->_check_usages(storage => int($expected/1024)); - $self->_check_smmap('cassandane', 'OK'); +sub test_exceeding_storage { + my ($self) = @_; + + xlog $self, "test exceeding the STORAGE quota limit"; + + my $talk = $self->{store}->get_client(); + + xlog $self, "set a low limit"; + $self->_set_quotaroot('user.cassandane'); + $self->_set_limits(storage => 210); + $self->_check_usages(storage => 0); + + xlog $self, "adding messages to get just below the limit"; + my %msgs; + my $slack = 200 * 1024; + my $n = 1; + my $expected = 0; + while ($slack > 1000) { + my $nlines = int(($slack - 640) / 23); + $nlines = 1000 if ($nlines > 1000); + + my $msg = $self->make_message("Message $n", extra_lines => $nlines); + my $len = length($msg->as_string()); + $slack -= $len; + $expected += $len; + xlog $self, "added $len bytes of message"; + $msgs{$n} = $msg; + $n++; + } + xlog $self, "check that the messages are all in the mailbox"; + $self->check_messages(\%msgs); + xlog $self, "check that the usage is just below the limit"; + $self->_check_usages(storage => int($expected / 1024)); + $self->_check_smmap('cassandane', 'OK'); + + xlog $self, "add a message that exceeds the limit"; + my $nlines = int(($slack - 640) / 23) * 2; + $nlines = 500 if ($nlines < 500); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + my $overmsg + = eval { $self->make_message("Message $n", extra_lines => $nlines) }; + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/over quota/i); + + xlog $self, "check that the exceeding message is not in the mailbox"; + $self->check_messages(\%msgs); + + xlog $self, "check that the quota usage is still the same"; + $self->_check_usages(storage => int($expected / 1024)); + $self->_check_smmap('cassandane', 'OK'); } diff --git a/cassandane/tiny-tests/Quota/move_near_limit b/cassandane/tiny-tests/Quota/move_near_limit index 9f5cb12d2d..37af9e8f39 100644 --- a/cassandane/tiny-tests/Quota/move_near_limit +++ b/cassandane/tiny-tests/Quota/move_near_limit @@ -1,61 +1,59 @@ #!perl use Cassandane::Tiny; -sub test_move_near_limit -{ - my ($self) = @_; - - xlog $self, "test move near the STORAGE quota limit"; - - my $talk = $self->{store}->get_client(); - - xlog $self, "set a low limit"; - $self->_set_quotaroot('user.cassandane'); - $self->_set_limits(storage => 210); - $self->_check_usages(storage => 0); - - xlog $self, "adding messages to get just below the limit"; - my %msgs; - my $slack = 200 * 1024; - my $n = 1; - my $expected = 0; - while ($slack > 1000) - { - my $nlines = int(($slack - 640) / 23); - $nlines = 1000 if ($nlines > 1000); - - my $msg = $self->make_message("Message $n", - extra_lines => $nlines); - my $len = length($msg->as_string()); - $slack -= $len; - $expected += $len; - xlog $self, "added $len bytes of message"; - $msgs{$n} = $msg; - $n++; - } - xlog $self, "check that the messages are all in the mailbox"; - $self->check_messages(\%msgs); - xlog $self, "check that the usage is just below the limit"; - $self->_check_usages(storage => int($expected/1024)); - $self->_check_smmap('cassandane', 'OK'); - - xlog $self, "add a message that exceeds the limit"; - my $nlines = int(($slack - 640) / 23) * 2; - $nlines = 500 if ($nlines < 500); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - my $overmsg = eval { $self->make_message("Message $n", extra_lines => $nlines) }; - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/over quota/i); - - $talk->create("INBOX.target"); - - xlog $self, "try to copy the messages"; - $talk->copy("1:*", "INBOX.target"); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/over quota/i); - - xlog $self, "move the messages"; - $talk->move("1:*", "INBOX.target"); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); +sub test_move_near_limit { + my ($self) = @_; + + xlog $self, "test move near the STORAGE quota limit"; + + my $talk = $self->{store}->get_client(); + + xlog $self, "set a low limit"; + $self->_set_quotaroot('user.cassandane'); + $self->_set_limits(storage => 210); + $self->_check_usages(storage => 0); + + xlog $self, "adding messages to get just below the limit"; + my %msgs; + my $slack = 200 * 1024; + my $n = 1; + my $expected = 0; + while ($slack > 1000) { + my $nlines = int(($slack - 640) / 23); + $nlines = 1000 if ($nlines > 1000); + + my $msg = $self->make_message("Message $n", extra_lines => $nlines); + my $len = length($msg->as_string()); + $slack -= $len; + $expected += $len; + xlog $self, "added $len bytes of message"; + $msgs{$n} = $msg; + $n++; + } + xlog $self, "check that the messages are all in the mailbox"; + $self->check_messages(\%msgs); + xlog $self, "check that the usage is just below the limit"; + $self->_check_usages(storage => int($expected / 1024)); + $self->_check_smmap('cassandane', 'OK'); + + xlog $self, "add a message that exceeds the limit"; + my $nlines = int(($slack - 640) / 23) * 2; + $nlines = 500 if ($nlines < 500); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + my $overmsg + = eval { $self->make_message("Message $n", extra_lines => $nlines) }; + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/over quota/i); + + $talk->create("INBOX.target"); + + xlog $self, "try to copy the messages"; + $talk->copy("1:*", "INBOX.target"); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/over quota/i); + + xlog $self, "move the messages"; + $talk->move("1:*", "INBOX.target"); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); } diff --git a/cassandane/tiny-tests/Quota/num_folders_delete_delayed b/cassandane/tiny-tests/Quota/num_folders_delete_delayed index bc194b8c45..b3cedccaba 100644 --- a/cassandane/tiny-tests/Quota/num_folders_delete_delayed +++ b/cassandane/tiny-tests/Quota/num_folders_delete_delayed @@ -2,23 +2,22 @@ use Cassandane::Tiny; sub test_num_folders_delete_delayed - :DelayedDelete -{ - my ($self) = @_; - $self->_set_quotaroot('user.cassandane'); - $self->_set_limits(storage => 12345, $self->res_mailbox => 500); + : DelayedDelete { + my ($self) = @_; + $self->_set_quotaroot('user.cassandane'); + $self->_set_limits(storage => 12345, $self->res_mailbox => 500); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.sub") || die "Failed to create subfolder"; + $talk->create("INBOX.sub") || die "Failed to create subfolder"; - $self->_check_usages(storage => 0, $self->res_mailbox => 2); + $self->_check_usages(storage => 0, $self->res_mailbox => 2); - $talk->create("INBOX.another"); + $talk->create("INBOX.another"); - $self->_check_usages(storage => 0, $self->res_mailbox => 3); + $self->_check_usages(storage => 0, $self->res_mailbox => 3); - $talk->delete("INBOX.another"); + $talk->delete("INBOX.another"); - $self->_check_usages(storage => 0, $self->res_mailbox => 2); + $self->_check_usages(storage => 0, $self->res_mailbox => 2); } diff --git a/cassandane/tiny-tests/Quota/num_folders_delete_immediate b/cassandane/tiny-tests/Quota/num_folders_delete_immediate index 0467c6cbf4..f7e8755067 100644 --- a/cassandane/tiny-tests/Quota/num_folders_delete_immediate +++ b/cassandane/tiny-tests/Quota/num_folders_delete_immediate @@ -1,23 +1,22 @@ #!perl use Cassandane::Tiny; -sub test_num_folders_delete_immediate -{ - my ($self) = @_; - $self->_set_quotaroot('user.cassandane'); - $self->_set_limits(storage => 12345, $self->res_mailbox => 500); +sub test_num_folders_delete_immediate { + my ($self) = @_; + $self->_set_quotaroot('user.cassandane'); + $self->_set_limits(storage => 12345, $self->res_mailbox => 500); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.sub") || die "Failed to create subfolder"; + $talk->create("INBOX.sub") || die "Failed to create subfolder"; - $self->_check_usages(storage => 0, $self->res_mailbox => 2); + $self->_check_usages(storage => 0, $self->res_mailbox => 2); - $talk->create("INBOX.another"); + $talk->create("INBOX.another"); - $self->_check_usages(storage => 0, $self->res_mailbox => 3); + $self->_check_usages(storage => 0, $self->res_mailbox => 3); - $talk->delete("INBOX.another"); + $talk->delete("INBOX.another"); - $self->_check_usages(storage => 0, $self->res_mailbox => 2); + $self->_check_usages(storage => 0, $self->res_mailbox => 2); } diff --git a/cassandane/tiny-tests/Quota/num_folders_rename b/cassandane/tiny-tests/Quota/num_folders_rename index 59ac66e832..18c81260c2 100644 --- a/cassandane/tiny-tests/Quota/num_folders_rename +++ b/cassandane/tiny-tests/Quota/num_folders_rename @@ -1,23 +1,22 @@ #!perl use Cassandane::Tiny; -sub test_num_folders_rename -{ - my ($self) = @_; - $self->_set_quotaroot('user.cassandane'); - $self->_set_limits(storage => 12345, $self->res_mailbox => 500); +sub test_num_folders_rename { + my ($self) = @_; + $self->_set_quotaroot('user.cassandane'); + $self->_set_limits(storage => 12345, $self->res_mailbox => 500); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.sub") || die "Failed to create subfolder"; + $talk->create("INBOX.sub") || die "Failed to create subfolder"; - $self->_check_usages(storage => 0, $self->res_mailbox => 2); + $self->_check_usages(storage => 0, $self->res_mailbox => 2); - $talk->create("INBOX.another"); + $talk->create("INBOX.another"); - $self->_check_usages(storage => 0, $self->res_mailbox => 3); + $self->_check_usages(storage => 0, $self->res_mailbox => 3); - $talk->rename("INBOX.another", "INBOX.out"); + $talk->rename("INBOX.another", "INBOX.out"); - $self->_check_usages(storage => 0, $self->res_mailbox => 3); + $self->_check_usages(storage => 0, $self->res_mailbox => 3); } diff --git a/cassandane/tiny-tests/Quota/overquota b/cassandane/tiny-tests/Quota/overquota index 706b384b84..4a43e51fad 100644 --- a/cassandane/tiny-tests/Quota/overquota +++ b/cassandane/tiny-tests/Quota/overquota @@ -1,84 +1,80 @@ #!perl use Cassandane::Tiny; -sub test_overquota -{ - my ($self) = @_; +sub test_overquota { + my ($self) = @_; - xlog $self, "test account which is over STORAGE quota limit"; + xlog $self, "test account which is over STORAGE quota limit"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - xlog $self, "set a low limit"; - $self->_set_quotaroot('user.cassandane'); - $self->_set_limits(storage => 210); - $self->_check_usages(storage => 0); + xlog $self, "set a low limit"; + $self->_set_quotaroot('user.cassandane'); + $self->_set_limits(storage => 210); + $self->_check_usages(storage => 0); - xlog $self, "adding messages to get just below the limit"; - my %msgs; - my $slack = 200 * 1024; - my $n = 1; - my $expected = 0; - while ($slack > 1000) - { - my $nlines = int(($slack - 640) / 23); - $nlines = 1000 if ($nlines > 1000); + xlog $self, "adding messages to get just below the limit"; + my %msgs; + my $slack = 200 * 1024; + my $n = 1; + my $expected = 0; + while ($slack > 1000) { + my $nlines = int(($slack - 640) / 23); + $nlines = 1000 if ($nlines > 1000); - my $msg = $self->make_message("Message $n", - extra_lines => $nlines); - my $len = length($msg->as_string()); - $slack -= $len; - $expected += $len; - xlog $self, "added $len bytes of message"; - $msgs{$n} = $msg; - $n++; - } - xlog $self, "check that the messages are all in the mailbox"; - $self->check_messages(\%msgs); - xlog $self, "check that the usage is just below the limit"; - $self->_check_usages(storage => int($expected/1024)); - $self->_check_smmap('cassandane', 'OK'); + my $msg = $self->make_message("Message $n", extra_lines => $nlines); + my $len = length($msg->as_string()); + $slack -= $len; + $expected += $len; + xlog $self, "added $len bytes of message"; + $msgs{$n} = $msg; + $n++; + } + xlog $self, "check that the messages are all in the mailbox"; + $self->check_messages(\%msgs); + xlog $self, "check that the usage is just below the limit"; + $self->_check_usages(storage => int($expected / 1024)); + $self->_check_smmap('cassandane', 'OK'); - xlog $self, "reduce the quota limit"; - $self->_set_limits(storage => 100); + xlog $self, "reduce the quota limit"; + $self->_set_limits(storage => 100); - xlog $self, "check that usage is unchanged"; - $self->_check_usages(storage => int($expected/1024)); - xlog $self, "check that smmap reports over quota"; - $self->_check_smmap('cassandane', 'TEMP'); + xlog $self, "check that usage is unchanged"; + $self->_check_usages(storage => int($expected / 1024)); + xlog $self, "check that smmap reports over quota"; + $self->_check_smmap('cassandane', 'TEMP'); - xlog $self, "try to add another message"; - my $overmsg = eval { $self->make_message("Message $n") }; - my $ex = $@; - if ($ex) { - $self->assert($ex =~ m/over quota/i); - } - else { - $self->assert_str_equals('no', $talk->get_last_completion_response()); - $self->assert($talk->get_last_error() =~ m/over quota/i); - } + xlog $self, "try to add another message"; + my $overmsg = eval { $self->make_message("Message $n") }; + my $ex = $@; + if ($ex) { + $self->assert($ex =~ m/over quota/i); + } else { + $self->assert_str_equals('no', $talk->get_last_completion_response()); + $self->assert($talk->get_last_error() =~ m/over quota/i); + } - xlog $self, "check that the exceeding message is not in the mailbox"; - $self->check_messages(\%msgs); + xlog $self, "check that the exceeding message is not in the mailbox"; + $self->check_messages(\%msgs); - xlog $self, "check that the quota usage is still unchanged"; - $self->_check_usages(storage => int($expected/1024)); - $self->_check_smmap('cassandane', 'TEMP'); + xlog $self, "check that the quota usage is still unchanged"; + $self->_check_usages(storage => int($expected / 1024)); + $self->_check_smmap('cassandane', 'TEMP'); - my $delmsg = delete $msgs{1}; - my $dellen = length($delmsg->as_string()); - xlog $self, "delete the first message ($dellen bytes)"; - $talk->select("INBOX"); - $talk->store('1', '+flags', '(\\deleted)'); - $talk->close(); + my $delmsg = delete $msgs{1}; + my $dellen = length($delmsg->as_string()); + xlog $self, "delete the first message ($dellen bytes)"; + $talk->select("INBOX"); + $talk->store('1', '+flags', '(\\deleted)'); + $talk->close(); - xlog $self, "check that the deleted message is no longer in the mailbox"; - $self->check_messages(\%msgs); + xlog $self, "check that the deleted message is no longer in the mailbox"; + $self->check_messages(\%msgs); - xlog $self, "check that the usage has gone down"; - $expected -= $dellen; - $self->_check_usages(storage => int($expected/1024)); + xlog $self, "check that the usage has gone down"; + $expected -= $dellen; + $self->_check_usages(storage => int($expected / 1024)); - xlog $self, "check that we are still over quota"; - $self->_check_smmap('cassandane', 'TEMP'); + xlog $self, "check that we are still over quota"; + $self->_check_smmap('cassandane', 'TEMP'); } diff --git a/cassandane/tiny-tests/Quota/quota_d b/cassandane/tiny-tests/Quota/quota_d index 3914c048a8..23f1af47a8 100644 --- a/cassandane/tiny-tests/Quota/quota_d +++ b/cassandane/tiny-tests/Quota/quota_d @@ -2,79 +2,82 @@ use Cassandane::Tiny; sub test_quota_d - :UnixHierarchySep :AltNamespace :VirtDomains -{ - my ($self) = @_; + : UnixHierarchySep : AltNamespace : VirtDomains { + my ($self) = @_; - my @users = qw( - alice@foo.com - bob@foo.com - chris@bar.com - dave@qux.com - ); + my @users = qw( + alice@foo.com + bob@foo.com + chris@bar.com + dave@qux.com + ); - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - foreach my $user (@users) { - $admintalk->create("user/$user"); - $self->_set_limits( - quotaroot => "user/$user", - storage => 100000, - message => 50000, - $self->res_annot_storage => 10000, - ); + foreach my $user (@users) { + $admintalk->create("user/$user"); + $self->_set_limits( + quotaroot => "user/$user", + storage => 100000, + message => 50000, + $self->res_annot_storage => 10000, + ); - my $svc = $self->{instance}->get_service('imap'); - my $userstore = $svc->create_store(username => $user); - my $usertalk = $userstore->get_client(); + my $svc = $self->{instance}->get_service('imap'); + my $userstore = $svc->create_store(username => $user); + my $usertalk = $userstore->get_client(); - foreach my $submbox ('Drafts', 'Junk', 'Sent', 'Trash') { - xlog $self, "creating $submbox..."; - $usertalk->create($submbox); - $self->assert_str_equals('ok', - $usertalk->get_last_completion_response()); - } + foreach my $submbox ('Drafts', 'Junk', 'Sent', 'Trash') { + xlog $self, "creating $submbox..."; + $usertalk->create($submbox); + $self->assert_str_equals('ok', $usertalk->get_last_completion_response()); + } - foreach my $mbox (qw(INBOX Drafts Sent Junk Trash)) { - $usertalk->select($mbox); - foreach (1..3) { - $self->make_message("msg $_ in $mbox", store => $userstore); - } - } + foreach my $mbox (qw(INBOX Drafts Sent Junk Trash)) { + $usertalk->select($mbox); + foreach (1 .. 3) { + $self->make_message("msg $_ in $mbox", store => $userstore); + } } + } - xlog $self, "run quota"; - my $outfile = $self->{instance}->{basedir} . '/quota.out'; - $self->{instance}->run_command( - { cyrus => 1, - redirects => { - stderr => $outfile, - stdout => $outfile, - }, - }, - 'quota'); + xlog $self, "run quota"; + my $outfile = $self->{instance}->{basedir} . '/quota.out'; + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { + stderr => $outfile, + stdout => $outfile, + }, + }, + 'quota' + ); - # should have reported quotas for all users - my $content = slurp_file($outfile); - foreach my $user (@users) { - $self->assert_matches(qr{$user}, $content); - } + # should have reported quotas for all users + my $content = slurp_file($outfile); + foreach my $user (@users) { + $self->assert_matches(qr{$user}, $content); + } - xlog $self, "run quota -d foo.com"; - $outfile = $self->{instance}->{basedir} . '/quota_d.out'; - $self->{instance}->run_command( - { cyrus => 1, - redirects => { - stderr => $outfile, - stdout => $outfile, - }, - }, - 'quota', '-d', 'foo.com'); + xlog $self, "run quota -d foo.com"; + $outfile = $self->{instance}->{basedir} . '/quota_d.out'; + $self->{instance}->run_command( + { + cyrus => 1, + redirects => { + stderr => $outfile, + stdout => $outfile, + }, + }, + 'quota', '-d', + 'foo.com' + ); - # should not report quotas for users in other domains! - $content = slurp_file($outfile); - $self->assert_matches(qr{alice\@foo.com}, $content); - $self->assert_matches(qr{bob\@foo.com}, $content); - $self->assert_does_not_match(qr{chris\@bar.com}, $content); - $self->assert_does_not_match(qr{dave\@qux.com}, $content); + # should not report quotas for users in other domains! + $content = slurp_file($outfile); + $self->assert_matches(qr{alice\@foo.com}, $content); + $self->assert_matches(qr{bob\@foo.com}, $content); + $self->assert_does_not_match(qr{chris\@bar.com}, $content); + $self->assert_does_not_match(qr{dave\@qux.com}, $content); } diff --git a/cassandane/tiny-tests/Quota/quota_f b/cassandane/tiny-tests/Quota/quota_f index 58afc1774c..55f652380b 100644 --- a/cassandane/tiny-tests/Quota/quota_f +++ b/cassandane/tiny-tests/Quota/quota_f @@ -1,121 +1,134 @@ #!perl use Cassandane::Tiny; -sub test_quota_f -{ - my ($self) = @_; +sub test_quota_f { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "set ourselves a basic usage quota"; - $self->_set_limits( - quotaroot => 'user.cassandane', - storage => 100000, - message => 50000, - $self->res_annot_storage => 10000, - ); - $self->_check_usages( - quotaroot => 'user.cassandane', - storage => 0, - message => 0, - $self->res_annot_storage => 0, - ); + xlog $self, "set ourselves a basic usage quota"; + $self->_set_limits( + quotaroot => 'user.cassandane', + storage => 100000, + message => 50000, + $self->res_annot_storage => 10000, + ); + $self->_check_usages( + quotaroot => 'user.cassandane', + storage => 0, + message => 0, + $self->res_annot_storage => 0, + ); + + xlog $self, "create some messages to use various quota resources"; + $self->{instance}->create_user("quotafuser"); + $self->_set_limits( + quotaroot => 'user.quotafuser', + storage => 100000, + message => 50000, + $self->res_annot_storage => 10000, + ); + $self->{adminstore}->set_folder("user.quotafuser"); + my $quotafuser_expected_storage = 0; + my $quotafuser_expected_message = 0; + my $quotafuser_expected_annotation_storage = 0; - xlog $self, "create some messages to use various quota resources"; - $self->{instance}->create_user("quotafuser"); - $self->_set_limits( - quotaroot => 'user.quotafuser', - storage => 100000, - message => 50000, - $self->res_annot_storage => 10000, + for (1 .. 3) { + my $msg = $self->make_message( + "QuotaFUser $_", + store => $self->{adminstore}, + extra_lines => 17000 ); - $self->{adminstore}->set_folder("user.quotafuser"); - my $quotafuser_expected_storage = 0; - my $quotafuser_expected_message = 0; - my $quotafuser_expected_annotation_storage = 0; - for (1..3) { - my $msg = $self->make_message("QuotaFUser $_", store => $self->{adminstore}, extra_lines => 17000); - $quotafuser_expected_storage += length($msg->as_string()); - $quotafuser_expected_message++; - } - my $annotation = $self->make_random_data(10); - $quotafuser_expected_annotation_storage += length($annotation); - $admintalk->setmetadata('user.quotafuser', '/private/comment', { Quote => $annotation }); + $quotafuser_expected_storage += length($msg->as_string()); + $quotafuser_expected_message++; + } + my $annotation = $self->make_random_data(10); + $quotafuser_expected_annotation_storage += length($annotation); + $admintalk->setmetadata('user.quotafuser', '/private/comment', + { Quote => $annotation }); - my $cassandane_expected_storage = 0; - my $cassandane_expected_message = 0; - my $cassandane_expected_annotation_storage = 0; - for (1..10) { - my $msg = $self->make_message("Cassandane $_", extra_lines => 5000); - $cassandane_expected_storage += length($msg->as_string()); - $cassandane_expected_message++; - } - $annotation = $self->make_random_data(3); - $cassandane_expected_annotation_storage += length($annotation); - $admintalk->setmetadata('user.cassandane', '/private/comment', { Quote => $annotation }); + my $cassandane_expected_storage = 0; + my $cassandane_expected_message = 0; + my $cassandane_expected_annotation_storage = 0; + for (1 .. 10) { + my $msg = $self->make_message("Cassandane $_", extra_lines => 5000); + $cassandane_expected_storage += length($msg->as_string()); + $cassandane_expected_message++; + } + $annotation = $self->make_random_data(3); + $cassandane_expected_annotation_storage += length($annotation); + $admintalk->setmetadata('user.cassandane', '/private/comment', + { Quote => $annotation }); - xlog $self, "check usages"; - $self->_check_usages( - quotaroot => 'user.quotafuser', - storage => int($quotafuser_expected_storage/1024), - message => $quotafuser_expected_message, - $self->res_annot_storage => int($quotafuser_expected_annotation_storage/1024), - ); - $self->_check_usages( - quotaroot => 'user.cassandane', - storage => int($cassandane_expected_storage/1024), - message => $cassandane_expected_message, - $self->res_annot_storage => int($cassandane_expected_annotation_storage/1024), - ); + xlog $self, "check usages"; + $self->_check_usages( + quotaroot => 'user.quotafuser', + storage => int($quotafuser_expected_storage / 1024), + message => $quotafuser_expected_message, + $self->res_annot_storage => + int($quotafuser_expected_annotation_storage / 1024), + ); + $self->_check_usages( + quotaroot => 'user.cassandane', + storage => int($cassandane_expected_storage / 1024), + message => $cassandane_expected_message, + $self->res_annot_storage => + int($cassandane_expected_annotation_storage / 1024), + ); - xlog $self, "create a bogus quota file"; - $self->_zap_quota(quotaroot => 'user.quotafuser'); + xlog $self, "create a bogus quota file"; + $self->_zap_quota(quotaroot => 'user.quotafuser'); - xlog $self, "check usages"; - $self->_check_usages( - quotaroot => 'user.quotafuser', - storage => 0, - message => 0, - $self->res_annot_storage => 0, - ); - $self->_check_usages( - quotaroot => 'user.cassandane', - storage => int($cassandane_expected_storage/1024), - message => $cassandane_expected_message, - $self->res_annot_storage => int($cassandane_expected_annotation_storage/1024), - ); + xlog $self, "check usages"; + $self->_check_usages( + quotaroot => 'user.quotafuser', + storage => 0, + message => 0, + $self->res_annot_storage => 0, + ); + $self->_check_usages( + quotaroot => 'user.cassandane', + storage => int($cassandane_expected_storage / 1024), + message => $cassandane_expected_message, + $self->res_annot_storage => + int($cassandane_expected_annotation_storage / 1024), + ); - xlog $self, "find and add the quota"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); + xlog $self, "find and add the quota"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); - xlog $self, "check usages"; - $self->_check_usages( - quotaroot => 'user.quotafuser', - storage => int($quotafuser_expected_storage/1024), - message => $quotafuser_expected_message, - $self->res_annot_storage => int($quotafuser_expected_annotation_storage/1024), - ); - $self->_check_usages( - quotaroot => 'user.cassandane', - storage => int($cassandane_expected_storage/1024), - message => $cassandane_expected_message, - $self->res_annot_storage => int($cassandane_expected_annotation_storage/1024), - ); + xlog $self, "check usages"; + $self->_check_usages( + quotaroot => 'user.quotafuser', + storage => int($quotafuser_expected_storage / 1024), + message => $quotafuser_expected_message, + $self->res_annot_storage => + int($quotafuser_expected_annotation_storage / 1024), + ); + $self->_check_usages( + quotaroot => 'user.cassandane', + storage => int($cassandane_expected_storage / 1024), + message => $cassandane_expected_message, + $self->res_annot_storage => + int($cassandane_expected_annotation_storage / 1024), + ); - xlog $self, "re-run the quota utility"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); + xlog $self, "re-run the quota utility"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); - xlog $self, "check usages"; - $self->_check_usages( - quotaroot => 'user.quotafuser', - storage => int($quotafuser_expected_storage/1024), - message => $quotafuser_expected_message, - $self->res_annot_storage => int($quotafuser_expected_annotation_storage/1024), - ); - $self->_check_usages( - quotaroot => 'user.cassandane', - storage => int($cassandane_expected_storage/1024), - message => $cassandane_expected_message, - $self->res_annot_storage => int($cassandane_expected_annotation_storage/1024), - ); + xlog $self, "check usages"; + $self->_check_usages( + quotaroot => 'user.quotafuser', + storage => int($quotafuser_expected_storage / 1024), + message => $quotafuser_expected_message, + $self->res_annot_storage => + int($quotafuser_expected_annotation_storage / 1024), + ); + $self->_check_usages( + quotaroot => 'user.cassandane', + storage => int($cassandane_expected_storage / 1024), + message => $cassandane_expected_message, + $self->res_annot_storage => + int($cassandane_expected_annotation_storage / 1024), + ); } diff --git a/cassandane/tiny-tests/Quota/quota_f_nested_qr b/cassandane/tiny-tests/Quota/quota_f_nested_qr index 0d09590bda..61d3211acb 100644 --- a/cassandane/tiny-tests/Quota/quota_f_nested_qr +++ b/cassandane/tiny-tests/Quota/quota_f_nested_qr @@ -2,67 +2,68 @@ use Cassandane::Tiny; sub test_quota_f_nested_qr - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Test that quota -f correctly calculates the STORAGE quota"; - xlog $self, "with a nested quotaroot and a folder whose name sorts after"; - xlog $self, "the nested quotaroot [Bug 3621]"; + xlog $self, "Test that quota -f correctly calculates the STORAGE quota"; + xlog $self, "with a nested quotaroot and a folder whose name sorts after"; + xlog $self, "the nested quotaroot [Bug 3621]"; - my $inbox = "user.cassandane"; - # These names are significant - we need subfolders both before and - # after the subfolder on which we will set the nested quotaroot - my @folders = ( $inbox, "$inbox.aaa", "$inbox.nnn", "$inbox.zzz" ); + my $inbox = "user.cassandane"; + # These names are significant - we need subfolders both before and + # after the subfolder on which we will set the nested quotaroot + my @folders = ($inbox, "$inbox.aaa", "$inbox.nnn", "$inbox.zzz"); - xlog $self, "add messages to use some STORAGE quota"; - my %exp; - my $n = 5; - foreach my $f (@folders) - { - $self->{store}->set_folder($f); - for (1..$n) { - my $msg = $self->make_message("$f $_", - extra_lines => 10 + rand(5000)); - $exp{$f} += length($msg->as_string()); - } - $n += 5; - xlog $self, "Expect " . $exp{$f} . " on " . $f; + xlog $self, "add messages to use some STORAGE quota"; + my %exp; + my $n = 5; + foreach my $f (@folders) { + $self->{store}->set_folder($f); + for (1 .. $n) { + my $msg = $self->make_message("$f $_", extra_lines => 10 + rand(5000)); + $exp{$f} += length($msg->as_string()); } + $n += 5; + xlog $self, "Expect " . $exp{$f} . " on " . $f; + } - xlog $self, "set a quota on inbox"; - $self->_set_limits(quotaroot => $inbox, storage => 100000); + xlog $self, "set a quota on inbox"; + $self->_set_limits(quotaroot => $inbox, storage => 100000); - xlog $self, "should have correct STORAGE quota"; - my $ex0 = $exp{$inbox} + $exp{"$inbox.aaa"} + $exp{"$inbox.nnn"} + $exp{"$inbox.zzz"}; - $self->_check_usages(quotaroot => $inbox, storage => int($ex0/1024)); + xlog $self, "should have correct STORAGE quota"; + my $ex0 + = $exp{$inbox} + + $exp{"$inbox.aaa"} + + $exp{"$inbox.nnn"} + + $exp{"$inbox.zzz"}; + $self->_check_usages(quotaroot => $inbox, storage => int($ex0 / 1024)); - xlog $self, "set a quota on inbox.nnn - a nested quotaroot"; - $self->_set_limits(quotaroot => "$inbox.nnn", storage => 200000); + xlog $self, "set a quota on inbox.nnn - a nested quotaroot"; + $self->_set_limits(quotaroot => "$inbox.nnn", storage => 200000); - xlog $self, "should have correct STORAGE quota for both roots"; - my $ex1 = $exp{$inbox} + $exp{"$inbox.aaa"} + $exp{"$inbox.zzz"}; - my $ex2 = $exp{"$inbox.nnn"}; - $self->_check_usages(quotaroot => $inbox, storage => int($ex1/1024)); - $self->_check_usages(quotaroot => "$inbox.nnn", storage => int($ex2/1024)); + xlog $self, "should have correct STORAGE quota for both roots"; + my $ex1 = $exp{$inbox} + $exp{"$inbox.aaa"} + $exp{"$inbox.zzz"}; + my $ex2 = $exp{"$inbox.nnn"}; + $self->_check_usages(quotaroot => $inbox, storage => int($ex1 / 1024)); + $self->_check_usages(quotaroot => "$inbox.nnn", storage => int($ex2 / 1024)); - xlog $self, "create a bogus quota file"; - $self->_zap_quota(quotaroot => $inbox); - $self->_zap_quota(quotaroot => "$inbox.nnn"); - $self->_check_usages(quotaroot => $inbox, storage => 0); - $self->_check_usages(quotaroot => "$inbox.nnn", storage => 0); + xlog $self, "create a bogus quota file"; + $self->_zap_quota(quotaroot => $inbox); + $self->_zap_quota(quotaroot => "$inbox.nnn"); + $self->_check_usages(quotaroot => $inbox, storage => 0); + $self->_check_usages(quotaroot => "$inbox.nnn", storage => 0); - xlog $self, "run quota -f to find and add the quota"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); + xlog $self, "run quota -f to find and add the quota"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); - xlog $self, "check that STORAGE quota is restored for both roots"; - $self->_check_usages(quotaroot => $inbox, storage => int($ex1/1024)); - $self->_check_usages(quotaroot => "$inbox.nnn", storage => int($ex2/1024)); + xlog $self, "check that STORAGE quota is restored for both roots"; + $self->_check_usages(quotaroot => $inbox, storage => int($ex1 / 1024)); + $self->_check_usages(quotaroot => "$inbox.nnn", storage => int($ex2 / 1024)); - xlog $self, "run quota -f again"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); + xlog $self, "run quota -f again"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); - xlog $self, "check that STORAGE quota is still correct for both roots"; - $self->_check_usages(quotaroot => $inbox, storage => int($ex1/1024)); - $self->_check_usages(quotaroot => "$inbox.nnn", storage => int($ex2/1024)); + xlog $self, "check that STORAGE quota is still correct for both roots"; + $self->_check_usages(quotaroot => $inbox, storage => int($ex1 / 1024)); + $self->_check_usages(quotaroot => "$inbox.nnn", storage => int($ex2 / 1024)); } diff --git a/cassandane/tiny-tests/Quota/quota_f_no_improved_mboxlist_sort b/cassandane/tiny-tests/Quota/quota_f_no_improved_mboxlist_sort index 7f60430ea2..f79de675c4 100644 --- a/cassandane/tiny-tests/Quota/quota_f_no_improved_mboxlist_sort +++ b/cassandane/tiny-tests/Quota/quota_f_no_improved_mboxlist_sort @@ -3,90 +3,86 @@ use Cassandane::Tiny; # https://github.com/cyrusimap/cyrus-imapd/issues/2877 sub test_quota_f_no_improved_mboxlist_sort - :unixHierarchySep :AltNamespace :VirtDomains :NoStartInstances -{ - my ($self) = @_; + : unixHierarchySep : AltNamespace : VirtDomains : NoStartInstances { + my ($self) = @_; - my $user = 'user1@example.com'; - my @otherusers = ( - 'user0@example.com', - 'user1-z@example.com', - 'user2@example.com', - ); + my $user = 'user1@example.com'; + my @otherusers + = ('user0@example.com', 'user1-z@example.com', 'user2@example.com',); + + $self->{instance}->{config}->set('improved_mboxlist_sort', 'no'); + $self->_start_instances(); - $self->{instance}->{config}->set('improved_mboxlist_sort', 'no'); - $self->_start_instances(); + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->create("user/$user"); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl("user/$user", $user, 'lrswipkxtecdan'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->create("user/$user"); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); - $admintalk->setacl("user/$user", $user, 'lrswipkxtecdan'); - $self->assert_str_equals('ok', - $admintalk->get_last_completion_response()); + xlog $self, "set ourselves a basic usage quota"; + $self->_set_limits( + quotaroot => "user/$user", + storage => 100000, + message => 50000, + $self->res_annot_storage => 10000, + ); + $self->_check_usages( + quotaroot => "user/$user", + storage => 0, + message => 0, + $self->res_annot_storage => 0, + ); - xlog $self, "set ourselves a basic usage quota"; + # create some other users to tickle sort-order issues? + foreach my $x (@otherusers) { + $admintalk->create("user/$x"); $self->_set_limits( - quotaroot => "user/$user", - storage => 100000, - message => 50000, - $self->res_annot_storage => 10000, - ); - $self->_check_usages( - quotaroot => "user/$user", - storage => 0, - message => 0, - $self->res_annot_storage => 0, + quotaroot => "user/$x", + storage => 100000, + message => 50000, + $self->res_annot_storage => 10000, ); + } - # create some other users to tickle sort-order issues? - foreach my $x (@otherusers) { - $admintalk->create("user/$x"); - $self->_set_limits( - quotaroot => "user/$x", - storage => 100000, - message => 50000, - $self->res_annot_storage => 10000, - ); - } - - my $svc = $self->{instance}->get_service('imap'); - my $userstore = $svc->create_store(username => $user); - my $usertalk = $userstore->get_client(); + my $svc = $self->{instance}->get_service('imap'); + my $userstore = $svc->create_store(username => $user); + my $usertalk = $userstore->get_client(); - foreach my $submbox ('Drafts', 'Junk', 'Sent', 'Trash') { - xlog $self, "creating $submbox..."; - $usertalk->create($submbox); - $self->assert_str_equals('ok', - $usertalk->get_last_completion_response()); - } + foreach my $submbox ('Drafts', 'Junk', 'Sent', 'Trash') { + xlog $self, "creating $submbox..."; + $usertalk->create($submbox); + $self->assert_str_equals('ok', $usertalk->get_last_completion_response()); + } - $usertalk->list("", "*"); + $usertalk->list("", "*"); - foreach my $mbox (qw(INBOX Drafts Sent Junk Trash)) { - $usertalk->select($mbox); - foreach (1..3) { - $self->make_message("msg $_ in $mbox", store => $userstore); - } + foreach my $mbox (qw(INBOX Drafts Sent Junk Trash)) { + $usertalk->select($mbox); + foreach (1 .. 3) { + $self->make_message("msg $_ in $mbox", store => $userstore); } + } - xlog $self, "run quota -d"; - $self->{instance}->run_command({ cyrus => 1 }, - 'quota', '-d', 'example.com'); + xlog $self, "run quota -d"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-d', 'example.com'); - xlog $self, "run quota -d -f"; - my $outfile = $self->{instance}->{basedir} . '/quota.out'; - my @data = $self->{instance}->run_command({ - cyrus => 1, - redirects => { - stderr => $outfile, - stdout => $outfile, - }, - }, 'quota', '-f', '-d', 'example.com'); + xlog $self, "run quota -d -f"; + my $outfile = $self->{instance}->{basedir} . '/quota.out'; + my @data = $self->{instance}->run_command( + { + cyrus => 1, + redirects => { + stderr => $outfile, + stdout => $outfile, + }, + }, + 'quota', '-f', '-d', + 'example.com' + ); - my $str = slurp_file($outfile); - xlog $self, $str; + my $str = slurp_file($outfile); + xlog $self, $str; - #example.com!user.user1.Junk: quota root example.com!user.user1 --> (none) - $self->assert_does_not_match(qr{ quota root \S+ --> \(none\)}, $str); + #example.com!user.user1.Junk: quota root example.com!user.user1 --> (none) + $self->assert_does_not_match(qr{ quota root \S+ --> \(none\)}, $str); } diff --git a/cassandane/tiny-tests/Quota/quota_f_prefix b/cassandane/tiny-tests/Quota/quota_f_prefix index 66ab345cd4..efd3f2e5a1 100644 --- a/cassandane/tiny-tests/Quota/quota_f_prefix +++ b/cassandane/tiny-tests/Quota/quota_f_prefix @@ -1,172 +1,237 @@ #!perl use Cassandane::Tiny; -sub test_quota_f_prefix -{ - my ($self) = @_; - - xlog $self, "Testing prefix matches with quota -f [IRIS-1029]"; - - my $admintalk = $self->{adminstore}->get_client(); - - # surround with other users too - $self->{instance}->create_user("aabefore", - subdirs => [ qw(subdir subdir2) ]); - - $self->{instance}->create_user("zzafter", - subdirs => [ qw(subdir subdir2) ]); - - $self->{instance}->create_user("base", - subdirs => [ qw(subdir subdir2) ]); - $self->_set_limits(quotaroot => 'user.base', storage => 1000000); - my $exp_base = 0; - - xlog $self, "Adding messages to user.base"; - $self->{adminstore}->set_folder("user.base"); - for (1..10) { - my $msg = $self->make_message("base $_", - store => $self->{adminstore}, - extra_lines => 5000+rand(50000)); - $exp_base += length($msg->as_string()); - } - - xlog $self, "Adding messages to user.base.subdir2"; - $self->{adminstore}->set_folder("user.base.subdir2"); - for (1..10) { - my $msg = $self->make_message("base subdir2 $_", - store => $self->{adminstore}, - extra_lines => 5000+rand(50000)); - $exp_base += length($msg->as_string()); - } - - $self->{instance}->create_user("baseplus", - subdirs => [ qw(subdir) ]); - $self->_set_limits(quotaroot => 'user.baseplus', storage => 1000000); - my $exp_baseplus = 0; - - xlog $self, "Adding messages to user.baseplus"; - $self->{adminstore}->set_folder("user.baseplus"); - for (1..10) { - my $msg = $self->make_message("baseplus $_", - store => $self->{adminstore}, - extra_lines => 5000+rand(50000)); - $exp_baseplus += length($msg->as_string()); - } - - xlog $self, "Adding messages to user.baseplus.subdir"; - $self->{adminstore}->set_folder("user.baseplus.subdir"); - for (1..10) { - my $msg = $self->make_message("baseplus subdir $_", - store => $self->{adminstore}, - extra_lines => 5000+rand(50000)); - $exp_baseplus += length($msg->as_string()); - } - - xlog $self, "Check that the quotas were updated as expected"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($exp_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($exp_baseplus/1024)); - - xlog $self, "Run quota -f"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); - - xlog $self, "Check that the quotas were unchanged by quota -f"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($exp_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($exp_baseplus/1024)); - - my $bogus_base = $exp_base + 20000 + rand(30000); - my $bogus_baseplus = $exp_baseplus + 50000 + rand(80000); - xlog $self, "Write incorrect values to the quota db"; - $self->_zap_quota(quotaroot => 'user.base', - useds => { storage => $bogus_base }); - $self->_zap_quota(quotaroot => 'user.baseplus', - useds => { storage => $bogus_baseplus }); - - xlog $self, "Check that the quotas are now bogus"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($bogus_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($bogus_baseplus/1024)); - - xlog $self, "Run quota -f with no prefix"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); - - xlog $self, "Check that the quotas were all fixed"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($exp_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($exp_baseplus/1024)); - - xlog $self, "Write incorrect values to the quota db"; - $self->_zap_quota(quotaroot => "user.base", - useds => { storage => $bogus_base }); - $self->_zap_quota(quotaroot => "user.baseplus", - useds => { storage => $bogus_baseplus }); - - xlog $self, "Check that the quotas are now bogus"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($bogus_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($bogus_baseplus/1024)); - - xlog $self, "Run quota -f on user.base only"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f', 'user.base'); - - xlog $self, "Check that only the user.base and user.baseplus quotas were fixed"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($exp_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($exp_baseplus/1024)); - - xlog $self, "Write incorrect values to the quota db"; - $self->_zap_quota(quotaroot => "user.base", - useds => { storage => $bogus_base }); - $self->_zap_quota(quotaroot => "user.baseplus", - useds => { storage => $bogus_baseplus }); - - xlog $self, "Check that the quotas are now bogus"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($bogus_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($bogus_baseplus/1024)); - - xlog $self, "Run quota -f on user.baseplus only"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f', 'user.baseplus'); - - xlog $self, "Check that only the user.baseplus quotas were fixed"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($bogus_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($exp_baseplus/1024)); - - xlog $self, "Write incorrect values to the quota db"; - $self->_zap_quota(quotaroot => "user.base", - useds => { storage => $bogus_base }); - $self->_zap_quota(quotaroot => "user.baseplus", - useds => { storage => $bogus_baseplus }); - - xlog $self, "Check that the quotas are now bogus"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($bogus_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($bogus_baseplus/1024)); - - xlog $self, "Run quota -f -u on user base "; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f', '-u', 'base'); - - xlog $self, "Check that only the user base quotas were fixed"; - $self->_check_usages(quotaroot => 'user.base', - storage => int($exp_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($bogus_baseplus/1024)); - - xlog $self, "Run a final quota -f to fix up everything"; - $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); - $self->_check_usages(quotaroot => 'user.base', - storage => int($exp_base/1024)); - $self->_check_usages(quotaroot => 'user.baseplus', - storage => int($exp_baseplus/1024)); +sub test_quota_f_prefix { + my ($self) = @_; + + xlog $self, "Testing prefix matches with quota -f [IRIS-1029]"; + + my $admintalk = $self->{adminstore}->get_client(); + + # surround with other users too + $self->{instance}->create_user("aabefore", subdirs => [qw(subdir subdir2)]); + + $self->{instance}->create_user("zzafter", subdirs => [qw(subdir subdir2)]); + + $self->{instance}->create_user("base", subdirs => [qw(subdir subdir2)]); + $self->_set_limits(quotaroot => 'user.base', storage => 1000000); + my $exp_base = 0; + + xlog $self, "Adding messages to user.base"; + $self->{adminstore}->set_folder("user.base"); + for (1 .. 10) { + my $msg = $self->make_message( + "base $_", + store => $self->{adminstore}, + extra_lines => 5000 + rand(50000) + ); + $exp_base += length($msg->as_string()); + } + + xlog $self, "Adding messages to user.base.subdir2"; + $self->{adminstore}->set_folder("user.base.subdir2"); + for (1 .. 10) { + my $msg = $self->make_message( + "base subdir2 $_", + store => $self->{adminstore}, + extra_lines => 5000 + rand(50000) + ); + $exp_base += length($msg->as_string()); + } + + $self->{instance}->create_user("baseplus", subdirs => [qw(subdir)]); + $self->_set_limits(quotaroot => 'user.baseplus', storage => 1000000); + my $exp_baseplus = 0; + + xlog $self, "Adding messages to user.baseplus"; + $self->{adminstore}->set_folder("user.baseplus"); + for (1 .. 10) { + my $msg = $self->make_message( + "baseplus $_", + store => $self->{adminstore}, + extra_lines => 5000 + rand(50000) + ); + $exp_baseplus += length($msg->as_string()); + } + + xlog $self, "Adding messages to user.baseplus.subdir"; + $self->{adminstore}->set_folder("user.baseplus.subdir"); + for (1 .. 10) { + my $msg = $self->make_message( + "baseplus subdir $_", + store => $self->{adminstore}, + extra_lines => 5000 + rand(50000) + ); + $exp_baseplus += length($msg->as_string()); + } + + xlog $self, "Check that the quotas were updated as expected"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($exp_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($exp_baseplus / 1024) + ); + + xlog $self, "Run quota -f"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); + + xlog $self, "Check that the quotas were unchanged by quota -f"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($exp_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($exp_baseplus / 1024) + ); + + my $bogus_base = $exp_base + 20000 + rand(30000); + my $bogus_baseplus = $exp_baseplus + 50000 + rand(80000); + xlog $self, "Write incorrect values to the quota db"; + $self->_zap_quota( + quotaroot => 'user.base', + useds => { storage => $bogus_base } + ); + $self->_zap_quota( + quotaroot => 'user.baseplus', + useds => { storage => $bogus_baseplus } + ); + + xlog $self, "Check that the quotas are now bogus"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($bogus_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($bogus_baseplus / 1024) + ); + + xlog $self, "Run quota -f with no prefix"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); + + xlog $self, "Check that the quotas were all fixed"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($exp_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($exp_baseplus / 1024) + ); + + xlog $self, "Write incorrect values to the quota db"; + $self->_zap_quota( + quotaroot => "user.base", + useds => { storage => $bogus_base } + ); + $self->_zap_quota( + quotaroot => "user.baseplus", + useds => { storage => $bogus_baseplus } + ); + + xlog $self, "Check that the quotas are now bogus"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($bogus_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($bogus_baseplus / 1024) + ); + + xlog $self, "Run quota -f on user.base only"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f', 'user.base'); + + xlog $self, + "Check that only the user.base and user.baseplus quotas were fixed"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($exp_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($exp_baseplus / 1024) + ); + + xlog $self, "Write incorrect values to the quota db"; + $self->_zap_quota( + quotaroot => "user.base", + useds => { storage => $bogus_base } + ); + $self->_zap_quota( + quotaroot => "user.baseplus", + useds => { storage => $bogus_baseplus } + ); + + xlog $self, "Check that the quotas are now bogus"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($bogus_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($bogus_baseplus / 1024) + ); + + xlog $self, "Run quota -f on user.baseplus only"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'quota', '-f', 'user.baseplus'); + + xlog $self, "Check that only the user.baseplus quotas were fixed"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($bogus_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($exp_baseplus / 1024) + ); + + xlog $self, "Write incorrect values to the quota db"; + $self->_zap_quota( + quotaroot => "user.base", + useds => { storage => $bogus_base } + ); + $self->_zap_quota( + quotaroot => "user.baseplus", + useds => { storage => $bogus_baseplus } + ); + + xlog $self, "Check that the quotas are now bogus"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($bogus_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($bogus_baseplus / 1024) + ); + + xlog $self, "Run quota -f -u on user base "; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f', '-u', 'base'); + + xlog $self, "Check that only the user base quotas were fixed"; + $self->_check_usages( + quotaroot => 'user.base', + storage => int($exp_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($bogus_baseplus / 1024) + ); + + xlog $self, "Run a final quota -f to fix up everything"; + $self->{instance}->run_command({ cyrus => 1 }, 'quota', '-f'); + $self->_check_usages( + quotaroot => 'user.base', + storage => int($exp_base / 1024) + ); + $self->_check_usages( + quotaroot => 'user.baseplus', + storage => int($exp_baseplus / 1024) + ); } diff --git a/cassandane/tiny-tests/Quota/quota_f_unixhs b/cassandane/tiny-tests/Quota/quota_f_unixhs index 193f2a0021..0e8d4855ea 100644 --- a/cassandane/tiny-tests/Quota/quota_f_unixhs +++ b/cassandane/tiny-tests/Quota/quota_f_unixhs @@ -2,33 +2,35 @@ use Cassandane::Tiny; sub test_quota_f_unixhs - :UnixHierarchySep -{ - my ($self) = @_; + : UnixHierarchySep { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - xlog $self, "set ourselves a basic usage quota"; - $self->_set_limits( - quotaroot => 'user/cassandane', - storage => 100000, - message => 50000, - $self->res_annot_storage => 10000, - ); - $self->_check_usages( - quotaroot => 'user/cassandane', - storage => 0, - message => 0, - $self->res_annot_storage => 0, - ); + xlog $self, "set ourselves a basic usage quota"; + $self->_set_limits( + quotaroot => 'user/cassandane', + storage => 100000, + message => 50000, + $self->res_annot_storage => 10000, + ); + $self->_check_usages( + quotaroot => 'user/cassandane', + storage => 0, + message => 0, + $self->res_annot_storage => 0, + ); - xlog $self, "run quota -f"; - my @data = $self->{instance}->run_command({ - cyrus => 1, - redirects => { stdout => $self->{instance}{basedir} . '/quota.out' }, - }, 'quota', '-f'); + xlog $self, "run quota -f"; + my @data = $self->{instance}->run_command( + { + cyrus => 1, + redirects => { stdout => $self->{instance}{basedir} . '/quota.out' }, + }, + 'quota', '-f' + ); - my $str = slurp_file($self->{instance}{basedir} . '/quota.out'); + my $str = slurp_file($self->{instance}{basedir} . '/quota.out'); - $self->assert_matches(qr{STORAGE user/cassandane}, $str); + $self->assert_matches(qr{STORAGE user/cassandane}, $str); } diff --git a/cassandane/tiny-tests/Quota/quota_f_vs_update b/cassandane/tiny-tests/Quota/quota_f_vs_update index 0d8ac3a057..e0077b7d00 100644 --- a/cassandane/tiny-tests/Quota/quota_f_vs_update +++ b/cassandane/tiny-tests/Quota/quota_f_vs_update @@ -3,69 +3,67 @@ use Cassandane::Tiny; # Test races between quota -f and updates to mailboxes sub test_quota_f_vs_update - :NoAltNameSpace -{ - my ($self) = @_; + : NoAltNameSpace { + my ($self) = @_; - my $basefolder = "user.cassandane"; - my @folders = qw(a b c d e); - my $msg; - my $expected; + my $basefolder = "user.cassandane"; + my @folders = qw(a b c d e); + my $msg; + my $expected; - xlog $self, "Set up a large but limited quota"; - $self->_set_quotaroot($basefolder); - $self->_set_limits(storage => 1000000); - $self->_check_usages(storage => 0); - my $talk = $self->{store}->get_client(); + xlog $self, "Set up a large but limited quota"; + $self->_set_quotaroot($basefolder); + $self->_set_limits(storage => 1000000); + $self->_check_usages(storage => 0); + my $talk = $self->{store}->get_client(); - xlog $self, "Create some sub folders"; - for my $f (@folders) - { - $talk->create("$basefolder.$f") || die "Failed $@"; - $self->{store}->set_folder("$basefolder.$f"); - $msg = $self->make_message("Cassandane $f", - extra_lines => 2000+rand(5000)); - $expected += length($msg->as_string()); - } - # unselect so quota -f can lock the mailboxes - $talk->unselect(); + xlog $self, "Create some sub folders"; + for my $f (@folders) { + $talk->create("$basefolder.$f") || die "Failed $@"; + $self->{store}->set_folder("$basefolder.$f"); + $msg + = $self->make_message("Cassandane $f", extra_lines => 2000 + rand(5000)); + $expected += length($msg->as_string()); + } + # unselect so quota -f can lock the mailboxes + $talk->unselect(); - xlog $self, "Check that we have some quota usage"; - $self->_check_usages(storage => int($expected/1024)); + xlog $self, "Check that we have some quota usage"; + $self->_check_usages(storage => int($expected / 1024)); - xlog $self, "Start a quota -f scan"; - $self->{instance}->quota_Z_go($basefolder); - $self->{instance}->quota_Z_go("$basefolder.a"); - $self->{instance}->quota_Z_go("$basefolder.b"); - my (@bits) = $self->{instance}->run_command({ cyrus => 1, background => 1 }, - 'quota', '-Z', '-f', $basefolder); + xlog $self, "Start a quota -f scan"; + $self->{instance}->quota_Z_go($basefolder); + $self->{instance}->quota_Z_go("$basefolder.a"); + $self->{instance}->quota_Z_go("$basefolder.b"); + my (@bits) = $self->{instance}->run_command({ cyrus => 1, background => 1 }, + 'quota', '-Z', '-f', $basefolder); - # waiting for quota -f to ensure that - # a) the -Z mechanism is working and - # b) quota -f has at least initialised and started scanning. - $self->{instance}->quota_Z_wait("$basefolder.b"); + # waiting for quota -f to ensure that + # a) the -Z mechanism is working and + # b) quota -f has at least initialised and started scanning. + $self->{instance}->quota_Z_wait("$basefolder.b"); - # quota -f is now waiting to be allowed to proceed to "c" + # quota -f is now waiting to be allowed to proceed to "c" - xlog $self, "Mailbox update behind the scan"; - $self->{store}->set_folder("$basefolder.b"); - $msg = $self->make_message("Cassandane b UPDATE", - extra_lines => 2000+rand(3000)); - $expected += length($msg->as_string()); + xlog $self, "Mailbox update behind the scan"; + $self->{store}->set_folder("$basefolder.b"); + $msg = $self->make_message("Cassandane b UPDATE", + extra_lines => 2000 + rand(3000)); + $expected += length($msg->as_string()); - xlog $self, "Mailbox update in front of the scan"; - $self->{store}->set_folder("$basefolder.d"); - $msg = $self->make_message("Cassandane d UPDATE", - extra_lines => 2000+rand(3000)); - $expected += length($msg->as_string()); + xlog $self, "Mailbox update in front of the scan"; + $self->{store}->set_folder("$basefolder.d"); + $msg = $self->make_message("Cassandane d UPDATE", + extra_lines => 2000 + rand(3000)); + $expected += length($msg->as_string()); - xlog $self, "Let quota -f continue and finish"; - $self->{instance}->quota_Z_go("$basefolder.c"); - $self->{instance}->quota_Z_go("$basefolder.d"); - $self->{instance}->quota_Z_go("$basefolder.e"); - $self->{instance}->quota_Z_wait("$basefolder.e"); - $self->{instance}->reap_command(@bits); + xlog $self, "Let quota -f continue and finish"; + $self->{instance}->quota_Z_go("$basefolder.c"); + $self->{instance}->quota_Z_go("$basefolder.d"); + $self->{instance}->quota_Z_go("$basefolder.e"); + $self->{instance}->quota_Z_wait("$basefolder.e"); + $self->{instance}->reap_command(@bits); - xlog $self, "Check that we have the correct quota usage"; - $self->_check_usages(storage => int($expected/1024)); + xlog $self, "Check that we have the correct quota usage"; + $self->_check_usages(storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/quotarename b/cassandane/tiny-tests/Quota/quotarename index 2b32244bb0..d391f5536f 100644 --- a/cassandane/tiny-tests/Quota/quotarename +++ b/cassandane/tiny-tests/Quota/quotarename @@ -4,92 +4,92 @@ use Cassandane::Tiny; # # Test renames # -sub test_quotarename -{ - my ($self) = @_; +sub test_quotarename { + my ($self) = @_; - my $admintalk = $self->{adminstore}->get_client(); - my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); - # Right - let's set ourselves a basic usage quota - $self->_set_quotaroot('user.cassandane'); - $self->_set_limits( - storage => 100000, - message => 50000, - $self->res_annot_storage => 10000, - ); - $self->_check_usages( - storage => 0, - message => 0, - $self->res_annot_storage => 0, - ); + # Right - let's set ourselves a basic usage quota + $self->_set_quotaroot('user.cassandane'); + $self->_set_limits( + storage => 100000, + message => 50000, + $self->res_annot_storage => 10000, + ); + $self->_check_usages( + storage => 0, + message => 0, + $self->res_annot_storage => 0, + ); - my $expected_storage = 0; - my $expected_message = 0; - my $expected_annotation_storage = 0; - my $uid = 1; - for (1..10) { - my $msg = $self->make_message("Message $_", extra_lines => 5000); - $expected_storage += length($msg->as_string()); - $expected_message++; + my $expected_storage = 0; + my $expected_message = 0; + my $expected_annotation_storage = 0; + my $uid = 1; + for (1 .. 10) { + my $msg = $self->make_message("Message $_", extra_lines => 5000); + $expected_storage += length($msg->as_string()); + $expected_message++; - my $annotation = $self->make_random_data(1); - $expected_annotation_storage += length($annotation); - $talk->store('' . $uid, 'annotation', ['/comment', ['value.priv', { Quote => $annotation }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $uid++; - } + my $annotation = $self->make_random_data(1); + $expected_annotation_storage += length($annotation); + $talk->store('' . $uid, 'annotation', + [ '/comment', [ 'value.priv', { Quote => $annotation } ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uid++; + } - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), - ); + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); - $talk->create("INBOX.sub") || die "Failed to create subfolder"; - $self->{store}->set_folder("INBOX.sub"); - $talk->select($self->{store}->{folder}) || die; - my $expected_storage_more = $expected_storage; - my $expected_message_more = $expected_message; - my $expected_annotation_storage_more = $expected_annotation_storage; - $uid = 1; - for (1..10) { + $talk->create("INBOX.sub") || die "Failed to create subfolder"; + $self->{store}->set_folder("INBOX.sub"); + $talk->select($self->{store}->{folder}) || die; + my $expected_storage_more = $expected_storage; + my $expected_message_more = $expected_message; + my $expected_annotation_storage_more = $expected_annotation_storage; + $uid = 1; + for (1 .. 10) { - my $msg = $self->make_message("Message $_", - extra_lines => 10 + rand(5000)); - $expected_storage_more += length($msg->as_string()); - $expected_message_more++; + my $msg = $self->make_message("Message $_", extra_lines => 10 + rand(5000)); + $expected_storage_more += length($msg->as_string()); + $expected_message_more++; - my $annotation = $self->make_random_data(1); - $expected_annotation_storage_more += length($annotation); - $talk->store('' . $uid, 'annotation', ['/comment', ['value.priv', { Quote => $annotation }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $uid++; - } - $self->{store}->set_folder("INBOX"); - $talk->select($self->{store}->{folder}) || die; + my $annotation = $self->make_random_data(1); + $expected_annotation_storage_more += length($annotation); + $talk->store('' . $uid, 'annotation', + [ '/comment', [ 'value.priv', { Quote => $annotation } ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uid++; + } + $self->{store}->set_folder("INBOX"); + $talk->select($self->{store}->{folder}) || die; - $self->_check_usages( - storage => int($expected_storage_more/1024), - message => $expected_message_more, - $self->res_annot_storage => int($expected_annotation_storage_more/1024), - ); + $self->_check_usages( + storage => int($expected_storage_more / 1024), + message => $expected_message_more, + $self->res_annot_storage => int($expected_annotation_storage_more / 1024), + ); - $talk->rename("INBOX.sub", "INBOX.othersub") || die; - $talk->select("INBOX.othersub") || die; + $talk->rename("INBOX.sub", "INBOX.othersub") || die; + $talk->select("INBOX.othersub") || die; - # usage should be the same after a rename - $self->_check_usages( - storage => int($expected_storage_more/1024), - message => $expected_message_more, - $self->res_annot_storage => int($expected_annotation_storage_more/1024), - ); + # usage should be the same after a rename + $self->_check_usages( + storage => int($expected_storage_more / 1024), + message => $expected_message_more, + $self->res_annot_storage => int($expected_annotation_storage_more / 1024), + ); - $talk->delete("INBOX.othersub") || die; + $talk->delete("INBOX.othersub") || die; - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), - ); + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); } diff --git a/cassandane/tiny-tests/Quota/reconstruct b/cassandane/tiny-tests/Quota/reconstruct index cc4c2e9ffd..88ac00bef1 100644 --- a/cassandane/tiny-tests/Quota/reconstruct +++ b/cassandane/tiny-tests/Quota/reconstruct @@ -1,126 +1,135 @@ #!perl use Cassandane::Tiny; -sub test_reconstruct -{ - my ($self) = @_; - - xlog $self, "test resources usage calculated when reconstructing an index"; - - $self->_set_quotaroot('user.cassandane'); - my $folder = 'INBOX'; - my $fentry = '/private/comment'; - my $mentry1 = '/comment'; - my $mentry2 = '/altsubject'; - my $mattrib = 'value.priv'; - - my $store = $self->{store}; - $store->set_fetch_attributes('uid', - "annotation ($mentry1 $mattrib)", - "annotation ($mentry2 $mattrib)"); - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - xlog $self, "set ourselves a basic limit"; - $self->_set_limits( - storage => 100000, - message => 50000, - $self->res_annot_storage => 100000, +sub test_reconstruct { + my ($self) = @_; + + xlog $self, "test resources usage calculated when reconstructing an index"; + + $self->_set_quotaroot('user.cassandane'); + my $folder = 'INBOX'; + my $fentry = '/private/comment'; + my $mentry1 = '/comment'; + my $mentry2 = '/altsubject'; + my $mattrib = 'value.priv'; + + my $store = $self->{store}; + $store->set_fetch_attributes( + 'uid', + "annotation ($mentry1 $mattrib)", + "annotation ($mentry2 $mattrib)" + ); + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + xlog $self, "set ourselves a basic limit"; + $self->_set_limits( + storage => 100000, + message => 50000, + $self->res_annot_storage => 100000, + ); + $self->_check_usages( + storage => 0, + message => 0, + $self->res_annot_storage => 0, + ); + my $expected_annotation_storage = 0; + my $expected_storage = 0; + my $expected_message = 0; + + xlog $self, "store annotations"; + my $data = $self->make_random_data(10); + $expected_annotation_storage += length($data); + $talk->setmetadata($folder, $fentry, { Quote => $data }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "add some messages"; + my $uid = 1; + my %exp; + for (1 .. 10) { + my $msg = $self->make_message("Message $_", extra_lines => 10 + rand(5000)); + $exp{$uid} = $msg; + my $data1 = $self->make_random_data(7); + my $data2 = $self->make_random_data(3); + $msg->set_attribute('uid', $uid); + $msg->set_annotation($mentry1, $mattrib, $data1); + $msg->set_annotation($mentry2, $mattrib, $data2); + $talk->store( + '' . $uid, + 'annotation', + [ + $mentry1, [ $mattrib, { Quote => $data1 } ], + $mentry2, [ $mattrib, { Quote => $data2 } ] + ] ); - $self->_check_usages( - storage => 0, - message => 0, - $self->res_annot_storage => 0, - ); - my $expected_annotation_storage = 0; - my $expected_storage = 0; - my $expected_message = 0; - - xlog $self, "store annotations"; - my $data = $self->make_random_data(10); - $expected_annotation_storage += length($data); - $talk->setmetadata($folder, $fentry, { Quote => $data }); $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "add some messages"; - my $uid = 1; - my %exp; - for (1..10) + $expected_annotation_storage += (length($data1) + length($data2)); + $expected_storage += length($msg->as_string()); + $expected_message++; + $uid++; + } + + xlog $self, "Check the messages are all there"; + $self->check_messages(\%exp); + + xlog $self, "Check the mailbox annotation is still there"; + my $res = $talk->getmetadata($folder, $fentry); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( { - my $msg = $self->make_message("Message $_", - extra_lines => 10 + rand(5000)); - $exp{$uid} = $msg; - my $data1 = $self->make_random_data(7); - my $data2 = $self->make_random_data(3); - $msg->set_attribute('uid', $uid); - $msg->set_annotation($mentry1, $mattrib, $data1); - $msg->set_annotation($mentry2, $mattrib, $data2); - $talk->store('' . $uid, 'annotation', - [$mentry1, [$mattrib, { Quote => $data1 }], - $mentry2, [$mattrib, { Quote => $data2 }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $expected_annotation_storage += (length($data1) + length($data2)); - $expected_storage += length($msg->as_string()); - $expected_message++; - $uid++; - } - - xlog $self, "Check the messages are all there"; - $self->check_messages(\%exp); - - xlog $self, "Check the mailbox annotation is still there"; - my $res = $talk->getmetadata($folder, $fentry); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => $data } - }, $res); - - xlog $self, "Check the quota usage is as expected"; - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), - ); - - $self->{store}->disconnect(); - $self->{adminstore}->disconnect(); - $talk = undef; - $admintalk = undef; - - xlog $self, "Moving the cyrus.index file out of the way"; - my $datadir = $self->{instance}->folder_to_directory('user.cassandane'); - my $cyrus_index = "$datadir/cyrus.index"; - $self->assert_file_test($cyrus_index, '-f'); - rename($cyrus_index, $cyrus_index . '.NOT') - or die "Cannot rename $cyrus_index: $!"; - - xlog $self, "Running reconstruct"; - $self->{instance}->run_command({ cyrus => 1 }, - 'reconstruct', 'user.cassandane'); - xlog $self, "Running quota -f"; - $self->{instance}->run_command({ cyrus => 1 }, - 'quota', '-f', "user.cassandane"); - - $talk = $store->get_client(); - - xlog $self, "Check the messages are still all there"; - $self->check_messages(\%exp); - - xlog $self, "Check the mailbox annotation is still there"; - $res = $talk->getmetadata($folder, $fentry); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => $data } - }, $res); - - xlog $self, "Check the quota usage is still as expected"; - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), - ); - - # We should have generated a SYNCERROR or two - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: opening index/); + $folder => { $fentry => $data } + }, + $res + ); + + xlog $self, "Check the quota usage is as expected"; + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); + + $self->{store}->disconnect(); + $self->{adminstore}->disconnect(); + $talk = undef; + $admintalk = undef; + + xlog $self, "Moving the cyrus.index file out of the way"; + my $datadir = $self->{instance}->folder_to_directory('user.cassandane'); + my $cyrus_index = "$datadir/cyrus.index"; + $self->assert_file_test($cyrus_index, '-f'); + rename($cyrus_index, $cyrus_index . '.NOT') + or die "Cannot rename $cyrus_index: $!"; + + xlog $self, "Running reconstruct"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); + xlog $self, "Running quota -f"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'quota', '-f', "user.cassandane"); + + $talk = $store->get_client(); + + xlog $self, "Check the messages are still all there"; + $self->check_messages(\%exp); + + xlog $self, "Check the mailbox annotation is still there"; + $res = $talk->getmetadata($folder, $fentry); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( + { + $folder => { $fentry => $data } + }, + $res + ); + + xlog $self, "Check the quota usage is still as expected"; + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); + + # We should have generated a SYNCERROR or two + $self->assert_syslog_matches($self->{instance}, qr/IOERROR: opening index/); } diff --git a/cassandane/tiny-tests/Quota/reconstruct_orphans b/cassandane/tiny-tests/Quota/reconstruct_orphans index f871b50296..6f9e379037 100644 --- a/cassandane/tiny-tests/Quota/reconstruct_orphans +++ b/cassandane/tiny-tests/Quota/reconstruct_orphans @@ -1,142 +1,150 @@ #!perl use Cassandane::Tiny; -sub test_reconstruct_orphans -{ - my ($self) = @_; - - xlog $self, "test resources usage calculated when reconstructing an index"; - xlog $self, "with messages disappearing, resulting in orphan annotations"; - - $self->_set_quotaroot('user.cassandane'); - my $folder = 'INBOX'; - my $fentry = '/private/comment'; - my $mentry1 = '/comment'; - my $mentry2 = '/altsubject'; - my $mattrib = 'value.priv'; - - my $store = $self->{store}; - $store->set_fetch_attributes('uid', - "annotation ($mentry1 $mattrib)", - "annotation ($mentry2 $mattrib)"); - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - xlog $self, "set ourselves a basic limit"; - $self->_set_limits( - storage => 100000, - message => 50000, - $self->res_annot_storage => 100000, +sub test_reconstruct_orphans { + my ($self) = @_; + + xlog $self, "test resources usage calculated when reconstructing an index"; + xlog $self, "with messages disappearing, resulting in orphan annotations"; + + $self->_set_quotaroot('user.cassandane'); + my $folder = 'INBOX'; + my $fentry = '/private/comment'; + my $mentry1 = '/comment'; + my $mentry2 = '/altsubject'; + my $mattrib = 'value.priv'; + + my $store = $self->{store}; + $store->set_fetch_attributes( + 'uid', + "annotation ($mentry1 $mattrib)", + "annotation ($mentry2 $mattrib)" + ); + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + xlog $self, "set ourselves a basic limit"; + $self->_set_limits( + storage => 100000, + message => 50000, + $self->res_annot_storage => 100000, + ); + $self->_check_usages( + storage => 0, + message => 0, + $self->res_annot_storage => 0, + ); + my $expected_annotation_storage = 0; + my $expected_storage = 0; + my $expected_message = 0; + + xlog $self, "store annotations"; + my $data = $self->make_random_data(10); + $expected_annotation_storage += length($data); + $talk->setmetadata($folder, $fentry, { Quote => $data }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "add some messages"; + my $uid = 1; + my %exp; + for (1 .. 10) { + my $msg = $self->make_message("Message $_", extra_lines => 10 + rand(5000)); + $exp{$uid} = $msg; + my $data1 = $self->make_random_data(7); + my $data2 = $self->make_random_data(3); + $msg->set_attribute('uid', $uid); + $msg->set_annotation($mentry1, $mattrib, $data1); + $msg->set_annotation($mentry2, $mattrib, $data2); + $talk->store( + '' . $uid, + 'annotation', + [ + $mentry1, [ $mattrib, { Quote => $data1 } ], + $mentry2, [ $mattrib, { Quote => $data2 } ] + ] ); - $self->_check_usages( - storage => 0, - message => 0, - $self->res_annot_storage => 0, - ); - my $expected_annotation_storage = 0; - my $expected_storage = 0; - my $expected_message = 0; - - xlog $self, "store annotations"; - my $data = $self->make_random_data(10); - $expected_annotation_storage += length($data); - $talk->setmetadata($folder, $fentry, { Quote => $data }); $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "add some messages"; - my $uid = 1; - my %exp; - for (1..10) + $expected_annotation_storage += (length($data1) + length($data2)); + $expected_storage += length($msg->as_string()); + $expected_message++; + $uid++; + } + + xlog $self, "Check the messages are all there"; + $self->check_messages(\%exp); + + xlog $self, "Check the mailbox annotation is still there"; + my $res = $talk->getmetadata($folder, $fentry); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( { - my $msg = $self->make_message("Message $_", - extra_lines => 10 + rand(5000)); - $exp{$uid} = $msg; - my $data1 = $self->make_random_data(7); - my $data2 = $self->make_random_data(3); - $msg->set_attribute('uid', $uid); - $msg->set_annotation($mentry1, $mattrib, $data1); - $msg->set_annotation($mentry2, $mattrib, $data2); - $talk->store('' . $uid, 'annotation', - [$mentry1, [$mattrib, { Quote => $data1 }], - $mentry2, [$mattrib, { Quote => $data2 }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $expected_annotation_storage += (length($data1) + length($data2)); - $expected_storage += length($msg->as_string()); - $expected_message++; - $uid++; - } - - xlog $self, "Check the messages are all there"; - $self->check_messages(\%exp); - - xlog $self, "Check the mailbox annotation is still there"; - my $res = $talk->getmetadata($folder, $fentry); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => $data } - }, $res); - - xlog $self, "Check the quota usage is as expected"; - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), - ); - - $self->{store}->disconnect(); - $self->{adminstore}->disconnect(); - $talk = undef; - $admintalk = undef; - - xlog $self, "Moving the cyrus.index file out of the way"; - my $datadir = $self->{instance}->folder_to_directory('user.cassandane'); - my $cyrus_index = "$datadir/cyrus.index"; - $self->assert_file_test($cyrus_index, '-f'); - rename($cyrus_index, $cyrus_index . '.NOT') - or die "Cannot rename $cyrus_index: $!"; - - xlog $self, "Delete a couple of messages"; - foreach $uid (2, 7) + $folder => { $fentry => $data } + }, + $res + ); + + xlog $self, "Check the quota usage is as expected"; + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); + + $self->{store}->disconnect(); + $self->{adminstore}->disconnect(); + $talk = undef; + $admintalk = undef; + + xlog $self, "Moving the cyrus.index file out of the way"; + my $datadir = $self->{instance}->folder_to_directory('user.cassandane'); + my $cyrus_index = "$datadir/cyrus.index"; + $self->assert_file_test($cyrus_index, '-f'); + rename($cyrus_index, $cyrus_index . '.NOT') + or die "Cannot rename $cyrus_index: $!"; + + xlog $self, "Delete a couple of messages"; + foreach $uid (2, 7) { + xlog $self, "Deleting uid $uid"; + unlink("$datadir/$uid."); + + my $msg = delete $exp{$uid}; + my $data1 = $msg->get_annotation($mentry1, $mattrib); + my $data2 = $msg->get_annotation($mentry2, $mattrib); + + $expected_annotation_storage -= (length($data1) + length($data2)); + $expected_storage -= length($msg->as_string()); + $expected_message--; + } + + xlog $self, "Running reconstruct"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'reconstruct', 'user.cassandane'); + xlog $self, "Running quota -f"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'quota', '-f', "user.cassandane"); + + $talk = $store->get_client(); + + xlog $self, "Check the messages are still all there"; + $self->check_messages(\%exp); + + xlog $self, "Check the mailbox annotation is still there"; + $res = $talk->getmetadata($folder, $fentry); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( { - xlog $self, "Deleting uid $uid"; - unlink("$datadir/$uid."); - - my $msg = delete $exp{$uid}; - my $data1 = $msg->get_annotation($mentry1, $mattrib); - my $data2 = $msg->get_annotation($mentry2, $mattrib); - - $expected_annotation_storage -= (length($data1) + length($data2)); - $expected_storage -= length($msg->as_string()); - $expected_message--; - } - - xlog $self, "Running reconstruct"; - $self->{instance}->run_command({ cyrus => 1 }, - 'reconstruct', 'user.cassandane'); - xlog $self, "Running quota -f"; - $self->{instance}->run_command({ cyrus => 1 }, - 'quota', '-f', "user.cassandane"); - - $talk = $store->get_client(); - - xlog $self, "Check the messages are still all there"; - $self->check_messages(\%exp); - - xlog $self, "Check the mailbox annotation is still there"; - $res = $talk->getmetadata($folder, $fentry); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $folder => { $fentry => $data } - }, $res); - - xlog $self, "Check the quota usage is still as expected"; - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), - ); - - # We should have generated a SYNCERROR or two - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: opening index/); + $folder => { $fentry => $data } + }, + $res + ); + + xlog $self, "Check the quota usage is still as expected"; + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); + + # We should have generated a SYNCERROR or two + $self->assert_syslog_matches($self->{instance}, qr/IOERROR: opening index/); } diff --git a/cassandane/tiny-tests/Quota/rename_withannot b/cassandane/tiny-tests/Quota/rename_withannot index 4f11a915cf..5503f51952 100644 --- a/cassandane/tiny-tests/Quota/rename_withannot +++ b/cassandane/tiny-tests/Quota/rename_withannot @@ -1,150 +1,172 @@ #!perl use Cassandane::Tiny; -sub test_rename_withannot -{ - my ($self) = @_; - my ($cyrus_version) = Cassandane::Instance->get_version(); - - xlog $self, "test resources usage survives rename"; - - $self->_set_quotaroot('user.cassandane'); - my $src = 'INBOX.src'; - my $dest = 'INBOX.dest'; - my $fentry = '/private/comment'; - my $mentry1 = '/comment'; - my $mentry2 = '/altsubject'; - my $mattrib = 'value.priv'; - my $vendsize = "/shared/vendor/cmu/cyrus-imapd/size"; - my $vendannot = "/shared/vendor/cmu/cyrus-imapd/annotsize"; - - my $store = $self->{store}; - $store->set_fetch_attributes('uid', - "annotation ($mentry1 $mattrib)", - "annotation ($mentry2 $mattrib)"); - my $talk = $store->get_client(); - my $admintalk = $self->{adminstore}->get_client(); - - $talk->create($src) || die "Failed to create subfolder"; - $store->set_folder($src); - - xlog $self, "set ourselves a basic limit"; - $self->_set_limits( - storage => 100000, - message => 50000, - $self->res_annot_storage => 100000, +sub test_rename_withannot { + my ($self) = @_; + my ($cyrus_version) = Cassandane::Instance->get_version(); + + xlog $self, "test resources usage survives rename"; + + $self->_set_quotaroot('user.cassandane'); + my $src = 'INBOX.src'; + my $dest = 'INBOX.dest'; + my $fentry = '/private/comment'; + my $mentry1 = '/comment'; + my $mentry2 = '/altsubject'; + my $mattrib = 'value.priv'; + my $vendsize = "/shared/vendor/cmu/cyrus-imapd/size"; + my $vendannot = "/shared/vendor/cmu/cyrus-imapd/annotsize"; + + my $store = $self->{store}; + $store->set_fetch_attributes( + 'uid', + "annotation ($mentry1 $mattrib)", + "annotation ($mentry2 $mattrib)" + ); + my $talk = $store->get_client(); + my $admintalk = $self->{adminstore}->get_client(); + + $talk->create($src) || die "Failed to create subfolder"; + $store->set_folder($src); + + xlog $self, "set ourselves a basic limit"; + $self->_set_limits( + storage => 100000, + message => 50000, + $self->res_annot_storage => 100000, + ); + $self->_check_usages( + storage => 0, + message => 0, + $self->res_annot_storage => 0, + ); + my $expected_annotation_storage = 0; + my $expected_storage = 0; + my $expected_message = 0; + + xlog $self, "store annotations"; + my $data = $self->make_random_data(10); + $expected_annotation_storage += length($data); + $talk->setmetadata($src, $fentry, { Quote => $data }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "add some messages"; + my $uid = 1; + my %exp; + for (1 .. 10) { + my $msg = $self->make_message("Message $_", extra_lines => 10 + rand(5000)); + $exp{$uid} = $msg; + my $data1 = $self->make_random_data(7); + my $data2 = $self->make_random_data(3); + $msg->set_attribute('uid', $uid); + $msg->set_annotation($mentry1, $mattrib, $data1); + $msg->set_annotation($mentry2, $mattrib, $data2); + $talk->store( + '' . $uid, + 'annotation', + [ + $mentry1, [ $mattrib, { Quote => $data1 } ], + $mentry2, [ $mattrib, { Quote => $data2 } ] + ] ); - $self->_check_usages( - storage => 0, - message => 0, - $self->res_annot_storage => 0, - ); - my $expected_annotation_storage = 0; - my $expected_storage = 0; - my $expected_message = 0; - - xlog $self, "store annotations"; - my $data = $self->make_random_data(10); - $expected_annotation_storage += length($data); - $talk->setmetadata($src, $fentry, { Quote => $data }); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "add some messages"; - my $uid = 1; - my %exp; - for (1..10) - { - my $msg = $self->make_message("Message $_", - extra_lines => 10 + rand(5000)); - $exp{$uid} = $msg; - my $data1 = $self->make_random_data(7); - my $data2 = $self->make_random_data(3); - $msg->set_attribute('uid', $uid); - $msg->set_annotation($mentry1, $mattrib, $data1); - $msg->set_annotation($mentry2, $mattrib, $data2); - $talk->store('' . $uid, 'annotation', - [$mentry1, [$mattrib, { Quote => $data1 }], - $mentry2, [$mattrib, { Quote => $data2 }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $expected_annotation_storage += (length($data1) + length($data2)); - $expected_storage += length($msg->as_string()); - $expected_message++; - $uid++; - } - - my $res; - - xlog $self, "Check the messages are all there"; - $self->check_messages(\%exp); - - xlog $self, "check that the used size matches"; - $res = $talk->getmetadata($src, $vendsize); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $src => { $vendsize => $expected_storage }, - }, $res); - - if ($cyrus_version >= 3) { - xlog $self, "check that the annot size matches"; - $res = $talk->getmetadata($src, $vendannot); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $src => { $vendannot => $expected_annotation_storage }, - }, $res); - } - - xlog $self, "Check the mailbox annotation is still there"; - $res = $talk->getmetadata($src, $fentry); $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $src => { $fentry => $data } - }, $res); - - xlog $self, "Check the quota usage is as expected"; - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), - ); + $expected_annotation_storage += (length($data1) + length($data2)); + $expected_storage += length($msg->as_string()); + $expected_message++; + $uid++; + } - xlog $self, "rename $src to $dest"; - $talk->rename($src, $dest); - $store->set_folder($dest); + my $res; - xlog $self, "Check the messages are all there"; - $self->check_messages(\%exp); + xlog $self, "Check the messages are all there"; + $self->check_messages(\%exp); - xlog $self, "Check the old mailbox annotation is not there"; - $res = $talk->getmetadata($src, $fentry); - $self->assert_str_equals('no', $talk->get_last_completion_response()); - - xlog $self, "Check the new mailbox annotation is there"; - $res = $talk->getmetadata($dest, $fentry); + xlog $self, "check that the used size matches"; + $res = $talk->getmetadata($src, $vendsize); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( + { + $src => { $vendsize => $expected_storage }, + }, + $res + ); + + if ($cyrus_version >= 3) { + xlog $self, "check that the annot size matches"; + $res = $talk->getmetadata($src, $vendannot); $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $dest => { $fentry => $data } - }, $res); + $self->assert_deep_equals( + { + $src => { $vendannot => $expected_annotation_storage }, + }, + $res + ); + } - xlog $self, "check that the used size still matches"; - $res = $talk->getmetadata($dest, $vendsize); + xlog $self, "Check the mailbox annotation is still there"; + $res = $talk->getmetadata($src, $fentry); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( + { + $src => { $fentry => $data } + }, + $res + ); + + xlog $self, "Check the quota usage is as expected"; + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); + + xlog $self, "rename $src to $dest"; + $talk->rename($src, $dest); + $store->set_folder($dest); + + xlog $self, "Check the messages are all there"; + $self->check_messages(\%exp); + + xlog $self, "Check the old mailbox annotation is not there"; + $res = $talk->getmetadata($src, $fentry); + $self->assert_str_equals('no', $talk->get_last_completion_response()); + + xlog $self, "Check the new mailbox annotation is there"; + $res = $talk->getmetadata($dest, $fentry); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( + { + $dest => { $fentry => $data } + }, + $res + ); + + xlog $self, "check that the used size still matches"; + $res = $talk->getmetadata($dest, $vendsize); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $self->assert_deep_equals( + { + $dest => { $vendsize => $expected_storage }, + }, + $res + ); + + if ($cyrus_version >= 3) { + xlog $self, "check that the annot size still matches"; + $res = $talk->getmetadata($dest, $vendannot); $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $dest => { $vendsize => $expected_storage }, - }, $res); - - if ($cyrus_version >= 3) { - xlog $self, "check that the annot size still matches"; - $res = $talk->getmetadata($dest, $vendannot); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $self->assert_deep_equals({ - $dest => { $vendannot => $expected_annotation_storage }, - }, $res); - } - - xlog $self, "Check the quota usage is still as expected"; - $self->_check_usages( - storage => int($expected_storage/1024), - message => $expected_message, - $self->res_annot_storage => int($expected_annotation_storage/1024), + $self->assert_deep_equals( + { + $dest => { $vendannot => $expected_annotation_storage }, + }, + $res ); + } + + xlog $self, "Check the quota usage is still as expected"; + $self->_check_usages( + storage => int($expected_storage / 1024), + message => $expected_message, + $self->res_annot_storage => int($expected_annotation_storage / 1024), + ); } diff --git a/cassandane/tiny-tests/Quota/replication_annotstorage b/cassandane/tiny-tests/Quota/replication_annotstorage index f1c770ee1d..37383adfaf 100644 --- a/cassandane/tiny-tests/Quota/replication_annotstorage +++ b/cassandane/tiny-tests/Quota/replication_annotstorage @@ -3,108 +3,105 @@ use Cassandane::Tiny; # Magic: the word 'replication' in the name enables a replica sub test_replication_annotstorage - :needs_component_replication -{ - my ($self) = @_; - - xlog $self, "testing replication of X-ANNOTATION-STORAGE quota"; - - my $folder = "user.cassandane"; - my $mastertalk = $self->{master_adminstore}->get_client(); - my $replicatalk = $self->{replica_adminstore}->get_client(); - - my @res; - - xlog $self, "checking there are no initial quotas"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('no', $mastertalk->get_last_completion_response()); - $self->assert($mastertalk->get_last_error() =~ m/Quota root does not exist/i); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('no', $replicatalk->get_last_completion_response()); - $self->assert($replicatalk->get_last_error() =~ m/Quota root does not exist/i); - - xlog $self, "set an X-ANNOTATION-STORAGE quota on the master"; - $mastertalk->setquota($folder, "(" . $self->res_annot_storage . " 12345)"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); - - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals([$self->res_annot_storage, 0, 12345], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals([$self->res_annot_storage, 0, 12345], \@res); - - xlog $self, "change the X-ANNOTATION-STORAGE quota on the master"; - $mastertalk->setquota($folder, "(" . $self->res_annot_storage. " 67890)"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); - - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals([$self->res_annot_storage, 0, 67890], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals([$self->res_annot_storage, 0, 67890], \@res); - - xlog $self, "add an annotation to use some quota"; - my $data = $self->make_random_data(13); - my $msg = $self->make_message("Message A", store => $self->{master_store}); - $mastertalk->store('1', 'annotation', ['/comment', ['value.priv', { Quote => $data }]]); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + : needs_component_replication { + my ($self) = @_; + + xlog $self, "testing replication of X-ANNOTATION-STORAGE quota"; + + my $folder = "user.cassandane"; + my $mastertalk = $self->{master_adminstore}->get_client(); + my $replicatalk = $self->{replica_adminstore}->get_client(); + + my @res; + + xlog $self, "checking there are no initial quotas"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('no', $mastertalk->get_last_completion_response()); + $self->assert($mastertalk->get_last_error() =~ m/Quota root does not exist/i); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('no', $replicatalk->get_last_completion_response()); + $self->assert( + $replicatalk->get_last_error() =~ m/Quota root does not exist/i); + + xlog $self, "set an X-ANNOTATION-STORAGE quota on the master"; + $mastertalk->setquota($folder, "(" . $self->res_annot_storage . " 12345)"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); + + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([ $self->res_annot_storage, 0, 12345 ], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([ $self->res_annot_storage, 0, 12345 ], \@res); + + xlog $self, "change the X-ANNOTATION-STORAGE quota on the master"; + $mastertalk->setquota($folder, "(" . $self->res_annot_storage . " 67890)"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); + + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([ $self->res_annot_storage, 0, 67890 ], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([ $self->res_annot_storage, 0, 67890 ], \@res); + + xlog $self, "add an annotation to use some quota"; + my $data = $self->make_random_data(13); + my $msg = $self->make_message("Message A", store => $self->{master_store}); + $mastertalk->store('1', 'annotation', + [ '/comment', [ 'value.priv', { Quote => $data } ] ]); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); ## This doesn't work because per-mailbox annots are not ## replicated when sync_client is run in -u mode...sigh # $mastertalk->setmetadata($folder, '/private/comment', { Quote => $data }); # $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - my $used = int(length($data)/1024); - - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); - - xlog $self, "check the annotation used some quota on the master"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals([ - $self->res_annot_storage, $used, 67890 - ], \@res); - - xlog $self, "check the annotation used some quota on the replica"; - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals([ - $self->res_annot_storage, $used, 67890 - ], \@res); - - xlog $self, "clear the X-ANNOTATION-STORAGE quota on the master"; - $mastertalk->setquota($folder, "()"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); - - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); + my $used = int(length($data) / 1024); + + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); + + xlog $self, "check the annotation used some quota on the master"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([ $self->res_annot_storage, $used, 67890 ], \@res); + + xlog $self, "check the annotation used some quota on the replica"; + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([ $self->res_annot_storage, $used, 67890 ], \@res); + + xlog $self, "clear the X-ANNOTATION-STORAGE quota on the master"; + $mastertalk->setquota($folder, "()"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); + + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); } diff --git a/cassandane/tiny-tests/Quota/replication_message b/cassandane/tiny-tests/Quota/replication_message index 018a65c9e3..ac051259f9 100644 --- a/cassandane/tiny-tests/Quota/replication_message +++ b/cassandane/tiny-tests/Quota/replication_message @@ -3,77 +3,77 @@ use Cassandane::Tiny; # Magic: the word 'replication' in the name enables a replica sub test_replication_message - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of MESSAGE quota"; + xlog $self, "testing replication of MESSAGE quota"; - my $mastertalk = $self->{master_adminstore}->get_client(); - my $replicatalk = $self->{replica_adminstore}->get_client(); + my $mastertalk = $self->{master_adminstore}->get_client(); + my $replicatalk = $self->{replica_adminstore}->get_client(); - my $folder = "user.cassandane"; - my @res; + my $folder = "user.cassandane"; + my @res; - xlog $self, "checking there are no initial quotas"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('no', $mastertalk->get_last_completion_response()); - $self->assert($mastertalk->get_last_error() =~ m/Quota root does not exist/i); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('no', $replicatalk->get_last_completion_response()); - $self->assert($replicatalk->get_last_error() =~ m/Quota root does not exist/i); + xlog $self, "checking there are no initial quotas"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('no', $mastertalk->get_last_completion_response()); + $self->assert($mastertalk->get_last_error() =~ m/Quota root does not exist/i); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('no', $replicatalk->get_last_completion_response()); + $self->assert( + $replicatalk->get_last_error() =~ m/Quota root does not exist/i); - xlog $self, "set a STORAGE quota on the master"; - $mastertalk->setquota($folder, "(message 12345)"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + xlog $self, "set a STORAGE quota on the master"; + $mastertalk->setquota($folder, "(message 12345)"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals(['MESSAGE', 0, 12345], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals(['MESSAGE', 0, 12345], \@res); + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([ 'MESSAGE', 0, 12345 ], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([ 'MESSAGE', 0, 12345 ], \@res); - xlog $self, "change the MESSAGE quota on the master"; - $mastertalk->setquota($folder, "(message 67890)"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + xlog $self, "change the MESSAGE quota on the master"; + $mastertalk->setquota($folder, "(message 67890)"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals(['MESSAGE', 0, 67890], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals(['MESSAGE', 0, 67890], \@res); + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([ 'MESSAGE', 0, 67890 ], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([ 'MESSAGE', 0, 67890 ], \@res); - xlog $self, "clear the MESSAGE quota on the master"; - $mastertalk->setquota($folder, "()"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + xlog $self, "clear the MESSAGE quota on the master"; + $mastertalk->setquota($folder, "()"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); } diff --git a/cassandane/tiny-tests/Quota/replication_storage b/cassandane/tiny-tests/Quota/replication_storage index 5be76544df..20675063c4 100644 --- a/cassandane/tiny-tests/Quota/replication_storage +++ b/cassandane/tiny-tests/Quota/replication_storage @@ -3,77 +3,77 @@ use Cassandane::Tiny; # Magic: the word 'replication' in the name enables a replica sub test_replication_storage - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - xlog $self, "testing replication of STORAGE quota"; + xlog $self, "testing replication of STORAGE quota"; - my $mastertalk = $self->{master_adminstore}->get_client(); - my $replicatalk = $self->{replica_adminstore}->get_client(); + my $mastertalk = $self->{master_adminstore}->get_client(); + my $replicatalk = $self->{replica_adminstore}->get_client(); - my $folder = "user.cassandane"; - my @res; + my $folder = "user.cassandane"; + my @res; - xlog $self, "checking there are no initial quotas"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('no', $mastertalk->get_last_completion_response()); - $self->assert($mastertalk->get_last_error() =~ m/Quota root does not exist/i); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('no', $replicatalk->get_last_completion_response()); - $self->assert($replicatalk->get_last_error() =~ m/Quota root does not exist/i); + xlog $self, "checking there are no initial quotas"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('no', $mastertalk->get_last_completion_response()); + $self->assert($mastertalk->get_last_error() =~ m/Quota root does not exist/i); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('no', $replicatalk->get_last_completion_response()); + $self->assert( + $replicatalk->get_last_error() =~ m/Quota root does not exist/i); - xlog $self, "set a STORAGE quota on the master"; - $mastertalk->setquota($folder, "(storage 12345)"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + xlog $self, "set a STORAGE quota on the master"; + $mastertalk->setquota($folder, "(storage 12345)"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals(['STORAGE', 0, 12345], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals(['STORAGE', 0, 12345], \@res); + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([ 'STORAGE', 0, 12345 ], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([ 'STORAGE', 0, 12345 ], \@res); - xlog $self, "change the STORAGE quota on the master"; - $mastertalk->setquota($folder, "(storage 67890)"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + xlog $self, "change the STORAGE quota on the master"; + $mastertalk->setquota($folder, "(storage 67890)"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals(['STORAGE', 0, 67890], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals(['STORAGE', 0, 67890], \@res); + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([ 'STORAGE', 0, 67890 ], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([ 'STORAGE', 0, 67890 ], \@res); - xlog $self, "clear the STORAGE quota on the master"; - $mastertalk->setquota($folder, "()"); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + xlog $self, "clear the STORAGE quota on the master"; + $mastertalk->setquota($folder, "()"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - xlog $self, "run replication"; - $self->run_replication(); - $self->check_replication('cassandane'); - $mastertalk = $self->{master_adminstore}->get_client(); - $replicatalk = $self->{replica_adminstore}->get_client(); + xlog $self, "run replication"; + $self->run_replication(); + $self->check_replication('cassandane'); + $mastertalk = $self->{master_adminstore}->get_client(); + $replicatalk = $self->{replica_adminstore}->get_client(); - xlog $self, "check that the new quota is at both ends"; - @res = $mastertalk->getquota($folder); - $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); - @res = $replicatalk->getquota($folder); - $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); - $self->assert_deep_equals([], \@res); + xlog $self, "check that the new quota is at both ends"; + @res = $mastertalk->getquota($folder); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); + @res = $replicatalk->getquota($folder); + $self->assert_str_equals('ok', $replicatalk->get_last_completion_response()); + $self->assert_deep_equals([], \@res); } diff --git a/cassandane/tiny-tests/Quota/storage_convquota_delayed b/cassandane/tiny-tests/Quota/storage_convquota_delayed index 193803a8c7..6e896224d0 100644 --- a/cassandane/tiny-tests/Quota/storage_convquota_delayed +++ b/cassandane/tiny-tests/Quota/storage_convquota_delayed @@ -2,56 +2,54 @@ use Cassandane::Tiny; sub test_storage_convquota_delayed - :min_version_3_3 :Conversations :ConversationsQuota :DelayedDelete -{ - my ($self) = @_; + : min_version_3_3 : Conversations : ConversationsQuota : DelayedDelete { + my ($self) = @_; - xlog $self, "test increasing usage of the STORAGE quota resource as messages are added"; - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits(storage => 100000); - $self->_check_usages(storage => 0); - my $talk = $self->{store}->get_client(); + xlog $self, + "test increasing usage of the STORAGE quota resource as messages are added"; + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits(storage => 100000); + $self->_check_usages(storage => 0); + my $talk = $self->{store}->get_client(); - my $KEY = "/shared/vendor/cmu/cyrus-imapd/userrawquota"; + my $KEY = "/shared/vendor/cmu/cyrus-imapd/userrawquota"; - $talk->create("INBOX.sub") || die "Failed to create subfolder"; + $talk->create("INBOX.sub") || die "Failed to create subfolder"; - # append some messages - $self->{store}->set_folder("INBOX"); - my $msg = $self->make_message("Message 1", - extra_lines => 10 + rand(5000)); - my $size1 = length($msg->as_string()); + # append some messages + $self->{store}->set_folder("INBOX"); + my $msg = $self->make_message("Message 1", extra_lines => 10 + rand(5000)); + my $size1 = length($msg->as_string()); - $self->{store}->set_folder("INBOX.sub"); - my $msg2 = $self->make_message("Message 2", - extra_lines => 10 + rand(5000)); - my $size2 = length($msg2->as_string()); + $self->{store}->set_folder("INBOX.sub"); + my $msg2 = $self->make_message("Message 2", extra_lines => 10 + rand(5000)); + my $size2 = length($msg2->as_string()); - my $data1 = $talk->getmetadata("INBOX", $KEY); - my ($rawusage1) = $data1->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; + my $data1 = $talk->getmetadata("INBOX", $KEY); + my ($rawusage1) = $data1->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; - $self->_check_usages(storage => int(($size1+$size2)/1024)); - $self->assert_num_equals(int(($size1+$size2)/1024), $rawusage1); + $self->_check_usages(storage => int(($size1 + $size2) / 1024)); + $self->assert_num_equals(int(($size1 + $size2) / 1024), $rawusage1); - $talk->select("INBOX"); - $talk->copy("1", "INBOX.sub"); + $talk->select("INBOX"); + $talk->copy("1", "INBOX.sub"); - my $data2 = $talk->getmetadata("INBOX", $KEY); - my ($rawusage2) = $data2->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; + my $data2 = $talk->getmetadata("INBOX", $KEY); + my ($rawusage2) = $data2->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; - # quota usage hasn't changed, because we don't get double-charged - $self->_check_usages(storage => int(($size1+$size2)/1024)); - # but raw usage has gone up by another copy of message 1 - $self->assert_num_equals(int(($size1+$size2+$size1)/1024), $rawusage2); + # quota usage hasn't changed, because we don't get double-charged + $self->_check_usages(storage => int(($size1 + $size2) / 1024)); + # but raw usage has gone up by another copy of message 1 + $self->assert_num_equals(int(($size1 + $size2 + $size1) / 1024), $rawusage2); - $talk->delete("INBOX.sub"); + $talk->delete("INBOX.sub"); - my $data3 = $talk->getmetadata("INBOX", $KEY); - my ($rawusage3) = $data3->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; + my $data3 = $talk->getmetadata("INBOX", $KEY); + my ($rawusage3) = $data3->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; - # we just lost all copies of message2 - $self->_check_usages(storage => int($size1/1024)); - # and also the second copy of message1, so just size1 left - $self->assert_num_equals(int($size1/1024), $rawusage3); + # we just lost all copies of message2 + $self->_check_usages(storage => int($size1 / 1024)); + # and also the second copy of message1, so just size1 left + $self->assert_num_equals(int($size1 / 1024), $rawusage3); } diff --git a/cassandane/tiny-tests/Quota/storage_convquota_immediate b/cassandane/tiny-tests/Quota/storage_convquota_immediate index 5730818953..ff1854c948 100644 --- a/cassandane/tiny-tests/Quota/storage_convquota_immediate +++ b/cassandane/tiny-tests/Quota/storage_convquota_immediate @@ -2,56 +2,54 @@ use Cassandane::Tiny; sub test_storage_convquota_immediate - :min_version_3_3 :Conversations :ConversationsQuota :ImmediateDelete -{ - my ($self) = @_; + : min_version_3_3 : Conversations : ConversationsQuota : ImmediateDelete { + my ($self) = @_; - xlog $self, "test increasing usage of the STORAGE quota resource as messages are added"; - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits(storage => 100000); - $self->_check_usages(storage => 0); - my $talk = $self->{store}->get_client(); + xlog $self, + "test increasing usage of the STORAGE quota resource as messages are added"; + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits(storage => 100000); + $self->_check_usages(storage => 0); + my $talk = $self->{store}->get_client(); - my $KEY = "/shared/vendor/cmu/cyrus-imapd/userrawquota"; + my $KEY = "/shared/vendor/cmu/cyrus-imapd/userrawquota"; - $talk->create("INBOX.sub") || die "Failed to create subfolder"; + $talk->create("INBOX.sub") || die "Failed to create subfolder"; - # append some messages - $self->{store}->set_folder("INBOX"); - my $msg = $self->make_message("Message 1", - extra_lines => 10 + rand(5000)); - my $size1 = length($msg->as_string()); + # append some messages + $self->{store}->set_folder("INBOX"); + my $msg = $self->make_message("Message 1", extra_lines => 10 + rand(5000)); + my $size1 = length($msg->as_string()); - $self->{store}->set_folder("INBOX.sub"); - my $msg2 = $self->make_message("Message 2", - extra_lines => 10 + rand(5000)); - my $size2 = length($msg2->as_string()); + $self->{store}->set_folder("INBOX.sub"); + my $msg2 = $self->make_message("Message 2", extra_lines => 10 + rand(5000)); + my $size2 = length($msg2->as_string()); - my $data1 = $talk->getmetadata("INBOX", $KEY); - my ($rawusage1) = $data1->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; + my $data1 = $talk->getmetadata("INBOX", $KEY); + my ($rawusage1) = $data1->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; - $self->_check_usages(storage => int(($size1+$size2)/1024)); - $self->assert_num_equals(int(($size1+$size2)/1024), $rawusage1); + $self->_check_usages(storage => int(($size1 + $size2) / 1024)); + $self->assert_num_equals(int(($size1 + $size2) / 1024), $rawusage1); - $talk->select("INBOX"); - $talk->copy("1", "INBOX.sub"); + $talk->select("INBOX"); + $talk->copy("1", "INBOX.sub"); - my $data2 = $talk->getmetadata("INBOX", $KEY); - my ($rawusage2) = $data2->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; + my $data2 = $talk->getmetadata("INBOX", $KEY); + my ($rawusage2) = $data2->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; - # quota usage hasn't changed, because we don't get double-charged - $self->_check_usages(storage => int(($size1+$size2)/1024)); - # but raw usage has gone up by another copy of message 1 - $self->assert_num_equals(int(($size1+$size2+$size1)/1024), $rawusage2); + # quota usage hasn't changed, because we don't get double-charged + $self->_check_usages(storage => int(($size1 + $size2) / 1024)); + # but raw usage has gone up by another copy of message 1 + $self->assert_num_equals(int(($size1 + $size2 + $size1) / 1024), $rawusage2); - $talk->delete("INBOX.sub"); + $talk->delete("INBOX.sub"); - my $data3 = $talk->getmetadata("INBOX", $KEY); - my ($rawusage3) = $data3->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; + my $data3 = $talk->getmetadata("INBOX", $KEY); + my ($rawusage3) = $data3->{'INBOX'}{$KEY} =~ m/STORAGE (\d+)/; - # we just lost all copies of message2 - $self->_check_usages(storage => int($size1/1024)); - # and also the second copy of message1, so just size1 left - $self->assert_num_equals(int($size1/1024), $rawusage3); + # we just lost all copies of message2 + $self->_check_usages(storage => int($size1 / 1024)); + # and also the second copy of message1, so just size1 left + $self->assert_num_equals(int($size1 / 1024), $rawusage3); } diff --git a/cassandane/tiny-tests/Quota/using_annotstorage_mbox b/cassandane/tiny-tests/Quota/using_annotstorage_mbox index 0c8a801591..c2bfd568e4 100644 --- a/cassandane/tiny-tests/Quota/using_annotstorage_mbox +++ b/cassandane/tiny-tests/Quota/using_annotstorage_mbox @@ -1,54 +1,52 @@ #!perl use Cassandane::Tiny; -sub test_using_annotstorage_mbox -{ - my ($self) = @_; - - xlog $self, "test setting X-ANNOTATION-STORAGE quota resource after"; - xlog $self, "per-mailbox annotations are added"; - - $self->_set_quotaroot('user.cassandane'); - my $talk = $self->{store}->get_client(); - - xlog $self, "set ourselves a basic limit"; - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => 0); - - $talk->create("INBOX.sub") || die "Failed to create subfolder"; - - xlog $self, "store annotations"; - my %expecteds = (); - my $expected = 0; - foreach my $folder ("INBOX", "INBOX.sub") - { - $expecteds{$folder} = 0; - $self->{store}->set_folder($folder); - my $data = ''; - while ($expecteds{$folder} <= 60*1024) - { - my $moredata = $self->make_random_data(5); - $data .= $moredata; - $talk->setmetadata($self->{store}->{folder}, '/private/comment', { Quote => $data }); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $expecteds{$folder} += length($moredata); - $expected += length($moredata); - xlog $self, "EXPECTING $expected on $folder"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - } +sub test_using_annotstorage_mbox { + my ($self) = @_; + + xlog $self, "test setting X-ANNOTATION-STORAGE quota resource after"; + xlog $self, "per-mailbox annotations are added"; + + $self->_set_quotaroot('user.cassandane'); + my $talk = $self->{store}->get_client(); + + xlog $self, "set ourselves a basic limit"; + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => 0); + + $talk->create("INBOX.sub") || die "Failed to create subfolder"; + + xlog $self, "store annotations"; + my %expecteds = (); + my $expected = 0; + foreach my $folder ("INBOX", "INBOX.sub") { + $expecteds{$folder} = 0; + $self->{store}->set_folder($folder); + my $data = ''; + while ($expecteds{$folder} <= 60 * 1024) { + my $moredata = $self->make_random_data(5); + $data .= $moredata; + $talk->setmetadata($self->{store}->{folder}, + '/private/comment', { Quote => $data }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $expecteds{$folder} += length($moredata); + $expected += length($moredata); + xlog $self, "EXPECTING $expected on $folder"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } - - # delete subfolder - xlog $self, "Deleting a folder"; - $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; - $expected -= delete($expecteds{"INBOX.sub"}); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - # delete remaining annotations - $self->{store}->set_folder("INBOX"); - $talk->setmetadata($self->{store}->{folder}, '/private/comment', undef); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $expected -= delete($expecteds{"INBOX"}); - $self->assert_num_equals(0, $expected); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + } + + # delete subfolder + xlog $self, "Deleting a folder"; + $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; + $expected -= delete($expecteds{"INBOX.sub"}); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + # delete remaining annotations + $self->{store}->set_folder("INBOX"); + $talk->setmetadata($self->{store}->{folder}, '/private/comment', undef); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $expected -= delete($expecteds{"INBOX"}); + $self->assert_num_equals(0, $expected); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_annotstorage_mbox_late b/cassandane/tiny-tests/Quota/using_annotstorage_mbox_late index 5786370da6..4592669d19 100644 --- a/cassandane/tiny-tests/Quota/using_annotstorage_mbox_late +++ b/cassandane/tiny-tests/Quota/using_annotstorage_mbox_late @@ -1,52 +1,50 @@ #!perl use Cassandane::Tiny; -sub test_using_annotstorage_mbox_late -{ - my ($self) = @_; - - xlog $self, "test increasing usage of the X-ANNOTATION-STORAGE quota"; - xlog $self, "resource as per-mailbox annotations are added"; - - $self->_set_quotaroot('user.cassandane'); - my $talk = $self->{store}->get_client(); - - $self->_check_no_quota(); - - $talk->create("INBOX.sub") || die "Failed to create subfolder"; - - xlog $self, "store annotations"; - my %expecteds = (); - my $expected = 0; - foreach my $folder ("INBOX", "INBOX.sub") - { - $expecteds{$folder} = 0; - $self->{store}->set_folder($folder); - my $data = ''; - while ($expecteds{$folder} <= 60*1024) - { - my $moredata = $self->make_random_data(5); - $data .= $moredata; - $talk->setmetadata($self->{store}->{folder}, '/private/comment', { Quote => $data }); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $expecteds{$folder} += length($moredata); - $expected += length($moredata); - } +sub test_using_annotstorage_mbox_late { + my ($self) = @_; + + xlog $self, "test increasing usage of the X-ANNOTATION-STORAGE quota"; + xlog $self, "resource as per-mailbox annotations are added"; + + $self->_set_quotaroot('user.cassandane'); + my $talk = $self->{store}->get_client(); + + $self->_check_no_quota(); + + $talk->create("INBOX.sub") || die "Failed to create subfolder"; + + xlog $self, "store annotations"; + my %expecteds = (); + my $expected = 0; + foreach my $folder ("INBOX", "INBOX.sub") { + $expecteds{$folder} = 0; + $self->{store}->set_folder($folder); + my $data = ''; + while ($expecteds{$folder} <= 60 * 1024) { + my $moredata = $self->make_random_data(5); + $data .= $moredata; + $talk->setmetadata($self->{store}->{folder}, + '/private/comment', { Quote => $data }); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $expecteds{$folder} += length($moredata); + $expected += length($moredata); } - - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - # delete subfolder - $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; - $expected -= delete($expecteds{"INBOX.sub"}); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - # delete remaining annotations - $self->{store}->set_folder("INBOX"); - $talk->setmetadata($self->{store}->{folder}, '/private/comment', undef); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $expected -= delete($expecteds{"INBOX"}); - $self->assert_num_equals(0, $expected); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + } + + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + # delete subfolder + $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; + $expected -= delete($expecteds{"INBOX.sub"}); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + # delete remaining annotations + $self->{store}->set_folder("INBOX"); + $talk->setmetadata($self->{store}->{folder}, '/private/comment', undef); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $expected -= delete($expecteds{"INBOX"}); + $self->assert_num_equals(0, $expected); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_annotstorage_msg b/cassandane/tiny-tests/Quota/using_annotstorage_msg index 401ac27231..8c167d5178 100644 --- a/cassandane/tiny-tests/Quota/using_annotstorage_msg +++ b/cassandane/tiny-tests/Quota/using_annotstorage_msg @@ -1,74 +1,74 @@ #!perl use Cassandane::Tiny; -sub test_using_annotstorage_msg -{ - my ($self) = @_; +sub test_using_annotstorage_msg { + my ($self) = @_; - xlog $self, "test setting X-ANNOTATION-STORAGE quota resource after"; - xlog $self, "per-message annotations are added"; + xlog $self, "test setting X-ANNOTATION-STORAGE quota resource after"; + xlog $self, "per-message annotations are added"; - $self->_set_quotaroot('user.cassandane'); - my $talk = $self->{store}->get_client(); + $self->_set_quotaroot('user.cassandane'); + my $talk = $self->{store}->get_client(); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => 0); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => 0); - $talk->create("INBOX.sub1") || die "Failed to create subfolder"; - $talk->create("INBOX.sub2") || die "Failed to create subfolder"; + $talk->create("INBOX.sub1") || die "Failed to create subfolder"; + $talk->create("INBOX.sub2") || die "Failed to create subfolder"; - xlog $self, "make some messages to hang annotations on"; - my %expecteds = (); - my $expected = 0; - foreach my $folder ("INBOX", "INBOX.sub1", "INBOX.sub2") - { - $self->{store}->set_folder($folder); - $expecteds{$folder} = 0; - my $uid = 1; - for (1..5) - { - $self->make_message("Message $uid"); + xlog $self, "make some messages to hang annotations on"; + my %expecteds = (); + my $expected = 0; + foreach my $folder ("INBOX", "INBOX.sub1", "INBOX.sub2") { + $self->{store}->set_folder($folder); + $expecteds{$folder} = 0; + my $uid = 1; + for (1 .. 5) { + $self->make_message("Message $uid"); - my $data = $self->make_random_data(10); - $talk->store('' . $uid, 'annotation', ['/comment', ['value.priv', { Quote => $data }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $uid++; - $expecteds{$folder} += length($data); - $expected += length($data); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - } + my $data = $self->make_random_data(10); + $talk->store('' . $uid, 'annotation', + [ '/comment', [ 'value.priv', { Quote => $data } ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uid++; + $expecteds{$folder} += length($data); + $expected += length($data); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } + } - xlog $self, "delete subfolder sub1"; - $talk->delete("INBOX.sub1") || die "Failed to delete subfolder"; - $expected -= delete($expecteds{"INBOX.sub1"}); + xlog $self, "delete subfolder sub1"; + $talk->delete("INBOX.sub1") || die "Failed to delete subfolder"; + $expected -= delete($expecteds{"INBOX.sub1"}); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); - xlog $self, "delete messages in sub2"; - $talk->select("INBOX.sub2"); - $talk->store('1:*', '+flags', '(\\deleted)'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $talk->expunge(); + xlog $self, "delete messages in sub2"; + $talk->select("INBOX.sub2"); + $talk->store('1:*', '+flags', '(\\deleted)'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->expunge(); - $expected -= delete($expecteds{"INBOX.sub2"}); + $expected -= delete($expecteds{"INBOX.sub2"}); - xlog $self, "Unlike STORAGE, X-ANNOTATION-STORAGE quota is reduced immediately"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + xlog $self, + "Unlike STORAGE, X-ANNOTATION-STORAGE quota is reduced immediately"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); - $self->run_delayed_expunge(); - $talk = $self->{store}->get_client(); + $self->run_delayed_expunge(); + $talk = $self->{store}->get_client(); - xlog $self, "X-ANNOTATION-STORAGE quota should not have changed during delayed expunge"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + xlog $self, + "X-ANNOTATION-STORAGE quota should not have changed during delayed expunge"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); - xlog $self, "delete annotations on INBOX"; - $talk->select("INBOX"); - $talk->store('1:*', 'annotation', ['/comment', ['value.priv', undef]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $talk->close(); - $expected -= delete($expecteds{"INBOX"}); - $self->assert_num_equals(0, $expected); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + xlog $self, "delete annotations on INBOX"; + $talk->select("INBOX"); + $talk->store('1:*', 'annotation', [ '/comment', [ 'value.priv', undef ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->close(); + $expected -= delete($expecteds{"INBOX"}); + $self->assert_num_equals(0, $expected); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_dedel b/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_dedel index 05578344e4..6668e47864 100644 --- a/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_dedel +++ b/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_dedel @@ -2,101 +2,100 @@ use Cassandane::Tiny; sub test_using_annotstorage_msg_copy_dedel - :DelayedDelete -{ - my ($self) = @_; - - xlog $self, "testing X-ANNOTATION-STORAGE quota usage as messages are COPYd"; - xlog $self, "and original folder is deleted, delete_mode=delayed version"; - xlog $self, "(BZ3527)"; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $from_folder = 'INBOX.from'; - my $to_folder = 'INBOX.to'; - - xlog $self, "Check the delete mode is \"delayed\""; - my $delete_mode = $self->{instance}->{config}->get('delete_mode'); - $self->assert_str_equals('delayed', $delete_mode); - - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => 0); - my $talk = $self->{store}->get_client(); - - my $store = $self->{store}; - $store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Create subfolders to copy from and to"; - $talk = $store->get_client(); - $talk->create($from_folder) - or die "Cannot create mailbox $from_folder: $@"; - $talk->create($to_folder) - or die "Cannot create mailbox $to_folder: $@"; - - $store->set_folder($from_folder); - - xlog $self, "Append some messages and store annotations"; - my %exp; - my $expected = 0; - my $uid = 1; - for (1..20) - { - my $data = $self->make_random_data(10); - my $msg = $self->make_message("Message $uid"); - $msg->set_attribute('uid', $uid); - $msg->set_annotation($entry, $attrib, $data); - $exp{$uid} = $msg; - $talk->store('' . $uid, 'annotation', [$entry, [$attrib, { Quote => $data }]]); - $expected += length($data); - $uid++; - } - - xlog $self, "Check the annotations are there"; - $self->check_messages(\%exp); - xlog $self, "Check the quota usage is correct"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - xlog $self, "COPY the messages"; - $talk = $store->get_client(); - $talk->copy('1:*', $to_folder); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Messages are now in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Check the quota usage is now doubled"; - $self->_check_usages($self->res_annot_storage => int(2*$expected/1024)); - - xlog $self, "Messages are still in the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Delete the origin folder"; - $talk = $store->get_client(); - $talk->unselect(); - $talk->delete($from_folder) - or die "Cannot delete folder $from_folder: $@"; - - xlog $self, "Messages are still in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - # Note that, unlike with delayed expunge, with delayed delete the - # annotations are deleted immediately and so the negative delta to - # quota is applied immediately. Whether this is sensible is a - # different question. - - xlog $self, "Check the quota usage is back to single"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - $self->run_delayed_expunge(); - - xlog $self, "Check the quota usage is still back to single"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + : DelayedDelete { + my ($self) = @_; + + xlog $self, "testing X-ANNOTATION-STORAGE quota usage as messages are COPYd"; + xlog $self, "and original folder is deleted, delete_mode=delayed version"; + xlog $self, "(BZ3527)"; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $from_folder = 'INBOX.from'; + my $to_folder = 'INBOX.to'; + + xlog $self, "Check the delete mode is \"delayed\""; + my $delete_mode = $self->{instance}->{config}->get('delete_mode'); + $self->assert_str_equals('delayed', $delete_mode); + + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => 0); + my $talk = $self->{store}->get_client(); + + my $store = $self->{store}; + $store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Create subfolders to copy from and to"; + $talk = $store->get_client(); + $talk->create($from_folder) + or die "Cannot create mailbox $from_folder: $@"; + $talk->create($to_folder) + or die "Cannot create mailbox $to_folder: $@"; + + $store->set_folder($from_folder); + + xlog $self, "Append some messages and store annotations"; + my %exp; + my $expected = 0; + my $uid = 1; + for (1 .. 20) { + my $data = $self->make_random_data(10); + my $msg = $self->make_message("Message $uid"); + $msg->set_attribute('uid', $uid); + $msg->set_annotation($entry, $attrib, $data); + $exp{$uid} = $msg; + $talk->store('' . $uid, 'annotation', + [ $entry, [ $attrib, { Quote => $data } ] ]); + $expected += length($data); + $uid++; + } + + xlog $self, "Check the annotations are there"; + $self->check_messages(\%exp); + xlog $self, "Check the quota usage is correct"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + xlog $self, "COPY the messages"; + $talk = $store->get_client(); + $talk->copy('1:*', $to_folder); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Messages are now in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Check the quota usage is now doubled"; + $self->_check_usages($self->res_annot_storage => int(2 * $expected / 1024)); + + xlog $self, "Messages are still in the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Delete the origin folder"; + $talk = $store->get_client(); + $talk->unselect(); + $talk->delete($from_folder) + or die "Cannot delete folder $from_folder: $@"; + + xlog $self, "Messages are still in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + # Note that, unlike with delayed expunge, with delayed delete the + # annotations are deleted immediately and so the negative delta to + # quota is applied immediately. Whether this is sensible is a + # different question. + + xlog $self, "Check the quota usage is back to single"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + $self->run_delayed_expunge(); + + xlog $self, "Check the quota usage is still back to single"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_deimm b/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_deimm index 9e0a274f80..1c6671adb9 100644 --- a/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_deimm +++ b/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_deimm @@ -2,91 +2,90 @@ use Cassandane::Tiny; sub test_using_annotstorage_msg_copy_deimm - :ImmediateDelete -{ - my ($self) = @_; - - xlog $self, "testing X-ANNOTATION-STORAGE quota usage as messages are COPYd"; - xlog $self, "and original folder is deleted, delete_mode=immediate version"; - xlog $self, "(BZ3527)"; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $from_folder = 'INBOX.from'; - my $to_folder = 'INBOX.to'; - - xlog $self, "Check the delete mode is \"immediate\""; - my $delete_mode = $self->{instance}->{config}->get('delete_mode'); - $self->assert_str_equals('immediate', $delete_mode); - - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => 0); - my $talk = $self->{store}->get_client(); - - my $store = $self->{store}; - $store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Create subfolders to copy from and to"; - $talk = $store->get_client(); - $talk->create($from_folder) - or die "Cannot create mailbox $from_folder: $@"; - $talk->create($to_folder) - or die "Cannot create mailbox $to_folder: $@"; - - $store->set_folder($from_folder); - - xlog $self, "Append some messages and store annotations"; - my %exp; - my $expected = 0; - my $uid = 1; - for (1..20) - { - my $data = $self->make_random_data(10); - my $msg = $self->make_message("Message $uid"); - $msg->set_attribute('uid', $uid); - $msg->set_annotation($entry, $attrib, $data); - $exp{$uid} = $msg; - $talk->store('' . $uid, 'annotation', [$entry, [$attrib, { Quote => $data }]]); - $expected += length($data); - $uid++; - } - - xlog $self, "Check the annotations are there"; - $self->check_messages(\%exp); - xlog $self, "Check the quota usage is correct"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - xlog $self, "COPY the messages"; - $talk = $store->get_client(); - $talk->copy('1:*', $to_folder); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Messages are now in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Check the quota usage is now doubled"; - $self->_check_usages($self->res_annot_storage => int(2*$expected/1024)); - - xlog $self, "Messages are still in the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Delete the origin folder"; - $talk = $store->get_client(); - $talk->unselect(); - $talk->delete($from_folder) - or die "Cannot delete folder $from_folder: $@"; - - xlog $self, "Messages are still in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Check the quota usage is back to single"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + : ImmediateDelete { + my ($self) = @_; + + xlog $self, "testing X-ANNOTATION-STORAGE quota usage as messages are COPYd"; + xlog $self, "and original folder is deleted, delete_mode=immediate version"; + xlog $self, "(BZ3527)"; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $from_folder = 'INBOX.from'; + my $to_folder = 'INBOX.to'; + + xlog $self, "Check the delete mode is \"immediate\""; + my $delete_mode = $self->{instance}->{config}->get('delete_mode'); + $self->assert_str_equals('immediate', $delete_mode); + + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => 0); + my $talk = $self->{store}->get_client(); + + my $store = $self->{store}; + $store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Create subfolders to copy from and to"; + $talk = $store->get_client(); + $talk->create($from_folder) + or die "Cannot create mailbox $from_folder: $@"; + $talk->create($to_folder) + or die "Cannot create mailbox $to_folder: $@"; + + $store->set_folder($from_folder); + + xlog $self, "Append some messages and store annotations"; + my %exp; + my $expected = 0; + my $uid = 1; + for (1 .. 20) { + my $data = $self->make_random_data(10); + my $msg = $self->make_message("Message $uid"); + $msg->set_attribute('uid', $uid); + $msg->set_annotation($entry, $attrib, $data); + $exp{$uid} = $msg; + $talk->store('' . $uid, 'annotation', + [ $entry, [ $attrib, { Quote => $data } ] ]); + $expected += length($data); + $uid++; + } + + xlog $self, "Check the annotations are there"; + $self->check_messages(\%exp); + xlog $self, "Check the quota usage is correct"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + xlog $self, "COPY the messages"; + $talk = $store->get_client(); + $talk->copy('1:*', $to_folder); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Messages are now in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Check the quota usage is now doubled"; + $self->_check_usages($self->res_annot_storage => int(2 * $expected / 1024)); + + xlog $self, "Messages are still in the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Delete the origin folder"; + $talk = $store->get_client(); + $talk->unselect(); + $talk->delete($from_folder) + or die "Cannot delete folder $from_folder: $@"; + + xlog $self, "Messages are still in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Check the quota usage is back to single"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_exdel b/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_exdel index 570358c3b5..866b1d2c03 100644 --- a/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_exdel +++ b/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_exdel @@ -2,102 +2,101 @@ use Cassandane::Tiny; sub test_using_annotstorage_msg_copy_exdel - :DelayedExpunge -{ - my ($self) = @_; - - xlog $self, "testing X-ANNOTATION-STORAGE quota usage as messages are COPYd"; - xlog $self, "and original messages are deleted, expunge_mode=delayed version"; - xlog $self, "(BZ3527)"; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $from_folder = 'INBOX.from'; - my $to_folder = 'INBOX.to'; - - xlog $self, "Check the expunge mode is \"delayed\""; - my $expunge_mode = $self->{instance}->{config}->get('expunge_mode'); - $self->assert_str_equals('delayed', $expunge_mode); - - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => 0); - my $talk = $self->{store}->get_client(); - - my $store = $self->{store}; - $store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Create subfolders to copy from and to"; - $talk = $store->get_client(); - $talk->create($from_folder) - or die "Cannot create mailbox $from_folder: $@"; - $talk->create($to_folder) - or die "Cannot create mailbox $to_folder: $@"; - - $store->set_folder($from_folder); - - xlog $self, "Append some messages and store annotations"; - my %exp; - my $expected = 0; - my $uid = 1; - for (1..20) - { - my $data = $self->make_random_data(10); - my $msg = $self->make_message("Message $uid"); - $msg->set_attribute('uid', $uid); - $msg->set_annotation($entry, $attrib, $data); - $exp{$uid} = $msg; - $talk->store('' . $uid, 'annotation', [$entry, [$attrib, { Quote => $data }]]); - $expected += length($data); - $uid++; - } - - xlog $self, "Check the annotations are there"; - $self->check_messages(\%exp); - xlog $self, "Check the quota usage is correct"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - xlog $self, "COPY the messages"; - $talk = $store->get_client(); - $talk->copy('1:*', $to_folder); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Messages are now in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Check the quota usage is now doubled"; - $self->_check_usages($self->res_annot_storage => int(2*$expected/1024)); - - xlog $self, "Messages are still in the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Delete the messages from the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $talk = $store->get_client(); - $talk->store('1:*', '+flags', '(\\Deleted)'); - $talk->expunge(); - - xlog $self, "Messages are gone from the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $self->check_messages({}); - - xlog $self, "Messages are still in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Check the quota usage has reduced again"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - $self->run_delayed_expunge(); - - xlog $self, "Check the quota usage is still the same"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + : DelayedExpunge { + my ($self) = @_; + + xlog $self, "testing X-ANNOTATION-STORAGE quota usage as messages are COPYd"; + xlog $self, "and original messages are deleted, expunge_mode=delayed version"; + xlog $self, "(BZ3527)"; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $from_folder = 'INBOX.from'; + my $to_folder = 'INBOX.to'; + + xlog $self, "Check the expunge mode is \"delayed\""; + my $expunge_mode = $self->{instance}->{config}->get('expunge_mode'); + $self->assert_str_equals('delayed', $expunge_mode); + + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => 0); + my $talk = $self->{store}->get_client(); + + my $store = $self->{store}; + $store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Create subfolders to copy from and to"; + $talk = $store->get_client(); + $talk->create($from_folder) + or die "Cannot create mailbox $from_folder: $@"; + $talk->create($to_folder) + or die "Cannot create mailbox $to_folder: $@"; + + $store->set_folder($from_folder); + + xlog $self, "Append some messages and store annotations"; + my %exp; + my $expected = 0; + my $uid = 1; + for (1 .. 20) { + my $data = $self->make_random_data(10); + my $msg = $self->make_message("Message $uid"); + $msg->set_attribute('uid', $uid); + $msg->set_annotation($entry, $attrib, $data); + $exp{$uid} = $msg; + $talk->store('' . $uid, 'annotation', + [ $entry, [ $attrib, { Quote => $data } ] ]); + $expected += length($data); + $uid++; + } + + xlog $self, "Check the annotations are there"; + $self->check_messages(\%exp); + xlog $self, "Check the quota usage is correct"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + xlog $self, "COPY the messages"; + $talk = $store->get_client(); + $talk->copy('1:*', $to_folder); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Messages are now in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Check the quota usage is now doubled"; + $self->_check_usages($self->res_annot_storage => int(2 * $expected / 1024)); + + xlog $self, "Messages are still in the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Delete the messages from the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $talk = $store->get_client(); + $talk->store('1:*', '+flags', '(\\Deleted)'); + $talk->expunge(); + + xlog $self, "Messages are gone from the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $self->check_messages({}); + + xlog $self, "Messages are still in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Check the quota usage has reduced again"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + $self->run_delayed_expunge(); + + xlog $self, "Check the quota usage is still the same"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_eximm b/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_eximm index c28ef1ffdc..ec2560851b 100644 --- a/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_eximm +++ b/cassandane/tiny-tests/Quota/using_annotstorage_msg_copy_eximm @@ -2,97 +2,97 @@ use Cassandane::Tiny; sub test_using_annotstorage_msg_copy_eximm - :ImmediateExpunge -{ - my ($self) = @_; - - xlog $self, "testing X-ANNOTATION-STORAGE quota usage as messages are COPYd"; - xlog $self, "and original messages are deleted, expunge_mode=immediate version"; - xlog $self, "(BZ3527)"; - - my $entry = '/comment'; - my $attrib = 'value.priv'; - my $from_folder = 'INBOX.from'; - my $to_folder = 'INBOX.to'; - - xlog $self, "Check the expunge mode is \"immediate\""; - my $expunge_mode = $self->{instance}->{config}->get('expunge_mode'); - $self->assert_str_equals('immediate', $expunge_mode); - - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => 0); - my $talk = $self->{store}->get_client(); - - my $store = $self->{store}; - $store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); - - xlog $self, "Create subfolders to copy from and to"; - $talk = $store->get_client(); - $talk->create($from_folder) - or die "Cannot create mailbox $from_folder: $@"; - $talk->create($to_folder) - or die "Cannot create mailbox $to_folder: $@"; - - $store->set_folder($from_folder); - - xlog $self, "Append some messages and store annotations"; - my %exp; - my $expected = 0; - my $uid = 1; - for (1..20) - { - my $data = $self->make_random_data(10); - my $msg = $self->make_message("Message $uid"); - $msg->set_attribute('uid', $uid); - $msg->set_annotation($entry, $attrib, $data); - $exp{$uid} = $msg; - $talk->store('' . $uid, 'annotation', [$entry, [$attrib, { Quote => $data }]]); - $expected += length($data); - $uid++; - } - - xlog $self, "Check the annotations are there"; - $self->check_messages(\%exp); - xlog $self, "Check the quota usage is correct"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); - - xlog $self, "COPY the messages"; - $talk = $store->get_client(); - $talk->copy('1:*', $to_folder); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - - xlog $self, "Messages are now in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Check the quota usage is now doubled"; - $self->_check_usages($self->res_annot_storage => int(2*$expected/1024)); - - xlog $self, "Messages are still in the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Delete the messages from the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $talk = $store->get_client(); - $talk->store('1:*', '+flags', '(\\Deleted)'); - $talk->expunge(); - - xlog $self, "Messages are gone from the origin folder"; - $store->set_folder($from_folder); - $store->_select(); - $self->check_messages({}); - - xlog $self, "Messages are still in the destination folder"; - $store->set_folder($to_folder); - $store->_select(); - $self->check_messages(\%exp); - - xlog $self, "Check the quota usage is back to single"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + : ImmediateExpunge { + my ($self) = @_; + + xlog $self, "testing X-ANNOTATION-STORAGE quota usage as messages are COPYd"; + xlog $self, + "and original messages are deleted, expunge_mode=immediate version"; + xlog $self, "(BZ3527)"; + + my $entry = '/comment'; + my $attrib = 'value.priv'; + my $from_folder = 'INBOX.from'; + my $to_folder = 'INBOX.to'; + + xlog $self, "Check the expunge mode is \"immediate\""; + my $expunge_mode = $self->{instance}->{config}->get('expunge_mode'); + $self->assert_str_equals('immediate', $expunge_mode); + + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => 0); + my $talk = $self->{store}->get_client(); + + my $store = $self->{store}; + $store->set_fetch_attributes('uid', "annotation ($entry $attrib)"); + + xlog $self, "Create subfolders to copy from and to"; + $talk = $store->get_client(); + $talk->create($from_folder) + or die "Cannot create mailbox $from_folder: $@"; + $talk->create($to_folder) + or die "Cannot create mailbox $to_folder: $@"; + + $store->set_folder($from_folder); + + xlog $self, "Append some messages and store annotations"; + my %exp; + my $expected = 0; + my $uid = 1; + for (1 .. 20) { + my $data = $self->make_random_data(10); + my $msg = $self->make_message("Message $uid"); + $msg->set_attribute('uid', $uid); + $msg->set_annotation($entry, $attrib, $data); + $exp{$uid} = $msg; + $talk->store('' . $uid, 'annotation', + [ $entry, [ $attrib, { Quote => $data } ] ]); + $expected += length($data); + $uid++; + } + + xlog $self, "Check the annotations are there"; + $self->check_messages(\%exp); + xlog $self, "Check the quota usage is correct"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); + + xlog $self, "COPY the messages"; + $talk = $store->get_client(); + $talk->copy('1:*', $to_folder); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + + xlog $self, "Messages are now in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Check the quota usage is now doubled"; + $self->_check_usages($self->res_annot_storage => int(2 * $expected / 1024)); + + xlog $self, "Messages are still in the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Delete the messages from the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $talk = $store->get_client(); + $talk->store('1:*', '+flags', '(\\Deleted)'); + $talk->expunge(); + + xlog $self, "Messages are gone from the origin folder"; + $store->set_folder($from_folder); + $store->_select(); + $self->check_messages({}); + + xlog $self, "Messages are still in the destination folder"; + $store->set_folder($to_folder); + $store->_select(); + $self->check_messages(\%exp); + + xlog $self, "Check the quota usage is back to single"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_annotstorage_msg_late b/cassandane/tiny-tests/Quota/using_annotstorage_msg_late index e0e56efcf3..b200600bec 100644 --- a/cassandane/tiny-tests/Quota/using_annotstorage_msg_late +++ b/cassandane/tiny-tests/Quota/using_annotstorage_msg_late @@ -1,71 +1,70 @@ #!perl use Cassandane::Tiny; -sub test_using_annotstorage_msg_late -{ - my ($self) = @_; +sub test_using_annotstorage_msg_late { + my ($self) = @_; - xlog $self, "test increasing usage of the X-ANNOTATION-STORAGE quota"; - xlog $self, "resource as per-message annotations are added"; + xlog $self, "test increasing usage of the X-ANNOTATION-STORAGE quota"; + xlog $self, "resource as per-message annotations are added"; - $self->_set_quotaroot('user.cassandane'); - my $talk = $self->{store}->get_client(); + $self->_set_quotaroot('user.cassandane'); + my $talk = $self->{store}->get_client(); - $self->_check_no_quota(); + $self->_check_no_quota(); - $talk->create("INBOX.sub1") || die "Failed to create subfolder"; - $talk->create("INBOX.sub2") || die "Failed to create subfolder"; + $talk->create("INBOX.sub1") || die "Failed to create subfolder"; + $talk->create("INBOX.sub2") || die "Failed to create subfolder"; - xlog $self, "make some messages to hang annotations on"; - my %expecteds = (); - my $expected = 0; - foreach my $folder ("INBOX", "INBOX.sub1", "INBOX.sub2") - { - $self->{store}->set_folder($folder); - $expecteds{$folder} = 0; - my $uid = 1; - for (1..5) - { - $self->make_message("Message $uid"); + xlog $self, "make some messages to hang annotations on"; + my %expecteds = (); + my $expected = 0; + foreach my $folder ("INBOX", "INBOX.sub1", "INBOX.sub2") { + $self->{store}->set_folder($folder); + $expecteds{$folder} = 0; + my $uid = 1; + for (1 .. 5) { + $self->make_message("Message $uid"); - my $data = $self->make_random_data(10); - $talk->store('' . $uid, 'annotation', ['/comment', ['value.priv', { Quote => $data }]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $uid++; - $expecteds{$folder} += length($data); - $expected += length($data); - } + my $data = $self->make_random_data(10); + $talk->store('' . $uid, 'annotation', + [ '/comment', [ 'value.priv', { Quote => $data } ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $uid++; + $expecteds{$folder} += length($data); + $expected += length($data); } + } - $self->_set_limits($self->res_annot_storage => 100000); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + $self->_set_limits($self->res_annot_storage => 100000); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); - xlog $self, "delete subfolder sub1"; - $talk->delete("INBOX.sub1") || die "Failed to delete subfolder"; - $expected -= delete($expecteds{"INBOX.sub1"}); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + xlog $self, "delete subfolder sub1"; + $talk->delete("INBOX.sub1") || die "Failed to delete subfolder"; + $expected -= delete($expecteds{"INBOX.sub1"}); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); - xlog $self, "delete messages in sub2"; - $talk->select("INBOX.sub2"); - $talk->store('1:*', '+flags', '(\\deleted)'); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $talk->expunge(); + xlog $self, "delete messages in sub2"; + $talk->select("INBOX.sub2"); + $talk->store('1:*', '+flags', '(\\deleted)'); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $talk->expunge(); - xlog $self, "X-ANNOTATION-STORAGE quota goes down immediately"; - $expected -= delete($expecteds{"INBOX.sub2"}); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + xlog $self, "X-ANNOTATION-STORAGE quota goes down immediately"; + $expected -= delete($expecteds{"INBOX.sub2"}); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); - $self->run_delayed_expunge(); - $talk = $self->{store}->get_client(); + $self->run_delayed_expunge(); + $talk = $self->{store}->get_client(); - xlog $self, "X-ANNOTATION-STORAGE quota should have been unchanged by expunge"; - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + xlog $self, + "X-ANNOTATION-STORAGE quota should have been unchanged by expunge"; + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); - xlog $self, "delete annotations on INBOX"; - $talk->select("INBOX"); - $talk->store('1:*', 'annotation', ['/comment', ['value.priv', undef]]); - $self->assert_str_equals('ok', $talk->get_last_completion_response()); - $expected -= delete($expecteds{"INBOX"}); - $self->assert_num_equals(0, $expected); - $self->_check_usages($self->res_annot_storage => int($expected/1024)); + xlog $self, "delete annotations on INBOX"; + $talk->select("INBOX"); + $talk->store('1:*', 'annotation', [ '/comment', [ 'value.priv', undef ] ]); + $self->assert_str_equals('ok', $talk->get_last_completion_response()); + $expected -= delete($expecteds{"INBOX"}); + $self->assert_num_equals(0, $expected); + $self->_check_usages($self->res_annot_storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_message b/cassandane/tiny-tests/Quota/using_message index 070af3d169..2efcd6fe67 100644 --- a/cassandane/tiny-tests/Quota/using_message +++ b/cassandane/tiny-tests/Quota/using_message @@ -1,48 +1,46 @@ #!perl use Cassandane::Tiny; -sub test_using_message -{ - my ($self) = @_; - - xlog $self, "test increasing usage of the MESSAGE quota resource as messages are added"; - - $self->_set_quotaroot('user.cassandane'); - my $talk = $self->{store}->get_client(); - - xlog $self, "set ourselves a basic limit"; - $self->_set_limits(message => 50000); - $self->_check_usages(message => 0); - - $talk->create("INBOX.sub") || die "Failed to create subfolder"; - - # append some messages - my %expecteds = (); - my $expected = 0; - foreach my $folder ("INBOX", "INBOX.sub") - { - $expecteds{$folder} = 0; - $self->{store}->set_folder($folder); - - for (1..10) - { - my $msg = $self->make_message("Message $_"); - $expecteds{$folder}++; - $expected++; - $self->_check_usages(message => $expected); - } - } +sub test_using_message { + my ($self) = @_; + + xlog $self, + "test increasing usage of the MESSAGE quota resource as messages are added"; + + $self->_set_quotaroot('user.cassandane'); + my $talk = $self->{store}->get_client(); + + xlog $self, "set ourselves a basic limit"; + $self->_set_limits(message => 50000); + $self->_check_usages(message => 0); - # delete subfolder - $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; - $expected -= $expecteds{"INBOX.sub"}; - $self->_check_usages(message => $expected); - - # delete messages - $talk->select("INBOX"); - $talk->store('1:*', '+flags', '(\\deleted)'); - $talk->close(); - $expected -= delete($expecteds{"INBOX"}); - $self->assert_num_equals(0, $expected); - $self->_check_usages(message => $expected); + $talk->create("INBOX.sub") || die "Failed to create subfolder"; + + # append some messages + my %expecteds = (); + my $expected = 0; + foreach my $folder ("INBOX", "INBOX.sub") { + $expecteds{$folder} = 0; + $self->{store}->set_folder($folder); + + for (1 .. 10) { + my $msg = $self->make_message("Message $_"); + $expecteds{$folder}++; + $expected++; + $self->_check_usages(message => $expected); + } + } + + # delete subfolder + $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; + $expected -= $expecteds{"INBOX.sub"}; + $self->_check_usages(message => $expected); + + # delete messages + $talk->select("INBOX"); + $talk->store('1:*', '+flags', '(\\deleted)'); + $talk->close(); + $expected -= delete($expecteds{"INBOX"}); + $self->assert_num_equals(0, $expected); + $self->_check_usages(message => $expected); } diff --git a/cassandane/tiny-tests/Quota/using_message_late b/cassandane/tiny-tests/Quota/using_message_late index 7611140b7f..f8f1a10dbe 100644 --- a/cassandane/tiny-tests/Quota/using_message_late +++ b/cassandane/tiny-tests/Quota/using_message_late @@ -1,47 +1,44 @@ #!perl use Cassandane::Tiny; -sub test_using_message_late -{ - my ($self) = @_; - - xlog $self, "test setting MESSAGE quota resource after messages are added"; - - $self->_set_quotaroot('user.cassandane'); - my $talk = $self->{store}->get_client(); - $self->_check_no_quota(); - - $talk->create("INBOX.sub") || die "Failed to create subfolder"; - - # append some messages - my %expecteds = (); - my $expected = 0; - foreach my $folder ("INBOX", "INBOX.sub") - { - $expecteds{$folder} = 0; - $self->{store}->set_folder($folder); - - for (1..10) - { - my $msg = $self->make_message("Message $_"); - $expecteds{$folder}++; - $expected++; - } - } +sub test_using_message_late { + my ($self) = @_; + + xlog $self, "test setting MESSAGE quota resource after messages are added"; + + $self->_set_quotaroot('user.cassandane'); + my $talk = $self->{store}->get_client(); + $self->_check_no_quota(); - $self->_set_limits(message => 50000); - $self->_check_usages(message => $expected); - - # delete subfolder - $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; - $expected -= $expecteds{"INBOX.sub"}; - $self->_check_usages(message => $expected); - - # delete messages - $talk->select("INBOX"); - $talk->store('1:*', '+flags', '(\\deleted)'); - $talk->close(); - $expected -= delete($expecteds{"INBOX"}); - $self->assert_num_equals(0, $expected); - $self->_check_usages(message => $expected); + $talk->create("INBOX.sub") || die "Failed to create subfolder"; + + # append some messages + my %expecteds = (); + my $expected = 0; + foreach my $folder ("INBOX", "INBOX.sub") { + $expecteds{$folder} = 0; + $self->{store}->set_folder($folder); + + for (1 .. 10) { + my $msg = $self->make_message("Message $_"); + $expecteds{$folder}++; + $expected++; + } + } + + $self->_set_limits(message => 50000); + $self->_check_usages(message => $expected); + + # delete subfolder + $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; + $expected -= $expecteds{"INBOX.sub"}; + $self->_check_usages(message => $expected); + + # delete messages + $talk->select("INBOX"); + $talk->store('1:*', '+flags', '(\\deleted)'); + $talk->close(); + $expected -= delete($expecteds{"INBOX"}); + $self->assert_num_equals(0, $expected); + $self->_check_usages(message => $expected); } diff --git a/cassandane/tiny-tests/Quota/using_storage b/cassandane/tiny-tests/Quota/using_storage index e968995a60..7c6c988e32 100644 --- a/cassandane/tiny-tests/Quota/using_storage +++ b/cassandane/tiny-tests/Quota/using_storage @@ -1,50 +1,48 @@ #!perl use Cassandane::Tiny; -sub test_using_storage -{ - my ($self) = @_; +sub test_using_storage { + my ($self) = @_; - xlog $self, "test increasing usage of the STORAGE quota resource as messages are added"; - $self->_set_quotaroot('user.cassandane'); - xlog $self, "set ourselves a basic limit"; - $self->_set_limits(storage => 100000); - $self->_check_usages(storage => 0); - my $talk = $self->{store}->get_client(); + xlog $self, + "test increasing usage of the STORAGE quota resource as messages are added"; + $self->_set_quotaroot('user.cassandane'); + xlog $self, "set ourselves a basic limit"; + $self->_set_limits(storage => 100000); + $self->_check_usages(storage => 0); + my $talk = $self->{store}->get_client(); - $talk->create("INBOX.sub") || die "Failed to create subfolder"; + $talk->create("INBOX.sub") || die "Failed to create subfolder"; - # append some messages - my %expecteds = (); - my $expected = 0; - foreach my $folder ("INBOX", "INBOX.sub") - { - $expecteds{$folder} = 0; - $self->{store}->set_folder($folder); + # append some messages + my %expecteds = (); + my $expected = 0; + foreach my $folder ("INBOX", "INBOX.sub") { + $expecteds{$folder} = 0; + $self->{store}->set_folder($folder); - for (1..10) - { - my $msg = $self->make_message("Message $_", - extra_lines => 10 + rand(5000)); - my $len = length($msg->as_string()); - $expecteds{$folder} += $len; - $expected += $len; - xlog $self, "added $len bytes of message"; - $self->_check_usages(storage => int($expected/1024)); - } + for (1 .. 10) { + my $msg + = $self->make_message("Message $_", extra_lines => 10 + rand(5000)); + my $len = length($msg->as_string()); + $expecteds{$folder} += $len; + $expected += $len; + xlog $self, "added $len bytes of message"; + $self->_check_usages(storage => int($expected / 1024)); } + } - # delete subfolder - $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; - $expected -= delete($expecteds{"INBOX.sub"}); - $self->_check_usages(storage => int($expected/1024)); - $self->_check_smmap('cassandane', 'OK'); + # delete subfolder + $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; + $expected -= delete($expecteds{"INBOX.sub"}); + $self->_check_usages(storage => int($expected / 1024)); + $self->_check_smmap('cassandane', 'OK'); - # delete messages - $talk->select("INBOX"); - $talk->store('1:*', '+flags', '(\\deleted)'); - $talk->close(); - $expected -= delete($expecteds{"INBOX"}); - $self->assert_num_equals(0, $expected); - $self->_check_usages(storage => int($expected/1024)); + # delete messages + $talk->select("INBOX"); + $talk->store('1:*', '+flags', '(\\deleted)'); + $talk->close(); + $expected -= delete($expecteds{"INBOX"}); + $self->assert_num_equals(0, $expected); + $self->_check_usages(storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Quota/using_storage_late b/cassandane/tiny-tests/Quota/using_storage_late index 8b5cccd234..fff563fb65 100644 --- a/cassandane/tiny-tests/Quota/using_storage_late +++ b/cassandane/tiny-tests/Quota/using_storage_late @@ -1,51 +1,48 @@ #!perl use Cassandane::Tiny; -sub test_using_storage_late -{ - my ($self) = @_; - - xlog $self, "test setting STORAGE quota resource after messages are added"; - - $self->_set_quotaroot('user.cassandane'); - $self->_check_no_quota(); - my $talk = $self->{store}->get_client(); - - $talk->create("INBOX.sub") || die "Failed to create subfolder"; - - # append some messages - my %expecteds = (); - my $expected = 0; - foreach my $folder ("INBOX", "INBOX.sub") - { - $expecteds{$folder} = 0; - $self->{store}->set_folder($folder); - - for (1..10) - { - my $msg = $self->make_message("Message $_", - extra_lines => 10 + rand(5000)); - my $len = length($msg->as_string()); - $expecteds{$folder} += $len; - $expected += $len; - xlog $self, "added $len bytes of message"; - } +sub test_using_storage_late { + my ($self) = @_; + + xlog $self, "test setting STORAGE quota resource after messages are added"; + + $self->_set_quotaroot('user.cassandane'); + $self->_check_no_quota(); + my $talk = $self->{store}->get_client(); + + $talk->create("INBOX.sub") || die "Failed to create subfolder"; + + # append some messages + my %expecteds = (); + my $expected = 0; + foreach my $folder ("INBOX", "INBOX.sub") { + $expecteds{$folder} = 0; + $self->{store}->set_folder($folder); + + for (1 .. 10) { + my $msg + = $self->make_message("Message $_", extra_lines => 10 + rand(5000)); + my $len = length($msg->as_string()); + $expecteds{$folder} += $len; + $expected += $len; + xlog $self, "added $len bytes of message"; } - - $self->_set_limits(storage => 100000); - $self->_check_usages(storage => int($expected/1024)); - $self->_check_smmap('cassandane', 'OK'); - - # delete subfolder - $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; - $expected -= delete($expecteds{"INBOX.sub"}); - $self->_check_usages(storage => int($expected/1024)); - - # delete messages - $talk->select("INBOX"); - $talk->store('1:*', '+flags', '(\\deleted)'); - $talk->close(); - $expected -= delete($expecteds{"INBOX"}); - $self->assert_num_equals(0, $expected); - $self->_check_usages(storage => int($expected/1024)); + } + + $self->_set_limits(storage => 100000); + $self->_check_usages(storage => int($expected / 1024)); + $self->_check_smmap('cassandane', 'OK'); + + # delete subfolder + $talk->delete("INBOX.sub") || die "Failed to delete subfolder"; + $expected -= delete($expecteds{"INBOX.sub"}); + $self->_check_usages(storage => int($expected / 1024)); + + # delete messages + $talk->select("INBOX"); + $talk->store('1:*', '+flags', '(\\deleted)'); + $talk->close(); + $expected -= delete($expecteds{"INBOX"}); + $self->assert_num_equals(0, $expected); + $self->_check_usages(storage => int($expected / 1024)); } diff --git a/cassandane/tiny-tests/Replication/alternate_globalannots b/cassandane/tiny-tests/Replication/alternate_globalannots index 7ee8206d2c..b511fa8568 100644 --- a/cassandane/tiny-tests/Replication/alternate_globalannots +++ b/cassandane/tiny-tests/Replication/alternate_globalannots @@ -3,22 +3,21 @@ use Cassandane::Tiny; # trying to reproduce error reported in https://git.cyrus.foundation/T228 sub test_alternate_globalannots - :NoStartInstances :needs_component_replication -{ - my ($self) = @_; + : NoStartInstances : needs_component_replication { + my ($self) = @_; - # first, set a different annotation_db_path on the master server - my $annotation_db_path = $self->{instance}->get_basedir() - . "/conf/non-default-annotations.db"; - $self->{instance}->{config}->set('annotation_db_path' => $annotation_db_path); + # first, set a different annotation_db_path on the master server + my $annotation_db_path + = $self->{instance}->get_basedir() . "/conf/non-default-annotations.db"; + $self->{instance}->{config}->set('annotation_db_path' => $annotation_db_path); - # now we can start the instances - $self->_start_instances(); + # now we can start the instances + $self->_start_instances(); - # A replication will automatically occur when the instances are started, - # in order to make sure the cassandane user exists on both hosts. - # So if we get here without crashing, replication works. - xlog $self, "initial replication was successful"; + # A replication will automatically occur when the instances are started, + # in order to make sure the cassandane user exists on both hosts. + # So if we get here without crashing, replication works. + xlog $self, "initial replication was successful"; - $self->assert(1); + $self->assert(1); } diff --git a/cassandane/tiny-tests/Replication/append b/cassandane/tiny-tests/Replication/append index 3b561b10cd..e9ff9f285e 100644 --- a/cassandane/tiny-tests/Replication/append +++ b/cassandane/tiny-tests/Replication/append @@ -5,30 +5,31 @@ use Cassandane::Tiny; # Test replication of messages APPENDed to the master # sub test_append - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - xlog $self, "generating messages A..D"; - my %exp; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{B} = $self->make_message("Message B", store => $master_store); - $exp{C} = $self->make_message("Message C", store => $master_store); - $exp{D} = $self->make_message("Message D", store => $master_store); + xlog $self, "generating messages A..D"; + my %exp; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{B} = $self->make_message("Message B", store => $master_store); + $exp{C} = $self->make_message("Message C", store => $master_store); + $exp{D} = $self->make_message("Message D", store => $master_store); - xlog $self, "Before replication, the master should have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "Before replication, the replica should have no messages"; - $self->check_messages({}, store => $replica_store); + xlog $self, "Before replication, the master should have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "Before replication, the replica should have no messages"; + $self->check_messages({}, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, the master should still have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should now have all four messages"; - $self->check_messages(\%exp, store => $replica_store); + xlog $self, + "After replication, the master should still have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, + "After replication, the replica should now have all four messages"; + $self->check_messages(\%exp, store => $replica_store); } diff --git a/cassandane/tiny-tests/Replication/appendmulti_diskfull b/cassandane/tiny-tests/Replication/appendmulti_diskfull index 4d20d3add6..ec68c9c2cb 100644 --- a/cassandane/tiny-tests/Replication/appendmulti_diskfull +++ b/cassandane/tiny-tests/Replication/appendmulti_diskfull @@ -5,12 +5,11 @@ use Cassandane::Tiny; # Test handling of replication when append fails due to disk error # sub test_appendmulti_diskfull - :CSyncReplication :NoStartInstances :min_version_3_5 - :needs_component_replication -{ - my ($self) = @_; + : CSyncReplication : NoStartInstances : min_version_3_5 + : needs_component_replication { + my ($self) = @_; - my $canary = << 'EOF'; + my $canary = << 'EOF'; From: Fred J. Bloggs To: Sarah Jane Smith Subject: this is just to say @@ -31,58 +30,64 @@ it was delicious so tweet and so coaled EOF - $canary =~ s/\n/\r\n/g; - my $canaryguid = "f2eaa91974c50ec3cfb530014362e92efb06a9ba"; + $canary =~ s/\n/\r\n/g; + my $canaryguid = "f2eaa91974c50ec3cfb530014362e92efb06a9ba"; - $self->{replica}->{config}->set('debug_writefail_guid' => $canaryguid); - $self->_start_instances(); + $self->{replica}->{config}->set('debug_writefail_guid' => $canaryguid); + $self->_start_instances(); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my %exp; - $exp{1} = $self->make_message("msg 1", uid => 1, store => $master_store); - $exp{2} = $self->make_message("msg 2", uid => 2, store => $master_store); - $exp{3} = Cassandane::Message->new(raw => $canary, - attrs => { UID => 3 }), + my %exp; + $exp{1} = $self->make_message("msg 1", uid => 1, store => $master_store); + $exp{2} = $self->make_message("msg 2", uid => 2, store => $master_store); + $exp{3} = Cassandane::Message->new( + raw => $canary, + attrs => { UID => 3 } + ), $self->_save_message($exp{3}, $master_store); - $exp{4} = $self->make_message("msg 4", uid => 4, store => $master_store); + $exp{4} = $self->make_message("msg 4", uid => 4, store => $master_store); - xlog $self, "messages should be on master only"; - $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); - $self->check_messages({}, keyed_on => 'uid', store => $replica_store); + xlog $self, "messages should be on master only"; + $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); + $self->check_messages({}, keyed_on => 'uid', store => $replica_store); - xlog $self, "running replication..."; - eval { - $self->run_replication(); - }; - my $e = $@; + xlog $self, "running replication..."; + eval { $self->run_replication(); }; + my $e = $@; - # sync_client should have exited with an error - $self->assert($e); - $self->assert_matches(qr/child\sprocess\s + # sync_client should have exited with an error + $self->assert($e); + $self->assert_matches( + qr/child\sprocess\s \(binary\ssync_client\spid\s\d+\)\s exited\swith\scode/x, - $e->to_string()); + $e->to_string() + ); - # sync_client should have logged the BAD response - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: received bad response/); + # sync_client should have logged the BAD response + $self->assert_syslog_matches($self->{instance}, + qr/IOERROR: received bad response/); - if ($self->{instance}->{have_syslog_replacement}) { - # sync server should have logged the write error - my @lines = $self->{replica}->getsyslog(); - $self->assert_matches(qr{IOERROR:\sfailed\sto\supload\sfile + if ($self->{instance}->{have_syslog_replacement}) { + # sync server should have logged the write error + my @lines = $self->{replica}->getsyslog(); + $self->assert_matches( + qr{IOERROR:\sfailed\sto\supload\sfile (?:\s\(simulated\))?:\sguid=<$canaryguid> }x, - "@lines"); + "@lines" + ); - # contents of message 4 should not appear on the wire (or logs) as - # junk commands! we need sync_server specifically for this (and not - # a replication-aware imapd), because only sync_server logs bad - # commands. - $self->assert_does_not_match(qr/IOERROR:\sreceived\sbad\scommand:\s + # contents of message 4 should not appear on the wire (or logs) as + # junk commands! we need sync_server specifically for this (and not + # a replication-aware imapd), because only sync_server logs bad + # commands. + $self->assert_does_not_match( + qr/IOERROR:\sreceived\sbad\scommand:\s command=/x, - "@lines"); - } + "@lines" + ); + } } diff --git a/cassandane/tiny-tests/Replication/appendone_diskfull b/cassandane/tiny-tests/Replication/appendone_diskfull index 5deb2374d6..750e864ed0 100644 --- a/cassandane/tiny-tests/Replication/appendone_diskfull +++ b/cassandane/tiny-tests/Replication/appendone_diskfull @@ -5,11 +5,10 @@ use Cassandane::Tiny; # Test handling of replication when append fails due to disk error # sub test_appendone_diskfull - :NoStartInstances :min_version_3_5 :needs_component_replication -{ - my ($self) = @_; + : NoStartInstances : min_version_3_5 : needs_component_replication { + my ($self) = @_; - my $canary = << 'EOF'; + my $canary = << 'EOF'; From: Fred J. Bloggs To: Sarah Jane Smith Subject: this is just to say @@ -30,44 +29,48 @@ it was delicious so tweet and so coaled EOF - $canary =~ s/\n/\r\n/g; - my $canaryguid = "f2eaa91974c50ec3cfb530014362e92efb06a9ba"; + $canary =~ s/\n/\r\n/g; + my $canaryguid = "f2eaa91974c50ec3cfb530014362e92efb06a9ba"; - $self->{replica}->{config}->set('debug_writefail_guid' => $canaryguid); - $self->_start_instances(); + $self->{replica}->{config}->set('debug_writefail_guid' => $canaryguid); + $self->_start_instances(); - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my %exp; - $exp{1} = Cassandane::Message->new(raw => $canary, - attrs => { UID => 1 }), + my %exp; + $exp{1} = Cassandane::Message->new( + raw => $canary, + attrs => { UID => 1 } + ), $self->_save_message($exp{1}, $master_store); - xlog $self, "message should be on master only"; - $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); - $self->check_messages({}, keyed_on => 'uid', store => $replica_store); + xlog $self, "message should be on master only"; + $self->check_messages(\%exp, keyed_on => 'uid', store => $master_store); + $self->check_messages({}, keyed_on => 'uid', store => $replica_store); - xlog $self, "running replication..."; - eval { - $self->run_replication(); - }; - my $e = $@; + xlog $self, "running replication..."; + eval { $self->run_replication(); }; + my $e = $@; - # sync_client should have exited with an error - $self->assert($e); - $self->assert_matches(qr/child\sprocess\s + # sync_client should have exited with an error + $self->assert($e); + $self->assert_matches( + qr/child\sprocess\s \(binary\ssync_client\spid\s\d+\)\s exited\swith\scode/x, - $e->to_string()); + $e->to_string() + ); - # sync_client should have logged the BAD response - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: received bad response/); + # sync_client should have logged the BAD response + $self->assert_syslog_matches($self->{instance}, + qr/IOERROR: received bad response/); - # sync server should have logged the write error - $self->assert_syslog_matches($self->{replica}, - qr{IOERROR:\sfailed\sto\supload\sfile + # sync server should have logged the write error + $self->assert_syslog_matches( + $self->{replica}, + qr{IOERROR:\sfailed\sto\supload\sfile (?:\s\(simulated\))?:\sguid=<$canaryguid> - }x); + }x + ); } diff --git a/cassandane/tiny-tests/Replication/clean_remote_shutdown_while_rolling b/cassandane/tiny-tests/Replication/clean_remote_shutdown_while_rolling index 045fca3322..246828ade3 100644 --- a/cassandane/tiny-tests/Replication/clean_remote_shutdown_while_rolling +++ b/cassandane/tiny-tests/Replication/clean_remote_shutdown_while_rolling @@ -2,56 +2,56 @@ use Cassandane::Tiny; sub test_clean_remote_shutdown_while_rolling - :CSyncReplication :SyncLog :min_version_3_5 - :needs_component_replication -{ - my ($self) = @_; - - my $mtalk = $self->{master_store}->get_client(); - - $mtalk->create('INBOX.a.b'); - - # get a rolling sync_client started - # XXX can't just run_replication bc it expects sync_client to finish - my @cmd = qw( sync_client -v -v -o -R ); - my $sync_client_pid = $self->{instance}->run_command( - { - cyrus => 1, - background => 1, - handlers => { - exited_abnormally => sub { - my ($child, $code) = @_; - xlog "child process $child->{binary}\[$child->{pid}\]" - . " exited with code $code"; - return $code; - }, - }, + : CSyncReplication : SyncLog : min_version_3_5 + : needs_component_replication { + my ($self) = @_; + + my $mtalk = $self->{master_store}->get_client(); + + $mtalk->create('INBOX.a.b'); + + # get a rolling sync_client started + # XXX can't just run_replication bc it expects sync_client to finish + my @cmd = qw( sync_client -v -v -o -R ); + my $sync_client_pid = $self->{instance}->run_command( + { + cyrus => 1, + background => 1, + handlers => { + exited_abnormally => sub { + my ($child, $code) = @_; + xlog "child process $child->{binary}\[$child->{pid}\]" + . " exited with code $code"; + return $code; }, - @cmd); + }, + }, + @cmd + ); - # make sure sync_client has time to connect in the first place - sleep 3; + # make sure sync_client has time to connect in the first place + sleep 3; - # stop the replica - $self->{replica}->stop(); + # stop the replica + $self->{replica}->stop(); - # make more changes on master - $mtalk = $self->{master_store}->get_client(); - $mtalk->create('INBOX.a.b.c'); + # make more changes on master + $mtalk = $self->{master_store}->get_client(); + $mtalk->create('INBOX.a.b.c'); - # give sync_client another moment to wake up and see the new log entry - sleep 3; + # give sync_client another moment to wake up and see the new log entry + sleep 3; - # by now it should have noticed the disconnected replica, and either - # shut itself down cleanly, or IOERRORed + # by now it should have noticed the disconnected replica, and either + # shut itself down cleanly, or IOERRORed - # it should have exited already, but signal it if it hasn't, and - # do the cleanup - my $ec = $self->{instance}->stop_command($sync_client_pid); + # it should have exited already, but signal it if it hasn't, and + # do the cleanup + my $ec = $self->{instance}->stop_command($sync_client_pid); - # if it exited itself, this will be zero. if it hung around until - # signalled, 75. - $self->assert_equals(0, $ec); + # if it exited itself, this will be zero. if it hung around until + # signalled, 75. + $self->assert_equals(0, $ec); - # should not be errors in syslog! + # should not be errors in syslog! } diff --git a/cassandane/tiny-tests/Replication/connect_once b/cassandane/tiny-tests/Replication/connect_once index 36e0ba4bad..3358b41760 100644 --- a/cassandane/tiny-tests/Replication/connect_once +++ b/cassandane/tiny-tests/Replication/connect_once @@ -2,46 +2,46 @@ use Cassandane::Tiny; sub test_connect_once - :CSyncReplication :min_version_3_9 - :needs_component_replication -{ - my ($self) = @_; + : CSyncReplication : min_version_3_9 + : needs_component_replication { + my ($self) = @_; - # stop the replica - $self->{replica}->stop(); + # stop the replica + $self->{replica}->stop(); - # start a sync_client, which won't be able to connect - # n.b. can't just run_replication bc it expects sync_client to finish - my $errfile = "$self->{instance}->{basedir}/stderr.out"; - my @cmd = qw( sync_client -v -v -o -m user.cassandane ); - my $sync_client_pid = $self->{instance}->run_command( - { - cyrus => 1, - background => 1, - handlers => { - exited_abnormally => sub { - my ($child, $code) = @_; - xlog "child process $child->{binary}\[$child->{pid}\]" - . " exited with code $code"; - return $code; - }, - }, - redirects => { stderr => $errfile }, + # start a sync_client, which won't be able to connect + # n.b. can't just run_replication bc it expects sync_client to finish + my $errfile = "$self->{instance}->{basedir}/stderr.out"; + my @cmd = qw( sync_client -v -v -o -m user.cassandane ); + my $sync_client_pid = $self->{instance}->run_command( + { + cyrus => 1, + background => 1, + handlers => { + exited_abnormally => sub { + my ($child, $code) = @_; + xlog "child process $child->{binary}\[$child->{pid}\]" + . " exited with code $code"; + return $code; }, - @cmd); + }, + redirects => { stderr => $errfile }, + }, + @cmd + ); - # give sync_client time to fail to connect - sleep 10; + # give sync_client time to fail to connect + sleep 10; - # clean up whatever's left of it - my $ec = $self->{instance}->stop_command($sync_client_pid); + # clean up whatever's left of it + my $ec = $self->{instance}->stop_command($sync_client_pid); - # if it exited itself due to being unable to connect, this will be 1. - # if it was shut down by stop_command, 75 - $self->assert_not_equals(75, $ec); - $self->assert_equals(1, $ec); + # if it exited itself due to being unable to connect, this will be 1. + # if it was shut down by stop_command, 75 + $self->assert_not_equals(75, $ec); + $self->assert_equals(1, $ec); - my $output = slurp_file($errfile); - $self->assert_matches(qr/Can not connect to server/, $output); - $self->assert_does_not_match(qr/retrying in \d+ seconds/, $output); + my $output = slurp_file($errfile); + $self->assert_matches(qr/Can not connect to server/, $output); + $self->assert_does_not_match(qr/retrying in \d+ seconds/, $output); } diff --git a/cassandane/tiny-tests/Replication/delete_longname b/cassandane/tiny-tests/Replication/delete_longname index a02a2fa635..07addd7d02 100644 --- a/cassandane/tiny-tests/Replication/delete_longname +++ b/cassandane/tiny-tests/Replication/delete_longname @@ -2,29 +2,30 @@ use Cassandane::Tiny; sub test_delete_longname - :AllowMoves :Replication :SyncLog :DelayedDelete :min_version_3_3 - :needs_component_replication -{ - my ($self) = @_; + : AllowMoves : Replication : SyncLog : DelayedDelete : min_version_3_3 + : needs_component_replication { + my ($self) = @_; - my $mtalk = $self->{master_store}->get_client(); + my $mtalk = $self->{master_store}->get_client(); - #define MAX_MAILBOX_NAME 490 - my $name = "INBOX.this is a really long name 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2.3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3.foo"; - my ($success) = $mtalk->create($name); - die "Failed to create" unless $success; + #define MAX_MAILBOX_NAME 490 + my $name + = "INBOX.this is a really long name 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2.3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3.foo"; + my ($success) = $mtalk->create($name); + die "Failed to create" unless $success; - # replicate and check initial state - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); + # replicate and check initial state + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); - # reconnect - $mtalk = $self->{master_store}->get_client(); + # reconnect + $mtalk = $self->{master_store}->get_client(); - $mtalk->delete($name); + $mtalk->delete($name); - $self->run_replication(rolling => 1, inputfile => $synclogfname) if -f $synclogfname; + $self->run_replication(rolling => 1, inputfile => $synclogfname) + if -f $synclogfname; - $self->check_replication('cassandane'); + $self->check_replication('cassandane'); } diff --git a/cassandane/tiny-tests/Replication/full_rename b/cassandane/tiny-tests/Replication/full_rename index f7fd016d85..5e59059a0e 100644 --- a/cassandane/tiny-tests/Replication/full_rename +++ b/cassandane/tiny-tests/Replication/full_rename @@ -5,56 +5,55 @@ use Cassandane::Tiny; # Test replication of mailbox only after a rename # sub test_full_rename - :NoAltNameSpace :needs_component_replication :AllowMoves :Replication :SyncLog :DelayedDelete -{ - my ($self) = @_; - - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - - xlog $self, "SYNC LOG FNAME $synclogfname"; - - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - - my $mastertalk = $master_store->get_client(); - my $replicatalk = $replica_store->get_client(); - - $mastertalk->create("INBOX.sub"); - $master_store->set_folder("INBOX.sub"); - - xlog $self, "append some messages"; - my %exp; - my $N = 1; - for (1..$N) - { - my $msg = $self->make_message("Message $_", store => $master_store); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp, $master_store); - - xlog $self, "run initial replication"; - $self->run_replication(); - #$self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); - $self->check_replication('cassandane'); - - xlog $self, "rename user"; - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->rename("user.cassandane", "user.dest"); - - $self->{instance}->getsyslog(); - $self->{replica}->getsyslog(); - - $self->run_replication(user => 'dest'); - $self->check_replication('dest'); - - xlog $self, "Rename again"; - $admintalk = $self->{adminstore}->get_client(); - $admintalk->rename("user.dest", "user.cassandane"); - - # replication works again - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); - $self->check_replication('cassandane'); + : NoAltNameSpace : needs_component_replication : AllowMoves : Replication : + SyncLog : DelayedDelete { + my ($self) = @_; + + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + + xlog $self, "SYNC LOG FNAME $synclogfname"; + + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + + my $mastertalk = $master_store->get_client(); + my $replicatalk = $replica_store->get_client(); + + $mastertalk->create("INBOX.sub"); + $master_store->set_folder("INBOX.sub"); + + xlog $self, "append some messages"; + my %exp; + my $N = 1; + for (1 .. $N) { + my $msg = $self->make_message("Message $_", store => $master_store); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp, $master_store); + + xlog $self, "run initial replication"; + $self->run_replication(); + #$self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); + $self->check_replication('cassandane'); + + xlog $self, "rename user"; + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->rename("user.cassandane", "user.dest"); + + $self->{instance}->getsyslog(); + $self->{replica}->getsyslog(); + + $self->run_replication(user => 'dest'); + $self->check_replication('dest'); + + xlog $self, "Rename again"; + $admintalk = $self->{adminstore}->get_client(); + $admintalk->rename("user.dest", "user.cassandane"); + + # replication works again + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); + $self->check_replication('cassandane'); } diff --git a/cassandane/tiny-tests/Replication/intermediate_rename b/cassandane/tiny-tests/Replication/intermediate_rename index a8fe8bcec8..b86f298c5f 100644 --- a/cassandane/tiny-tests/Replication/intermediate_rename +++ b/cassandane/tiny-tests/Replication/intermediate_rename @@ -2,28 +2,27 @@ use Cassandane::Tiny; sub test_intermediate_rename - :AllowMoves :Replication :SyncLog :DelayedDelete :min_version_3_3 - :needs_component_replication -{ - my ($self) = @_; + : AllowMoves : Replication : SyncLog : DelayedDelete : min_version_3_3 + : needs_component_replication { + my ($self) = @_; - my $mtalk = $self->{master_store}->get_client(); + my $mtalk = $self->{master_store}->get_client(); - $mtalk->create('INBOX.a.b'); + $mtalk->create('INBOX.a.b'); - # replicate and check initial state - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); + # replicate and check initial state + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); - # reconnect - $mtalk = $self->{master_store}->get_client(); + # reconnect + $mtalk = $self->{master_store}->get_client(); - $mtalk->create('INBOX.a'); - $mtalk->rename('INBOX.a', 'INBOX.new'); + $mtalk->create('INBOX.a'); + $mtalk->rename('INBOX.a', 'INBOX.new'); - #$self->run_replication(rolling => 1, inputfile => $synclogfname); - $self->run_replication(); + #$self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->run_replication(); - $self->check_replication('cassandane'); + $self->check_replication('cassandane'); } diff --git a/cassandane/tiny-tests/Replication/intermediate_upgrade b/cassandane/tiny-tests/Replication/intermediate_upgrade index 3d42995aec..2dea578dc7 100644 --- a/cassandane/tiny-tests/Replication/intermediate_upgrade +++ b/cassandane/tiny-tests/Replication/intermediate_upgrade @@ -2,26 +2,26 @@ use Cassandane::Tiny; sub test_intermediate_upgrade - :AllowMoves :Replication :SyncLog :DelayedDelete :min_version_3_3 - :needs_component_replication -{ - my ($self) = @_; + : AllowMoves : Replication : SyncLog : DelayedDelete : min_version_3_3 + : needs_component_replication { + my ($self) = @_; - my $mtalk = $self->{master_store}->get_client(); + my $mtalk = $self->{master_store}->get_client(); - $mtalk->create('INBOX.a.b'); + $mtalk->create('INBOX.a.b'); - # replicate and check initial state - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - $self->run_replication(rolling => 1, inputfile => $synclogfname); - unlink($synclogfname); + # replicate and check initial state + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + $self->run_replication(rolling => 1, inputfile => $synclogfname); + unlink($synclogfname); - # reconnect - $mtalk = $self->{master_store}->get_client(); + # reconnect + $mtalk = $self->{master_store}->get_client(); - $mtalk->create('INBOX.a'); + $mtalk->create('INBOX.a'); - $self->run_replication(rolling => 1, inputfile => $synclogfname) if -f $synclogfname; + $self->run_replication(rolling => 1, inputfile => $synclogfname) + if -f $synclogfname; - $self->check_replication('cassandane'); + $self->check_replication('cassandane'); } diff --git a/cassandane/tiny-tests/Replication/mboxgroups b/cassandane/tiny-tests/Replication/mboxgroups index e3cd1c2c1a..d2fa0186e2 100644 --- a/cassandane/tiny-tests/Replication/mboxgroups +++ b/cassandane/tiny-tests/Replication/mboxgroups @@ -2,115 +2,60 @@ use Cassandane::Tiny; sub test_mboxgroups - :needs_component_replication :Mboxgroups :ReverseACLs -{ - my ($self) = @_; - - my $user = 'brandnew'; - $self->{instance}->create_user($user); - - my $mastersvc = $self->{instance}->get_service('imap'); - my $masterstore = $mastersvc->create_store(username => $user); - my $mastertalk = $masterstore->get_client(); - - my $adminstore = $mastersvc->create_store(username => 'admin'); - my $admintalk = $adminstore->get_client(); - - $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'cassandane', 'group:shared'); - $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'brandnew', 'group:shared'); - - $admintalk->setacl("user.cassandane", "group:shared", "lrs"); - - $mastertalk->create("INBOX.Test") || die; - $mastertalk->create("INBOX.Test.Sub") || die; - $mastertalk->create("INBOX.Test Foo") || die; - - my $ldata = $mastertalk->list("", "*"); - $self->assert_deep_equals($ldata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX' - ], - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX.Test' - ], - [ - [ - '\\HasNoChildren' - ], - '.', - 'INBOX.Test.Sub' - ], - [ - [ - '\\HasNoChildren' - ], - '.', - 'INBOX.Test Foo' - ], - [ - [ - '\\HasNoChildren' - ], - '.', - 'Other Users.cassandane' - ], - ]); - - # run replication - $self->run_replication(user => $user); - $self->run_replication(user => 'cassandane'); - $self->check_replication($user); - $self->check_replication('cassandane'); - - # verify replica store can see folder - my $replicasvc = $self->{replica}->get_service('imap'); - my $replicastore = $replicasvc->create_store(username => $user); - my $replicatalk = $replicastore->get_client(); - - my $rdata = $replicatalk->list("", "*"); - $self->assert_deep_equals($rdata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX' - ], - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX.Test' - ], - [ - [ - '\\HasNoChildren' - ], - '.', - 'INBOX.Test.Sub' - ], - [ - [ - '\\HasNoChildren' - ], - '.', - 'INBOX.Test Foo' - ], - [ - [ - '\\HasNoChildren' - ], - '.', - 'Other Users.cassandane' - ], - ]); + : needs_component_replication : Mboxgroups : ReverseACLs { + my ($self) = @_; + + my $user = 'brandnew'; + $self->{instance}->create_user($user); + + my $mastersvc = $self->{instance}->get_service('imap'); + my $masterstore = $mastersvc->create_store(username => $user); + my $mastertalk = $masterstore->get_client(); + + my $adminstore = $mastersvc->create_store(username => 'admin'); + my $admintalk = $adminstore->get_client(); + + $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'cassandane', 'group:shared'); + $admintalk->_imap_cmd('SETUSERGROUP', 0, '', 'brandnew', 'group:shared'); + + $admintalk->setacl("user.cassandane", "group:shared", "lrs"); + + $mastertalk->create("INBOX.Test") || die; + $mastertalk->create("INBOX.Test.Sub") || die; + $mastertalk->create("INBOX.Test Foo") || die; + + my $ldata = $mastertalk->list("", "*"); + $self->assert_deep_equals( + $ldata, + [ + [ ['\\HasChildren'], '.', 'INBOX' ], + [ ['\\HasChildren'], '.', 'INBOX.Test' ], + [ ['\\HasNoChildren'], '.', 'INBOX.Test.Sub' ], + [ ['\\HasNoChildren'], '.', 'INBOX.Test Foo' ], + [ ['\\HasNoChildren'], '.', 'Other Users.cassandane' ], + ] + ); + + # run replication + $self->run_replication(user => $user); + $self->run_replication(user => 'cassandane'); + $self->check_replication($user); + $self->check_replication('cassandane'); + + # verify replica store can see folder + my $replicasvc = $self->{replica}->get_service('imap'); + my $replicastore = $replicasvc->create_store(username => $user); + my $replicatalk = $replicastore->get_client(); + + my $rdata = $replicatalk->list("", "*"); + $self->assert_deep_equals( + $rdata, + [ + [ ['\\HasChildren'], '.', 'INBOX' ], + [ ['\\HasChildren'], '.', 'INBOX.Test' ], + [ ['\\HasNoChildren'], '.', 'INBOX.Test.Sub' ], + [ ['\\HasNoChildren'], '.', 'INBOX.Test Foo' ], + [ ['\\HasNoChildren'], '.', 'Other Users.cassandane' ], + ] + ); } diff --git a/cassandane/tiny-tests/Replication/replication_mailbox_new_enough b/cassandane/tiny-tests/Replication/replication_mailbox_new_enough index 9fe886d8f4..7619ba9fc2 100644 --- a/cassandane/tiny-tests/Replication/replication_mailbox_new_enough +++ b/cassandane/tiny-tests/Replication/replication_mailbox_new_enough @@ -3,18 +3,17 @@ use Cassandane::Tiny; # this test is too tricky to get working on uuid mailboxes sub test_replication_mailbox_new_enough - :max_version_3_4 :needs_component_replication -{ - my ($self) = @_; + : max_version_3_4 : needs_component_replication { + my ($self) = @_; - my $user = 'cassandane'; - my $exit_code = 0; + my $user = 'cassandane'; + my $exit_code = 0; - # successfully replicate a mailbox new enough to contain guids - my $mailbox10 = $self->{instance}->install_old_mailbox($user, 10); - $self->run_replication(mailbox => $mailbox10); + # successfully replicate a mailbox new enough to contain guids + my $mailbox10 = $self->{instance}->install_old_mailbox($user, 10); + $self->run_replication(mailbox => $mailbox10); - # successfully replicate a mailbox new enough to contain guids - my $mailbox12 = $self->{instance}->install_old_mailbox($user, 12); - $self->run_replication(mailbox => $mailbox12); + # successfully replicate a mailbox new enough to contain guids + my $mailbox12 = $self->{instance}->install_old_mailbox($user, 12); + $self->run_replication(mailbox => $mailbox12); } diff --git a/cassandane/tiny-tests/Replication/replication_mailbox_too_old b/cassandane/tiny-tests/Replication/replication_mailbox_too_old index 24e35e2d4a..559b1c22e1 100644 --- a/cassandane/tiny-tests/Replication/replication_mailbox_too_old +++ b/cassandane/tiny-tests/Replication/replication_mailbox_too_old @@ -3,80 +3,81 @@ use Cassandane::Tiny; # this test is too tricky to get working on uuid mailboxes sub test_replication_mailbox_too_old - :max_version_3_4 :needs_component_replication -{ - my ($self) = @_; + : max_version_3_4 : needs_component_replication { + my ($self) = @_; - my $user = 'cassandane'; - my $exit_code; + my $user = 'cassandane'; + my $exit_code; - my $master_instance = $self->{instance}; - my $replica_instance = $self->{replica}; + my $master_instance = $self->{instance}; + my $replica_instance = $self->{replica}; - # logs will all be in the master instance, because that's where - # sync_client runs from. - my $log_base = "$master_instance->{basedir}/$self->{_name}"; + # logs will all be in the master instance, because that's where + # sync_client runs from. + my $log_base = "$master_instance->{basedir}/$self->{_name}"; - # add a version9 mailbox to the replica only, and try to replicate. - # replication will fail, because the initial GET USER will barf - # upon encountering the old mailbox. - $replica_instance->install_old_mailbox($user, 9); - my $log_firstreject = "$log_base-firstreject.stderr"; - $exit_code = 0; - $self->run_replication( - user => $user, - handlers => { - exited_abnormally => sub { (undef, $exit_code) = @_; }, - }, - redirects => { stderr => $log_firstreject }, - ); - $self->assert_equals(1, $exit_code); - $self->assert(qr/USER received NO response: IMAP_MAILBOX_NOTSUPPORTED/, - slurp_file($log_firstreject)); + # add a version9 mailbox to the replica only, and try to replicate. + # replication will fail, because the initial GET USER will barf + # upon encountering the old mailbox. + $replica_instance->install_old_mailbox($user, 9); + my $log_firstreject = "$log_base-firstreject.stderr"; + $exit_code = 0; + $self->run_replication( + user => $user, + handlers => { + exited_abnormally => sub { (undef, $exit_code) = @_; }, + }, + redirects => { stderr => $log_firstreject }, + ); + $self->assert_equals(1, $exit_code); + $self->assert(qr/USER received NO response: IMAP_MAILBOX_NOTSUPPORTED/, + slurp_file($log_firstreject)); - # add the version9 mailbox to the master, and try to replicate. - # mailbox will be found and rejected locally, and replication will - # fail. - $master_instance->install_old_mailbox($user, 9); - my $log_localreject = "$log_base-localreject.stderr"; - $exit_code = 0; - $self->run_replication( - user => $user, - handlers => { - exited_abnormally => sub { (undef, $exit_code) = @_; }, - }, - redirects => { stderr => $log_localreject }, - ); - $self->assert_equals(1, $exit_code); - $self->assert(qr/Operation is not supported on mailbox/, - slurp_file($log_localreject)); + # add the version9 mailbox to the master, and try to replicate. + # mailbox will be found and rejected locally, and replication will + # fail. + $master_instance->install_old_mailbox($user, 9); + my $log_localreject = "$log_base-localreject.stderr"; + $exit_code = 0; + $self->run_replication( + user => $user, + handlers => { + exited_abnormally => sub { (undef, $exit_code) = @_; }, + }, + redirects => { stderr => $log_localreject }, + ); + $self->assert_equals(1, $exit_code); + $self->assert(qr/Operation is not supported on mailbox/, + slurp_file($log_localreject)); - # upgrade the version9 mailbox on the master, and try to replicate. - # replication will fail, because the initial GET USER will barf - # upon encountering the old mailbox. - $master_instance->run_command({ cyrus => 1 }, qw(reconstruct -V max -u), $user); - my $log_remotereject = "$log_base-remotereject.stderr"; - $exit_code = 0; - $self->run_replication( - user => $user, - handlers => { - exited_abnormally => sub { (undef, $exit_code) = @_; }, - }, - redirects => { stderr => $log_remotereject }, - ); - $self->assert_equals(1, $exit_code); - $self->assert(qr/USER received NO response: IMAP_MAILBOX_NOTSUPPORTED/, - slurp_file($log_remotereject)); + # upgrade the version9 mailbox on the master, and try to replicate. + # replication will fail, because the initial GET USER will barf + # upon encountering the old mailbox. + $master_instance->run_command({ cyrus => 1 }, + qw(reconstruct -V max -u), $user); + my $log_remotereject = "$log_base-remotereject.stderr"; + $exit_code = 0; + $self->run_replication( + user => $user, + handlers => { + exited_abnormally => sub { (undef, $exit_code) = @_; }, + }, + redirects => { stderr => $log_remotereject }, + ); + $self->assert_equals(1, $exit_code); + $self->assert(qr/USER received NO response: IMAP_MAILBOX_NOTSUPPORTED/, + slurp_file($log_remotereject)); - # upgrade the version9 mailbox on the replica, and try to replicate. - # replication will succeed because both ends are capable of replication. - $replica_instance->run_command({ cyrus => 1 }, qw(reconstruct -V max -u), $user); - $exit_code = 0; - $self->run_replication( - user => $user, - handlers => { - exited_abnormally => sub { (undef, $exit_code) = @_; }, - }, - ); - $self->assert_equals(0, $exit_code); + # upgrade the version9 mailbox on the replica, and try to replicate. + # replication will succeed because both ends are capable of replication. + $replica_instance->run_command({ cyrus => 1 }, + qw(reconstruct -V max -u), $user); + $exit_code = 0; + $self->run_replication( + user => $user, + handlers => { + exited_abnormally => sub { (undef, $exit_code) = @_; }, + }, + ); + $self->assert_equals(0, $exit_code); } diff --git a/cassandane/tiny-tests/Replication/replication_repair_zero_msgs b/cassandane/tiny-tests/Replication/replication_repair_zero_msgs index 0577f21c16..29a4013db5 100644 --- a/cassandane/tiny-tests/Replication/replication_repair_zero_msgs +++ b/cassandane/tiny-tests/Replication/replication_repair_zero_msgs @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_replication_repair_zero_msgs - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - my $mastertalk = $self->{master_store}->get_client(); - my $replicatalk = $self->{replica_store}->get_client(); + my $mastertalk = $self->{master_store}->get_client(); + my $replicatalk = $self->{replica_store}->get_client(); - # raise the modseq on the master end - $mastertalk->setmetadata("INBOX", "/shared/comment", "foo"); - $mastertalk->setmetadata("INBOX", "/shared/comment", ""); - $mastertalk->setmetadata("INBOX", "/shared/comment", "foo"); - $mastertalk->setmetadata("INBOX", "/shared/comment", ""); + # raise the modseq on the master end + $mastertalk->setmetadata("INBOX", "/shared/comment", "foo"); + $mastertalk->setmetadata("INBOX", "/shared/comment", ""); + $mastertalk->setmetadata("INBOX", "/shared/comment", "foo"); + $mastertalk->setmetadata("INBOX", "/shared/comment", ""); - my $msg = $self->make_message("to be deleted", store => $self->{replica_store}); + my $msg + = $self->make_message("to be deleted", store => $self->{replica_store}); - $replicatalk->store($msg->{attrs}->{uid}, '+flags', '(\\deleted)'); - $replicatalk->expunge(); + $replicatalk->store($msg->{attrs}->{uid}, '+flags', '(\\deleted)'); + $replicatalk->expunge(); - $self->run_replication(user => 'cassandane'); + $self->run_replication(user => 'cassandane'); } diff --git a/cassandane/tiny-tests/Replication/replication_with_modified_seen_flag b/cassandane/tiny-tests/Replication/replication_with_modified_seen_flag index 63692fcaa8..7c68e834ea 100644 --- a/cassandane/tiny-tests/Replication/replication_with_modified_seen_flag +++ b/cassandane/tiny-tests/Replication/replication_with_modified_seen_flag @@ -2,84 +2,86 @@ use Cassandane::Tiny; sub test_replication_with_modified_seen_flag - :needs_component_replication -{ - my ($self) = @_; - - my $master_store = $self->{master_store}; - $master_store->set_fetch_attributes(qw(uid flags)); - - my $replica_store = $self->{replica_store}; - $replica_store->set_fetch_attributes(qw(uid flags)); - - - xlog $self, "generating messages A & B"; - my %exp; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{A}->set_attributes(id => 1, uid => 1, flags => []); - $exp{B} = $self->make_message("Message B", store => $master_store); - $exp{B}->set_attributes(id => 2, uid => 2, flags => []); - - xlog $self, "Before replication: Ensure that master has two messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "Before replication: Ensure that replica has no messages"; - $self->check_messages({}, store => $replica_store); - - xlog $self, "Run Replication!"; - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog $self, "After replication: Ensure that master has two messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication: Ensure replica now has two messages"; - $self->check_messages(\%exp, store => $replica_store); - - xlog $self, "Set \\Seen on Message B"; - my $mtalk = $master_store->get_client(); - $master_store->_select(); - $mtalk->store('2', '+flags', '(\\Seen)'); - $exp{B}->set_attributes(flags => ['\\Seen']); - $mtalk->unselect(); - xlog $self, "Before replication: Ensure that master has two messages and flags are set"; - $self->check_messages(\%exp, store => $master_store); - - xlog $self, "Before replication: Ensure that replica does not have the \\Seen flag set on Message B"; - my $rtalk = $replica_store->get_client(); - $replica_store->_select(); - my $res = $rtalk->fetch("2", "(flags)"); - my $flags = $res->{2}->{flags}; - $self->assert(not grep { $_ eq "\\Seen"} @$flags); - - xlog $self, "Run Replication!"; - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog $self, "After replication: Ensure that replica does have the \\Seen flag set on Message B"; - $rtalk = $replica_store->get_client(); - $replica_store->_select(); - $res = $rtalk->fetch("2", "(flags)"); - $flags = $res->{2}->{flags}; - $self->assert(grep { $_ eq "\\Seen"} @$flags); - - xlog $self, "Clear \\Seen flag on Message B on master."; - $mtalk = $master_store->get_client(); - $master_store->_select(); - $mtalk->store('2', '-flags', '(\\Seen)'); - - xlog $self, "Run Replication!"; - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog $self, "After replication: Check both master and replica has no \\Seen flag on Message C"; - $mtalk = $master_store->get_client(); - $master_store->_select(); - $res = $mtalk->fetch("2", "(flags)"); - $flags = $res->{2}->{flags}; - $self->assert(not grep { $_ eq "\\Seen"} @$flags); - - $rtalk = $replica_store->get_client(); - $replica_store->_select(); - $res = $rtalk->fetch("3", "(flags)"); - $flags = $res->{3}->{flags}; - $self->assert(not grep { $_ eq "\\Seen"} @$flags); + : needs_component_replication { + my ($self) = @_; + + my $master_store = $self->{master_store}; + $master_store->set_fetch_attributes(qw(uid flags)); + + my $replica_store = $self->{replica_store}; + $replica_store->set_fetch_attributes(qw(uid flags)); + + xlog $self, "generating messages A & B"; + my %exp; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{A}->set_attributes(id => 1, uid => 1, flags => []); + $exp{B} = $self->make_message("Message B", store => $master_store); + $exp{B}->set_attributes(id => 2, uid => 2, flags => []); + + xlog $self, "Before replication: Ensure that master has two messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "Before replication: Ensure that replica has no messages"; + $self->check_messages({}, store => $replica_store); + + xlog $self, "Run Replication!"; + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog $self, "After replication: Ensure that master has two messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "After replication: Ensure replica now has two messages"; + $self->check_messages(\%exp, store => $replica_store); + + xlog $self, "Set \\Seen on Message B"; + my $mtalk = $master_store->get_client(); + $master_store->_select(); + $mtalk->store('2', '+flags', '(\\Seen)'); + $exp{B}->set_attributes(flags => ['\\Seen']); + $mtalk->unselect(); + xlog $self, + "Before replication: Ensure that master has two messages and flags are set"; + $self->check_messages(\%exp, store => $master_store); + + xlog $self, + "Before replication: Ensure that replica does not have the \\Seen flag set on Message B"; + my $rtalk = $replica_store->get_client(); + $replica_store->_select(); + my $res = $rtalk->fetch("2", "(flags)"); + my $flags = $res->{2}->{flags}; + $self->assert(not grep { $_ eq "\\Seen" } @$flags); + + xlog $self, "Run Replication!"; + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog $self, + "After replication: Ensure that replica does have the \\Seen flag set on Message B"; + $rtalk = $replica_store->get_client(); + $replica_store->_select(); + $res = $rtalk->fetch("2", "(flags)"); + $flags = $res->{2}->{flags}; + $self->assert(grep { $_ eq "\\Seen" } @$flags); + + xlog $self, "Clear \\Seen flag on Message B on master."; + $mtalk = $master_store->get_client(); + $master_store->_select(); + $mtalk->store('2', '-flags', '(\\Seen)'); + + xlog $self, "Run Replication!"; + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog $self, + "After replication: Check both master and replica has no \\Seen flag on Message C"; + $mtalk = $master_store->get_client(); + $master_store->_select(); + $res = $mtalk->fetch("2", "(flags)"); + $flags = $res->{2}->{flags}; + $self->assert(not grep { $_ eq "\\Seen" } @$flags); + + $rtalk = $replica_store->get_client(); + $replica_store->_select(); + $res = $rtalk->fetch("3", "(flags)"); + $flags = $res->{3}->{flags}; + $self->assert(not grep { $_ eq "\\Seen" } @$flags); } diff --git a/cassandane/tiny-tests/Replication/reset_on_master b/cassandane/tiny-tests/Replication/reset_on_master index 5b7d3980ad..e30b91de9b 100644 --- a/cassandane/tiny-tests/Replication/reset_on_master +++ b/cassandane/tiny-tests/Replication/reset_on_master @@ -5,44 +5,43 @@ use Cassandane::Tiny; # wasn't correctly looking only for children of that name, so it would try # to delete the wrong user's mailbox. sub test_reset_on_master - :DelayedDelete :min_version_3_3 :needs_component_replication -{ - my ($self) = @_; - $self->{instance}->create_user("user2"); - - my $mastersvc = $self->{instance}->get_service('imap'); - my $astore = $mastersvc->create_store(username => "user2"); - my $atalk = $astore->get_client(); - - xlog "Creating some users with some deleted mailboxes"; - $atalk->create("INBOX.hi"); - $atalk->create("INBOX.no"); - $atalk->delete("INBOX.hi"); - - $self->run_replication(user => "user2"); - - # reset user2 - $self->{instance}->run_command({cyrus => 1}, 'sync_reset', '-f', "user2"); - - my $file = $self->{instance}->{basedir} . "/sync.log"; - open(FH, ">", $file); - print FH "UNMAILBOX user.user2.hi\n"; - print FH "MAILBOX user.user2.hi\n"; - print FH "UNMAILBOX user.user2.no\n"; - print FH "MAILBOX user.user2.no\n"; - print FH "MAILBOX user.cassandane\n"; - close(FH); - - $self->{instance}->getsyslog(); - $self->{replica}->getsyslog(); - xlog $self, "Run replication from a file with just the mailbox name in it"; - $self->run_replication(inputfile => $file, rolling => 1); - - my $pattern = qr{ + : DelayedDelete : min_version_3_3 : needs_component_replication { + my ($self) = @_; + $self->{instance}->create_user("user2"); + + my $mastersvc = $self->{instance}->get_service('imap'); + my $astore = $mastersvc->create_store(username => "user2"); + my $atalk = $astore->get_client(); + + xlog "Creating some users with some deleted mailboxes"; + $atalk->create("INBOX.hi"); + $atalk->create("INBOX.no"); + $atalk->delete("INBOX.hi"); + + $self->run_replication(user => "user2"); + + # reset user2 + $self->{instance}->run_command({ cyrus => 1 }, 'sync_reset', '-f', "user2"); + + my $file = $self->{instance}->{basedir} . "/sync.log"; + open(FH, ">", $file); + print FH "UNMAILBOX user.user2.hi\n"; + print FH "MAILBOX user.user2.hi\n"; + print FH "UNMAILBOX user.user2.no\n"; + print FH "MAILBOX user.user2.no\n"; + print FH "MAILBOX user.cassandane\n"; + close(FH); + + $self->{instance}->getsyslog(); + $self->{replica}->getsyslog(); + xlog $self, "Run replication from a file with just the mailbox name in it"; + $self->run_replication(inputfile => $file, rolling => 1); + + my $pattern = qr{ \bSYNCNOTICE:\sattempt\sto\sUNMAILBOX\swithout\sa\stombstone (?: \suser\.user2\.no\b | :\smailbox= ) }x; - $self->assert_syslog_matches($self->{instance}, $pattern); + $self->assert_syslog_matches($self->{instance}, $pattern); } diff --git a/cassandane/tiny-tests/Replication/rolling_retry_wait_limit b/cassandane/tiny-tests/Replication/rolling_retry_wait_limit index 7786feadd9..cd497c040b 100644 --- a/cassandane/tiny-tests/Replication/rolling_retry_wait_limit +++ b/cassandane/tiny-tests/Replication/rolling_retry_wait_limit @@ -2,57 +2,57 @@ use Cassandane::Tiny; sub test_rolling_retry_wait_limit - :CSyncReplication :NoStartInstances :min_version_3_5 - :needs_component_replication -{ - my ($self) = @_; - my $maxwait = 20; - - $self->{instance}->{config}->set( - 'sync_log' => 1, - 'sync_reconnect_maxwait' => "${maxwait}s", - ); - $self->_start_instances(); - - # stop the replica - $self->{replica}->stop(); - - # get a rolling sync_client started, which won't be able to connect - # XXX can't just run_replication bc it expects sync_client to finish - my $errfile = "$self->{instance}->{basedir}/stderr.out"; - my @cmd = qw( sync_client -v -v -R ); - my $sync_client_pid = $self->{instance}->run_command( - { - cyrus => 1, - background => 1, - handlers => { - exited_abnormally => sub { - my ($child, $code) = @_; - xlog "child process $child->{binary}\[$child->{pid}\]" - . " exited with code $code"; - return $code; - }, - }, - redirects => { stderr => $errfile }, + : CSyncReplication : NoStartInstances : min_version_3_5 + : needs_component_replication { + my ($self) = @_; + my $maxwait = 20; + + $self->{instance}->{config}->set( + 'sync_log' => 1, + 'sync_reconnect_maxwait' => "${maxwait}s", + ); + $self->_start_instances(); + + # stop the replica + $self->{replica}->stop(); + + # get a rolling sync_client started, which won't be able to connect + # XXX can't just run_replication bc it expects sync_client to finish + my $errfile = "$self->{instance}->{basedir}/stderr.out"; + my @cmd = qw( sync_client -v -v -R ); + my $sync_client_pid = $self->{instance}->run_command( + { + cyrus => 1, + background => 1, + handlers => { + exited_abnormally => sub { + my ($child, $code) = @_; + xlog "child process $child->{binary}\[$child->{pid}\]" + . " exited with code $code"; + return $code; }, - @cmd); - - # wait around for a while to give sync_client time to go through its - # reconnect loop a few times. first will be 15, then 20, then 20, - # then 20 (but we'll kill it 5s in) - sleep 60; - - # grant mercy - my $ec = $self->{instance}->stop_command($sync_client_pid); - - # if it exited itself, this will be zero. if it hung around until - # signalled, 75. - $self->assert_equals(75, $ec); - - # check stderr for "retrying in ... seconds" lines, making sure none - # exceed our limit - my $output = slurp_file($errfile); - my @waits = $output =~ m/retrying in (\d+) seconds/g; - $self->assert_num_lte($maxwait, $_) for @waits; - $self->assert_deep_equals([ 15, 20, 20, 20 ], \@waits); + }, + redirects => { stderr => $errfile }, + }, + @cmd + ); + + # wait around for a while to give sync_client time to go through its + # reconnect loop a few times. first will be 15, then 20, then 20, + # then 20 (but we'll kill it 5s in) + sleep 60; + + # grant mercy + my $ec = $self->{instance}->stop_command($sync_client_pid); + + # if it exited itself, this will be zero. if it hung around until + # signalled, 75. + $self->assert_equals(75, $ec); + + # check stderr for "retrying in ... seconds" lines, making sure none + # exceed our limit + my $output = slurp_file($errfile); + my @waits = $output =~ m/retrying in (\d+) seconds/g; + $self->assert_num_lte($maxwait, $_) for @waits; + $self->assert_deep_equals([ 15, 20, 20, 20 ], \@waits); } diff --git a/cassandane/tiny-tests/Replication/shared_folder b/cassandane/tiny-tests/Replication/shared_folder index e4e73e937d..6ff17e9173 100644 --- a/cassandane/tiny-tests/Replication/shared_folder +++ b/cassandane/tiny-tests/Replication/shared_folder @@ -5,47 +5,48 @@ use Cassandane::Tiny; # Test replication of messages APPENDed to the master # sub test_shared_folder - :Replication :SyncLog :needs_component_replication :NoAltNamespace -{ - my ($self) = @_; + : Replication : SyncLog : needs_component_replication : NoAltNamespace { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $mastersvc = $self->{instance}->get_service('imap'); - my $adminstore = $mastersvc->create_store(username => 'admin'); - my $admintalk = $adminstore->get_client(); + my $mastersvc = $self->{instance}->get_service('imap'); + my $adminstore = $mastersvc->create_store(username => 'admin'); + my $admintalk = $adminstore->get_client(); - my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; + my $synclogfname = "$self->{instance}->{basedir}/conf/sync/log"; - xlog $self, "creating shared folder"; + xlog $self, "creating shared folder"; - $admintalk->create('shared.folder'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $admintalk->setacl('shared.folder', 'cassandane' => 'lrswipkxtecdn'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->create('shared.folder'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $admintalk->setacl('shared.folder', 'cassandane' => 'lrswipkxtecdn'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->run_replication(rolling => 1, inputfile => $synclogfname); - $master_store->set_folder('shared.folder'); - $replica_store->set_folder('shared.folder'); + $master_store->set_folder('shared.folder'); + $replica_store->set_folder('shared.folder'); - xlog $self, "generating messages A..D"; - my %exp; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{B} = $self->make_message("Message B", store => $master_store); - $exp{C} = $self->make_message("Message C", store => $master_store); - $exp{D} = $self->make_message("Message D", store => $master_store); + xlog $self, "generating messages A..D"; + my %exp; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{B} = $self->make_message("Message B", store => $master_store); + $exp{C} = $self->make_message("Message C", store => $master_store); + $exp{D} = $self->make_message("Message D", store => $master_store); - xlog $self, "Before replication, the master should have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "Before replication, the replica should have no messages"; - $self->check_messages({}, store => $replica_store); + xlog $self, "Before replication, the master should have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "Before replication, the replica should have no messages"; + $self->check_messages({}, store => $replica_store); - $self->run_replication(rolling => 1, inputfile => $synclogfname); + $self->run_replication(rolling => 1, inputfile => $synclogfname); - xlog $self, "After replication, the master should still have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should now have all four messages"; - $self->check_messages(\%exp, store => $replica_store); + xlog $self, + "After replication, the master should still have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, + "After replication, the replica should now have all four messages"; + $self->check_messages(\%exp, store => $replica_store); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication b/cassandane/tiny-tests/Replication/sieve_replication index c26a0bedc2..c812eec72b 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication +++ b/cassandane/tiny-tests/Replication/sieve_replication @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_sieve_replication - :needs_component_sieve :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : needs_component_replication { + my ($self) = @_; - my $user = 'cassandane'; - my $scriptname = 'test1'; - my $scriptcontent = <<'EOF'; + my $user = 'cassandane'; + my $scriptname = 'test1'; + my $scriptcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -16,31 +15,31 @@ if address :is :all "From" "autoreject@example.org" } EOF - # first, verify that sieve script does not exist on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that sieve script does not exist on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install sieve script on master - $self->{instance}->install_sieve_script($scriptcontent, name=>$scriptname); + # then, install sieve script on master + $self->{instance}->install_sieve_script($scriptcontent, name => $scriptname); - # then, verify that sieve script exists on master but not on replica - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); + # then, verify that sieve script exists on master but not on replica + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, run replication, - $self->run_replication(); - $self->check_replication('cassandane'); + # then, run replication, + $self->run_replication(); + $self->check_replication('cassandane'); - # then, verify that sieve script exists on both master and replica - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); + # then, verify that sieve script exists on both master and replica + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_delete_unactivate b/cassandane/tiny-tests/Replication/sieve_replication_delete_unactivate index fb64246f8c..2a831958c5 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_delete_unactivate +++ b/cassandane/tiny-tests/Replication/sieve_replication_delete_unactivate @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_sieve_replication_delete_unactivate - :needs_component_sieve :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : needs_component_replication { + my ($self) = @_; - my $user = 'cassandane'; - my $scriptname = 'test1'; - my $scriptcontent = <<'EOF'; + my $user = 'cassandane'; + my $scriptname = 'test1'; + my $scriptcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -16,31 +15,31 @@ if address :is :all "From" "autoreject@example.org" } EOF - # first, verify that sieve script does not exist on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that sieve script does not exist on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install sieve script on replica only - $self->{replica}->install_sieve_script($scriptcontent, name=>$scriptname); + # then, install sieve script on replica only + $self->{replica}->install_sieve_script($scriptcontent, name => $scriptname); - # then, verify that sieve script exists on replica only - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user, $scriptname); + # then, verify that sieve script exists on replica only + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user, $scriptname); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); - # then, run replication, - $self->run_replication(); - $self->check_replication('cassandane'); + # then, run replication, + $self->run_replication(); + $self->check_replication('cassandane'); - # then, verify that sieve script no longer exists on either - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_noactive($self->{instance}, $user, $scriptname); + # then, verify that sieve script no longer exists on either + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_noactive($self->{instance}, $user, $scriptname); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 1); - $self->assert_sieve_noactive($self->{replica}, $user, $scriptname); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 1); + $self->assert_sieve_noactive($self->{replica}, $user, $scriptname); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_delete_unactivate_unixhs b/cassandane/tiny-tests/Replication/sieve_replication_delete_unactivate_unixhs index b0095da1f4..6aa0215f8b 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_delete_unactivate_unixhs +++ b/cassandane/tiny-tests/Replication/sieve_replication_delete_unactivate_unixhs @@ -2,49 +2,50 @@ use Cassandane::Tiny; sub test_sieve_replication_delete_unactivate_unixhs - :needs_component_sieve :UnixHierarchySep - :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : UnixHierarchySep + : needs_component_replication { + my ($self) = @_; - my $user = 'some.body'; - my $scriptname = 'test1'; - my $scriptcontent = <<'EOF'; + my $user = 'some.body'; + my $scriptname = 'test1'; + my $scriptcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { reject "testing"; } EOF - $self->{instance}->create_user($user); + $self->{instance}->create_user($user); - # first, verify that sieve script does not exist on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that sieve script does not exist on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install sieve script on replica only - $self->{replica}->install_sieve_script($scriptcontent, - name=>$scriptname, - username=>$user); + # then, install sieve script on replica only + $self->{replica}->install_sieve_script( + $scriptcontent, + name => $scriptname, + username => $user + ); - # then, verify that sieve script exists on replica only - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user, $scriptname); + # then, verify that sieve script exists on replica only + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user, $scriptname); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); - # then, run replication, - $self->run_replication(user=>$user); - $self->check_replication($user); + # then, run replication, + $self->run_replication(user => $user); + $self->check_replication($user); - # then, verify that sieve script no longer exists on either - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_noactive($self->{instance}, $user, $scriptname); + # then, verify that sieve script no longer exists on either + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_noactive($self->{instance}, $user, $scriptname); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 1); - $self->assert_sieve_noactive($self->{replica}, $user, $scriptname); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 1); + $self->assert_sieve_noactive($self->{replica}, $user, $scriptname); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_different b/cassandane/tiny-tests/Replication/sieve_replication_different index 23da3dc677..dce0e7fcd7 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_different +++ b/cassandane/tiny-tests/Replication/sieve_replication_different @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_sieve_replication_different - :needs_component_sieve :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : needs_component_replication { + my ($self) = @_; - my $user = 'cassandane'; - my $script1name = 'test1'; - my $script1content = <<'EOF'; + my $user = 'cassandane'; + my $script1name = 'test1'; + my $script1content = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -16,8 +15,8 @@ if address :is :all "From" "autoreject@example.org" } EOF - my $script2name = 'test2'; - my $script2content = <<'EOF'; + my $script2name = 'test2'; + my $script2content = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -25,40 +24,41 @@ if address :is :all "From" "autoreject@example.org" } EOF - # first, verify that neither script exists on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $script1name, 0); - $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that neither script exists on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $script1name, 0); + $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $script1name, 0); - $self->assert_sieve_not_exists($self->{replica}, $user, $script2name, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $script1name, 0); + $self->assert_sieve_not_exists($self->{replica}, $user, $script2name, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install different sieve script on master and replica - $self->{instance}->install_sieve_script($script1content, name=>$script1name); - $self->{replica}->install_sieve_script($script2content, name=>$script2name); + # then, install different sieve script on master and replica + $self->{instance} + ->install_sieve_script($script1content, name => $script1name); + $self->{replica}->install_sieve_script($script2content, name => $script2name); - # then, verify that each sieve script exists on one only - $self->assert_sieve_exists($self->{instance}, $user, $script1name, 0); - $self->assert_sieve_active($self->{instance}, $user, $script1name); - $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 0); + # then, verify that each sieve script exists on one only + $self->assert_sieve_exists($self->{instance}, $user, $script1name, 0); + $self->assert_sieve_active($self->{instance}, $user, $script1name); + $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 0); - $self->assert_sieve_exists($self->{replica}, $user, $script2name, 0); - $self->assert_sieve_active($self->{replica}, $user, $script2name); - $self->assert_sieve_not_exists($self->{replica}, $user, $script1name, 0); + $self->assert_sieve_exists($self->{replica}, $user, $script2name, 0); + $self->assert_sieve_active($self->{replica}, $user, $script2name); + $self->assert_sieve_not_exists($self->{replica}, $user, $script1name, 0); - # then, run replication, - # the one that exists on master only will be replicated - # the one that exists on replica only will be deleted - $self->run_replication(); - $self->check_replication('cassandane'); + # then, run replication, + # the one that exists on master only will be replicated + # the one that exists on replica only will be deleted + $self->run_replication(); + $self->check_replication('cassandane'); - # then, verify that scripts are in expected state - $self->assert_sieve_exists($self->{instance}, $user, $script1name, 1); - $self->assert_sieve_active($self->{instance}, $user, $script1name); - $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 1); + # then, verify that scripts are in expected state + $self->assert_sieve_exists($self->{instance}, $user, $script1name, 1); + $self->assert_sieve_active($self->{instance}, $user, $script1name); + $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 1); - $self->assert_sieve_exists($self->{replica}, $user, $script1name, 1); - $self->assert_sieve_active($self->{replica}, $user, $script1name); - $self->assert_sieve_not_exists($self->{replica}, $user, $script2name, 1); + $self->assert_sieve_exists($self->{replica}, $user, $script1name, 1); + $self->assert_sieve_active($self->{replica}, $user, $script1name); + $self->assert_sieve_not_exists($self->{replica}, $user, $script2name, 1); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_different_unixhs b/cassandane/tiny-tests/Replication/sieve_replication_different_unixhs index 6bb545c7eb..4d9eeda049 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_different_unixhs +++ b/cassandane/tiny-tests/Replication/sieve_replication_different_unixhs @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_sieve_replication_different_unixhs - :needs_component_sieve :UnixHierarchySep - :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : UnixHierarchySep + : needs_component_replication { + my ($self) = @_; - my $user = 'some.body'; - my $script1name = 'test1'; - my $script1content = <<'EOF'; + my $user = 'some.body'; + my $script1name = 'test1'; + my $script1content = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -17,54 +16,58 @@ if address :is :all "From" "autoreject@example.org" } EOF - my $script2name = 'test2'; - my $script2content = <<'EOF'; + my $script2name = 'test2'; + my $script2content = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { reject "more testing"; } EOF - $self->{instance}->create_user($user); + $self->{instance}->create_user($user); - # first, verify that neither script exists on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $script1name, 0); - $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that neither script exists on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $script1name, 0); + $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $script1name, 0); - $self->assert_sieve_not_exists($self->{replica}, $user, $script2name, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $script1name, 0); + $self->assert_sieve_not_exists($self->{replica}, $user, $script2name, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install different sieve script on master and replica - $self->{instance}->install_sieve_script($script1content, - name=>$script1name, - username=>$user); - $self->{replica}->install_sieve_script($script2content, - name=>$script2name, - username=>$user); + # then, install different sieve script on master and replica + $self->{instance}->install_sieve_script( + $script1content, + name => $script1name, + username => $user + ); + $self->{replica}->install_sieve_script( + $script2content, + name => $script2name, + username => $user + ); - # then, verify that each sieve script exists on one only - $self->assert_sieve_exists($self->{instance}, $user, $script1name, 0); - $self->assert_sieve_active($self->{instance}, $user, $script1name); - $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 0); + # then, verify that each sieve script exists on one only + $self->assert_sieve_exists($self->{instance}, $user, $script1name, 0); + $self->assert_sieve_active($self->{instance}, $user, $script1name); + $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 0); - $self->assert_sieve_exists($self->{replica}, $user, $script2name, 0); - $self->assert_sieve_active($self->{replica}, $user, $script2name); - $self->assert_sieve_not_exists($self->{replica}, $user, $script1name, 0); + $self->assert_sieve_exists($self->{replica}, $user, $script2name, 0); + $self->assert_sieve_active($self->{replica}, $user, $script2name); + $self->assert_sieve_not_exists($self->{replica}, $user, $script1name, 0); - # then, run replication, - # the one that exists on master only will be replicated - # the one that exists on replica only will be deleted - $self->run_replication(user=>$user); - $self->check_replication($user); + # then, run replication, + # the one that exists on master only will be replicated + # the one that exists on replica only will be deleted + $self->run_replication(user => $user); + $self->check_replication($user); - # then, verify that scripts are in expected state - $self->assert_sieve_exists($self->{instance}, $user, $script1name, 1); - $self->assert_sieve_active($self->{instance}, $user, $script1name); - $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 1); + # then, verify that scripts are in expected state + $self->assert_sieve_exists($self->{instance}, $user, $script1name, 1); + $self->assert_sieve_active($self->{instance}, $user, $script1name); + $self->assert_sieve_not_exists($self->{instance}, $user, $script2name, 1); - $self->assert_sieve_exists($self->{replica}, $user, $script1name, 1); - $self->assert_sieve_active($self->{replica}, $user, $script1name); - $self->assert_sieve_not_exists($self->{replica}, $user, $script2name, 1); + $self->assert_sieve_exists($self->{replica}, $user, $script1name, 1); + $self->assert_sieve_active($self->{replica}, $user, $script1name); + $self->assert_sieve_not_exists($self->{replica}, $user, $script2name, 1); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_exists b/cassandane/tiny-tests/Replication/sieve_replication_exists index 4998608134..d035355524 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_exists +++ b/cassandane/tiny-tests/Replication/sieve_replication_exists @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_sieve_replication_exists - :needs_component_sieve :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : needs_component_replication { + my ($self) = @_; - my $user = 'cassandane'; - my $scriptname = 'test1'; - my $scriptcontent = <<'EOF'; + my $user = 'cassandane'; + my $scriptname = 'test1'; + my $scriptcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -16,32 +15,32 @@ if address :is :all "From" "autoreject@example.org" } EOF - # first, verify that sieve script does not exist on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that sieve script does not exist on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install sieve script on both master and replica - $self->{instance}->install_sieve_script($scriptcontent, name=>$scriptname); - $self->{replica}->install_sieve_script($scriptcontent, name=>$scriptname); + # then, install sieve script on both master and replica + $self->{instance}->install_sieve_script($scriptcontent, name => $scriptname); + $self->{replica}->install_sieve_script($scriptcontent, name => $scriptname); - # then, verify that sieve script exists on both - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); + # then, verify that sieve script exists on both + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); - # then, run replication, - $self->run_replication(); - $self->check_replication('cassandane'); + # then, run replication, + $self->run_replication(); + $self->check_replication('cassandane'); - # then, verify that sieve script still exists on both master and replica - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); + # then, verify that sieve script still exists on both master and replica + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_exists_unixhs b/cassandane/tiny-tests/Replication/sieve_replication_exists_unixhs index 6b20ab1187..c947e30905 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_exists_unixhs +++ b/cassandane/tiny-tests/Replication/sieve_replication_exists_unixhs @@ -2,52 +2,55 @@ use Cassandane::Tiny; sub test_sieve_replication_exists_unixhs - :needs_component_sieve :UnixHierarchySep - :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : UnixHierarchySep + : needs_component_replication { + my ($self) = @_; - my $user = 'some.body'; - my $scriptname = 'test1'; - my $scriptcontent = <<'EOF'; + my $user = 'some.body'; + my $scriptname = 'test1'; + my $scriptcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { reject "testing"; } EOF - $self->{instance}->create_user($user); - - # first, verify that sieve script does not exist on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user); - - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); - - # then, install sieve script on both master and replica - $self->{instance}->install_sieve_script($scriptcontent, - name=>$scriptname, - username=>$user); - $self->{replica}->install_sieve_script($scriptcontent, - name=>$scriptname, - username=>$user); - - # then, verify that sieve script exists on both - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); - - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); - - # then, run replication, - $self->run_replication(user=>$user); - $self->check_replication($user); - - # then, verify that sieve script still exists on both master and replica - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); - - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->{instance}->create_user($user); + + # first, verify that sieve script does not exist on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user); + + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); + + # then, install sieve script on both master and replica + $self->{instance}->install_sieve_script( + $scriptcontent, + name => $scriptname, + username => $user + ); + $self->{replica}->install_sieve_script( + $scriptcontent, + name => $scriptname, + username => $user + ); + + # then, verify that sieve script exists on both + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); + + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); + + # then, run replication, + $self->run_replication(user => $user); + $self->check_replication($user); + + # then, verify that sieve script still exists on both master and replica + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); + + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_stale b/cassandane/tiny-tests/Replication/sieve_replication_stale index 939bfea49e..c0622767ef 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_stale +++ b/cassandane/tiny-tests/Replication/sieve_replication_stale @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_sieve_replication_stale - :needs_component_sieve :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : needs_component_replication { + my ($self) = @_; - my $user = 'cassandane'; - my $scriptname = 'test1'; - my $scriptoldcontent = <<'EOF'; + my $user = 'cassandane'; + my $scriptname = 'test1'; + my $scriptoldcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -16,7 +15,7 @@ if address :is :all "From" "autoreject@example.org" } EOF - my $scriptnewcontent = <<'EOF'; + my $scriptnewcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -24,45 +23,47 @@ if address :is :all "From" "autoreject@example.org" } EOF - # first, verify that script does not exist on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that script does not exist on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install "old" script on replica... - $self->{replica}->install_sieve_script($scriptoldcontent, name=>$scriptname); + # then, install "old" script on replica... + $self->{replica} + ->install_sieve_script($scriptoldcontent, name => $scriptname); - # ... and "new" script on master, a little later - sleep 2; - $self->{instance}->install_sieve_script($scriptnewcontent, name=>$scriptname); + # ... and "new" script on master, a little later + sleep 2; + $self->{instance} + ->install_sieve_script($scriptnewcontent, name => $scriptname); - # then, verify that different sieve script content exists at each end - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_matches($self->{instance}, $user, $scriptname, - $scriptnewcontent); + # then, verify that different sieve script content exists at each end + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); + $self->assert_sieve_matches($self->{instance}, $user, $scriptname, + $scriptnewcontent); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); - $self->assert_sieve_matches($self->{replica}, $user, $scriptname, - $scriptoldcontent); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_matches($self->{replica}, $user, $scriptname, + $scriptoldcontent); - # then, run replication, - # the one that exists on replica is different to and older than the one - # on master, so it will be replaced with the one from master - $self->run_replication(); - $self->check_replication('cassandane'); + # then, run replication, + # the one that exists on replica is different to and older than the one + # on master, so it will be replaced with the one from master + $self->run_replication(); + $self->check_replication('cassandane'); - # then, verify that scripts are in expected state - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_matches($self->{instance}, $user, $scriptname, - $scriptnewcontent); + # then, verify that scripts are in expected state + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); + $self->assert_sieve_matches($self->{instance}, $user, $scriptname, + $scriptnewcontent); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); - $self->assert_sieve_matches($self->{replica}, $user, $scriptname, - $scriptnewcontent); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_matches($self->{replica}, $user, $scriptname, + $scriptnewcontent); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_stale_unixhs b/cassandane/tiny-tests/Replication/sieve_replication_stale_unixhs index 2c36d4c1bc..75f558407f 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_stale_unixhs +++ b/cassandane/tiny-tests/Replication/sieve_replication_stale_unixhs @@ -2,14 +2,13 @@ use Cassandane::Tiny; sub test_sieve_replication_stale_unixhs - :needs_component_sieve :UnixHierarchySep - :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : UnixHierarchySep + : needs_component_replication { + my ($self) = @_; - my $user = 'some.body'; - my $scriptname = 'test1'; - my $scriptoldcontent = <<'EOF'; + my $user = 'some.body'; + my $scriptname = 'test1'; + my $scriptoldcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { @@ -17,58 +16,62 @@ if address :is :all "From" "autoreject@example.org" } EOF - my $scriptnewcontent = <<'EOF'; + my $scriptnewcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { reject "more testing"; } EOF - $self->{instance}->create_user($user); + $self->{instance}->create_user($user); - # first, verify that script does not exist on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that script does not exist on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install "old" script on replica... - $self->{replica}->install_sieve_script($scriptoldcontent, - name=>$scriptname, - username=>$user); + # then, install "old" script on replica... + $self->{replica}->install_sieve_script( + $scriptoldcontent, + name => $scriptname, + username => $user + ); - # ... and "new" script on master, a little later - sleep 2; - $self->{instance}->install_sieve_script($scriptnewcontent, - name=>$scriptname, - username=>$user); + # ... and "new" script on master, a little later + sleep 2; + $self->{instance}->install_sieve_script( + $scriptnewcontent, + name => $scriptname, + username => $user + ); - # then, verify that different sieve script content exists at each end - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_matches($self->{instance}, $user, $scriptname, - $scriptnewcontent); + # then, verify that different sieve script content exists at each end + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); + $self->assert_sieve_matches($self->{instance}, $user, $scriptname, + $scriptnewcontent); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); - $self->assert_sieve_matches($self->{replica}, $user, $scriptname, - $scriptoldcontent); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_matches($self->{replica}, $user, $scriptname, + $scriptoldcontent); - # then, run replication, - # the one that exists on replica is different to and older than the one - # on master, so it will be replaced with the one from master - $self->run_replication(user=>$user); - $self->check_replication($user); + # then, run replication, + # the one that exists on replica is different to and older than the one + # on master, so it will be replaced with the one from master + $self->run_replication(user => $user); + $self->check_replication($user); - # then, verify that scripts are in expected state - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_matches($self->{instance}, $user, $scriptname, - $scriptnewcontent); + # then, verify that scripts are in expected state + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); + $self->assert_sieve_matches($self->{instance}, $user, $scriptname, + $scriptnewcontent); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); - $self->assert_sieve_matches($self->{replica}, $user, $scriptname, - $scriptnewcontent); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_matches($self->{replica}, $user, $scriptname, + $scriptnewcontent); } diff --git a/cassandane/tiny-tests/Replication/sieve_replication_unixhs b/cassandane/tiny-tests/Replication/sieve_replication_unixhs index 476057150c..7c009dfea4 100644 --- a/cassandane/tiny-tests/Replication/sieve_replication_unixhs +++ b/cassandane/tiny-tests/Replication/sieve_replication_unixhs @@ -2,49 +2,50 @@ use Cassandane::Tiny; sub test_sieve_replication_unixhs - :needs_component_sieve :UnixHierarchySep - :needs_component_replication -{ - my ($self) = @_; + : needs_component_sieve : UnixHierarchySep + : needs_component_replication { + my ($self) = @_; - my $user = 'some.body'; - my $scriptname = 'test1'; - my $scriptcontent = <<'EOF'; + my $user = 'some.body'; + my $scriptname = 'test1'; + my $scriptcontent = <<'EOF'; require ["reject","fileinto"]; if address :is :all "From" "autoreject@example.org" { reject "testing"; } EOF - $self->{instance}->create_user($user); + $self->{instance}->create_user($user); - # first, verify that sieve script does not exist on master or replica - $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{instance}, $user); + # first, verify that sieve script does not exist on master or replica + $self->assert_sieve_not_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{instance}, $user); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, install sieve script on master - $self->{instance}->install_sieve_script($scriptcontent, - name=>$scriptname, - username=>$user); + # then, install sieve script on master + $self->{instance}->install_sieve_script( + $scriptcontent, + name => $scriptname, + username => $user + ); - # then, verify that sieve script exists on master but not on replica - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); + # then, verify that sieve script exists on master but not on replica + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 0); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); - $self->assert_sieve_noactive($self->{replica}, $user); + $self->assert_sieve_not_exists($self->{replica}, $user, $scriptname, 0); + $self->assert_sieve_noactive($self->{replica}, $user); - # then, run replication, - $self->run_replication(user=>$user); - $self->check_replication($user); + # then, run replication, + $self->run_replication(user => $user); + $self->check_replication($user); - # then, verify that sieve script exists on both master and replica - $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{instance}, $user, $scriptname); + # then, verify that sieve script exists on both master and replica + $self->assert_sieve_exists($self->{instance}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{instance}, $user, $scriptname); - $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); - $self->assert_sieve_active($self->{replica}, $user, $scriptname); + $self->assert_sieve_exists($self->{replica}, $user, $scriptname, 1); + $self->assert_sieve_active($self->{replica}, $user, $scriptname); } diff --git a/cassandane/tiny-tests/Replication/splitbrain b/cassandane/tiny-tests/Replication/splitbrain index 023425427f..b3c8037cf8 100644 --- a/cassandane/tiny-tests/Replication/splitbrain +++ b/cassandane/tiny-tests/Replication/splitbrain @@ -5,67 +5,67 @@ use Cassandane::Tiny; # Test replication of messages APPENDed to the master # sub test_splitbrain - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - xlog $self, "generating messages A..D"; - my %exp; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{B} = $self->make_message("Message B", store => $master_store); - $exp{C} = $self->make_message("Message C", store => $master_store); - $exp{D} = $self->make_message("Message D", store => $master_store); + xlog $self, "generating messages A..D"; + my %exp; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{B} = $self->make_message("Message B", store => $master_store); + $exp{C} = $self->make_message("Message C", store => $master_store); + $exp{D} = $self->make_message("Message D", store => $master_store); - xlog $self, "Before replication, the master should have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "Before replication, the replica should have no messages"; - $self->check_messages({}, store => $replica_store); + xlog $self, "Before replication, the master should have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "Before replication, the replica should have no messages"; + $self->check_messages({}, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, the master should still have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should now have all four messages"; - $self->check_messages(\%exp, store => $replica_store); + xlog $self, + "After replication, the master should still have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, + "After replication, the replica should now have all four messages"; + $self->check_messages(\%exp, store => $replica_store); - my %mexp = %exp; - my %rexp = %exp; + my %mexp = %exp; + my %rexp = %exp; - $mexp{E} = $self->make_message("Message E", store => $master_store); - $rexp{F} = $self->make_message("Message F", store => $replica_store); + $mexp{E} = $self->make_message("Message E", store => $master_store); + $rexp{F} = $self->make_message("Message F", store => $replica_store); - # uid is 5 at both ends - $rexp{F}->set_attribute(uid => 5); + # uid is 5 at both ends + $rexp{F}->set_attribute(uid => 5); - xlog $self, "No replication, the master should have its 5 messages"; - $self->check_messages(\%mexp, store => $master_store); - xlog $self, "No replication, the replica should have the other 5 messages"; - $self->check_messages(\%rexp, store => $replica_store); + xlog $self, "No replication, the master should have its 5 messages"; + $self->check_messages(\%mexp, store => $master_store); + xlog $self, "No replication, the replica should have the other 5 messages"; + $self->check_messages(\%rexp, store => $replica_store); - $self->run_replication(); + $self->run_replication(); - # replication will generate a couple of SYNCERRORS in syslog - my $pattern = qr{ + # replication will generate a couple of SYNCERRORS in syslog + my $pattern = qr{ \bSYNCERROR:\sguid\smismatch (?: \suser\.cassandane\s5\b | :\smailbox=\suid=<5> ) }x; - $self->assert_syslog_matches($self->{instance}, $pattern); + $self->assert_syslog_matches($self->{instance}, $pattern); - $self->check_replication('cassandane'); + $self->check_replication('cassandane'); - - %exp = (%mexp, %rexp); - # we could calculate 6 and 7 by sorting from GUID, but easiest is to ignore UIDs - $exp{E}->set_attribute(uid => undef); - $exp{F}->set_attribute(uid => undef); - xlog $self, "After replication, the master should have all 6 messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should have all 6 messages"; - $self->check_messages(\%exp, store => $replica_store); + %exp = (%mexp, %rexp); +# we could calculate 6 and 7 by sorting from GUID, but easiest is to ignore UIDs + $exp{E}->set_attribute(uid => undef); + $exp{F}->set_attribute(uid => undef); + xlog $self, "After replication, the master should have all 6 messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "After replication, the replica should have all 6 messages"; + $self->check_messages(\%exp, store => $replica_store); } diff --git a/cassandane/tiny-tests/Replication/splitbrain_bothexpunge b/cassandane/tiny-tests/Replication/splitbrain_bothexpunge index 0158527313..903079384e 100644 --- a/cassandane/tiny-tests/Replication/splitbrain_bothexpunge +++ b/cassandane/tiny-tests/Replication/splitbrain_bothexpunge @@ -5,66 +5,68 @@ use Cassandane::Tiny; # Test replication of messages APPENDed to the master # sub test_splitbrain_bothexpunge - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - xlog $self, "generating messages A..D"; - my %exp; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{B} = $self->make_message("Message B", store => $master_store); - $exp{C} = $self->make_message("Message C", store => $master_store); - $exp{D} = $self->make_message("Message D", store => $master_store); + xlog $self, "generating messages A..D"; + my %exp; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{B} = $self->make_message("Message B", store => $master_store); + $exp{C} = $self->make_message("Message C", store => $master_store); + $exp{D} = $self->make_message("Message D", store => $master_store); - xlog $self, "Before replication, the master should have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "Before replication, the replica should have no messages"; - $self->check_messages({}, store => $replica_store); + xlog $self, "Before replication, the master should have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "Before replication, the replica should have no messages"; + $self->check_messages({}, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, the master should still have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should now have all four messages"; - $self->check_messages(\%exp, store => $replica_store); + xlog $self, + "After replication, the master should still have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, + "After replication, the replica should now have all four messages"; + $self->check_messages(\%exp, store => $replica_store); - my %mexp = %exp; - my %rexp = %exp; + my %mexp = %exp; + my %rexp = %exp; - $mexp{E} = $self->make_message("Message E", store => $master_store); - $rexp{F} = $self->make_message("Message F", store => $replica_store); + $mexp{E} = $self->make_message("Message E", store => $master_store); + $rexp{F} = $self->make_message("Message F", store => $replica_store); - # uid is 5 at both ends - $rexp{F}->set_attribute(uid => 5); + # uid is 5 at both ends + $rexp{F}->set_attribute(uid => 5); - xlog $self, "No replication, the master should have its 5 messages"; - $self->check_messages(\%mexp, store => $master_store); - xlog $self, "No replication, the replica should have the other 5 messages"; - $self->check_messages(\%rexp, store => $replica_store); + xlog $self, "No replication, the master should have its 5 messages"; + $self->check_messages(\%mexp, store => $master_store); + xlog $self, "No replication, the replica should have the other 5 messages"; + $self->check_messages(\%rexp, store => $replica_store); - xlog $self, "Delete and expunge the message on the master"; - my $talk = $master_store->get_client(); - $master_store->_select(); - $talk->store('5', '+flags', '(\\Deleted)'); - $talk->expunge(); - delete $mexp{E}; + xlog $self, "Delete and expunge the message on the master"; + my $talk = $master_store->get_client(); + $master_store->_select(); + $talk->store('5', '+flags', '(\\Deleted)'); + $talk->expunge(); + delete $mexp{E}; - xlog $self, "Delete and expunge the message on the master"; - my $rtalk = $replica_store->get_client(); - $replica_store->_select(); - $rtalk->store('5', '+flags', '(\\Deleted)'); - $rtalk->expunge(); - delete $rexp{F}; + xlog $self, "Delete and expunge the message on the master"; + my $rtalk = $replica_store->get_client(); + $replica_store->_select(); + $rtalk->store('5', '+flags', '(\\Deleted)'); + $rtalk->expunge(); + delete $rexp{F}; - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, the master should have just the original 4 messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should have the same 4 messages"; - $self->check_messages(\%exp, store => $replica_store); + xlog $self, + "After replication, the master should have just the original 4 messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "After replication, the replica should have the same 4 messages"; + $self->check_messages(\%exp, store => $replica_store); } diff --git a/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_nonempty b/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_nonempty index c945da1821..abcf9c3ff0 100644 --- a/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_nonempty +++ b/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_nonempty @@ -5,55 +5,51 @@ use Cassandane::Tiny; # Test non-empty mailbox causes replication to abort # sub test_splitbrain_different_uniqueid_nonempty - :min_version_3_5 :needs_component_replication :NoReplicaonly -{ - my ($self) = @_; + : min_version_3_5 : needs_component_replication : NoReplicaonly { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $mtalk = $master_store->get_client(); - my $rtalk = $replica_store->get_client(); + my $mtalk = $master_store->get_client(); + my $rtalk = $replica_store->get_client(); - $mtalk->create('INBOX.subfolder'); - my $mres = $mtalk->status("INBOX.subfolder", ['mailboxid']); - my $mid = $mres->{mailboxid}[0]; - $rtalk->create('INBOX.subfolder'); - my $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); - my $rid = $rres->{mailboxid}[0]; + $mtalk->create('INBOX.subfolder'); + my $mres = $mtalk->status("INBOX.subfolder", ['mailboxid']); + my $mid = $mres->{mailboxid}[0]; + $rtalk->create('INBOX.subfolder'); + my $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); + my $rid = $rres->{mailboxid}[0]; - $self->assert_not_null($mid); - $self->assert_not_null($rid); - $self->assert_str_not_equals($mid, $rid); + $self->assert_not_null($mid); + $self->assert_not_null($rid); + $self->assert_str_not_equals($mid, $rid); - $master_store->set_folder("INBOX.subfolder"); - $replica_store->set_folder("INBOX.subfolder"); + $master_store->set_folder("INBOX.subfolder"); + $replica_store->set_folder("INBOX.subfolder"); - $self->make_message("Message A", store => $master_store); - $self->make_message("Message B", store => $replica_store); + $self->make_message("Message A", store => $master_store); + $self->make_message("Message B", store => $replica_store); - # this will fail - eval { - $self->run_replication(); - }; + # this will fail + eval { $self->run_replication(); }; - # sync_client should have logged the failure - if ($self->{instance}->{have_syslog_replacement}) { - my @mlines = $self->{instance}->getsyslog(); - $self->assert_matches(qr/IOERROR: user replication failed/, "@mlines"); - $self->assert_matches(qr/MAILBOX received NO response: IMAP_MAILBOX_MOVED/, "@mlines"); + # sync_client should have logged the failure + if ($self->{instance}->{have_syslog_replacement}) { + my @mlines = $self->{instance}->getsyslog(); + $self->assert_matches(qr/IOERROR: user replication failed/, "@mlines"); + $self->assert_matches(qr/MAILBOX received NO response: IMAP_MAILBOX_MOVED/, + "@mlines"); - } + } - # sync server should have logged the failure - $self->assert_syslog_matches( - $self->{replica}, - qr/SYNCERROR: mailbox uniqueid changed - retry/ - ); + # sync server should have logged the failure + $self->assert_syslog_matches($self->{replica}, + qr/SYNCERROR: mailbox uniqueid changed - retry/); - $rtalk = $replica_store->get_client(); - $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); - $rid = $rres->{mailboxid}[0]; + $rtalk = $replica_store->get_client(); + $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); + $rid = $rres->{mailboxid}[0]; - $self->assert_str_not_equals($mid, $rid); + $self->assert_str_not_equals($mid, $rid); } diff --git a/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_unused b/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_unused index 79730e640b..bfcf4bcbbe 100644 --- a/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_unused +++ b/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_unused @@ -5,37 +5,36 @@ use Cassandane::Tiny; # Test empty mailbox gets overwritten # sub test_splitbrain_different_uniqueid_unused - :min_version_3_5 :needs_component_replication :NoReplicaonly -{ - my ($self) = @_; + : min_version_3_5 : needs_component_replication : NoReplicaonly { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $mtalk = $master_store->get_client(); - my $rtalk = $replica_store->get_client(); + my $mtalk = $master_store->get_client(); + my $rtalk = $replica_store->get_client(); - $mtalk->create('INBOX.subfolder'); - my $mres = $mtalk->status("INBOX.subfolder", ['mailboxid']); - my $mid = $mres->{mailboxid}[0]; - $rtalk->create('INBOX.subfolder'); - my $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); - my $rid = $rres->{mailboxid}[0]; + $mtalk->create('INBOX.subfolder'); + my $mres = $mtalk->status("INBOX.subfolder", ['mailboxid']); + my $mid = $mres->{mailboxid}[0]; + $rtalk->create('INBOX.subfolder'); + my $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); + my $rid = $rres->{mailboxid}[0]; - $self->assert_not_null($mid); - $self->assert_not_null($rid); - $self->assert_str_not_equals($mid, $rid); + $self->assert_not_null($mid); + $self->assert_not_null($rid); + $self->assert_str_not_equals($mid, $rid); - $master_store->set_folder("INBOX.subfolder"); + $master_store->set_folder("INBOX.subfolder"); - $self->make_message("Message A", store => $master_store); + $self->make_message("Message A", store => $master_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - $rtalk = $replica_store->get_client(); - $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); - $rid = $rres->{mailboxid}[0]; + $rtalk = $replica_store->get_client(); + $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); + $rid = $rres->{mailboxid}[0]; - $self->assert_str_equals($mid, $rid); + $self->assert_str_equals($mid, $rid); } diff --git a/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_used b/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_used index df4c99ade2..e84b78665c 100644 --- a/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_used +++ b/cassandane/tiny-tests/Replication/splitbrain_different_uniqueid_used @@ -5,68 +5,64 @@ use Cassandane::Tiny; # Test mailbox that's had email but is now empty again # sub test_splitbrain_different_uniqueid_used - :min_version_3_5 :needs_component_replication :NoReplicaonly -{ - my ($self) = @_; - - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - - my $mtalk = $master_store->get_client(); - my $rtalk = $replica_store->get_client(); - - $mtalk->create('INBOX.subfolder'); - my $mres = $mtalk->status("INBOX.subfolder", ['mailboxid']); - my $mid = $mres->{mailboxid}[0]; - $rtalk->create('INBOX.subfolder'); - my $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); - my $rid = $rres->{mailboxid}[0]; - - $self->assert_not_null($mid); - $self->assert_not_null($rid); - $self->assert_str_not_equals($mid, $rid); - - $master_store->set_folder("INBOX.subfolder"); - $replica_store->set_folder("INBOX.subfolder"); - - $self->make_message("Message A", store => $master_store); - $self->make_message("Message B", store => $replica_store); - - $rtalk->select('INBOX.subfolder'); - $rtalk->store('1:*', '+flags', '\\Deleted'); - $rtalk->expunge(); - - # this will fail - eval { - $self->run_replication(); - }; - - if ($self->{instance}->{have_syslog_replacement}) { - # sync_client should have logged the failure - my @mlines = $self->{instance}->getsyslog(); - $self->assert_matches(qr/IOERROR: user replication failed/, "@mlines"); - $self->assert_matches(qr/MAILBOX received NO response: IMAP_MAILBOX_MOVED/, "@mlines"); - } - - # sync server should have logged the failure - $self->assert_syslog_matches( - $self->{replica}, - qr/SYNCERROR: mailbox uniqueid changed - retry/ - ); - - $rtalk = $replica_store->get_client(); - $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); - $rid = $rres->{mailboxid}[0]; - - $self->assert_str_not_equals($mid, $rid); - - xlog "Trying again with no-copyback"; - $self->run_replication(nosyncback => 1); - $self->check_replication('cassandane'); - - $rtalk = $replica_store->get_client(); - $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); - $rid = $rres->{mailboxid}[0]; - - $self->assert_str_equals($mid, $rid); + : min_version_3_5 : needs_component_replication : NoReplicaonly { + my ($self) = @_; + + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + + my $mtalk = $master_store->get_client(); + my $rtalk = $replica_store->get_client(); + + $mtalk->create('INBOX.subfolder'); + my $mres = $mtalk->status("INBOX.subfolder", ['mailboxid']); + my $mid = $mres->{mailboxid}[0]; + $rtalk->create('INBOX.subfolder'); + my $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); + my $rid = $rres->{mailboxid}[0]; + + $self->assert_not_null($mid); + $self->assert_not_null($rid); + $self->assert_str_not_equals($mid, $rid); + + $master_store->set_folder("INBOX.subfolder"); + $replica_store->set_folder("INBOX.subfolder"); + + $self->make_message("Message A", store => $master_store); + $self->make_message("Message B", store => $replica_store); + + $rtalk->select('INBOX.subfolder'); + $rtalk->store('1:*', '+flags', '\\Deleted'); + $rtalk->expunge(); + + # this will fail + eval { $self->run_replication(); }; + + if ($self->{instance}->{have_syslog_replacement}) { + # sync_client should have logged the failure + my @mlines = $self->{instance}->getsyslog(); + $self->assert_matches(qr/IOERROR: user replication failed/, "@mlines"); + $self->assert_matches(qr/MAILBOX received NO response: IMAP_MAILBOX_MOVED/, + "@mlines"); + } + + # sync server should have logged the failure + $self->assert_syslog_matches($self->{replica}, + qr/SYNCERROR: mailbox uniqueid changed - retry/); + + $rtalk = $replica_store->get_client(); + $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); + $rid = $rres->{mailboxid}[0]; + + $self->assert_str_not_equals($mid, $rid); + + xlog "Trying again with no-copyback"; + $self->run_replication(nosyncback => 1); + $self->check_replication('cassandane'); + + $rtalk = $replica_store->get_client(); + $rres = $rtalk->status("INBOX.subfolder", ['mailboxid']); + $rid = $rres->{mailboxid}[0]; + + $self->assert_str_equals($mid, $rid); } diff --git a/cassandane/tiny-tests/Replication/splitbrain_mailbox b/cassandane/tiny-tests/Replication/splitbrain_mailbox index e166839168..468bdc5024 100644 --- a/cassandane/tiny-tests/Replication/splitbrain_mailbox +++ b/cassandane/tiny-tests/Replication/splitbrain_mailbox @@ -5,73 +5,73 @@ use Cassandane::Tiny; # Test replication of mailbox only after a rename # sub test_splitbrain_mailbox - :min_version_3_1 :max_version_3_4 :NoAltNameSpace - :needs_component_replication -{ - my ($self) = @_; + : min_version_3_1 : max_version_3_4 : NoAltNameSpace + : needs_component_replication { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - my $mastertalk = $master_store->get_client(); - my $replicatalk = $replica_store->get_client(); + my $mastertalk = $master_store->get_client(); + my $replicatalk = $replica_store->get_client(); - $mastertalk->create("INBOX.src-name"); + $mastertalk->create("INBOX.src-name"); - xlog $self, "run initial replication"; - $self->run_replication(); - $self->check_replication('cassandane'); + xlog $self, "run initial replication"; + $self->run_replication(); + $self->check_replication('cassandane'); - $mastertalk = $master_store->get_client(); - $mastertalk->rename("INBOX.src-name", "INBOX.dest-name"); + $mastertalk = $master_store->get_client(); + $mastertalk->rename("INBOX.src-name", "INBOX.dest-name"); - $self->{instance}->getsyslog(); - $self->{replica}->getsyslog(); + $self->{instance}->getsyslog(); + $self->{replica}->getsyslog(); - xlog $self, "try replicating just the mailbox by name fails due to duplicate uniqueid"; - eval { $self->run_replication(mailbox => 'user.cassandane.dest-name') }; - $self->assert_matches(qr/exited with code 1/, "$@"); + xlog $self, + "try replicating just the mailbox by name fails due to duplicate uniqueid"; + eval { $self->run_replication(mailbox => 'user.cassandane.dest-name') }; + $self->assert_matches(qr/exited with code 1/, "$@"); - my $master_pattern = qr{ + my $master_pattern = qr{ \bMAILBOX\sreceived\sNO\sresponse:\sIMAP_MAILBOX_MOVED\b }x; - $self->assert_syslog_matches($self->{instance}, $master_pattern); + $self->assert_syslog_matches($self->{instance}, $master_pattern); - my $replica_pattern = qr{ + my $replica_pattern = qr{ (?: \bSYNCNOTICE:\sfailed\sto\screate\smailbox \suser\.cassandane\.dest-name\b | \bSYNCNOTICE:\smailbox\suniqueid\salready\sin\suse: \smailbox= ) }x; - $self->assert_syslog_matches($self->{replica}, $replica_pattern); - - xlog $self, "Run a full user replication to repair"; - $self->run_replication(); - $self->check_replication('cassandane'); - - xlog $self, "Rename again"; - $mastertalk = $master_store->get_client(); - $mastertalk->rename("INBOX.dest-name", "INBOX.foo"); - my $file = $self->{instance}->{basedir} . "/sync.log"; - open(FH, ">", $file); - print FH "MAILBOX user.cassandane.foo\n"; - close(FH); - - $self->{instance}->getsyslog(); - $self->{replica}->getsyslog(); - xlog $self, "Run replication from a file with just the mailbox name in it"; - $self->run_replication(inputfile => $file, rolling => 1); - - if ($self->{instance}->{have_syslog_replacement}) { - my @mastersyslog = $self->{instance}->getsyslog(); - my @replicasyslog = $self->{replica}->getsyslog(); - - my $master_pattern = qr{ + $self->assert_syslog_matches($self->{replica}, $replica_pattern); + + xlog $self, "Run a full user replication to repair"; + $self->run_replication(); + $self->check_replication('cassandane'); + + xlog $self, "Rename again"; + $mastertalk = $master_store->get_client(); + $mastertalk->rename("INBOX.dest-name", "INBOX.foo"); + my $file = $self->{instance}->{basedir} . "/sync.log"; + open(FH, ">", $file); + print FH "MAILBOX user.cassandane.foo\n"; + close(FH); + + $self->{instance}->getsyslog(); + $self->{replica}->getsyslog(); + xlog $self, "Run replication from a file with just the mailbox name in it"; + $self->run_replication(inputfile => $file, rolling => 1); + + if ($self->{instance}->{have_syslog_replacement}) { + my @mastersyslog = $self->{instance}->getsyslog(); + my @replicasyslog = $self->{replica}->getsyslog(); + + my $master_pattern = qr{ \bdo_folders\(\):\supdate\sfailed:\suser\.cassandane\.foo\b }x; - my $replica_pattern1 = qr{ + my $replica_pattern1 = qr{ (?: \bSYNCNOTICE:\sfailed\sto\screate\smailbox \suser\.cassandane\.foo\b | \bSYNCNOTICE:\smailbox\suniqueid\salready\sin\suse: @@ -79,17 +79,17 @@ sub test_splitbrain_mailbox ) }x; - my $replica_pattern2 = qr{ + my $replica_pattern2 = qr{ \bRename:\suser.cassandane\.dest-name\s->\suser\.cassandane\.foo\b }x; - # initial failures - $self->assert_matches($master_pattern, "@mastersyslog"); - $self->assert_matches($replica_pattern1, "@replicasyslog"); - # later success - $self->assert_matches($replica_pattern2, "@replicasyslog"); - } + # initial failures + $self->assert_matches($master_pattern, "@mastersyslog"); + $self->assert_matches($replica_pattern1, "@replicasyslog"); + # later success + $self->assert_matches($replica_pattern2, "@replicasyslog"); + } - # replication fixes itself - $self->check_replication('cassandane'); + # replication fixes itself + $self->check_replication('cassandane'); } diff --git a/cassandane/tiny-tests/Replication/splitbrain_masterexpunge b/cassandane/tiny-tests/Replication/splitbrain_masterexpunge index d5da2f3e10..1bdd5aa0f7 100644 --- a/cassandane/tiny-tests/Replication/splitbrain_masterexpunge +++ b/cassandane/tiny-tests/Replication/splitbrain_masterexpunge @@ -5,71 +5,72 @@ use Cassandane::Tiny; # Test replication of messages APPENDed to the master # sub test_splitbrain_masterexpunge - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - xlog $self, "generating messages A..D"; - my %exp; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{B} = $self->make_message("Message B", store => $master_store); - $exp{C} = $self->make_message("Message C", store => $master_store); - $exp{D} = $self->make_message("Message D", store => $master_store); + xlog $self, "generating messages A..D"; + my %exp; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{B} = $self->make_message("Message B", store => $master_store); + $exp{C} = $self->make_message("Message C", store => $master_store); + $exp{D} = $self->make_message("Message D", store => $master_store); - xlog $self, "Before replication, the master should have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "Before replication, the replica should have no messages"; - $self->check_messages({}, store => $replica_store); + xlog $self, "Before replication, the master should have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "Before replication, the replica should have no messages"; + $self->check_messages({}, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, the master should still have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should now have all four messages"; - $self->check_messages(\%exp, store => $replica_store); + xlog $self, + "After replication, the master should still have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, + "After replication, the replica should now have all four messages"; + $self->check_messages(\%exp, store => $replica_store); - my %mexp = %exp; - my %rexp = %exp; + my %mexp = %exp; + my %rexp = %exp; - $mexp{E} = $self->make_message("Message E", store => $master_store); - $rexp{F} = $self->make_message("Message F", store => $replica_store); + $mexp{E} = $self->make_message("Message E", store => $master_store); + $rexp{F} = $self->make_message("Message F", store => $replica_store); - # uid is 5 at both ends - $rexp{F}->set_attribute(uid => 5); + # uid is 5 at both ends + $rexp{F}->set_attribute(uid => 5); - xlog $self, "No replication, the master should have its 5 messages"; - $self->check_messages(\%mexp, store => $master_store); - xlog $self, "No replication, the replica should have the other 5 messages"; - $self->check_messages(\%rexp, store => $replica_store); + xlog $self, "No replication, the master should have its 5 messages"; + $self->check_messages(\%mexp, store => $master_store); + xlog $self, "No replication, the replica should have the other 5 messages"; + $self->check_messages(\%rexp, store => $replica_store); - xlog $self, "Delete and expunge the message on the master"; - my $talk = $master_store->get_client(); - $master_store->_select(); - $talk->store('5', '+flags', '(\\Deleted)'); - $talk->expunge(); - delete $mexp{E}; + xlog $self, "Delete and expunge the message on the master"; + my $talk = $master_store->get_client(); + $master_store->_select(); + $talk->store('5', '+flags', '(\\Deleted)'); + $talk->expunge(); + delete $mexp{E}; - xlog $self, "No replication, the master now only has 4 messages"; - $self->check_messages(\%mexp, store => $master_store); + xlog $self, "No replication, the master now only has 4 messages"; + $self->check_messages(\%mexp, store => $master_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - %exp = (%mexp, %rexp); - # we know that the message should be prompoted to UID 6 - $exp{F}->set_attribute(uid => 6); - xlog $self, "After replication, the master should have all 5 messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should have the same 5 messages"; - $self->check_messages(\%exp, store => $replica_store); + %exp = (%mexp, %rexp); + # we know that the message should be prompoted to UID 6 + $exp{F}->set_attribute(uid => 6); + xlog $self, "After replication, the master should have all 5 messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "After replication, the replica should have the same 5 messages"; + $self->check_messages(\%exp, store => $replica_store); - # We should have generated a SYNCERROR/SYNCNOTICE or two - $self->assert_syslog_matches($self->{instance}, - qr/SYNC(?:ERROR|NOTICE): guid mismatch/); - $self->assert_syslog_matches($self->{replica}, - qr/SYNC(?:ERROR|NOTICE): guid mismatch/); + # We should have generated a SYNCERROR/SYNCNOTICE or two + $self->assert_syslog_matches($self->{instance}, + qr/SYNC(?:ERROR|NOTICE): guid mismatch/); + $self->assert_syslog_matches($self->{replica}, + qr/SYNC(?:ERROR|NOTICE): guid mismatch/); } diff --git a/cassandane/tiny-tests/Replication/splitbrain_replicaexpunge b/cassandane/tiny-tests/Replication/splitbrain_replicaexpunge index 09fce8a687..efbc476477 100644 --- a/cassandane/tiny-tests/Replication/splitbrain_replicaexpunge +++ b/cassandane/tiny-tests/Replication/splitbrain_replicaexpunge @@ -5,69 +5,69 @@ use Cassandane::Tiny; # Test replication of messages APPENDed to the master # sub test_splitbrain_replicaexpunge - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; - xlog $self, "generating messages A..D"; - my %exp; - $exp{A} = $self->make_message("Message A", store => $master_store); - $exp{B} = $self->make_message("Message B", store => $master_store); - $exp{C} = $self->make_message("Message C", store => $master_store); - $exp{D} = $self->make_message("Message D", store => $master_store); + xlog $self, "generating messages A..D"; + my %exp; + $exp{A} = $self->make_message("Message A", store => $master_store); + $exp{B} = $self->make_message("Message B", store => $master_store); + $exp{C} = $self->make_message("Message C", store => $master_store); + $exp{D} = $self->make_message("Message D", store => $master_store); - xlog $self, "Before replication, the master should have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "Before replication, the replica should have no messages"; - $self->check_messages({}, store => $replica_store); + xlog $self, "Before replication, the master should have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "Before replication, the replica should have no messages"; + $self->check_messages({}, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, the master should still have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should now have all four messages"; - $self->check_messages(\%exp, store => $replica_store); + xlog $self, + "After replication, the master should still have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, + "After replication, the replica should now have all four messages"; + $self->check_messages(\%exp, store => $replica_store); - my %mexp = %exp; - my %rexp = %exp; + my %mexp = %exp; + my %rexp = %exp; - $mexp{E} = $self->make_message("Message E", store => $master_store); - $rexp{F} = $self->make_message("Message F", store => $replica_store); + $mexp{E} = $self->make_message("Message E", store => $master_store); + $rexp{F} = $self->make_message("Message F", store => $replica_store); - # uid is 5 at both ends - $rexp{F}->set_attribute(uid => 5); + # uid is 5 at both ends + $rexp{F}->set_attribute(uid => 5); - xlog $self, "No replication, the master should have its 5 messages"; - $self->check_messages(\%mexp, store => $master_store); - xlog $self, "No replication, the replica should have the other 5 messages"; - $self->check_messages(\%rexp, store => $replica_store); + xlog $self, "No replication, the master should have its 5 messages"; + $self->check_messages(\%mexp, store => $master_store); + xlog $self, "No replication, the replica should have the other 5 messages"; + $self->check_messages(\%rexp, store => $replica_store); - xlog $self, "Delete and expunge the message on the master"; - my $rtalk = $replica_store->get_client(); - $replica_store->_select(); - $rtalk->store('5', '+flags', '(\\Deleted)'); - $rtalk->expunge(); - delete $rexp{F}; + xlog $self, "Delete and expunge the message on the master"; + my $rtalk = $replica_store->get_client(); + $replica_store->_select(); + $rtalk->store('5', '+flags', '(\\Deleted)'); + $rtalk->expunge(); + delete $rexp{F}; - xlog $self, "No replication, the replica now only has 4 messages"; - $self->check_messages(\%rexp, store => $replica_store); + xlog $self, "No replication, the replica now only has 4 messages"; + $self->check_messages(\%rexp, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - %exp = (%mexp, %rexp); - # we know that the message should be prompoted to UID 6 - $exp{E}->set_attribute(uid => 6); - xlog $self, "After replication, the master should have all 5 messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should have the same 5 messages"; - $self->check_messages(\%exp, store => $replica_store); + %exp = (%mexp, %rexp); + # we know that the message should be prompoted to UID 6 + $exp{E}->set_attribute(uid => 6); + xlog $self, "After replication, the master should have all 5 messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "After replication, the replica should have the same 5 messages"; + $self->check_messages(\%exp, store => $replica_store); - # We should have generated a SYNCERROR or two - $self->assert_syslog_matches($self->{instance}, - qr/SYNCERROR: guid mismatch/); + # We should have generated a SYNCERROR or two + $self->assert_syslog_matches($self->{instance}, qr/SYNCERROR: guid mismatch/); } diff --git a/cassandane/tiny-tests/Replication/subscriptions b/cassandane/tiny-tests/Replication/subscriptions index b6f2a1e383..5dce24e158 100644 --- a/cassandane/tiny-tests/Replication/subscriptions +++ b/cassandane/tiny-tests/Replication/subscriptions @@ -2,98 +2,71 @@ use Cassandane::Tiny; sub test_subscriptions - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - my $user = 'brandnew'; - $self->{instance}->create_user($user); + my $user = 'brandnew'; + $self->{instance}->create_user($user); - # verify that subs file does not exist on master - # verify that subs file does not exist on replica - $self->assert_user_sub_not_exists($self->{instance}, $user); - $self->assert_user_sub_not_exists($self->{replica}, $user); + # verify that subs file does not exist on master + # verify that subs file does not exist on replica + $self->assert_user_sub_not_exists($self->{instance}, $user); + $self->assert_user_sub_not_exists($self->{replica}, $user); - # set up and verify some subscriptions on master - my $mastersvc = $self->{instance}->get_service('imap'); - my $masterstore = $mastersvc->create_store(username => $user); - my $mastertalk = $masterstore->get_client(); + # set up and verify some subscriptions on master + my $mastersvc = $self->{instance}->get_service('imap'); + my $masterstore = $mastersvc->create_store(username => $user); + my $mastertalk = $masterstore->get_client(); - $mastertalk->create("INBOX.Test") || die; - $mastertalk->create("INBOX.Test.Sub") || die; - $mastertalk->create("INBOX.Test Foo") || die; - $mastertalk->create("INBOX.Test Bar") || die; - $mastertalk->subscribe("INBOX") || die; - $mastertalk->subscribe("INBOX.Test") || die; - $mastertalk->subscribe("INBOX.Test.Sub") || die; - $mastertalk->subscribe("INBOX.Test Foo") || die; - $mastertalk->delete("INBOX.Test.Sub") || die; + $mastertalk->create("INBOX.Test") || die; + $mastertalk->create("INBOX.Test.Sub") || die; + $mastertalk->create("INBOX.Test Foo") || die; + $mastertalk->create("INBOX.Test Bar") || die; + $mastertalk->subscribe("INBOX") || die; + $mastertalk->subscribe("INBOX.Test") || die; + $mastertalk->subscribe("INBOX.Test.Sub") || die; + $mastertalk->subscribe("INBOX.Test Foo") || die; + $mastertalk->delete("INBOX.Test.Sub") || die; - my $subdata = $mastertalk->lsub("", "*"); - $self->assert_deep_equals($subdata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX' - ], - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX.Test' - ], - [ - [], - '.', - 'INBOX.Test Foo' - ], - ]); + my $subdata = $mastertalk->lsub("", "*"); + $self->assert_deep_equals( + $subdata, + [ + [ ['\\HasChildren'], '.', 'INBOX' ], + [ ['\\HasChildren'], '.', 'INBOX.Test' ], + [ [], '.', 'INBOX.Test Foo' ], + ] + ); - # drop the conf dir lock, so the subs get written out - $mastertalk->logout(); + # drop the conf dir lock, so the subs get written out + $mastertalk->logout(); - # verify that subs file exists on master - # verify that subs file does not exist on replica - $self->assert_user_sub_exists($self->{instance}, $user); - $self->assert_user_sub_not_exists($self->{replica}, $user); + # verify that subs file exists on master + # verify that subs file does not exist on replica + $self->assert_user_sub_exists($self->{instance}, $user); + $self->assert_user_sub_not_exists($self->{replica}, $user); - # run replication - $self->run_replication(user => $user); - $self->check_replication($user); + # run replication + $self->run_replication(user => $user); + $self->check_replication($user); - # verify that subs file exists on master - # verify that subs file exists on replica - $self->assert_user_sub_exists($self->{instance}, $user); - $self->assert_user_sub_exists($self->{replica}, $user); + # verify that subs file exists on master + # verify that subs file exists on replica + $self->assert_user_sub_exists($self->{instance}, $user); + $self->assert_user_sub_exists($self->{replica}, $user); - # verify replica store can see subs - my $replicasvc = $self->{replica}->get_service('imap'); - my $replicastore = $replicasvc->create_store(username => $user); - my $replicatalk = $replicastore->get_client(); + # verify replica store can see subs + my $replicasvc = $self->{replica}->get_service('imap'); + my $replicastore = $replicasvc->create_store(username => $user); + my $replicatalk = $replicastore->get_client(); - $subdata = $replicatalk->lsub("", "*"); - $self->assert_deep_equals($subdata, [ - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX' - ], - [ - [ - '\\HasChildren' - ], - '.', - 'INBOX.Test' - ], - [ - [], - '.', - 'INBOX.Test Foo' - ], - ]); + $subdata = $replicatalk->lsub("", "*"); + $self->assert_deep_equals( + $subdata, + [ + [ ['\\HasChildren'], '.', 'INBOX' ], + [ ['\\HasChildren'], '.', 'INBOX.Test' ], + [ [], '.', 'INBOX.Test Foo' ], + ] + ); } diff --git a/cassandane/tiny-tests/Replication/subscriptions_unixhs b/cassandane/tiny-tests/Replication/subscriptions_unixhs index 22ec51250b..10094a911d 100644 --- a/cassandane/tiny-tests/Replication/subscriptions_unixhs +++ b/cassandane/tiny-tests/Replication/subscriptions_unixhs @@ -2,98 +2,71 @@ use Cassandane::Tiny; sub test_subscriptions_unixhs - :UnixHierarchySep :needs_component_replication -{ - my ($self) = @_; + : UnixHierarchySep : needs_component_replication { + my ($self) = @_; - my $user = 'brand.new'; - $self->{instance}->create_user($user); + my $user = 'brand.new'; + $self->{instance}->create_user($user); - # verify that subs file does not exist on master - # verify that subs file does not exist on replica - $self->assert_user_sub_not_exists($self->{instance}, $user); - $self->assert_user_sub_not_exists($self->{replica}, $user); + # verify that subs file does not exist on master + # verify that subs file does not exist on replica + $self->assert_user_sub_not_exists($self->{instance}, $user); + $self->assert_user_sub_not_exists($self->{replica}, $user); - # set up and verify some subscriptions on master - my $mastersvc = $self->{instance}->get_service('imap'); - my $masterstore = $mastersvc->create_store(username => $user); - my $mastertalk = $masterstore->get_client(); + # set up and verify some subscriptions on master + my $mastersvc = $self->{instance}->get_service('imap'); + my $masterstore = $mastersvc->create_store(username => $user); + my $mastertalk = $masterstore->get_client(); - $mastertalk->create("INBOX/Test") || die; - $mastertalk->create("INBOX/Test/Sub") || die; - $mastertalk->create("INBOX/Test Foo") || die; - $mastertalk->create("INBOX/Test Bar") || die; - $mastertalk->subscribe("INBOX") || die; - $mastertalk->subscribe("INBOX/Test") || die; - $mastertalk->subscribe("INBOX/Test/Sub") || die; - $mastertalk->subscribe("INBOX/Test Foo") || die; - $mastertalk->delete("INBOX/Test/Sub") || die; + $mastertalk->create("INBOX/Test") || die; + $mastertalk->create("INBOX/Test/Sub") || die; + $mastertalk->create("INBOX/Test Foo") || die; + $mastertalk->create("INBOX/Test Bar") || die; + $mastertalk->subscribe("INBOX") || die; + $mastertalk->subscribe("INBOX/Test") || die; + $mastertalk->subscribe("INBOX/Test/Sub") || die; + $mastertalk->subscribe("INBOX/Test Foo") || die; + $mastertalk->delete("INBOX/Test/Sub") || die; - my $subdata = $mastertalk->lsub("", "*"); - $self->assert_deep_equals($subdata, [ - [ - [ - '\\HasChildren' - ], - '/', - 'INBOX' - ], - [ - [ - '\\HasChildren' - ], - '/', - 'INBOX/Test' - ], - [ - [], - '/', - 'INBOX/Test Foo' - ], - ]); + my $subdata = $mastertalk->lsub("", "*"); + $self->assert_deep_equals( + $subdata, + [ + [ ['\\HasChildren'], '/', 'INBOX' ], + [ ['\\HasChildren'], '/', 'INBOX/Test' ], + [ [], '/', 'INBOX/Test Foo' ], + ] + ); - # drop the conf dir lock, so the subs get written out - $mastertalk->logout(); + # drop the conf dir lock, so the subs get written out + $mastertalk->logout(); - # verify that subs file exists on master - # verify that subs file does not exist on replica - $self->assert_user_sub_exists($self->{instance}, $user); - $self->assert_user_sub_not_exists($self->{replica}, $user); + # verify that subs file exists on master + # verify that subs file does not exist on replica + $self->assert_user_sub_exists($self->{instance}, $user); + $self->assert_user_sub_not_exists($self->{replica}, $user); - # run replication - $self->run_replication(user => $user); - $self->check_replication($user); + # run replication + $self->run_replication(user => $user); + $self->check_replication($user); - # verify that subs file exists on master - # verify that subs file exists on replica - $self->assert_user_sub_exists($self->{instance}, $user); - $self->assert_user_sub_exists($self->{replica}, $user); + # verify that subs file exists on master + # verify that subs file exists on replica + $self->assert_user_sub_exists($self->{instance}, $user); + $self->assert_user_sub_exists($self->{replica}, $user); - # verify replica store can see subs - my $replicasvc = $self->{replica}->get_service('imap'); - my $replicastore = $replicasvc->create_store(username => $user); - my $replicatalk = $replicastore->get_client(); + # verify replica store can see subs + my $replicasvc = $self->{replica}->get_service('imap'); + my $replicastore = $replicasvc->create_store(username => $user); + my $replicatalk = $replicastore->get_client(); - $subdata = $replicatalk->lsub("", "*"); - $self->assert_deep_equals($subdata, [ - [ - [ - '\\HasChildren' - ], - '/', - 'INBOX' - ], - [ - [ - '\\HasChildren' - ], - '/', - 'INBOX/Test' - ], - [ - [], - '/', - 'INBOX/Test Foo' - ], - ]); + $subdata = $replicatalk->lsub("", "*"); + $self->assert_deep_equals( + $subdata, + [ + [ ['\\HasChildren'], '/', 'INBOX' ], + [ ['\\HasChildren'], '/', 'INBOX/Test' ], + [ [], '/', 'INBOX/Test Foo' ], + ] + ); } diff --git a/cassandane/tiny-tests/Replication/sync_empty_file b/cassandane/tiny-tests/Replication/sync_empty_file index 27fff2e652..e79cee86a5 100644 --- a/cassandane/tiny-tests/Replication/sync_empty_file +++ b/cassandane/tiny-tests/Replication/sync_empty_file @@ -3,16 +3,15 @@ use Cassandane::Tiny; # this is testing a bug where sync_client would abort on zero-length file sub test_sync_empty_file - :DelayedDelete :min_version_3_3 :needs_component_replication -{ - my ($self) = @_; + : DelayedDelete : min_version_3_3 : needs_component_replication { + my ($self) = @_; - $self->run_replication(); + $self->run_replication(); - my $file = $self->{instance}->{basedir} . "/sync.log"; - open(FH, ">", $file); - close(FH); + my $file = $self->{instance}->{basedir} . "/sync.log"; + open(FH, ">", $file); + close(FH); - xlog $self, "Run replication from an empty file"; - $self->run_replication(inputfile => $file, rolling => 1); + xlog $self, "Run replication from an empty file"; + $self->run_replication(inputfile => $file, rolling => 1); } diff --git a/cassandane/tiny-tests/Replication/sync_log_mailbox_with_spaces b/cassandane/tiny-tests/Replication/sync_log_mailbox_with_spaces index cbf88cd9e3..c47858db46 100644 --- a/cassandane/tiny-tests/Replication/sync_log_mailbox_with_spaces +++ b/cassandane/tiny-tests/Replication/sync_log_mailbox_with_spaces @@ -2,67 +2,61 @@ use Cassandane::Tiny; sub test_sync_log_mailbox_with_spaces - :DelayedDelete :NoStartInstances :needs_component_replication -{ - my ($self) = @_; - - my $channel = 'eggplant'; # look it's gotta be called something - - # make sure we get a sync log file in a predictable location - $self->{instance}->{config}->set('sync_log' => 'yes'); - $self->{instance}->{config}->set('sync_log_channels' => $channel); - $self->_start_instances(); - - # make some folders with and without spaces - my $master_store = $self->{master_store}; - my $mastertalk = $master_store->get_client(); - - $mastertalk->create("INBOX.2nd level with spaces"); - $self->assert_str_equals('ok', - $mastertalk->get_last_completion_response()); - - $mastertalk->create("INBOX.foo"); - $self->assert_str_equals('ok', - $mastertalk->get_last_completion_response()); - - $mastertalk->create("INBOX.foo.3rd level with spaces"); - $self->assert_str_equals('ok', - $mastertalk->get_last_completion_response()); - - # make sure the contents of the sync log file are correctly quoted - my $sync_log_fname = $self->{instance}->get_basedir() - . "/conf/sync/$channel/log"; - - open my $fh, '<', $sync_log_fname or die "open $sync_log_fname: $!"; - while (<$fh>) { - # We can take some shortcuts here because we're only testing - # for correct quoting of mailbox names with/without SPACES, - # and not other non-atom characters. - # We're also only acting on mailboxes, so we don't need this - # parser to consider any other log entries. - # An exhaustive test would be more complicated! - chomp; - - if (m/^MAILBOX "(.*)"$/) { - # Argument is quoted! - # We expect that we only added quotes where the single - # mboxname contained spaces, so assert that it contains - # spaces - $self->assert_matches(qr/\s/, $1); - } - elsif (m/^MAILBOX ([^"].*[^"])$/) { - # Argument is not quoted! - # We expect that if there were spaces, it would have been - # quoted, so assert that there are no spaces. - $self->assert_does_not_match(qr/\s/, $1); - } - else { - # something weird here! always assert - $self->assert(undef, "found unrecognised line in sync_log: $_"); - } + : DelayedDelete : NoStartInstances : needs_component_replication { + my ($self) = @_; + + my $channel = 'eggplant'; # look it's gotta be called something + + # make sure we get a sync log file in a predictable location + $self->{instance}->{config}->set('sync_log' => 'yes'); + $self->{instance}->{config}->set('sync_log_channels' => $channel); + $self->_start_instances(); + + # make some folders with and without spaces + my $master_store = $self->{master_store}; + my $mastertalk = $master_store->get_client(); + + $mastertalk->create("INBOX.2nd level with spaces"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + $mastertalk->create("INBOX.foo"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + $mastertalk->create("INBOX.foo.3rd level with spaces"); + $self->assert_str_equals('ok', $mastertalk->get_last_completion_response()); + + # make sure the contents of the sync log file are correctly quoted + my $sync_log_fname + = $self->{instance}->get_basedir() . "/conf/sync/$channel/log"; + + open my $fh, '<', $sync_log_fname or die "open $sync_log_fname: $!"; + while (<$fh>) { + # We can take some shortcuts here because we're only testing + # for correct quoting of mailbox names with/without SPACES, + # and not other non-atom characters. + # We're also only acting on mailboxes, so we don't need this + # parser to consider any other log entries. + # An exhaustive test would be more complicated! + chomp; + + if (m/^MAILBOX "(.*)"$/) { + # Argument is quoted! + # We expect that we only added quotes where the single + # mboxname contained spaces, so assert that it contains + # spaces + $self->assert_matches(qr/\s/, $1); + } elsif (m/^MAILBOX ([^"].*[^"])$/) { + # Argument is not quoted! + # We expect that if there were spaces, it would have been + # quoted, so assert that there are no spaces. + $self->assert_does_not_match(qr/\s/, $1); + } else { + # something weird here! always assert + $self->assert(undef, "found unrecognised line in sync_log: $_"); } - close $fh; + } + close $fh; - # no need to even replicate anything; everything we cared about was - # in the sync log :) + # no need to even replicate anything; everything we cared about was + # in the sync log :) } diff --git a/cassandane/tiny-tests/Replication/syncall_failinguser b/cassandane/tiny-tests/Replication/syncall_failinguser index 7d0ccba745..6751177227 100644 --- a/cassandane/tiny-tests/Replication/syncall_failinguser +++ b/cassandane/tiny-tests/Replication/syncall_failinguser @@ -5,11 +5,10 @@ use Cassandane::Tiny; # Test handling of replication when append fails due to disk error # sub test_syncall_failinguser - :NoStartInstances :min_version_3_6 :needs_component_replication -{ - my ($self) = @_; + : NoStartInstances : min_version_3_6 : needs_component_replication { + my ($self) = @_; - my $canary = << 'EOF'; + my $canary = << 'EOF'; From: Fred J. Bloggs To: Sarah Jane Smith Subject: this is just to say @@ -30,85 +29,90 @@ it was delicious so tweet and so coaled EOF - $canary =~ s/\n/\r\n/g; - my $canaryguid = "f2eaa91974c50ec3cfb530014362e92efb06a9ba"; - - $self->{replica}->{config}->set('debug_writefail_guid' => $canaryguid); - $self->_start_instances(); - - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - - $self->{instance}->create_user("a_early"); - $self->{instance}->create_user("z_late"); - - my $mastersvc = $self->{instance}->get_service('imap'); - my $astore = $mastersvc->create_store(username => "a_early"); - my $zstore = $mastersvc->create_store(username => "z_late"); - my $replicasvc = $self->{replica}->get_service('imap'); - my $replica_astore = $replicasvc->create_store(username => "a_early"); - my $replica_zstore = $replicasvc->create_store(username => "z_late"); - - xlog $self, "Creating a message in each user"; - my %apreexp; - my %cpreexp; - my %zpreexp; - $apreexp{1} = $self->make_message("Message A", store => $astore); - $cpreexp{1} = $self->make_message("Message C", store => $master_store); - $zpreexp{1} = $self->make_message("Message Z", store => $zstore); - - xlog $self, "Running all user replication"; - $self->run_replication(allusers => 1); - - xlog $self, "Creating a second message for each user (cassandane having the canary)"; - my %aexp = %apreexp; - my %cexp = %cpreexp; - my %zexp = %zpreexp; - $aexp{2} = $self->make_message("Message A2", store => $astore); - $cexp{2} = Cassandane::Message->new(raw => $canary, - attrs => { UID => 2 }), + $canary =~ s/\n/\r\n/g; + my $canaryguid = "f2eaa91974c50ec3cfb530014362e92efb06a9ba"; + + $self->{replica}->{config}->set('debug_writefail_guid' => $canaryguid); + $self->_start_instances(); + + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + + $self->{instance}->create_user("a_early"); + $self->{instance}->create_user("z_late"); + + my $mastersvc = $self->{instance}->get_service('imap'); + my $astore = $mastersvc->create_store(username => "a_early"); + my $zstore = $mastersvc->create_store(username => "z_late"); + my $replicasvc = $self->{replica}->get_service('imap'); + my $replica_astore = $replicasvc->create_store(username => "a_early"); + my $replica_zstore = $replicasvc->create_store(username => "z_late"); + + xlog $self, "Creating a message in each user"; + my %apreexp; + my %cpreexp; + my %zpreexp; + $apreexp{1} = $self->make_message("Message A", store => $astore); + $cpreexp{1} = $self->make_message("Message C", store => $master_store); + $zpreexp{1} = $self->make_message("Message Z", store => $zstore); + + xlog $self, "Running all user replication"; + $self->run_replication(allusers => 1); + + xlog $self, + "Creating a second message for each user (cassandane having the canary)"; + my %aexp = %apreexp; + my %cexp = %cpreexp; + my %zexp = %zpreexp; + $aexp{2} = $self->make_message("Message A2", store => $astore); + $cexp{2} = Cassandane::Message->new( + raw => $canary, + attrs => { UID => 2 } + ), $self->_save_message($cexp{2}, $master_store); - $zexp{2} = $self->make_message("Message Z2", store => $zstore); - - xlog $self, "new messages should be on master only"; - $self->check_messages(\%aexp, keyed_on => 'uid', store => $astore); - $self->check_messages(\%apreexp, keyed_on => 'uid', store => $replica_astore); - $self->check_messages(\%cexp, keyed_on => 'uid', store => $master_store); - $self->check_messages(\%cpreexp, keyed_on => 'uid', store => $replica_store); - $self->check_messages(\%zexp, keyed_on => 'uid', store => $zstore); - $self->check_messages(\%zpreexp, keyed_on => 'uid', store => $replica_zstore); - - xlog $self, "running replication..."; - eval { - $self->run_replication(allusers => 1); - }; - my $e = $@; - - # sync_client should have exited with an error - $self->assert($e); - $self->assert_matches(qr/child\sprocess\s + $zexp{2} = $self->make_message("Message Z2", store => $zstore); + + xlog $self, "new messages should be on master only"; + $self->check_messages(\%aexp, keyed_on => 'uid', store => $astore); + $self->check_messages(\%apreexp, keyed_on => 'uid', store => $replica_astore); + $self->check_messages(\%cexp, keyed_on => 'uid', store => $master_store); + $self->check_messages(\%cpreexp, keyed_on => 'uid', store => $replica_store); + $self->check_messages(\%zexp, keyed_on => 'uid', store => $zstore); + $self->check_messages(\%zpreexp, keyed_on => 'uid', store => $replica_zstore); + + xlog $self, "running replication..."; + eval { $self->run_replication(allusers => 1); }; + my $e = $@; + + # sync_client should have exited with an error + $self->assert($e); + $self->assert_matches( + qr/child\sprocess\s \(binary\ssync_client\spid\s\d+\)\s exited\swith\scode/x, - $e->to_string()); + $e->to_string() + ); - # sync_client should have logged the BAD response - $self->assert_syslog_matches($self->{instance}, - qr/IOERROR: received bad response/); + # sync_client should have logged the BAD response + $self->assert_syslog_matches($self->{instance}, + qr/IOERROR: received bad response/); - # sync server should have logged the write error - $self->assert_syslog_matches($self->{replica}, - qr{IOERROR:\sfailed\sto\supload\sfile + # sync server should have logged the write error + $self->assert_syslog_matches( + $self->{replica}, + qr{IOERROR:\sfailed\sto\supload\sfile (?:\s\(simulated\))?:\sguid=<$canaryguid> - }x); - - xlog $self, "Check that cassandane user wasn't updated, both others were"; - $self->check_replication('a_early'); - $self->check_replication('z_late'); - - $self->check_messages(\%aexp, keyed_on => 'uid', store => $astore); - $self->check_messages(\%aexp, keyed_on => 'uid', store => $replica_astore); - $self->check_messages(\%cexp, keyed_on => 'uid', store => $master_store); - $self->check_messages(\%cpreexp, keyed_on => 'uid', store => $replica_store); - $self->check_messages(\%zexp, keyed_on => 'uid', store => $zstore); - $self->check_messages(\%zexp, keyed_on => 'uid', store => $replica_zstore); + }x + ); + + xlog $self, "Check that cassandane user wasn't updated, both others were"; + $self->check_replication('a_early'); + $self->check_replication('z_late'); + + $self->check_messages(\%aexp, keyed_on => 'uid', store => $astore); + $self->check_messages(\%aexp, keyed_on => 'uid', store => $replica_astore); + $self->check_messages(\%cexp, keyed_on => 'uid', store => $master_store); + $self->check_messages(\%cpreexp, keyed_on => 'uid', store => $replica_store); + $self->check_messages(\%zexp, keyed_on => 'uid', store => $zstore); + $self->check_messages(\%zexp, keyed_on => 'uid', store => $replica_zstore); } diff --git a/cassandane/tiny-tests/Replication/toarchive b/cassandane/tiny-tests/Replication/toarchive index a768c0ba7e..d2ac66b1c7 100644 --- a/cassandane/tiny-tests/Replication/toarchive +++ b/cassandane/tiny-tests/Replication/toarchive @@ -2,100 +2,100 @@ use Cassandane::Tiny; sub test_toarchive - :NoStartInstances :ArchivePartition :min_version_3_7 - :needs_component_replication -{ - my ($self) = @_; - - my $repcfg = $self->{replica}->{config}; - $repcfg->set('debug_log_sync_partition_choice' => 'yes'); - $self->_start_instances(); - - my $mtalk = $self->{master_store}->get_client(); - $self->{master_store}->_select(); - $self->assert_num_equals(1, $mtalk->uid()); - $self->{master_store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Append 3 old messages"; - my %msg; - foreach my $id (1..3) { - my $olddate = DateTime->now(); - $olddate->add(DateTime::Duration->new(months => -4 + $id)); - - $msg{$id} = $self->make_message("Message $id", - date => $olddate, - store => $self->{master_store}); - $msg{$id}->set_attributes(id => $id, - uid => $id, - flags => []); + : NoStartInstances : ArchivePartition : min_version_3_7 + : needs_component_replication { + my ($self) = @_; + + my $repcfg = $self->{replica}->{config}; + $repcfg->set('debug_log_sync_partition_choice' => 'yes'); + $self->_start_instances(); + + my $mtalk = $self->{master_store}->get_client(); + $self->{master_store}->_select(); + $self->assert_num_equals(1, $mtalk->uid()); + $self->{master_store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Append 3 old messages"; + my %msg; + foreach my $id (1 .. 3) { + my $olddate = DateTime->now(); + $olddate->add(DateTime::Duration->new(months => -4 + $id)); + + $msg{$id} = $self->make_message( + "Message $id", + date => $olddate, + store => $self->{master_store} + ); + $msg{$id}->set_attributes( + id => $id, + uid => $id, + flags => [] + ); + } + + xlog $self, "Append 3 current messages"; + foreach my $id (4 .. 6) { + $msg{$id} + = $self->make_message("Message $id", store => $self->{master_store}); + $msg{$id}->set_attributes( + id => $id, + uid => $id, + flags => [] + ); + } + + xlog $self, "Run cyr_expire to archive old messages"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d'); + + $self->check_messages(\%msg); + + my $mbpath = $self->{instance}->run_mbpath('-u', 'cassandane'); + my $mdatadir = $mbpath->{data}; + my $marchivedir = $mbpath->{archive}; + + foreach my $id (1 .. 6) { + if ($id > 3) { + $self->assert_file_test("$mdatadir/$id.", '-f'); + $self->assert_not_file_test("$marchivedir/$id.", '-f'); + } else { + $self->assert_not_file_test("$mdatadir/$id.", '-f'); + $self->assert_file_test("$marchivedir/$id.", '-f'); } + } - xlog $self, "Append 3 current messages"; - foreach my $id (4..6) { - $msg{$id} = $self->make_message("Message $id", - store => $self->{master_store}); - $msg{$id}->set_attributes(id => $id, - uid => $id, - flags => []); - } - - xlog $self, "Run cyr_expire to archive old messages"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d' ); - - $self->check_messages(\%msg); - - my $mbpath = $self->{instance}->run_mbpath('-u', 'cassandane'); - my $mdatadir = $mbpath->{data}; - my $marchivedir = $mbpath->{archive}; + $self->{replica}->getsyslog(); # discard setup noise - foreach my $id (1..6) { - if ($id > 3) { - $self->assert_file_test("$mdatadir/$id.", '-f'); - $self->assert_not_file_test("$marchivedir/$id.", '-f'); - } - else { - $self->assert_not_file_test("$mdatadir/$id.", '-f'); - $self->assert_file_test("$marchivedir/$id.", '-f'); - } - } - - $self->{replica}->getsyslog(); # discard setup noise + xlog $self, "Run replication, staging on archive partition"; + $self->run_replication('stagetoarchive' => 1); + $self->check_replication('cassandane'); - xlog $self, "Run replication, staging on archive partition"; - $self->run_replication('stagetoarchive' => 1); - $self->check_replication('cassandane'); + # ensure we made the correct choices about how to stage + if ($self->{replica}->{have_syslog_replacement}) { + my $part = $repcfg->substitute($repcfg->get('archivepartition-default')); - # ensure we made the correct choices about how to stage - if ($self->{replica}->{have_syslog_replacement}) { - my $part = $repcfg->substitute( - $repcfg->get('archivepartition-default') - ); + my @choices = $self->{replica} + ->getsyslog(qr{debug_log_sync_partition_choice: chose reserve path}); - my @choices = $self->{replica}->getsyslog(qr{debug_log_sync_partition_choice: chose reserve path}); + $self->assert_num_equals(scalar(keys %msg), scalar @choices); - $self->assert_num_equals(scalar(keys %msg), scalar @choices); - - foreach my $choice (@choices) { - $self->assert_matches(qr{\bbase=<$part>}, - $choice); - $self->assert_matches(qr{\breserve_path=<$part/sync\./}, - $choice); - } + foreach my $choice (@choices) { + $self->assert_matches(qr{\bbase=<$part>}, $choice); + $self->assert_matches(qr{\breserve_path=<$part/sync\./}, $choice); } - - # ensure that data ends up in the correct places - $mbpath = $self->{replica}->run_mbpath('-u', 'cassandane'); - my $rdatadir = $mbpath->{data}; - my $rarchivedir = $mbpath->{archive}; - - foreach my $id (1..6) { - if ($id > 3) { - $self->assert_file_test("$rdatadir/$id.", '-f'); - $self->assert_not_file_test("$rarchivedir/$id.", '-f'); - } - else { - $self->assert_not_file_test("$rdatadir/$id.", '-f'); - $self->assert_file_test("$rarchivedir/$id.", '-f'); - } + } + + # ensure that data ends up in the correct places + $mbpath = $self->{replica}->run_mbpath('-u', 'cassandane'); + my $rdatadir = $mbpath->{data}; + my $rarchivedir = $mbpath->{archive}; + + foreach my $id (1 .. 6) { + if ($id > 3) { + $self->assert_file_test("$rdatadir/$id.", '-f'); + $self->assert_not_file_test("$rarchivedir/$id.", '-f'); + } else { + $self->assert_not_file_test("$rdatadir/$id.", '-f'); + $self->assert_file_test("$rarchivedir/$id.", '-f'); } + } } diff --git a/cassandane/tiny-tests/Replication/toarchive_noarchive b/cassandane/tiny-tests/Replication/toarchive_noarchive index e1a36618ab..3dec3b03af 100644 --- a/cassandane/tiny-tests/Replication/toarchive_noarchive +++ b/cassandane/tiny-tests/Replication/toarchive_noarchive @@ -2,97 +2,99 @@ use Cassandane::Tiny; sub test_toarchive_noarchive - :NoStartInstances :min_version_3_7 :needs_component_replication -{ - my ($self) = @_; - - my $repcfg = $self->{replica}->{config}; - $repcfg->set('debug_log_sync_partition_choice' => 'yes'); - $self->_start_instances(); - - my $mtalk = $self->{master_store}->get_client(); - $self->{master_store}->_select(); - $self->assert_num_equals(1, $mtalk->uid()); - $self->{master_store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Append 3 old messages"; - my %msg; - foreach my $id (1..3) { - my $olddate = DateTime->now(); - $olddate->add(DateTime::Duration->new(months => -4 + $id)); - - $msg{$id} = $self->make_message("Message $id", - date => $olddate, - store => $self->{master_store}); - $msg{$id}->set_attributes(id => $id, - uid => $id, - flags => []); + : NoStartInstances : min_version_3_7 : needs_component_replication { + my ($self) = @_; + + my $repcfg = $self->{replica}->{config}; + $repcfg->set('debug_log_sync_partition_choice' => 'yes'); + $self->_start_instances(); + + my $mtalk = $self->{master_store}->get_client(); + $self->{master_store}->_select(); + $self->assert_num_equals(1, $mtalk->uid()); + $self->{master_store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Append 3 old messages"; + my %msg; + foreach my $id (1 .. 3) { + my $olddate = DateTime->now(); + $olddate->add(DateTime::Duration->new(months => -4 + $id)); + + $msg{$id} = $self->make_message( + "Message $id", + date => $olddate, + store => $self->{master_store} + ); + $msg{$id}->set_attributes( + id => $id, + uid => $id, + flags => [] + ); + } + + xlog $self, "Append 3 current messages"; + foreach my $id (4 .. 6) { + $msg{$id} + = $self->make_message("Message $id", store => $self->{master_store}); + $msg{$id}->set_attributes( + id => $id, + uid => $id, + flags => [] + ); + } + + xlog $self, "Run cyr_expire to archive old messages"; + $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d'); + + $self->check_messages(\%msg); + + my $mbpath = $self->{instance}->run_mbpath('-u', 'cassandane'); + my $mdatadir = $mbpath->{data}; + my $marchivedir = $mbpath->{archive}; + + # expect data and archive should be the same + $self->assert_str_equals($mdatadir, $marchivedir); + + # all messages in same place + foreach my $id (1 .. 6) { + $self->assert_file_test("$mdatadir/$id.", '-f'); + } + + $self->{replica}->getsyslog(); # discard setup noise + + xlog $self, "Run replication, staging on archive partition"; + $self->run_replication('stagetoarchive' => 1); + $self->check_replication('cassandane'); + + # ensure we made the correct choices about how to stage + if ($self->{replica}->{have_syslog_replacement}) { + my $part = $repcfg->substitute($repcfg->get('partition-default')); + + my @choices = $self->{replica} + ->getsyslog(qr{debug_log_sync_partition_choice: chose reserve path}); + + $self->assert_num_equals(scalar(keys %msg), scalar @choices); + + foreach my $choice (@choices) { + $self->assert_matches(qr{\bbase=<$part>}, $choice); + $self->assert_matches(qr{\breserve_path=<$part/sync\./}, $choice); } + } - xlog $self, "Append 3 current messages"; - foreach my $id (4..6) { - $msg{$id} = $self->make_message("Message $id", - store => $self->{master_store}); - $msg{$id}->set_attributes(id => $id, - uid => $id, - flags => []); - } - - xlog $self, "Run cyr_expire to archive old messages"; - $self->{instance}->run_command({ cyrus => 1 }, 'cyr_expire', '-A' => '7d' ); - - $self->check_messages(\%msg); - - my $mbpath = $self->{instance}->run_mbpath('-u', 'cassandane'); - my $mdatadir = $mbpath->{data}; - my $marchivedir = $mbpath->{archive}; - - # expect data and archive should be the same - $self->assert_str_equals($mdatadir, $marchivedir); - - # all messages in same place - foreach my $id (1..6) { - $self->assert_file_test("$mdatadir/$id.", '-f'); - } - - $self->{replica}->getsyslog(); # discard setup noise - - xlog $self, "Run replication, staging on archive partition"; - $self->run_replication('stagetoarchive' => 1); - $self->check_replication('cassandane'); + # ensure that data ends up in the correct places + $mbpath = $self->{replica}->run_mbpath('-u', 'cassandane'); + my $rdatadir = $mbpath->{data}; + my $rarchivedir = $mbpath->{archive}; - # ensure we made the correct choices about how to stage - if ($self->{replica}->{have_syslog_replacement}) { - my $part = $repcfg->substitute( - $repcfg->get('partition-default') - ); + # expect data and archive should be the same + $self->assert_str_equals($rdatadir, $rarchivedir); - my @choices = $self->{replica}->getsyslog(qr{debug_log_sync_partition_choice: chose reserve path}); + # all messages in same place + foreach my $id (1 .. 6) { + $self->assert_file_test("$rdatadir/$id.", '-f'); + } - $self->assert_num_equals(scalar(keys %msg), scalar @choices); - - foreach my $choice (@choices) { - $self->assert_matches(qr{\bbase=<$part>}, - $choice); - $self->assert_matches(qr{\breserve_path=<$part/sync\./}, - $choice); - } - } - - # ensure that data ends up in the correct places - $mbpath = $self->{replica}->run_mbpath('-u', 'cassandane'); - my $rdatadir = $mbpath->{data}; - my $rarchivedir = $mbpath->{archive}; - - # expect data and archive should be the same - $self->assert_str_equals($rdatadir, $rarchivedir); - - # all messages in same place - foreach my $id (1..6) { - $self->assert_file_test("$rdatadir/$id.", '-f'); - } - - foreach my $id (1..6) { - $self->assert_file_test("$rdatadir/$id.", '-f'); - } + foreach my $id (1 .. 6) { + $self->assert_file_test("$rdatadir/$id.", '-f'); + } } diff --git a/cassandane/tiny-tests/Replication/userflags b/cassandane/tiny-tests/Replication/userflags index 531d3033cd..339c550196 100644 --- a/cassandane/tiny-tests/Replication/userflags +++ b/cassandane/tiny-tests/Replication/userflags @@ -5,94 +5,95 @@ use Cassandane::Tiny; # Test replication of user-defined flags # sub test_userflags - :needs_component_replication -{ - my ($self) = @_; + : needs_component_replication { + my ($self) = @_; - my $master_store = $self->{master_store}; - my $replica_store = $self->{replica_store}; - $master_store->set_fetch_attributes(qw(uid flags)); - $replica_store->set_fetch_attributes(qw(uid flags)); + my $master_store = $self->{master_store}; + my $replica_store = $self->{replica_store}; + $master_store->set_fetch_attributes(qw(uid flags)); + $replica_store->set_fetch_attributes(qw(uid flags)); - xlog $self, "generating messages A..D"; - my %exp; - $exp{A} = $self->make_message("Message A", - flags => ["\\Flagged", '$UserFlagA'], - store => $master_store); - $exp{B} = $self->make_message("Message B", - flags => [ '$UserFlagB' ], - store => $master_store); - $exp{C} = $self->make_message("Message C", - flags => [ '$UserFlagC' ], - store => $master_store); - $exp{D} = $self->make_message("Message D", - flags => [ '$UserFlagD' ], - store => $master_store); + xlog $self, "generating messages A..D"; + my %exp; + $exp{A} = $self->make_message( + "Message A", + flags => [ "\\Flagged", '$UserFlagA' ], + store => $master_store + ); + $exp{B} = $self->make_message( + "Message B", + flags => ['$UserFlagB'], + store => $master_store + ); + $exp{C} = $self->make_message( + "Message C", + flags => ['$UserFlagC'], + store => $master_store + ); + $exp{D} = $self->make_message( + "Message D", + flags => ['$UserFlagD'], + store => $master_store + ); - my $master_talk = $master_store->get_client(); + my $master_talk = $master_store->get_client(); - xlog $self, "master PERMANENTFLAGS response should have all four flags"; - my $perm = $master_talk->get_response_code('permanentflags'); - my @flags = sort grep { !m{^\\} } @$perm; - $self->assert_deep_equals([ '$UserFlagA', - '$UserFlagB', - '$UserFlagC', - '$UserFlagD' ], \@flags); + xlog $self, "master PERMANENTFLAGS response should have all four flags"; + my $perm = $master_talk->get_response_code('permanentflags'); + my @flags = sort grep { !m{^\\} } @$perm; + $self->assert_deep_equals( + [ '$UserFlagA', '$UserFlagB', '$UserFlagC', '$UserFlagD' ], \@flags); - xlog $self, "clear some flags on master before replica ever sees them"; - $master_talk->store('1:4', '-flags', '($UserFlagC $UserFlagD)'); - $exp{C}->set_attribute(flags => undef); - $exp{D}->set_attribute(flags => undef); + xlog $self, "clear some flags on master before replica ever sees them"; + $master_talk->store('1:4', '-flags', '($UserFlagC $UserFlagD)'); + $exp{C}->set_attribute(flags => undef); + $exp{D}->set_attribute(flags => undef); - xlog $self, "master PERMANENTFLAGS response should still have all flags"; - $perm = $master_talk->get_response_code('permanentflags'); - @flags = sort grep { !m{^\\} } @$perm; - $self->assert_deep_equals([ '$UserFlagA', - '$UserFlagB', - '$UserFlagC', - '$UserFlagD' ], \@flags); + xlog $self, "master PERMANENTFLAGS response should still have all flags"; + $perm = $master_talk->get_response_code('permanentflags'); + @flags = sort grep { !m{^\\} } @$perm; + $self->assert_deep_equals( + [ '$UserFlagA', '$UserFlagB', '$UserFlagC', '$UserFlagD' ], \@flags); - my $replica_talk = $replica_store->get_client(); + my $replica_talk = $replica_store->get_client(); - xlog $self, "replica PERMANENTFLAGS response should have no userflags"; - $perm = $replica_talk->get_response_code('permanentflags'); - @flags = sort grep { !m{^\\} } @$perm; - $self->assert_deep_equals([], \@flags); + xlog $self, "replica PERMANENTFLAGS response should have no userflags"; + $perm = $replica_talk->get_response_code('permanentflags'); + @flags = sort grep { !m{^\\} } @$perm; + $self->assert_deep_equals([], \@flags); - xlog $self, "Before replication, the master should have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "Before replication, the replica should have no messages"; - $self->check_messages({}, store => $replica_store); + xlog $self, "Before replication, the master should have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, "Before replication, the replica should have no messages"; + $self->check_messages({}, store => $replica_store); - $self->run_replication(); - $self->check_replication('cassandane'); + $self->run_replication(); + $self->check_replication('cassandane'); - xlog $self, "After replication, the master should still have all four messages"; - $self->check_messages(\%exp, store => $master_store); - xlog $self, "After replication, the replica should now have all four messages"; - $self->check_messages(\%exp, store => $replica_store); + xlog $self, + "After replication, the master should still have all four messages"; + $self->check_messages(\%exp, store => $master_store); + xlog $self, + "After replication, the replica should now have all four messages"; + $self->check_messages(\%exp, store => $replica_store); - xlog $self, "master PERMANENTFLAGS response should still have all flags"; - $master_store->disconnect(); - $master_store->connect(); - $master_store->_select(); - $master_talk = $master_store->get_client(); - $perm = $master_talk->get_response_code('permanentflags'); - @flags = sort grep { !m{^\\} } @$perm; - $self->assert_deep_equals([ '$UserFlagA', - '$UserFlagB', - '$UserFlagC', - '$UserFlagD' ], \@flags); + xlog $self, "master PERMANENTFLAGS response should still have all flags"; + $master_store->disconnect(); + $master_store->connect(); + $master_store->_select(); + $master_talk = $master_store->get_client(); + $perm = $master_talk->get_response_code('permanentflags'); + @flags = sort grep { !m{^\\} } @$perm; + $self->assert_deep_equals( + [ '$UserFlagA', '$UserFlagB', '$UserFlagC', '$UserFlagD' ], \@flags); - xlog $self, "replica PERMANENTFLAGS response should now have all flags"; - $replica_store->disconnect(); - $replica_store->connect(); - $replica_store->_select(); - $replica_talk = $replica_store->get_client(); - $perm = $replica_talk->get_response_code('permanentflags'); - @flags = sort grep { !m{^\\} } @$perm; - $self->assert_deep_equals([ '$UserFlagA', - '$UserFlagB', - '$UserFlagC', - '$UserFlagD' ], \@flags); + xlog $self, "replica PERMANENTFLAGS response should now have all flags"; + $replica_store->disconnect(); + $replica_store->connect(); + $replica_store->_select(); + $replica_talk = $replica_store->get_client(); + $perm = $replica_talk->get_response_code('permanentflags'); + @flags = sort grep { !m{^\\} } @$perm; + $self->assert_deep_equals( + [ '$UserFlagA', '$UserFlagB', '$UserFlagC', '$UserFlagD' ], \@flags); } diff --git a/cassandane/tiny-tests/Replication/userprefix b/cassandane/tiny-tests/Replication/userprefix index 2556ee6e54..7e4ea3b164 100644 --- a/cassandane/tiny-tests/Replication/userprefix +++ b/cassandane/tiny-tests/Replication/userprefix @@ -5,40 +5,39 @@ use Cassandane::Tiny; # wasn't correctly looking only for children of that name, so it would try # to delete the wrong user's mailbox. sub test_userprefix - :DelayedDelete :needs_component_replication -{ - my ($self) = @_; - $self->{instance}->create_user("ua"); - $self->{instance}->create_user("uab"); + : DelayedDelete : needs_component_replication { + my ($self) = @_; + $self->{instance}->create_user("ua"); + $self->{instance}->create_user("uab"); - my $mastersvc = $self->{instance}->get_service('imap'); - my $astore = $mastersvc->create_store(username => "ua"); - my $atalk = $astore->get_client(); - my $bstore = $mastersvc->create_store(username => "uab"); - my $btalk = $bstore->get_client(); + my $mastersvc = $self->{instance}->get_service('imap'); + my $astore = $mastersvc->create_store(username => "ua"); + my $atalk = $astore->get_client(); + my $bstore = $mastersvc->create_store(username => "uab"); + my $btalk = $bstore->get_client(); - xlog "Creating some users with some deleted mailboxes"; - $atalk->create("INBOX.hi"); - $atalk->create("INBOX.no"); - $atalk->delete("INBOX.hi"); + xlog "Creating some users with some deleted mailboxes"; + $atalk->create("INBOX.hi"); + $atalk->create("INBOX.no"); + $atalk->delete("INBOX.hi"); - $btalk->create("INBOX.boo"); - $btalk->create("INBOX.noo"); - $btalk->delete("INBOX.boo"); + $btalk->create("INBOX.boo"); + $btalk->create("INBOX.noo"); + $btalk->delete("INBOX.boo"); - $self->run_replication(user => "ua"); - $self->run_replication(user => "uab"); + $self->run_replication(user => "ua"); + $self->run_replication(user => "uab"); - my $masterstore = $mastersvc->create_store(username => 'admin'); - my $admintalk = $masterstore->get_client(); + my $masterstore = $mastersvc->create_store(username => 'admin'); + my $admintalk = $masterstore->get_client(); - xlog "Deleting the user with the prefix name"; - $admintalk->delete("user.ua"); - $self->run_replication(user => "ua"); - $self->run_replication(user => "uab"); - # This would fail at the end with syslog IOERRORs before the bugfix: - # >1580698085>S1 SYNCAPPLY UNUSER ua - # <1580698085<* BYE Fatal error: Internal error: assertion failed: imap/mboxlist.c: 868: user_isnamespacelocked(userid) - # 0248020101/sync_client[20041]: IOERROR: UNUSER received * response: - # Error from sync_do_user(ua): bailing out! + xlog "Deleting the user with the prefix name"; + $admintalk->delete("user.ua"); + $self->run_replication(user => "ua"); + $self->run_replication(user => "uab"); +# This would fail at the end with syslog IOERRORs before the bugfix: +# >1580698085>S1 SYNCAPPLY UNUSER ua +# <1580698085<* BYE Fatal error: Internal error: assertion failed: imap/mboxlist.c: 868: user_isnamespacelocked(userid) +# 0248020101/sync_client[20041]: IOERROR: UNUSER received * response: +# Error from sync_do_user(ua): bailing out! } diff --git a/cassandane/tiny-tests/SearchFuzzy/audit_unindexed b/cassandane/tiny-tests/SearchFuzzy/audit_unindexed index 34e971c1c4..2f627ed5b2 100644 --- a/cassandane/tiny-tests/SearchFuzzy/audit_unindexed +++ b/cassandane/tiny-tests/SearchFuzzy/audit_unindexed @@ -2,62 +2,70 @@ use Cassandane::Tiny; sub test_audit_unindexed - :min_version_3_1 :needs_component_jmap -{ - # This test does some sneaky things to cyrus.indexed.db to force squatter - # report audit errors. It assumes a specific format for cyrus.indexed.db - # and Cyrus to preserve UIDVALDITY across two consecutive APPENDs. - # As such, it's likely to break for internal changes. + : min_version_3_1 : needs_component_jmap { + # This test does some sneaky things to cyrus.indexed.db to force squatter + # report audit errors. It assumes a specific format for cyrus.indexed.db + # and Cyrus to preserve UIDVALDITY across two consecutive APPENDs. + # As such, it's likely to break for internal changes. - my ($self) = @_; + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $basedir = $self->{instance}->{basedir}; - my $outfile = "$basedir/audit.tmp"; + my $basedir = $self->{instance}->{basedir}; + my $outfile = "$basedir/audit.tmp"; - *_readfile = sub { - open FH, '<', $outfile - or die "Cannot open $outfile for reading: $!"; - my @entries = readline(FH); - close FH; - return @entries; - }; + *_readfile = sub { + open FH, '<', $outfile + or die "Cannot open $outfile for reading: $!"; + my @entries = readline(FH); + close FH; + return @entries; + }; - xlog $self, "Create message UID 1 and index it in Xapian and cyrus.indexed.db."; - $self->make_message() || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, + "Create message UID 1 and index it in Xapian and cyrus.indexed.db."; + $self->make_message() || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog $self, "Create message UID 2 but *don't* index it."; - $self->make_message() || die; + xlog $self, "Create message UID 2 but *don't* index it."; + $self->make_message() || die; - my $data = $self->{instance}->run_mbpath(-u => 'cassandane'); - my $xapdir = $data->{xapian}{t1}; + my $data = $self->{instance}->run_mbpath(-u => 'cassandane'); + my $xapdir = $data->{xapian}{t1}; - xlog $self, "Read current cyrus.indexed.db."; - my ($key, $val); - my $result = $self->{instance}->run_dbcommand_cb(sub { + xlog $self, "Read current cyrus.indexed.db."; + my ($key, $val); + my $result = $self->{instance}->run_dbcommand_cb( + sub { my ($k, $v) = @_; return if $k =~ m/\*V\*/; $self->assert_null($key); ($key, $val) = ($k, $v); - }, "$xapdir/xapian/cyrus.indexed.db", "twoskip", ['SHOW']); - $self->assert_str_equals('ok', $result); - $self->assert_not_null($key); - $self->assert_not_null($val); - - xlog $self, "Add UID 2 to sequence set in cyrus.indexed.db"; - $self->{instance}->run_dbcommand("$xapdir/xapian/cyrus.indexed.db", "twoskip", ['SET', $key, $val . ':2']); - - xlog $self, "Run squatter audit"; - $result = $self->{instance}->run_command( - { - cyrus => 1, - redirects => { stdout => $outfile }, - }, - 'squatter', '-A' - ); - my @audits = _readfile(); - $self->assert_num_equals(1, scalar @audits); - $self->assert_str_equals("Unindexed message(s) in user.cassandane: 2 \n", $audits[0]); + }, + "$xapdir/xapian/cyrus.indexed.db", + "twoskip", + ['SHOW'] + ); + $self->assert_str_equals('ok', $result); + $self->assert_not_null($key); + $self->assert_not_null($val); + + xlog $self, "Add UID 2 to sequence set in cyrus.indexed.db"; + $self->{instance}->run_dbcommand("$xapdir/xapian/cyrus.indexed.db", + "twoskip", [ 'SET', $key, $val . ':2' ]); + + xlog $self, "Run squatter audit"; + $result = $self->{instance}->run_command( + { + cyrus => 1, + redirects => { stdout => $outfile }, + }, + 'squatter', + '-A' + ); + my @audits = _readfile(); + $self->assert_num_equals(1, scalar @audits); + $self->assert_str_equals("Unindexed message(s) in user.cassandane: 2 \n", + $audits[0]); } diff --git a/cassandane/tiny-tests/SearchFuzzy/cjk_words b/cassandane/tiny-tests/SearchFuzzy/cjk_words index 87b9b62aa1..e8c757c9e1 100644 --- a/cassandane/tiny-tests/SearchFuzzy/cjk_words +++ b/cassandane/tiny-tests/SearchFuzzy/cjk_words @@ -2,88 +2,88 @@ use Cassandane::Tiny; sub test_cjk_words - :min_version_3_0 :needs_search_xapian - :needs_search_xapian_cjk_tokens(words) -{ - my ($self) = @_; + : min_version_3_0 : needs_search_xapian + : needs_search_xapian_cjk_tokens(words) { + my ($self) = @_; - xlog $self, "Generate and index test messages."; + xlog $self, "Generate and index test messages."; -use utf8; - my $body = "明末時已經有香港地方的概念"; -no utf8; - $body = encode_base64(encode('UTF-8', $body)); - $body =~ s/\r?\n/\r\n/gs; - my %params = ( - mime_charset => "utf-8", - mime_encoding => 'base64', - body => $body, - ); - $self->make_message("1", %params) || die; + use utf8; + my $body = "明末時已經有香港地方的概念"; + no utf8; + $body = encode_base64(encode('UTF-8', $body)); + $body =~ s/\r?\n/\r\n/gs; + my %params = ( + mime_charset => "utf-8", + mime_encoding => 'base64', + body => $body, + ); + $self->make_message("1", %params) || die; - # Splits into the words: "み, 円, 月額, 申込 -use utf8; - $body = "申込み!月額円"; -no utf8; - $body = encode_base64(encode('UTF-8', $body)); - $body =~ s/\r?\n/\r\n/gs; - %params = ( - mime_charset => "utf-8", - mime_encoding => 'base64', - body => $body, - ); - $self->make_message("2", %params) || die; + # Splits into the words: "み, 円, 月額, 申込 + use utf8; + $body = "申込み!月額円"; + no utf8; + $body = encode_base64(encode('UTF-8', $body)); + $body =~ s/\r?\n/\r\n/gs; + %params = ( + mime_charset => "utf-8", + mime_encoding => 'base64', + body => $body, + ); + $self->make_message("2", %params) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - # Connect to IMAP - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + # Connect to IMAP + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - my $term; - # Search for a two-character CJK word -use utf8; - $term = "已經"; -no utf8; - xlog $self, "Get snippets for FUZZY text \"$term\""; - $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); + my $term; + # Search for a two-character CJK word + use utf8; + $term = "已經"; + no utf8; + xlog $self, "Get snippets for FUZZY text \"$term\""; + $r = $self->get_snippets('INBOX', $uids, { text => $term }); + $self->assert_num_not_equals( + index($r->{snippets}[0][3], "$term"), -1); - # Search for the CJK words 明末 and 時, note that the - # word order is reversed to the original message -use utf8; - $term = "時明末"; -no utf8; - xlog $self, "Get snippets for FUZZY text \"$term\""; - $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 1); + # Search for the CJK words 明末 and 時, note that the + # word order is reversed to the original message + use utf8; + $term = "時明末"; + no utf8; + xlog $self, "Get snippets for FUZZY text \"$term\""; + $r = $self->get_snippets('INBOX', $uids, { text => $term }); + $self->assert_num_equals(scalar @{ $r->{snippets} }, 1); - # Search for the partial CJK word 月 -use utf8; - $term = "月"; -no utf8; - xlog $self, "Get snippets for FUZZY text \"$term\""; - $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 0); + # Search for the partial CJK word 月 + use utf8; + $term = "月"; + no utf8; + xlog $self, "Get snippets for FUZZY text \"$term\""; + $r = $self->get_snippets('INBOX', $uids, { text => $term }); + $self->assert_num_equals(scalar @{ $r->{snippets} }, 0); - # Search for the interleaved, partial CJK word 額申 -use utf8; - $term = "額申"; -no utf8; - xlog $self, "Get snippets for FUZZY text \"$term\""; - $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 0); + # Search for the interleaved, partial CJK word 額申 + use utf8; + $term = "額申"; + no utf8; + xlog $self, "Get snippets for FUZZY text \"$term\""; + $r = $self->get_snippets('INBOX', $uids, { text => $term }); + $self->assert_num_equals(scalar @{ $r->{snippets} }, 0); - # Search for three of four words: "み, 月額, 申込", - # in different order than the original. -use utf8; - $term = "月額み申込"; -no utf8; - xlog $self, "Get snippets for FUZZY text \"$term\""; - $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 1); + # Search for three of four words: "み, 月額, 申込", + # in different order than the original. + use utf8; + $term = "月額み申込"; + no utf8; + xlog $self, "Get snippets for FUZZY text \"$term\""; + $r = $self->get_snippets('INBOX', $uids, { text => $term }); + $self->assert_num_equals(scalar @{ $r->{snippets} }, 1); } diff --git a/cassandane/tiny-tests/SearchFuzzy/copy_messages b/cassandane/tiny-tests/SearchFuzzy/copy_messages index 038c25d50a..39c795bfe9 100644 --- a/cassandane/tiny-tests/SearchFuzzy/copy_messages +++ b/cassandane/tiny-tests/SearchFuzzy/copy_messages @@ -2,17 +2,16 @@ use Cassandane::Tiny; sub test_copy_messages - :needs_search_xapian -{ - my ($self) = @_; + : needs_search_xapian { + my ($self) = @_; - $self->create_testmessages(); + $self->create_testmessages(); - my $talk = $self->{store}->get_client(); - $talk->create("INBOX.foo"); - $talk->select("INBOX"); - $talk->copy("1:*", "INBOX.foo"); + my $talk = $self->{store}->get_client(); + $talk->create("INBOX.foo"); + $talk->select("INBOX"); + $talk->copy("1:*", "INBOX.foo"); - xlog $self, "Run squatter again"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-i'); + xlog $self, "Run squatter again"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-i'); } diff --git a/cassandane/tiny-tests/SearchFuzzy/dedup_part_compact b/cassandane/tiny-tests/SearchFuzzy/dedup_part_compact index 35a7dfa344..3f704081ae 100644 --- a/cassandane/tiny-tests/SearchFuzzy/dedup_part_compact +++ b/cassandane/tiny-tests/SearchFuzzy/dedup_part_compact @@ -2,40 +2,41 @@ use Cassandane::Tiny; sub test_dedup_part_compact - :min_version_3_3 :needs_search_xapian -{ - my ($self) = @_; - - my $xapdirs = ($self->{instance}->run_mbpath(-u => 'cassandane'))->{xapian}; - - xlog "force duplicate part into index"; - $self->make_message('msgA', body => 'part1') || die; - $self->make_message('msgB', body => 'part1') || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-D'); - - xlog "assert duplicated parts"; - my ($gdocs, $parts) = $self->delve_docs($xapdirs->{t1} . "/xapian"); - $self->assert_num_equals(2, scalar @$parts); - $self->assert_str_equals(@$parts[0], @$parts[1]); - $self->assert_num_equals(2, scalar @$gdocs); - - xlog "compact and filter to t2 tier"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-z', 't2', '-t', 't1', '-F'); - - xlog "assert parts got deduplicated"; - ($gdocs, $parts) = $self->delve_docs($xapdirs->{t2} . "/xapian"); - $self->assert_num_equals(1, scalar @$parts); - $self->assert_num_equals(2, scalar @$gdocs); - - xlog "force duplicate part into t1 index"; - $self->make_message('msgC', body => 'part1') || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-i', '-D'); - - xlog "compact and filter to t3 tier"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-z', 't3', '-t', 't1,t2', '-F'); - - xlog "assert parts got deduplicated"; - ($gdocs, $parts) = $self->delve_docs($xapdirs->{t3} . "/xapian"); - $self->assert_num_equals(1, scalar @$parts); - $self->assert_num_equals(3, scalar @$gdocs); + : min_version_3_3 : needs_search_xapian { + my ($self) = @_; + + my $xapdirs = ($self->{instance}->run_mbpath(-u => 'cassandane'))->{xapian}; + + xlog "force duplicate part into index"; + $self->make_message('msgA', body => 'part1') || die; + $self->make_message('msgB', body => 'part1') || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-D'); + + xlog "assert duplicated parts"; + my ($gdocs, $parts) = $self->delve_docs($xapdirs->{t1} . "/xapian"); + $self->assert_num_equals(2, scalar @$parts); + $self->assert_str_equals(@$parts[0], @$parts[1]); + $self->assert_num_equals(2, scalar @$gdocs); + + xlog "compact and filter to t2 tier"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'squatter', '-z', 't2', '-t', 't1', '-F'); + + xlog "assert parts got deduplicated"; + ($gdocs, $parts) = $self->delve_docs($xapdirs->{t2} . "/xapian"); + $self->assert_num_equals(1, scalar @$parts); + $self->assert_num_equals(2, scalar @$gdocs); + + xlog "force duplicate part into t1 index"; + $self->make_message('msgC', body => 'part1') || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-i', '-D'); + + xlog "compact and filter to t3 tier"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'squatter', '-z', 't3', '-t', 't1,t2', '-F'); + + xlog "assert parts got deduplicated"; + ($gdocs, $parts) = $self->delve_docs($xapdirs->{t3} . "/xapian"); + $self->assert_num_equals(1, scalar @$parts); + $self->assert_num_equals(3, scalar @$gdocs); } diff --git a/cassandane/tiny-tests/SearchFuzzy/dedup_part_index b/cassandane/tiny-tests/SearchFuzzy/dedup_part_index index 6c0bee17a8..27e3f1e8b2 100644 --- a/cassandane/tiny-tests/SearchFuzzy/dedup_part_index +++ b/cassandane/tiny-tests/SearchFuzzy/dedup_part_index @@ -2,46 +2,46 @@ use Cassandane::Tiny; sub test_dedup_part_index - :min_version_3_3 :needs_search_xapian -{ - my ($self) = @_; - - my $xapdirs = ($self->{instance}->run_mbpath(-u => 'cassandane'))->{xapian}; - - $self->make_message('msgA', body => 'part1') || die; - $self->make_message('msgB', body => 'part2') || die; - - xlog "create duplicate part within the same indexing batch"; - $self->make_message('msgC', body => 'part1') || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog "create duplicate part in another indexing batch"; - $self->make_message('msgD', body => 'part1') || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-i'); - - xlog "assert deduplicated parts"; - my $delveout = $self->run_delve($xapdirs->{t1} . '/xapian', '-V0'); - $delveout =~ s/^Value 0 for each document: //; - my @docs = split ' ', $delveout; - my @parts = map { $_ =~ /^\d+:\*P\*/ ? substr($_, 5) : () } @docs; - my @gdocs = map { $_ =~ /^\d+:\*G\*/ ? substr($_, 5) : () } @docs; - $self->assert_num_equals(2, scalar @parts); - $self->assert_str_not_equals($parts[0], $parts[1]); - $self->assert_num_equals(4, scalar @gdocs); - - xlog "compact to t2 tier"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-z', 't2', '-t', 't1'); - - xlog "create duplicate part in top tier"; - $self->make_message('msgD', body => 'part1') || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-i'); - - xlog "Assert deduplicated parts across tiers"; - $delveout = $self->run_delve($xapdirs->{t1}. '/xapian.1', '-V0'); - $delveout =~ s/^Value 0 for each document: //; - @docs = split ' ', $delveout; - @parts = map { $_ =~ /^\d+:\*P\*/ ? substr($_, 5) : () } @docs; - @gdocs = map { $_ =~ /^\d+:\*G\*/ ? substr($_, 5) : () } @docs; - $self->assert_num_equals(0, scalar @parts); - $self->assert_num_equals(1, scalar @gdocs); + : min_version_3_3 : needs_search_xapian { + my ($self) = @_; + + my $xapdirs = ($self->{instance}->run_mbpath(-u => 'cassandane'))->{xapian}; + + $self->make_message('msgA', body => 'part1') || die; + $self->make_message('msgB', body => 'part2') || die; + + xlog "create duplicate part within the same indexing batch"; + $self->make_message('msgC', body => 'part1') || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog "create duplicate part in another indexing batch"; + $self->make_message('msgD', body => 'part1') || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-i'); + + xlog "assert deduplicated parts"; + my $delveout = $self->run_delve($xapdirs->{t1} . '/xapian', '-V0'); + $delveout =~ s/^Value 0 for each document: //; + my @docs = split ' ', $delveout; + my @parts = map { $_ =~ /^\d+:\*P\*/ ? substr($_, 5) : () } @docs; + my @gdocs = map { $_ =~ /^\d+:\*G\*/ ? substr($_, 5) : () } @docs; + $self->assert_num_equals(2, scalar @parts); + $self->assert_str_not_equals($parts[0], $parts[1]); + $self->assert_num_equals(4, scalar @gdocs); + + xlog "compact to t2 tier"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'squatter', '-z', 't2', '-t', 't1'); + + xlog "create duplicate part in top tier"; + $self->make_message('msgD', body => 'part1') || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-i'); + + xlog "Assert deduplicated parts across tiers"; + $delveout = $self->run_delve($xapdirs->{t1} . '/xapian.1', '-V0'); + $delveout =~ s/^Value 0 for each document: //; + @docs = split ' ', $delveout; + @parts = map { $_ =~ /^\d+:\*P\*/ ? substr($_, 5) : () } @docs; + @gdocs = map { $_ =~ /^\d+:\*G\*/ ? substr($_, 5) : () } @docs; + $self->assert_num_equals(0, scalar @parts); + $self->assert_num_equals(1, scalar @gdocs); } diff --git a/cassandane/tiny-tests/SearchFuzzy/detect_language b/cassandane/tiny-tests/SearchFuzzy/detect_language index 54b45b567f..dd9b7e01c5 100644 --- a/cassandane/tiny-tests/SearchFuzzy/detect_language +++ b/cassandane/tiny-tests/SearchFuzzy/detect_language @@ -2,32 +2,34 @@ use Cassandane::Tiny; sub test_detect_language - :min_version_3_2 :needs_search_xapian :needs_dependency_cld2 :SearchLanguage -{ - my ($self) = @_; + : min_version_3_2 : needs_search_xapian : needs_dependency_cld2 : + SearchLanguage { + my ($self) = @_; - $self->make_message("german", - mime_type => 'text/plain', - mime_charset => 'utf-8', - mime_encoding => 'quoted-printable', - body => '' - . "Der Ballon besa=C3=9F eine gewaltige Gr=C3=B6=C3=9Fe, er trug einen Korb, g=\r\n" - . "ro=C3=9F und ger=C3=A4umig und offenbar f=C3=BCr einen l=C3=A4ngeren Aufenthalt\r\n" - . "hergeric=htet. Die zwei M=C3=A4nner, welche sich darin befanden, schienen\r\n" - . "erfahrene Luftschiff=er zu sein, das sah man schon daraus, wie ruhig sie trotz\r\n" - . "der ungeheuren H=C3=B6he atmeten." - ); + $self->make_message( + "german", + mime_type => 'text/plain', + mime_charset => 'utf-8', + mime_encoding => 'quoted-printable', + body => '' + . "Der Ballon besa=C3=9F eine gewaltige Gr=C3=B6=C3=9Fe, er trug einen Korb, g=\r\n" + . "ro=C3=9F und ger=C3=A4umig und offenbar f=C3=BCr einen l=C3=A4ngeren Aufenthalt\r\n" + . "hergeric=htet. Die zwei M=C3=A4nner, welche sich darin befanden, schienen\r\n" + . "erfahrene Luftschiff=er zu sein, das sah man schon daraus, wie ruhig sie trotz\r\n" + . "der ungeheuren H=C3=B6he atmeten." + ); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $uids = $talk->search('fuzzy', 'body', 'atmet'); - $self->assert_deep_equals([1], $uids); + my $uids = $talk->search('fuzzy', 'body', 'atmet'); + $self->assert_deep_equals([1], $uids); - my $r = $talk->select("INBOX") || die; - $r = $self->get_snippets('INBOX', $uids, { body => 'atmet' }); -use utf8; - $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], ' Höhe atmeten.')); -no utf8; + my $r = $talk->select("INBOX") || die; + $r = $self->get_snippets('INBOX', $uids, { body => 'atmet' }); + use utf8; + $self->assert_num_not_equals(-1, + index($r->{snippets}[0][3], ' Höhe atmeten.')); + no utf8; } diff --git a/cassandane/tiny-tests/SearchFuzzy/detect_language_subject b/cassandane/tiny-tests/SearchFuzzy/detect_language_subject index 6581ca5e37..8709122e45 100644 --- a/cassandane/tiny-tests/SearchFuzzy/detect_language_subject +++ b/cassandane/tiny-tests/SearchFuzzy/detect_language_subject @@ -2,25 +2,23 @@ use Cassandane::Tiny; sub test_detect_language_subject - :min_version_3_2 :needs_search_xapian :needs_dependency_cld2 :SearchLanguage -{ - my ($self) = @_; + : min_version_3_2 : needs_search_xapian : needs_dependency_cld2 : + SearchLanguage { + my ($self) = @_; - my $body = "" + my $body + = "" . "--boundary\r\n" . "Content-Type: text/plain;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" . "Hoch oben in den L=C3=BCften =C3=BCber den reichgesegneten Landschaften des\r\n" . "s=C3=BCdlichen Frankreichs schwebte eine gewaltige dunkle Kugel.\r\n" . "\r\n" . "Ein Luftballon war es, der, in der Nacht aufgefahren, eine lange\r\n" - . "Dauerfahrt antreten wollte.\r\n" - . "\r\n" + . "Dauerfahrt antreten wollte.\r\n" . "\r\n" . "--boundary\r\n" . "Content-Type: text/plain;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" . "The Bellman, who was almost morbidly sensitive about appearances, used\r\n" . "to have the bowsprit unshipped once or twice a week to be revarnished,\r\n" . "and it more than once happened, when the time came for replacing it,\r\n" @@ -28,8 +26,7 @@ sub test_detect_language_subject . "\r\n" . "--boundary\r\n" . "Content-Type: text/plain;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" . "Verri=C3=A8res est abrit=C3=A9e du c=C3=B4t=C3=A9 du nord par une haute mon=\r\n" . "tagne, c'est une\r\n" . "des branches du Jura. Les cimes bris=C3=A9es du Verra se couvrent de neige\r\n" @@ -40,23 +37,23 @@ sub test_detect_language_subject . "mouvement =C3=A0 un grand nombre de scies =C3=A0 bois; c'est une industrie =\r\n" . "--boundary--\r\n"; - $self->make_message("A subject with the German word Landschaften", - mime_type => "multipart/mixed", - mime_boundary => "boundary", - body => $body - ); + $self->make_message( + "A subject with the German word Landschaften", + mime_type => "multipart/mixed", + mime_boundary => "boundary", + body => $body + ); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $uids = $talk->search('fuzzy', 'subject', 'Landschaft'); - $self->assert_deep_equals([1], $uids); + my $uids = $talk->search('fuzzy', 'subject', 'Landschaft'); + $self->assert_deep_equals([1], $uids); - my $r = $talk->select("INBOX") || die; - $r = $self->get_snippets('INBOX', $uids, { subject => 'Landschaft' }); - $self->assert_str_equals( - 'A subject with the German word Landschaften', - $r->{snippets}[0][3] - ); + my $r = $talk->select("INBOX") || die; + $r = $self->get_snippets('INBOX', $uids, { subject => 'Landschaft' }); + $self->assert_str_equals( + 'A subject with the German word Landschaften', + $r->{snippets}[0][3]); } diff --git a/cassandane/tiny-tests/SearchFuzzy/fuzzyalways_annot b/cassandane/tiny-tests/SearchFuzzy/fuzzyalways_annot index 9ff34fbfdc..816e5cdd97 100644 --- a/cassandane/tiny-tests/SearchFuzzy/fuzzyalways_annot +++ b/cassandane/tiny-tests/SearchFuzzy/fuzzyalways_annot @@ -2,54 +2,53 @@ use Cassandane::Tiny; sub test_fuzzyalways_annot - :min_version_3_3 :needs_search_xapian :SearchFuzzyAlways -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); - - $self->make_message('test', body => 'body') || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog "Assert IMAP SEARCH uses fuzzy search by default"; - - # Fuzzy search uses stemming. - my $uids = $imap->search('body', 'bodies') || die; - $self->assert_deep_equals([1], $uids); - # But does not do substring search. - $uids = $imap->search('body', 'bod') || die; - $self->assert_deep_equals([], $uids); - - xlog "Disable fuzzy search with annotation"; - my $entry = '/shared/vendor/cmu/cyrus-imapd/search-fuzzy-always'; - - # Must not set any mailbox other than INBOX. - $imap->create("INBOX.foo") or die "create INBOX.foo: $@"; - $imap->setmetadata('INBOX.foo', $entry, 'off'); - $self->assert_str_equals('no', $imap->get_last_completion_response()); - # Must set a valid imapd.conf switch value. - $imap->setmetadata('INBOX', $entry, 'x'); - $self->assert_str_equals('no', $imap->get_last_completion_response()); - # Set annotation value. - $imap->setmetadata('INBOX', $entry, 'off'); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); - - xlog "Assert annotation overrides IMAP SEARCH default"; - - # Regular search does no stemming. - $uids = $imap->search('body', 'bodies') || die; - $self->assert_deep_equals([], $uids); - # But does substring search. - $uids = $imap->search('body', 'bod') || die; - $self->assert_deep_equals([1], $uids); - - xlog "Remove annotation and fall back to config"; - $imap->setmetadata('INBOX', $entry, undef); - $self->assert_str_equals('ok', $imap->get_last_completion_response()); - - # Fuzzy search uses stemming. - $uids = $imap->search('body', 'bodies') || die; - $self->assert_deep_equals([1], $uids); - # But does not do substring search. - $uids = $imap->search('body', 'bod') || die; - $self->assert_deep_equals([], $uids); + : min_version_3_3 : needs_search_xapian : SearchFuzzyAlways { + my ($self) = @_; + my $imap = $self->{store}->get_client(); + + $self->make_message('test', body => 'body') || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog "Assert IMAP SEARCH uses fuzzy search by default"; + + # Fuzzy search uses stemming. + my $uids = $imap->search('body', 'bodies') || die; + $self->assert_deep_equals([1], $uids); + # But does not do substring search. + $uids = $imap->search('body', 'bod') || die; + $self->assert_deep_equals([], $uids); + + xlog "Disable fuzzy search with annotation"; + my $entry = '/shared/vendor/cmu/cyrus-imapd/search-fuzzy-always'; + + # Must not set any mailbox other than INBOX. + $imap->create("INBOX.foo") or die "create INBOX.foo: $@"; + $imap->setmetadata('INBOX.foo', $entry, 'off'); + $self->assert_str_equals('no', $imap->get_last_completion_response()); + # Must set a valid imapd.conf switch value. + $imap->setmetadata('INBOX', $entry, 'x'); + $self->assert_str_equals('no', $imap->get_last_completion_response()); + # Set annotation value. + $imap->setmetadata('INBOX', $entry, 'off'); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); + + xlog "Assert annotation overrides IMAP SEARCH default"; + + # Regular search does no stemming. + $uids = $imap->search('body', 'bodies') || die; + $self->assert_deep_equals([], $uids); + # But does substring search. + $uids = $imap->search('body', 'bod') || die; + $self->assert_deep_equals([1], $uids); + + xlog "Remove annotation and fall back to config"; + $imap->setmetadata('INBOX', $entry, undef); + $self->assert_str_equals('ok', $imap->get_last_completion_response()); + + # Fuzzy search uses stemming. + $uids = $imap->search('body', 'bodies') || die; + $self->assert_deep_equals([1], $uids); + # But does not do substring search. + $uids = $imap->search('body', 'bod') || die; + $self->assert_deep_equals([], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/html_only b/cassandane/tiny-tests/SearchFuzzy/html_only index 24656b7291..4b811366a1 100644 --- a/cassandane/tiny-tests/SearchFuzzy/html_only +++ b/cassandane/tiny-tests/SearchFuzzy/html_only @@ -2,40 +2,40 @@ use Cassandane::Tiny; sub test_html_only - :min_version_3_3 :needs_search_xapian -{ - my ($self) = @_; - my $talk = $self->{store}->get_client(); - - xlog "Index message with both html and plain text part"; - $self->make_message("test", - mime_type => "text/html", - body => "" - . "\r\n" - . "
This is an html LL123 xyzzy body.
\r\n" - . "{instance}->run_command({cyrus => 1}, 'squatter'); - - xlog "Assert that HTML in plain text is stripped"; - my $uids = $talk->search('fuzzy', 'body', 'html') || die; - $self->assert_deep_equals([1], $uids); - - $uids = $talk->search('fuzzy', 'body', 'div') || die; - $self->assert_deep_equals([], $uids); - - # make sure the "p" doesn't leak into a token - $uids = $talk->search('fuzzy', 'body', 'LL123p') || die; - $self->assert_deep_equals([], $uids); - - # make sure the real token gets indexed - $uids = $talk->search('fuzzy', 'body', 'LL123') || die; - $self->assert_deep_equals([1], $uids); - - # make sure the h11 doesn't leak - $uids = $talk->search('fuzzy', 'body', 'xyzzy1') || die; - $self->assert_deep_equals([], $uids); - $uids = $talk->search('fuzzy', 'body', 'xyzzy') || die; - $self->assert_deep_equals([1], $uids); + : min_version_3_3 : needs_search_xapian { + my ($self) = @_; + my $talk = $self->{store}->get_client(); + + xlog "Index message with both html and plain text part"; + $self->make_message( + "test", + mime_type => "text/html", + body => "" + . "\r\n" + . "
This is an html LL123 xyzzy body.
\r\n" + . "{instance}->run_command({ cyrus => 1 }, 'squatter'); + + xlog "Assert that HTML in plain text is stripped"; + my $uids = $talk->search('fuzzy', 'body', 'html') || die; + $self->assert_deep_equals([1], $uids); + + $uids = $talk->search('fuzzy', 'body', 'div') || die; + $self->assert_deep_equals([], $uids); + + # make sure the "p" doesn't leak into a token + $uids = $talk->search('fuzzy', 'body', 'LL123p') || die; + $self->assert_deep_equals([], $uids); + + # make sure the real token gets indexed + $uids = $talk->search('fuzzy', 'body', 'LL123') || die; + $self->assert_deep_equals([1], $uids); + + # make sure the h11 doesn't leak + $uids = $talk->search('fuzzy', 'body', 'xyzzy1') || die; + $self->assert_deep_equals([], $uids); + $uids = $talk->search('fuzzy', 'body', 'xyzzy') || die; + $self->assert_deep_equals([1], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/mix_fuzzy_and_nonfuzzy b/cassandane/tiny-tests/SearchFuzzy/mix_fuzzy_and_nonfuzzy index cb8aa104d2..5b31a64ba3 100644 --- a/cassandane/tiny-tests/SearchFuzzy/mix_fuzzy_and_nonfuzzy +++ b/cassandane/tiny-tests/SearchFuzzy/mix_fuzzy_and_nonfuzzy @@ -2,19 +2,19 @@ use Cassandane::Tiny; sub test_mix_fuzzy_and_nonfuzzy - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; - $self->create_testmessages(); - my $talk = $self->{store}->get_client(); + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; + $self->create_testmessages(); + my $talk = $self->{store}->get_client(); - xlog $self, "Select INBOX"; - $talk->select("INBOX") || die; + xlog $self, "Select INBOX"; + $talk->select("INBOX") || die; - xlog $self, "SEARCH for from \"foo\@example.com\" with FUZZY body \"connection\""; - my $r = $talk->search( - "fuzzy", ["body", { Quote => "connection" }], - "from", { Quote => "foo\@example.com" } - ) || die; - $self->assert_num_equals(2, scalar @$r); + xlog $self, + "SEARCH for from \"foo\@example.com\" with FUZZY body \"connection\""; + my $r = $talk->search( + "fuzzy", [ "body", { Quote => "connection" } ], + "from", { Quote => "foo\@example.com" } + ) || die; + $self->assert_num_equals(2, scalar @$r); } diff --git a/cassandane/tiny-tests/SearchFuzzy/noindex_multipartheaders b/cassandane/tiny-tests/SearchFuzzy/noindex_multipartheaders index cd391324e7..31dd7284e2 100644 --- a/cassandane/tiny-tests/SearchFuzzy/noindex_multipartheaders +++ b/cassandane/tiny-tests/SearchFuzzy/noindex_multipartheaders @@ -2,27 +2,21 @@ use Cassandane::Tiny; sub test_noindex_multipartheaders - :needs_search_xapian -{ - my ($self) = @_; + : needs_search_xapian { + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $body = "" + my $body + = "" . "--boundary\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body" - . "\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "body" . "\r\n" . "--boundary\r\n" . "Content-Type: application/octet-stream\r\n" - . "Content-Transfer-Encoding: base64\r\n" - . "\r\n" - . "SGVsbG8sIFdvcmxkIQ==" - . "\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + . "SGVsbG8sIFdvcmxkIQ==" . "\r\n" . "--boundary\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" . "Return-Path: \r\n" . "Mime-Version: 1.0\r\n" . "Content-Type: text/plain" @@ -31,44 +25,35 @@ sub test_noindex_multipartheaders . "From: blu\@local\r\n" . "Message-ID: \r\n" . "Date: Wed, 06 Oct 2016 14:59:07 +1100\r\n" - . "To: Test User \r\n" - . "\r\n" - . "embedded" - . "\r\n" + . "To: Test User \r\n" . "\r\n" + . "embedded" . "\r\n" . "--boundary--\r\n"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "boundary", - body => $body - ); - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my $r; - - $r = $talk->search( - "header", "Content-Type", { Quote => "multipart/mixed" } - ) || die; - $self->assert_num_equals(1, scalar @$r); - - # Don't index the headers of multiparts or embedded RFC822s - $r = $talk->search( - "header", "Content-Type", { Quote => "text/plain" } - ) || die; - $self->assert_num_equals(0, scalar @$r); - $r = $talk->search( - "fuzzy", "body", { Quote => "text/plain" } - ) || die; - $self->assert_num_equals(0, scalar @$r); - $r = $talk->search( - "fuzzy", "text", { Quote => "content" } - ) || die; - $self->assert_num_equals(0, scalar @$r); - - # But index the body of an embedded RFC822 - $r = $talk->search( - "fuzzy", "body", { Quote => "embedded" } - ) || die; - $self->assert_num_equals(1, scalar @$r); + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "boundary", + body => $body + ); + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my $r; + + $r = $talk->search("header", "Content-Type", { Quote => "multipart/mixed" }) + || die; + $self->assert_num_equals(1, scalar @$r); + + # Don't index the headers of multiparts or embedded RFC822s + $r = $talk->search("header", "Content-Type", { Quote => "text/plain" }) + || die; + $self->assert_num_equals(0, scalar @$r); + $r = $talk->search("fuzzy", "body", { Quote => "text/plain" }) || die; + $self->assert_num_equals(0, scalar @$r); + $r = $talk->search("fuzzy", "text", { Quote => "content" }) || die; + $self->assert_num_equals(0, scalar @$r); + + # But index the body of an embedded RFC822 + $r = $talk->search("fuzzy", "body", { Quote => "embedded" }) || die; + $self->assert_num_equals(1, scalar @$r); } diff --git a/cassandane/tiny-tests/SearchFuzzy/normalize_snippets b/cassandane/tiny-tests/SearchFuzzy/normalize_snippets index d517be2324..c1f2557be0 100644 --- a/cassandane/tiny-tests/SearchFuzzy/normalize_snippets +++ b/cassandane/tiny-tests/SearchFuzzy/normalize_snippets @@ -2,49 +2,50 @@ use Cassandane::Tiny; sub test_normalize_snippets - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; - - # Set up test message with funny characters -use utf8; - my @terms = ( "gären", "советской", "diĝir", "naïve", "léger" ); -no utf8; - my $body = encode_base64(encode('UTF-8', join(' ', @terms))); - $body =~ s/\r?\n/\r\n/gs; - - xlog $self, "Generate and index test messages."; - my %params = ( - mime_charset => "utf-8", - mime_encoding => 'base64', - body => $body, - ); - $self->make_message("1", %params) || die; - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my $talk = $self->{store}->get_client(); - - # Connect to IMAP - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - - # Assert that diacritics are matched and returned - foreach my $term (@terms) { - $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); - } - - # Assert that search without diacritics matches - if ($self->{skipdiacrit}) { - my $term = "naive"; - xlog $self, "Get snippets for FUZZY text \"$term\""; - $r = $self->get_snippets('INBOX', $uids, { 'text' => $term }); -use utf8; - $self->assert_num_not_equals(index($r->{snippets}[0][3], "naïve"), -1); -no utf8; - } + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; + + # Set up test message with funny characters + use utf8; + my @terms = ("gären", "советской", "diĝir", "naïve", "léger"); + no utf8; + my $body = encode_base64(encode('UTF-8', join(' ', @terms))); + $body =~ s/\r?\n/\r\n/gs; + + xlog $self, "Generate and index test messages."; + my %params = ( + mime_charset => "utf-8", + mime_encoding => 'base64', + body => $body, + ); + $self->make_message("1", %params) || die; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my $talk = $self->{store}->get_client(); + + # Connect to IMAP + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + + # Assert that diacritics are matched and returned + foreach my $term (@terms) { + $r = $self->get_snippets('INBOX', $uids, { text => $term }); + $self->assert_num_not_equals( + index($r->{snippets}[0][3], "$term"), -1); + } + + # Assert that search without diacritics matches + if ($self->{skipdiacrit}) { + my $term = "naive"; + xlog $self, "Get snippets for FUZZY text \"$term\""; + $r = $self->get_snippets('INBOX', $uids, { 'text' => $term }); + use utf8; + $self->assert_num_not_equals( + index($r->{snippets}[0][3], "naïve"), -1); + no utf8; + } } diff --git a/cassandane/tiny-tests/SearchFuzzy/not_match b/cassandane/tiny-tests/SearchFuzzy/not_match index 69ea340323..21c32425db 100644 --- a/cassandane/tiny-tests/SearchFuzzy/not_match +++ b/cassandane/tiny-tests/SearchFuzzy/not_match @@ -2,21 +2,20 @@ use Cassandane::Tiny; sub test_not_match - :min_version_3_0 :needs_search_xapian :needs_dependency_cld2 -{ - my ($self) = @_; - my $imap = $self->{store}->get_client(); - my $store = $self->{store}; + : min_version_3_0 : needs_search_xapian : needs_dependency_cld2 { + my ($self) = @_; + my $imap = $self->{store}->get_client(); + my $store = $self->{store}; - $imap->create("INBOX.A") or die; - $store->set_folder("INBOX.A"); - $self->make_message('fwd subject', body => 'a schenectady body'); - $self->make_message('chad subject', body => 'a futz body'); + $imap->create("INBOX.A") or die; + $store->set_folder("INBOX.A"); + $self->make_message('fwd subject', body => 'a schenectady body'); + $self->make_message('chad subject', body => 'a futz body'); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); - $talk->select("INBOX.A"); - my $uids = $talk->search('fuzzy', 'not', 'text', 'schenectady'); - $self->assert_deep_equals([2], $uids); + my $talk = $self->{store}->get_client(); + $talk->select("INBOX.A"); + my $uids = $talk->search('fuzzy', 'not', 'text', 'schenectady'); + $self->assert_deep_equals([2], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/reindex_mb_uniqueid b/cassandane/tiny-tests/SearchFuzzy/reindex_mb_uniqueid index 697465b849..f3769bd9ac 100644 --- a/cassandane/tiny-tests/SearchFuzzy/reindex_mb_uniqueid +++ b/cassandane/tiny-tests/SearchFuzzy/reindex_mb_uniqueid @@ -2,29 +2,30 @@ use Cassandane::Tiny; sub test_reindex_mb_uniqueid - :min_version_3_7 :needs_search_xapian -{ - my ($self) = @_; + : min_version_3_7 : needs_search_xapian { + my ($self) = @_; - my $xapdirs = ($self->{instance}->run_mbpath(-u => 'cassandane'))->{xapian}; - my $basedir = $self->{instance}->{basedir}; + my $xapdirs = ($self->{instance}->run_mbpath(-u => 'cassandane'))->{xapian}; + my $basedir = $self->{instance}->{basedir}; - $self->make_message('msgA', body => 'part1') || die; - $self->make_message('msgB', body => 'part1') || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-D'); + $self->make_message('msgA', body => 'part1') || die; + $self->make_message('msgB', body => 'part1') || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-D'); - xlog "compact and reindex tier"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v', '-z', 't2', '-t', 't1', '-T', 't1:0'); + xlog "compact and reindex tier"; + $self->{instance}->run_command({ cyrus => 1 }, + 'squatter', '-v', '-z', 't2', '-t', 't1', '-T', 't1:0'); - xlog "dump t2:cyrus.indexed.db"; - # assumes twoskip backend and version 2 format keys - my $srcfile = $xapdirs->{t2} . '/xapian/cyrus.indexed.db'; - my $dstfile = $basedir . '/tmp/cyrus.indexed.db.flat'; - $self->{instance}->run_command({cyrus => 1}, 'cvt_cyrusdb', $srcfile, 'twoskip', $dstfile, 'flat'); + xlog "dump t2:cyrus.indexed.db"; + # assumes twoskip backend and version 2 format keys + my $srcfile = $xapdirs->{t2} . '/xapian/cyrus.indexed.db'; + my $dstfile = $basedir . '/tmp/cyrus.indexed.db.flat'; + $self->{instance}->run_command({ cyrus => 1 }, + 'cvt_cyrusdb', $srcfile, 'twoskip', $dstfile, 'flat'); - xlog "assert reindexed tier contains a mailbox key"; - open(FH, "<$dstfile") || die; - my @mboxrows = grep { /^\*M\*[0-9a-zA-z\-_]+\*/ } ; - close FH; - $self->assert_num_equals(1, scalar @mboxrows); + xlog "assert reindexed tier contains a mailbox key"; + open(FH, "<$dstfile") || die; + my @mboxrows = grep { /^\*M\*[0-9a-zA-z\-_]+\*/ } ; + close FH; + $self->assert_num_equals(1, scalar @mboxrows); } diff --git a/cassandane/tiny-tests/SearchFuzzy/search_exactmatch b/cassandane/tiny-tests/SearchFuzzy/search_exactmatch index 866d07421f..f3a195554b 100644 --- a/cassandane/tiny-tests/SearchFuzzy/search_exactmatch +++ b/cassandane/tiny-tests/SearchFuzzy/search_exactmatch @@ -2,39 +2,38 @@ use Cassandane::Tiny; sub test_search_exactmatch - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; - xlog $self, "Generate and index test messages."; - $self->make_message("test1", - body => "Test1 body with some long text and there is even more ". - "and more and more and more and more and more and more ". - "and more and more and some text and more and more and ". - "and more and more and more and more and more and more ". - "and almost at the end some other text that is a match ", - ) || die; - $self->make_message("test2", - body => "Test2 body with some other text", - ) || die; + xlog $self, "Generate and index test messages."; + $self->make_message("test1", + body => "Test1 body with some long text and there is even more " + . "and more and more and more and more and more and more " + . "and more and more and some text and more and more and " + . "and more and more and more and more and more and more " + . "and almost at the end some other text that is a match ",) + || die; + $self->make_message("test2", body => "Test2 body with some other text",) + || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - xlog $self, 'SEARCH for FUZZY exact match'; - my $query = '"some text"'; - $uids = $talk->search('fuzzy', 'body', $query) || die; - $self->assert_num_equals(1, scalar @$uids); + xlog $self, 'SEARCH for FUZZY exact match'; + my $query = '"some text"'; + $uids = $talk->search('fuzzy', 'body', $query) || die; + $self->assert_num_equals(1, scalar @$uids); - my %m; - $r = $self->get_snippets('INBOX', $uids, { body => $query }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; - $self->assert(index($m{body}, "some text") != -1); - $self->assert(index($m{body}, "some long text") == -1); + my %m; + $r = $self->get_snippets('INBOX', $uids, { body => $query }); + %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; + $self->assert(index($m{body}, "some text") != -1); + $self->assert( + index($m{body}, "some long text") == -1); } diff --git a/cassandane/tiny-tests/SearchFuzzy/search_omit_html b/cassandane/tiny-tests/SearchFuzzy/search_omit_html index de089368bd..4bc76d5a45 100644 --- a/cassandane/tiny-tests/SearchFuzzy/search_omit_html +++ b/cassandane/tiny-tests/SearchFuzzy/search_omit_html @@ -2,45 +2,43 @@ use Cassandane::Tiny; sub test_search_omit_html - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; - - xlog $self, "Generate and index test messages."; - $self->make_message("toplevel", - mime_type => "text/html", - body => "
hello
" - ) || die; - - $self->make_message("embedded", - mime_type => "multipart/related", - mime_boundary => "boundary_1", - body => "" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "txt" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/html\r\n" - . "\r\n" - . "
world
" - . "\r\n--boundary_1--\r\n" - ) || die; - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my $talk = $self->{store}->get_client(); - - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - - $uids = $talk->search('fuzzy', 'body', 'div') || die; - $self->assert_num_equals(0, scalar @$uids); - - $uids = $talk->search('fuzzy', 'body', 'hello') || die; - $self->assert_num_equals(1, scalar @$uids); - - $uids = $talk->search('fuzzy', 'body', 'world') || die; - $self->assert_num_equals(1, scalar @$uids); + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; + + xlog $self, "Generate and index test messages."; + $self->make_message( + "toplevel", + mime_type => "text/html", + body => "
hello
" + ) || die; + + $self->make_message( + "embedded", + mime_type => "multipart/related", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "txt" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/html\r\n" . "\r\n" + . "
world
" + . "\r\n--boundary_1--\r\n" + ) || die; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my $talk = $self->{store}->get_client(); + + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + + $uids = $talk->search('fuzzy', 'body', 'div') || die; + $self->assert_num_equals(0, scalar @$uids); + + $uids = $talk->search('fuzzy', 'body', 'hello') || die; + $self->assert_num_equals(1, scalar @$uids); + + $uids = $talk->search('fuzzy', 'body', 'world') || die; + $self->assert_num_equals(1, scalar @$uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/search_omit_ical b/cassandane/tiny-tests/SearchFuzzy/search_omit_ical index 2d37baa144..4c03a6de71 100644 --- a/cassandane/tiny-tests/SearchFuzzy/search_omit_ical +++ b/cassandane/tiny-tests/SearchFuzzy/search_omit_ical @@ -2,118 +2,117 @@ use Cassandane::Tiny; sub test_search_omit_ical - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; - xlog $self, "Generate and index test messages."; + xlog $self, "Generate and index test messages."; - $self->make_message("test", - mime_type => "multipart/related", - mime_boundary => "boundary_1", - body => "" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "txt body" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/calendar;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" - . "BEGIN:VCALENDAR\r\n" - . "VERSION:2.0\r\n" - . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" - . "CALSCALE:GREGORIAN\r\n" - . "BEGIN:VTIMEZONE\r\n" - . "TZID:Europe/Vienna\r\n" - . "BEGIN:STANDARD\r\n" - . "DTSTART:19700101T000000\r\n" - . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" - . "TZOFFSETFROM:+0200\r\n" - . "TZOFFSETTO:+0100\r\n" - . "END:STANDARD\r\n" - . "BEGIN:DAYLIGHT\r\n" - . "DTSTART:19700101T000000\r\n" - . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\r\n" - . "TZOFFSETFROM:+0100\r\n" - . "TZOFFSETTO:+0200\r\n" - . "END:DAYLIGHT\r\n" - . "END:VTIMEZONE\r\n" - . "BEGIN:VEVENT\r\n" - . "SUMMARY:icalsummary\r\n" - . "DESCRIPTION:icaldesc\r\n" - . "LOCATION:icallocation\r\n" - . "CREATED:20180518T090306Z\r\n" - . "DTEND;TZID=Europe/Vienna:20180518T100000\r\n" - . "DTSTAMP:20180518T090306Z\r\n" - . "DTSTART;TZID=Europe/Vienna:20180518T090000\r\n" - . "LAST-MODIFIED:20180518T090306Z\r\n" - . "RRULE:FREQ=DAILY\r\n" - . "SEQUENCE:1\r\n" - . "SUMMARY:K=C3=A4se\r\n" - . "TRANSP:OPAQUE\r\n" - . "UID:1234567890\r\n" - . "END:VEVENT\r\n" - . "END:VCALENDAR\r\n" - . "\r\n--boundary_1--\r\n" - ) || die; + $self->make_message( + "test", + mime_type => "multipart/related", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "txt body" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/calendar;charset=utf-8\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" + . "BEGIN:VCALENDAR\r\n" + . "VERSION:2.0\r\n" + . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" + . "CALSCALE:GREGORIAN\r\n" + . "BEGIN:VTIMEZONE\r\n" + . "TZID:Europe/Vienna\r\n" + . "BEGIN:STANDARD\r\n" + . "DTSTART:19700101T000000\r\n" + . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" + . "TZOFFSETFROM:+0200\r\n" + . "TZOFFSETTO:+0100\r\n" + . "END:STANDARD\r\n" + . "BEGIN:DAYLIGHT\r\n" + . "DTSTART:19700101T000000\r\n" + . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\r\n" + . "TZOFFSETFROM:+0100\r\n" + . "TZOFFSETTO:+0200\r\n" + . "END:DAYLIGHT\r\n" + . "END:VTIMEZONE\r\n" + . "BEGIN:VEVENT\r\n" + . "SUMMARY:icalsummary\r\n" + . "DESCRIPTION:icaldesc\r\n" + . "LOCATION:icallocation\r\n" + . "CREATED:20180518T090306Z\r\n" + . "DTEND;TZID=Europe/Vienna:20180518T100000\r\n" + . "DTSTAMP:20180518T090306Z\r\n" + . "DTSTART;TZID=Europe/Vienna:20180518T090000\r\n" + . "LAST-MODIFIED:20180518T090306Z\r\n" + . "RRULE:FREQ=DAILY\r\n" + . "SEQUENCE:1\r\n" + . "SUMMARY:K=C3=A4se\r\n" + . "TRANSP:OPAQUE\r\n" + . "UID:1234567890\r\n" + . "END:VEVENT\r\n" + . "END:VCALENDAR\r\n" + . "\r\n--boundary_1--\r\n" + ) || die; - $self->make_message("top", - mime_type => "text/calendar", - body => "" - . "BEGIN:VCALENDAR\r\n" - . "VERSION:2.0\r\n" - . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" - . "CALSCALE:GREGORIAN\r\n" - . "BEGIN:VTIMEZONE\r\n" - . "TZID:Europe/Vienna\r\n" - . "BEGIN:STANDARD\r\n" - . "DTSTART:19700101T000000\r\n" - . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" - . "TZOFFSETFROM:+0200\r\n" - . "TZOFFSETTO:+0100\r\n" - . "END:STANDARD\r\n" - . "BEGIN:DAYLIGHT\r\n" - . "DTSTART:19700101T000000\r\n" - . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\r\n" - . "TZOFFSETFROM:+0100\r\n" - . "TZOFFSETTO:+0200\r\n" - . "END:DAYLIGHT\r\n" - . "END:VTIMEZONE\r\n" - . "BEGIN:VEVENT\r\n" - . "SUMMARY:icalsummary\r\n" - . "DESCRIPTION:icaldesc\r\n" - . "LOCATION:icallocation\r\n" - . "CREATED:20180518T090306Z\r\n" - . "DTEND;TZID=Europe/Vienna:20180518T100000\r\n" - . "DTSTAMP:20180518T090306Z\r\n" - . "DTSTART;TZID=Europe/Vienna:20180518T090000\r\n" - . "LAST-MODIFIED:20180518T090306Z\r\n" - . "RRULE:FREQ=DAILY\r\n" - . "SEQUENCE:1\r\n" - . "TRANSP:OPAQUE\r\n" - . "UID:1234567890\r\n" - . "END:VEVENT\r\n" - . "END:VCALENDAR\r\n" - ) || die; + $self->make_message( + "top", + mime_type => "text/calendar", + body => "" + . "BEGIN:VCALENDAR\r\n" + . "VERSION:2.0\r\n" + . "PRODID:-//CyrusIMAP.org/Cyrus 3.1.3-606//EN\r\n" + . "CALSCALE:GREGORIAN\r\n" + . "BEGIN:VTIMEZONE\r\n" + . "TZID:Europe/Vienna\r\n" + . "BEGIN:STANDARD\r\n" + . "DTSTART:19700101T000000\r\n" + . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" + . "TZOFFSETFROM:+0200\r\n" + . "TZOFFSETTO:+0100\r\n" + . "END:STANDARD\r\n" + . "BEGIN:DAYLIGHT\r\n" + . "DTSTART:19700101T000000\r\n" + . "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3\r\n" + . "TZOFFSETFROM:+0100\r\n" + . "TZOFFSETTO:+0200\r\n" + . "END:DAYLIGHT\r\n" + . "END:VTIMEZONE\r\n" + . "BEGIN:VEVENT\r\n" + . "SUMMARY:icalsummary\r\n" + . "DESCRIPTION:icaldesc\r\n" + . "LOCATION:icallocation\r\n" + . "CREATED:20180518T090306Z\r\n" + . "DTEND;TZID=Europe/Vienna:20180518T100000\r\n" + . "DTSTAMP:20180518T090306Z\r\n" + . "DTSTART;TZID=Europe/Vienna:20180518T090000\r\n" + . "LAST-MODIFIED:20180518T090306Z\r\n" + . "RRULE:FREQ=DAILY\r\n" + . "SEQUENCE:1\r\n" + . "TRANSP:OPAQUE\r\n" + . "UID:1234567890\r\n" + . "END:VEVENT\r\n" + . "END:VCALENDAR\r\n" + ) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - $uids = $talk->search('fuzzy', 'text', 'rrule') || die; - $self->assert_num_equals(0, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', 'rrule') || die; + $self->assert_num_equals(0, scalar @$uids); - $uids = $talk->search('fuzzy', 'subject', 'icalsummary') || die; - $self->assert_num_equals(2, scalar @$uids); + $uids = $talk->search('fuzzy', 'subject', 'icalsummary') || die; + $self->assert_num_equals(2, scalar @$uids); - $uids = $talk->search('fuzzy', 'text', 'icaldesc') || die; - $self->assert_num_equals(2, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', 'icaldesc') || die; + $self->assert_num_equals(2, scalar @$uids); - $uids = $talk->search('fuzzy', 'text', 'icallocation') || die; - $self->assert_num_equals(2, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', 'icallocation') || die; + $self->assert_num_equals(2, scalar @$uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/search_omit_vcard b/cassandane/tiny-tests/SearchFuzzy/search_omit_vcard index 3fbff67f5c..ce84104376 100644 --- a/cassandane/tiny-tests/SearchFuzzy/search_omit_vcard +++ b/cassandane/tiny-tests/SearchFuzzy/search_omit_vcard @@ -2,87 +2,86 @@ use Cassandane::Tiny; sub test_search_omit_vcard - :min_version_3_9 :needs_search_xapian -{ - my ($self) = @_; + : min_version_3_9 : needs_search_xapian { + my ($self) = @_; - xlog $self, "Generate and index test messages."; + xlog $self, "Generate and index test messages."; - $self->make_message("test", - mime_type => "multipart/related", - mime_boundary => "boundary_1", - body => "" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "txt body" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/vcard;charset=utf-8\r\n" - . "Content-Transfer-Encoding: quoted-printable\r\n" - . "\r\n" - . "BEGIN:VCARD\r\n" - . "VERSION:3.0\r\n" - . "UID:1234567890\r\n" - . "BDAY:1944-06-07\r\n" - . "N:Gump;Forrest;;Mr.\r\n" - . "FN:Forrest Gump\r\n" - . "ORG;PROP-ID=O1:Bubba Gump Shrimp Co.\r\n" - . "TITLE;PROP-ID=T1:Shrimp Man\r\n" - . "PHOTO;PROP-ID=P1;ENCODING=b;TYPE=JPEG:c29tZSBwaG90bw==\r\n" - . "foo.ADR;PROP-ID=A1:;;1501 Broadway;New York;NY;10036;USA\r\n" - . "foo.GEO:40.7571383482188;-73.98695548990568\r\n" - . "foo.TZ:-05:00\r\n" - . "EMAIL;TYPE=PREF:bgump\@example.com\r\n" - . "X-SOCIAL-PROFILE:https://example.com/\@bubba" - . "REV:2008-04-24T19:52:43Z\r\n" - . "END:VCARD\r\n" - . "\r\n--boundary_1--\r\n" - ) || die; + $self->make_message( + "test", + mime_type => "multipart/related", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "txt body" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/vcard;charset=utf-8\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" . "\r\n" + . "BEGIN:VCARD\r\n" + . "VERSION:3.0\r\n" + . "UID:1234567890\r\n" + . "BDAY:1944-06-07\r\n" + . "N:Gump;Forrest;;Mr.\r\n" + . "FN:Forrest Gump\r\n" + . "ORG;PROP-ID=O1:Bubba Gump Shrimp Co.\r\n" + . "TITLE;PROP-ID=T1:Shrimp Man\r\n" + . "PHOTO;PROP-ID=P1;ENCODING=b;TYPE=JPEG:c29tZSBwaG90bw==\r\n" + . "foo.ADR;PROP-ID=A1:;;1501 Broadway;New York;NY;10036;USA\r\n" + . "foo.GEO:40.7571383482188;-73.98695548990568\r\n" + . "foo.TZ:-05:00\r\n" + . "EMAIL;TYPE=PREF:bgump\@example.com\r\n" + . "X-SOCIAL-PROFILE:https://example.com/\@bubba" + . "REV:2008-04-24T19:52:43Z\r\n" + . "END:VCARD\r\n" + . "\r\n--boundary_1--\r\n" + ) || die; - $self->make_message("top", - mime_type => "text/vcard", - body => "" - . "BEGIN:VCARD\r\n" - . "VERSION:3.0\r\n" - . "UID:1234567890\r\n" - . "BDAY:1944-06-07\r\n" - . "N:Gump;Forrest;;Mr.\r\n" - . "FN:Forrest Gump\r\n" - . "ORG;PROP-ID=O1:Bubba Gump Shrimp Co.\r\n" - . "TITLE;PROP-ID=T1:Shrimp Man\r\n" - . "PHOTO;PROP-ID=P1;ENCODING=b;TYPE=JPEG:c29tZSBwaG90bw==\r\n" - . "foo.ADR;PROP-ID=A1:;;1501 Broadway;New York;NY;10036;USA\r\n" - . "foo.GEO:40.7571383482188;-73.98695548990568\r\n" - . "foo.TZ:-05:00\r\n" - . "EMAIL;TYPE=PREF:bgump\@example.com\r\n" - . "X-SOCIAL-PROFILE:https://example.com/\@bubba" - . "REV:2008-04-24T19:52:43Z\r\n" - . "END:VCARD\r\n" - ) || die; + $self->make_message( + "top", + mime_type => "text/vcard", + body => "" + . "BEGIN:VCARD\r\n" + . "VERSION:3.0\r\n" + . "UID:1234567890\r\n" + . "BDAY:1944-06-07\r\n" + . "N:Gump;Forrest;;Mr.\r\n" + . "FN:Forrest Gump\r\n" + . "ORG;PROP-ID=O1:Bubba Gump Shrimp Co.\r\n" + . "TITLE;PROP-ID=T1:Shrimp Man\r\n" + . "PHOTO;PROP-ID=P1;ENCODING=b;TYPE=JPEG:c29tZSBwaG90bw==\r\n" + . "foo.ADR;PROP-ID=A1:;;1501 Broadway;New York;NY;10036;USA\r\n" + . "foo.GEO:40.7571383482188;-73.98695548990568\r\n" + . "foo.TZ:-05:00\r\n" + . "EMAIL;TYPE=PREF:bgump\@example.com\r\n" + . "X-SOCIAL-PROFILE:https://example.com/\@bubba" + . "REV:2008-04-24T19:52:43Z\r\n" + . "END:VCARD\r\n" + ) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - $uids = $talk->search('fuzzy', 'text', '1944') || die; - $self->assert_num_equals(0, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', '1944') || die; + $self->assert_num_equals(0, scalar @$uids); - $uids = $talk->search('fuzzy', 'text', 'Forrest') || die; - $self->assert_num_equals(2, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', 'Forrest') || die; + $self->assert_num_equals(2, scalar @$uids); - $uids = $talk->search('fuzzy', 'text', 'Mr.') || die; - $self->assert_num_equals(2, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', 'Mr.') || die; + $self->assert_num_equals(2, scalar @$uids); - $uids = $talk->search('fuzzy', 'text', 'Shrimp') || die; - $self->assert_num_equals(2, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', 'Shrimp') || die; + $self->assert_num_equals(2, scalar @$uids); - $uids = $talk->search('fuzzy', 'text', 'example') || die; - $self->assert_num_equals(2, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', 'example') || die; + $self->assert_num_equals(2, scalar @$uids); - $uids = $talk->search('fuzzy', 'text', 'https') || die; - $self->assert_num_equals(2, scalar @$uids); + $uids = $talk->search('fuzzy', 'text', 'https') || die; + $self->assert_num_equals(2, scalar @$uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/search_subjectsnippet b/cassandane/tiny-tests/SearchFuzzy/search_subjectsnippet index e249d8787c..c849a0a816 100644 --- a/cassandane/tiny-tests/SearchFuzzy/search_subjectsnippet +++ b/cassandane/tiny-tests/SearchFuzzy/search_subjectsnippet @@ -2,38 +2,37 @@ use Cassandane::Tiny; sub test_search_subjectsnippet - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; - xlog $self, "Generate and index test messages."; - $self->make_message("[plumbing] Re: log server v0 live", - body => "Test1 body with some long text and there is even more ". - "and more and more and more and more and more and more ". - "and more and more and some text and more and more and ". - "and more and more and more and more and more and more ". - "and almost at the end some other text that is a match ", - ) || die; - $self->make_message("test2", - body => "Test2 body with some other text", - ) || die; + xlog $self, "Generate and index test messages."; + $self->make_message( + "[plumbing] Re: log server v0 live", + body => "Test1 body with some long text and there is even more " + . "and more and more and more and more and more and more " + . "and more and more and some text and more and more and " + . "and more and more and more and more and more and more " + . "and almost at the end some other text that is a match ", + ) || die; + $self->make_message("test2", body => "Test2 body with some other text",) + || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - xlog $self, 'SEARCH for FUZZY snippets'; - my $query = 'servers'; - $uids = $talk->search('fuzzy', 'text', $query) || die; - $self->assert_num_equals(1, scalar @$uids); + xlog $self, 'SEARCH for FUZZY snippets'; + my $query = 'servers'; + $uids = $talk->search('fuzzy', 'text', $query) || die; + $self->assert_num_equals(1, scalar @$uids); - my %m; - $r = $self->get_snippets('INBOX', $uids, { text => $query }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; - $self->assert_matches(qr/^\[plumbing\]/, $m{subject}); + my %m; + $r = $self->get_snippets('INBOX', $uids, { text => $query }); + %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; + $self->assert_matches(qr/^\[plumbing\]/, $m{subject}); } diff --git a/cassandane/tiny-tests/SearchFuzzy/skipdiacrit b/cassandane/tiny-tests/SearchFuzzy/skipdiacrit index ddd22f5014..435cc42777 100644 --- a/cassandane/tiny-tests/SearchFuzzy/skipdiacrit +++ b/cassandane/tiny-tests/SearchFuzzy/skipdiacrit @@ -2,52 +2,53 @@ use Cassandane::Tiny; sub test_skipdiacrit - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; - # Set up test messages - my $body = "Die Trauben gären."; - xlog $self, "Generate and index test messages."; - my %params = ( - mime_charset => "utf-8", - body => $body - ); - $self->make_message("1", %params) || die; - $body = "Gemüse schonend garen."; - %params = ( - mime_charset => "utf-8", - body => $body - ); - $self->make_message("2", %params) || die; + # Set up test messages + my $body = "Die Trauben gären."; + xlog $self, "Generate and index test messages."; + my %params = ( + mime_charset => "utf-8", + body => $body + ); + $self->make_message("1", %params) || die; + $body = "Gemüse schonend garen."; + %params = ( + mime_charset => "utf-8", + body => $body + ); + $self->make_message("2", %params) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - # Connect to IMAP - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + # Connect to IMAP + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - xlog $self, 'Search for "garen"'; - $r = $talk->search( - "charset", "utf-8", "fuzzy", ["text", { Quote => "garen" }], + xlog $self, 'Search for "garen"'; + $r + = $talk->search("charset", "utf-8", "fuzzy", + [ "text", { Quote => "garen" } ], ) || die; - if ($self->{skipdiacrit}) { - $self->assert_num_equals(2, scalar @$r); - } else { - $self->assert_num_equals(1, scalar @$r); - } + if ($self->{skipdiacrit}) { + $self->assert_num_equals(2, scalar @$r); + } else { + $self->assert_num_equals(1, scalar @$r); + } - xlog $self, 'Search for "gären"'; - $r = $talk->search( - "charset", "utf-8", "fuzzy", ["text", { Quote => "gären" }], + xlog $self, 'Search for "gären"'; + $r + = $talk->search("charset", "utf-8", "fuzzy", + [ "text", { Quote => "gären" } ], ) || die; - if ($self->{skipdiacrit}) { - $self->assert_num_equals(2, scalar @$r); - } else { - $self->assert_num_equals(1, scalar @$r); - } + if ($self->{skipdiacrit}) { + $self->assert_num_equals(2, scalar @$r); + } else { + $self->assert_num_equals(1, scalar @$r); + } } diff --git a/cassandane/tiny-tests/SearchFuzzy/snippet_wildcard b/cassandane/tiny-tests/SearchFuzzy/snippet_wildcard index cf9451c639..eefc03bea0 100644 --- a/cassandane/tiny-tests/SearchFuzzy/snippet_wildcard +++ b/cassandane/tiny-tests/SearchFuzzy/snippet_wildcard @@ -2,45 +2,40 @@ use Cassandane::Tiny; sub test_snippet_wildcard - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; - - # Set up Xapian database - xlog $self, "Generate and index test messages"; - my %params = ( - mime_charset => "utf-8", - ); - my $subject; - my $body; - - $subject = "1"; - $body = "Waiter! There's a foo in my soup!"; - $params{body} = $body; - $self->make_message($subject, %params) || die; - - $subject = "2"; - $body = "Let's foop the loop."; - $params{body} = $body; - $self->make_message($subject, %params) || die; - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my $talk = $self->{store}->get_client(); - - my $term = "foo"; - xlog $self, "SEARCH for FUZZY body $term*"; - my $r = $talk->search( - "fuzzy", ["body", { Quote => "$term*" }], - ) || die; - $self->assert_num_equals(2, scalar @$r); - my $uids = $r; - - xlog $self, "Select INBOX"; - $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - - xlog $self, "Get snippets for $term"; - $r = $self->get_snippets('INBOX', $uids, { 'text' => "$term*" }); - $self->assert_num_equals(2, scalar @{$r->{snippets}}); + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; + + # Set up Xapian database + xlog $self, "Generate and index test messages"; + my %params = (mime_charset => "utf-8",); + my $subject; + my $body; + + $subject = "1"; + $body = "Waiter! There's a foo in my soup!"; + $params{body} = $body; + $self->make_message($subject, %params) || die; + + $subject = "2"; + $body = "Let's foop the loop."; + $params{body} = $body; + $self->make_message($subject, %params) || die; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my $talk = $self->{store}->get_client(); + + my $term = "foo"; + xlog $self, "SEARCH for FUZZY body $term*"; + my $r = $talk->search("fuzzy", [ "body", { Quote => "$term*" } ],) || die; + $self->assert_num_equals(2, scalar @$r); + my $uids = $r; + + xlog $self, "Select INBOX"; + $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + + xlog $self, "Get snippets for $term"; + $r = $self->get_snippets('INBOX', $uids, { 'text' => "$term*" }); + $self->assert_num_equals(2, scalar @{ $r->{snippets} }); } diff --git a/cassandane/tiny-tests/SearchFuzzy/snippets_escapehtml b/cassandane/tiny-tests/SearchFuzzy/snippets_escapehtml index 24d66cad90..1d6e0604e6 100644 --- a/cassandane/tiny-tests/SearchFuzzy/snippets_escapehtml +++ b/cassandane/tiny-tests/SearchFuzzy/snippets_escapehtml @@ -2,41 +2,50 @@ use Cassandane::Tiny; sub test_snippets_escapehtml - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; - - xlog $self, "Generate and index test messages."; - $self->make_message("Test1 subject with an unescaped & in it", - mime_charset => "utf-8", - mime_type => "text/html", - body => "Test1 body with the same tag as snippets" - ) || die; - - $self->make_message("Test2 subject with a in it", - mime_charset => "utf-8", - mime_type => "text/plain", - body => "Test2 body with a , although it's plain text", - ) || die; - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - my $talk = $self->{store}->get_client(); - - # Connect to IMAP - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - my %m; - - $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test1' }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; - $self->assert_str_equals("Test1 body with the same tag as snippets", $m{body}); - $self->assert_str_equals("Test1 subject with an unescaped & in it", $m{subject}); - - $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test2' }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; - $self->assert_str_equals("Test2 body with a <tag/>, although it's plain text", $m{body}); - $self->assert_str_equals("Test2 subject with a <tag> in it", $m{subject}); + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; + + xlog $self, "Generate and index test messages."; + $self->make_message( + "Test1 subject with an unescaped & in it", + mime_charset => "utf-8", + mime_type => "text/html", + body => "Test1 body with the same tag as snippets" + ) || die; + + $self->make_message( + "Test2 subject with a in it", + mime_charset => "utf-8", + mime_type => "text/plain", + body => "Test2 body with a , although it's plain text", + ) || die; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + my $talk = $self->{store}->get_client(); + + # Connect to IMAP + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + my %m; + + $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test1' }); + %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; + $self->assert_str_equals( + "Test1 body with the same tag as snippets", + $m{body}); + $self->assert_str_equals( + "Test1 subject with an unescaped & in it", + $m{subject}); + + $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test2' }); + %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; + $self->assert_str_equals( + "Test2 body with a <tag/>, although it's plain text", + $m{body}); + $self->assert_str_equals( + "Test2 subject with a <tag> in it", + $m{subject}); } diff --git a/cassandane/tiny-tests/SearchFuzzy/snippets_termcover b/cassandane/tiny-tests/SearchFuzzy/snippets_termcover index 663b6df4e3..689888c251 100644 --- a/cassandane/tiny-tests/SearchFuzzy/snippets_termcover +++ b/cassandane/tiny-tests/SearchFuzzy/snippets_termcover @@ -2,69 +2,77 @@ use Cassandane::Tiny; sub test_snippets_termcover - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; - my $body = - "The 'charset' portion of an 'encoded-word' specifies the character ". - "set associated with the unencoded text. A 'charset' can be any of ". - "the character set names allowed in an MIME \"charset\" parameter of a ". - "\"text/plain\" body part, or any character set name registered with ". - "IANA for use with the MIME text/plain content-type. ". - "". + my $body + = "The 'charset' portion of an 'encoded-word' specifies the character " + . "set associated with the unencoded text. A 'charset' can be any of " + . "the character set names allowed in an MIME \"charset\" parameter of a " + . "\"text/plain\" body part, or any character set name registered with " + . "IANA for use with the MIME text/plain content-type. " . "" + . # Attempt to trick the snippet generator into picking the next two lines - "Here is a line with favourite but not without that other search word ". - "Here is another line with a favourite word but not the other one ". - "". - "Some character sets use code-switching techniques to switch between ". - "\"ASCII mode\" and other modes. If unencoded text in an 'encoded-word' ". - "contains a sequence which causes the charset interpreter to switch ". - "out of ASCII mode, it MUST contain additional control codes such that ". - "ASCII mode is again selected at the end of the 'encoded-word'. (This ". - "rule applies separately to each 'encoded-word', including adjacent ". - "encoded-word's within a single header field.) ". - "When there is a possibility of using more than one character set to ". - "represent the text in an 'encoded-word', and in the absence of ". - "private agreements between sender and recipients of a message, it is ". - "recommended that members of the ISO-8859-* series be used in ". - "preference to other character sets.". - "". + "Here is a line with favourite but not without that other search word " + . "Here is another line with a favourite word but not the other one " . "" + . "Some character sets use code-switching techniques to switch between " + . "\"ASCII mode\" and other modes. If unencoded text in an 'encoded-word' " + . "contains a sequence which causes the charset interpreter to switch " + . "out of ASCII mode, it MUST contain additional control codes such that " + . "ASCII mode is again selected at the end of the 'encoded-word'. (This " + . "rule applies separately to each 'encoded-word', including adjacent " + . "encoded-word's within a single header field.) " + . "When there is a possibility of using more than one character set to " + . "represent the text in an 'encoded-word', and in the absence of " + . "private agreements between sender and recipients of a message, it is " + . "recommended that members of the ISO-8859-* series be used in " + . "preference to other character sets." . "" + . # This is the line we want to get as a snippet "I don't have a favourite cereal. My favourite breakfast is oat meal."; - xlog $self, "Generate and index test messages."; - my %params = ( - mime_charset => "utf-8", - body => $body - ); - $self->make_message("1", %params) || die; + xlog $self, "Generate and index test messages."; + my %params = ( + mime_charset => "utf-8", + body => $body + ); + $self->make_message("1", %params) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - # Connect to IMAP - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - my $want = "favourite cereal"; + # Connect to IMAP + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + my $want = "favourite cereal"; - $r = $self->get_snippets('INBOX', $uids, { - operator => 'AND', - conditions => [{ - text => 'favourite', - }, { - text => 'cereal', - }, { - text => '"bogus gnarly"' - }], - }); - $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); + $r = $self->get_snippets( + 'INBOX', $uids, + { + operator => 'AND', + conditions => [ + { + text => 'favourite', + }, + { + text => 'cereal', + }, + { + text => '"bogus gnarly"' + } + ], + } + ); + $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); - $r = $self->get_snippets('INBOX', $uids, { - text => 'favourite cereal', - }); - $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); + $r = $self->get_snippets( + 'INBOX', $uids, + { + text => 'favourite cereal', + } + ); + $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); } diff --git a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cache b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cache index 44a6f7d79a..d0012f7d47 100644 --- a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cache +++ b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cache @@ -2,32 +2,31 @@ use Cassandane::Tiny; sub test_squatter_attachextract_cache - :min_version_3_9 :needs_search_xapian :SearchAttachmentExtractor -{ - my ($self) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_search_xapian : SearchAttachmentExtractor { + my ($self) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); - my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); - $self->start_echo_extractor(tracedir => $tracedir); + my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); + $self->start_echo_extractor(tracedir => $tracedir); - xlog "Create and index index messages"; - my $cachedir = tempdir(DIR => $instance->{basedir} . "/tmp"); - $self->squatter_attachextract_cache_run($cachedir); + xlog "Create and index index messages"; + my $cachedir = tempdir(DIR => $instance->{basedir} . "/tmp"); + $self->squatter_attachextract_cache_run($cachedir); - xlog "Assert text bodies of both messages are indexed"; - my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1,2], $uids); + xlog "Assert text bodies of both messages are indexed"; + my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([ 1, 2 ], $uids); - xlog "Assert attachments of both messages are indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); - $self->assert_deep_equals([1,2], $uids); + xlog "Assert attachments of both messages are indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); + $self->assert_deep_equals([ 1, 2 ], $uids); - xlog "Assert extractor only got called once"; - my @tracefiles = glob($tracedir."/*_PUT_*"); - $self->assert_num_equals(1, scalar @tracefiles); + xlog "Assert extractor only got called once"; + my @tracefiles = glob($tracedir . "/*_PUT_*"); + $self->assert_num_equals(1, scalar @tracefiles); - xlog "Assert cache contains one file"; - my @files = glob($cachedir."/*"); - $self->assert_num_equals(1, scalar @files); + xlog "Assert cache contains one file"; + my @files = glob($cachedir . "/*"); + $self->assert_num_equals(1, scalar @files); } diff --git a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cachedir_noperm b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cachedir_noperm index dcd9989379..a48dae2e87 100644 --- a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cachedir_noperm +++ b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cachedir_noperm @@ -2,34 +2,34 @@ use Cassandane::Tiny; sub test_squatter_attachextract_cachedir_noperm - :min_version_3_9 :needs_search_xapian :SearchAttachmentExtractor :NoCheckSyslog -{ - my ($self) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_search_xapian : SearchAttachmentExtractor : + NoCheckSyslog { + my ($self) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); - my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); - $self->start_echo_extractor(tracedir => $tracedir); + my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); + $self->start_echo_extractor(tracedir => $tracedir); - xlog "Run squatter with read-only cache directory"; - my $cachedir = tempdir(DIR => $instance->{basedir} . "/tmp"); - chmod 0400, $cachedir || die; - $self->squatter_attachextract_cache_run($cachedir, "--allow-partials"); + xlog "Run squatter with read-only cache directory"; + my $cachedir = tempdir(DIR => $instance->{basedir} . "/tmp"); + chmod 0400, $cachedir || die; + $self->squatter_attachextract_cache_run($cachedir, "--allow-partials"); - xlog "Assert text bodies of both messages are indexed"; - my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1,2], $uids); + xlog "Assert text bodies of both messages are indexed"; + my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([ 1, 2 ], $uids); - xlog "Assert attachments of both messages are not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); - $self->assert_deep_equals([], $uids); + xlog "Assert attachments of both messages are not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); + $self->assert_deep_equals([], $uids); - xlog "Assert extractor got called twice with attachment uploads"; - my @tracefiles = glob($tracedir."/*_PUT_*"); - $self->assert_num_equals(2, scalar @tracefiles); + xlog "Assert extractor got called twice with attachment uploads"; + my @tracefiles = glob($tracedir . "/*_PUT_*"); + $self->assert_num_equals(2, scalar @tracefiles); - xlog "Assert cache contains no file"; - chmod 0700, $cachedir || die; - my @files = glob($cachedir."/*"); - $self->assert_num_equals(0, scalar @files); + xlog "Assert cache contains no file"; + chmod 0700, $cachedir || die; + my @files = glob($cachedir . "/*"); + $self->assert_num_equals(0, scalar @files); } diff --git a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cacheonly b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cacheonly index c34f9462cb..9773e697db 100644 --- a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cacheonly +++ b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_cacheonly @@ -2,33 +2,33 @@ use Cassandane::Tiny; sub test_squatter_attachextract_cacheonly - :min_version_3_9 :needs_search_xapian :SearchAttachmentExtractor :NoCheckSyslog -{ - my ($self) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_search_xapian : SearchAttachmentExtractor : + NoCheckSyslog { + my ($self) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); - my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); - $self->start_echo_extractor(tracedir => $tracedir); + my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); + $self->start_echo_extractor(tracedir => $tracedir); - xlog "Instruct squatter to only use attachextract cache"; - my $cachedir = tempdir(DIR => $instance->{basedir} . "/tmp"); - $self->squatter_attachextract_cache_run($cachedir, - "--attachextract-cache-only", "--allow-partials"); + xlog "Instruct squatter to only use attachextract cache"; + my $cachedir = tempdir(DIR => $instance->{basedir} . "/tmp"); + $self->squatter_attachextract_cache_run($cachedir, + "--attachextract-cache-only", "--allow-partials"); - xlog "Assert text bodies of both messages are indexed"; - my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1,2], $uids); + xlog "Assert text bodies of both messages are indexed"; + my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([ 1, 2 ], $uids); - xlog "Assert attachments of both messages are not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); - $self->assert_deep_equals([], $uids); + xlog "Assert attachments of both messages are not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); + $self->assert_deep_equals([], $uids); - xlog "Assert extractor did not get got called"; - my @tracefiles = glob($tracedir."/*"); - $self->assert_num_equals(0, scalar @tracefiles); + xlog "Assert extractor did not get got called"; + my @tracefiles = glob($tracedir . "/*"); + $self->assert_num_equals(0, scalar @tracefiles); - xlog "Assert cache contains no file"; - my @files = glob($cachedir."/*"); - $self->assert_num_equals(0, scalar @files); + xlog "Assert cache contains no file"; + my @files = glob($cachedir . "/*"); + $self->assert_num_equals(0, scalar @files); } diff --git a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_nolock b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_nolock index 81a16034bc..80b74dab64 100644 --- a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_nolock +++ b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_nolock @@ -2,69 +2,70 @@ use Cassandane::Tiny; sub test_squatter_attachextract_nolock - :min_version_3_9 :needs_search_xapian :SearchAttachmentExtractor -{ - my ($self) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_search_xapian : SearchAttachmentExtractor { + my ($self) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); - my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); - $self->start_echo_extractor( - tracedir => $tracedir, - trace_delay_seconds => 1, - response_delay_seconds => 1, - ); + my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); + $self->start_echo_extractor( + tracedir => $tracedir, + trace_delay_seconds => 1, + response_delay_seconds => 1, + ); - xlog $self, "Make plain text message"; - $self->make_message("msg1", - mime_type => "text/plain", - body => "bodyterm"); + xlog $self, "Make plain text message"; + $self->make_message( + "msg1", + mime_type => "text/plain", + body => "bodyterm" + ); - xlog $self, "Make message with attachment"; - $self->make_message("msg2", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/pdf\r\n" - ."Content-Transfer-Encoding: base64\r\n" - ."\r\n" - # that's "attachterm" - ."YXR0YWNodGVybQo=" - ."\r\n--123456789abcdef--\r\n"); + xlog $self, "Make message with attachment"; + $self->make_message( + "msg2", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/pdf\r\n" + . "Content-Transfer-Encoding: base64\r\n" . "\r\n" + # that's "attachterm" + . "YXR0YWNodGVybQo=" . "\r\n--123456789abcdef--\r\n" + ); - xlog $self, "Clear syslog"; - $self->{instance}->getsyslog(); + xlog $self, "Clear syslog"; + $self->{instance}->getsyslog(); - xlog $self, "Run squatter"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v'); + xlog $self, "Run squatter"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-v'); - xlog $self, "Inspect syslog and extractor trace files"; - my @log = $self->{instance}->getsyslog(qr/squatter\[\d+\]: (released|reacquired) mailbox lock/); + xlog $self, "Inspect syslog and extractor trace files"; + my @log = $self->{instance} + ->getsyslog(qr/squatter\[\d+\]: (released|reacquired) mailbox lock/); - my ($released_timestamp) = ($log[0] =~ /released.+unixepoch=<(\d+)>/); - $self->assert_not_null($released_timestamp); + my ($released_timestamp) = ($log[0] =~ /released.+unixepoch=<(\d+)>/); + $self->assert_not_null($released_timestamp); - my @tracefiles = glob($tracedir."/*_PUT_*"); - $self->assert_num_equals(1, scalar @tracefiles); - my $extractor_timestamp = stat($tracefiles[0])->ctime; - $self->assert_not_null($extractor_timestamp); + my @tracefiles = glob($tracedir . "/*_PUT_*"); + $self->assert_num_equals(1, scalar @tracefiles); + my $extractor_timestamp = stat($tracefiles[0])->ctime; + $self->assert_not_null($extractor_timestamp); - my ($reacquired_timestamp) = ($log[1] =~ /reacquired.+unixepoch=<(\d+)>/); - $self->assert_not_null($reacquired_timestamp); + my ($reacquired_timestamp) = ($log[1] =~ /reacquired.+unixepoch=<(\d+)>/); + $self->assert_not_null($reacquired_timestamp); - xlog $self, "Assert extractor got called without mailbox lock"; - $self->assert_num_lt($extractor_timestamp, $released_timestamp); - $self->assert_num_lt($reacquired_timestamp, $extractor_timestamp); + xlog $self, "Assert extractor got called without mailbox lock"; + $self->assert_num_lt($extractor_timestamp, $released_timestamp); + $self->assert_num_lt($reacquired_timestamp, $extractor_timestamp); - xlog $self, "Assert terms actually got indexed"; - my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1,2], $uids); + xlog $self, "Assert terms actually got indexed"; + my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([ 1, 2 ], $uids); - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); - $self->assert_deep_equals([2], $uids); + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); + $self->assert_deep_equals([2], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_timeout b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_timeout index 4002f7de9a..ca7c7e61a5 100644 --- a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_timeout +++ b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_timeout @@ -2,68 +2,68 @@ use Cassandane::Tiny; sub test_squatter_attachextract_timeout - :min_version_3_9 :needs_search_xapian :SearchAttachmentExtractor :NoCheckSyslog -{ - my ($self) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); + : min_version_3_9 : needs_search_xapian : SearchAttachmentExtractor : + NoCheckSyslog { + my ($self) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); - my $tracedir = tempdir (DIR => $instance->{basedir} . "/tmp"); + my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); - # SearchAttachmentExtractor magic configures Cyrus to - # wait at most 3 seconds for a response from extractor + # SearchAttachmentExtractor magic configures Cyrus to + # wait at most 3 seconds for a response from extractor - $self->start_echo_extractor( - tracedir => $tracedir, - response_delay_seconds => [5], # timeout on first request only - ); + $self->start_echo_extractor( + tracedir => $tracedir, + response_delay_seconds => [5], # timeout on first request only + ); - xlog $self, "Make message with attachment"; - $self->make_message("msg1", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/pdf\r\n" - ."\r\n" - ."attachterm" - ."\r\n--123456789abcdef--\r\n"); + xlog $self, "Make message with attachment"; + $self->make_message( + "msg1", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/pdf\r\n" . "\r\n" + . "attachterm" + . "\r\n--123456789abcdef--\r\n" + ); - xlog $self, "Run squatter (allowing partials)"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v', '-p'); + xlog $self, "Run squatter (allowing partials)"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-v', '-p'); - xlog "Assert text body is indexed"; - my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1], $uids); + xlog "Assert text body is indexed"; + my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([1], $uids); - xlog "Assert attachement is not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); - $self->assert_deep_equals([], $uids); + xlog "Assert attachement is not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); + $self->assert_deep_equals([], $uids); - xlog "Assert extractor got called once"; - my @tracefiles = glob($tracedir."/*"); - $self->assert_num_equals(1, scalar @tracefiles); - $self->assert_matches(qr/req1_GET_/, $tracefiles[0]); + xlog "Assert extractor got called once"; + my @tracefiles = glob($tracedir . "/*"); + $self->assert_num_equals(1, scalar @tracefiles); + $self->assert_matches(qr/req1_GET_/, $tracefiles[0]); - xlog $self, "Rerun squatter for partials"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v', '-i', '-P'); + xlog $self, "Rerun squatter for partials"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-v', '-i', '-P'); - xlog "Assert text body is indexed"; - $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1], $uids); + xlog "Assert text body is indexed"; + $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([1], $uids); - xlog "Assert attachement is indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); - $self->assert_deep_equals([1], $uids); + xlog "Assert attachement is indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); + $self->assert_deep_equals([1], $uids); - xlog "Assert extractor got called three times"; - @tracefiles = glob($tracedir."/*"); - $self->assert_num_equals(3, scalar @tracefiles); - $self->assert_matches(qr/req1_GET_/, $tracefiles[0]); - $self->assert_matches(qr/req2_GET_/, $tracefiles[1]); - $self->assert_matches(qr/req3_PUT_/, $tracefiles[2]); + xlog "Assert extractor got called three times"; + @tracefiles = glob($tracedir . "/*"); + $self->assert_num_equals(3, scalar @tracefiles); + $self->assert_matches(qr/req1_GET_/, $tracefiles[0]); + $self->assert_matches(qr/req2_GET_/, $tracefiles[1]); + $self->assert_matches(qr/req3_PUT_/, $tracefiles[2]); } diff --git a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_unprocessable_content b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_unprocessable_content index bb39d4fbc8..4ed5f7f32d 100644 --- a/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_unprocessable_content +++ b/cassandane/tiny-tests/SearchFuzzy/squatter_attachextract_unprocessable_content @@ -2,95 +2,96 @@ use Cassandane::Tiny; sub test_squatter_attachextract_unprocessable_content - :min_version_3_9 :needs_search_xapian :SearchAttachmentExtractor :NoCheckSyslog -{ - my ($self) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); - - my $tracedir = tempdir (DIR => $instance->{basedir} . "/tmp"); - my $nrequests = 0; - - xlog "Start extractor server"; - my $handler = sub { - my ($conn, $req) = @_; - - $nrequests++; - - # touch trace file in tracedir - my @paths = split(q{/}, URI->new($req->uri)->path); - my $guid = pop(@paths); - my $fname = join(q{}, - $tracedir, "/req", $nrequests, "_", $req->method, "_$guid"); - open(my $fh, ">", $fname) or die "Can't open > $fname: $!"; - close $fh; - - my $res; - - if ($req->method eq 'HEAD') { - $res = HTTP::Response->new(404); - $res->content(""); - } elsif ($req->method eq 'GET') { - $res = HTTP::Response->new(404); - $res->content("nope"); - } else { - # return HTTP 422 Unprocessable Content - $res = HTTP::Response->new(422); - $res->content("nope"); - } - - $conn->send_response($res); - }; - - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); - $instance->start_httpd($handler, $uri->port()); - - xlog $self, "Make message with unprocessable attachment"; - $self->make_message("msg1", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/octet-stream\r\n" - ."\r\n" - ."attachterm" - ."\r\n--123456789abcdef--\r\n"); - - xlog $self, "Run squatter (allowing partials)"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v', '-p'); - - xlog "Assert text body is indexed"; - my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1], $uids); - - xlog "Assert attachement is not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); - $self->assert_deep_equals([], $uids); - - xlog "Assert extractor got called"; - my @tracefiles = glob($tracedir."/*"); - $self->assert_num_equals(2, scalar @tracefiles); - $self->assert_matches(qr/req1_GET_/, $tracefiles[0]); - $self->assert_matches(qr/req2_PUT_/, $tracefiles[1]); - - xlog $self, "Rerun squatter for partials"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v', '-i', '-P'); - - xlog "Assert text body is indexed"; - $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1], $uids); - - xlog "Assert attachement is not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); - $self->assert_deep_equals([], $uids); - - xlog "Assert extractor got called no more time"; - @tracefiles = glob($tracedir."/*"); - $self->assert_num_equals(2, scalar @tracefiles); - $self->assert_matches(qr/req1_GET_/, $tracefiles[0]); - $self->assert_matches(qr/req2_PUT_/, $tracefiles[1]); + : min_version_3_9 : needs_search_xapian : SearchAttachmentExtractor : + NoCheckSyslog { + my ($self) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); + + my $tracedir = tempdir(DIR => $instance->{basedir} . "/tmp"); + my $nrequests = 0; + + xlog "Start extractor server"; + my $handler = sub { + my ($conn, $req) = @_; + + $nrequests++; + + # touch trace file in tracedir + my @paths = split(q{/}, URI->new($req->uri)->path); + my $guid = pop(@paths); + my $fname + = join(q{}, $tracedir, "/req", $nrequests, "_", $req->method, "_$guid"); + open(my $fh, ">", $fname) or die "Can't open > $fname: $!"; + close $fh; + + my $res; + + if ($req->method eq 'HEAD') { + $res = HTTP::Response->new(404); + $res->content(""); + } elsif ($req->method eq 'GET') { + $res = HTTP::Response->new(404); + $res->content("nope"); + } else { + # return HTTP 422 Unprocessable Content + $res = HTTP::Response->new(422); + $res->content("nope"); + } + + $conn->send_response($res); + }; + + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); + $instance->start_httpd($handler, $uri->port()); + + xlog $self, "Make message with unprocessable attachment"; + $self->make_message( + "msg1", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/octet-stream\r\n" . "\r\n" + . "attachterm" + . "\r\n--123456789abcdef--\r\n" + ); + + xlog $self, "Run squatter (allowing partials)"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-v', '-p'); + + xlog "Assert text body is indexed"; + my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([1], $uids); + + xlog "Assert attachement is not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); + $self->assert_deep_equals([], $uids); + + xlog "Assert extractor got called"; + my @tracefiles = glob($tracedir . "/*"); + $self->assert_num_equals(2, scalar @tracefiles); + $self->assert_matches(qr/req1_GET_/, $tracefiles[0]); + $self->assert_matches(qr/req2_PUT_/, $tracefiles[1]); + + xlog $self, "Rerun squatter for partials"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-v', '-i', '-P'); + + xlog "Assert text body is indexed"; + $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([1], $uids); + + xlog "Assert attachement is not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attachterm'); + $self->assert_deep_equals([], $uids); + + xlog "Assert extractor got called no more time"; + @tracefiles = glob($tracedir . "/*"); + $self->assert_num_equals(2, scalar @tracefiles); + $self->assert_matches(qr/req1_GET_/, $tracefiles[0]); + $self->assert_matches(qr/req2_PUT_/, $tracefiles[1]); } diff --git a/cassandane/tiny-tests/SearchFuzzy/squatter_partials b/cassandane/tiny-tests/SearchFuzzy/squatter_partials index 503a543447..8a6148a106 100644 --- a/cassandane/tiny-tests/SearchFuzzy/squatter_partials +++ b/cassandane/tiny-tests/SearchFuzzy/squatter_partials @@ -2,101 +2,100 @@ use Cassandane::Tiny; sub test_squatter_partials - :min_version_3_3 :needs_search_xapian :SearchAttachmentExtractor :NoCheckSyslog -{ - my ($self) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_search_xapian : SearchAttachmentExtractor : + NoCheckSyslog { + my ($self) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); - xlog "Start extractor server"; - my $nrequests = 0; - my $handler = sub { - my ($conn, $req) = @_; - if ($req->method eq 'HEAD') { - my $res = HTTP::Response->new(204); - $res->content(""); - $conn->send_response($res); - } else { - $nrequests++; - if ($nrequests <= 4) { - # attach1: squatter sends GET and retries PUT 3 times - $conn->send_error(500); - } elsif ($nrequests == 5) { - # attach2: squatter sends GET - my $res = HTTP::Response->new(200); - $res->content("attach2"); - $conn->send_response($res); - } elsif ($nrequests == 6) { - # attach1 retry: squatter sends GET - my $res = HTTP::Response->new(200); - $res->content("attach1"); - $conn->send_response($res); - } else { - xlog "Unexpected request"; - $conn->send_error(500); - } - } - }; - $instance->start_httpd($handler, $uri->port()); + xlog "Start extractor server"; + my $nrequests = 0; + my $handler = sub { + my ($conn, $req) = @_; + if ($req->method eq 'HEAD') { + my $res = HTTP::Response->new(204); + $res->content(""); + $conn->send_response($res); + } else { + $nrequests++; + if ($nrequests <= 4) { + # attach1: squatter sends GET and retries PUT 3 times + $conn->send_error(500); + } elsif ($nrequests == 5) { + # attach2: squatter sends GET + my $res = HTTP::Response->new(200); + $res->content("attach2"); + $conn->send_response($res); + } elsif ($nrequests == 6) { + # attach1 retry: squatter sends GET + my $res = HTTP::Response->new(200); + $res->content("attach1"); + $conn->send_response($res); + } else { + xlog "Unexpected request"; + $conn->send_error(500); + } + } + }; + $instance->start_httpd($handler, $uri->port()); - xlog "Append emails with PDF attachments to trigger extractor"; - $self->make_message("msg1", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/pdf\r\n" - ."\r\n" - ."attach1" - ."\r\n--123456789abcdef--\r\n" - ) || die; - $self->make_message("msg2", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/pdf\r\n" - ."\r\n" - ."attach2" - ."\r\n--123456789abcdef--\r\n" - ) || die; + xlog "Append emails with PDF attachments to trigger extractor"; + $self->make_message( + "msg1", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/pdf\r\n" . "\r\n" + . "attach1" + . "\r\n--123456789abcdef--\r\n" + ) || die; + $self->make_message( + "msg2", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/pdf\r\n" . "\r\n" + . "attach2" + . "\r\n--123456789abcdef--\r\n" + ) || die; - xlog "Run squatter and allow partials"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-p', '-Z'); + xlog "Run squatter and allow partials"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-p', '-Z'); - xlog "Assert text bodies of both messages are indexed"; - my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1,2], $uids); + xlog "Assert text bodies of both messages are indexed"; + my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([ 1, 2 ], $uids); - xlog "Assert attachment of first message is not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach1'); - $self->assert_deep_equals([], $uids); + xlog "Assert attachment of first message is not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach1'); + $self->assert_deep_equals([], $uids); - xlog "Assert attachment of second message is indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach2'); - $self->assert_deep_equals([2], $uids); + xlog "Assert attachment of second message is indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach2'); + $self->assert_deep_equals([2], $uids); - xlog "Run incremental squatter without recovering partials"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-i'); + xlog "Run incremental squatter without recovering partials"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-i'); - xlog "Assert attachment of first message is not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach1'); - $self->assert_deep_equals([], $uids); + xlog "Assert attachment of first message is not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach1'); + $self->assert_deep_equals([], $uids); - xlog "Run incremental squatter with recovering partials"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-i', '-P'); + xlog "Run incremental squatter with recovering partials"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-i', '-P'); - xlog "Assert attachment of first message is indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach1'); - $self->assert_deep_equals([1], $uids); + xlog "Assert attachment of first message is indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach1'); + $self->assert_deep_equals([1], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/squatter_skip422 b/cassandane/tiny-tests/SearchFuzzy/squatter_skip422 index 40f89ca0d9..5c8d436afd 100644 --- a/cassandane/tiny-tests/SearchFuzzy/squatter_skip422 +++ b/cassandane/tiny-tests/SearchFuzzy/squatter_skip422 @@ -2,70 +2,69 @@ use Cassandane::Tiny; sub test_squatter_skip422 - :min_version_3_3 :needs_search_xapian :SearchAttachmentExtractor :NoCheckSyslog -{ - my ($self) = @_; - my $instance = $self->{instance}; - my $imap = $self->{store}->get_client(); + : min_version_3_3 : needs_search_xapian : SearchAttachmentExtractor : + NoCheckSyslog { + my ($self) = @_; + my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); - xlog "Start extractor server"; - my $nrequests = 0; - my $handler = sub { - my ($conn, $req) = @_; - if ($req->method eq 'HEAD') { - my $res = HTTP::Response->new(204); - $res->content(""); - $conn->send_response($res); - } else { - $conn->send_error(422); - } - }; - $instance->start_httpd($handler, $uri->port()); + xlog "Start extractor server"; + my $nrequests = 0; + my $handler = sub { + my ($conn, $req) = @_; + if ($req->method eq 'HEAD') { + my $res = HTTP::Response->new(204); + $res->content(""); + $conn->send_response($res); + } else { + $conn->send_error(422); + } + }; + $instance->start_httpd($handler, $uri->port()); - xlog "Append emails with PDF attachments to trigger extractor"; - $self->make_message("msg1", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/pdf\r\n" - ."\r\n" - ."attach1" - ."\r\n--123456789abcdef--\r\n" - ) || die; - $self->make_message("msg2", - mime_type => "multipart/related", - mime_boundary => "123456789abcdef", - body => "" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: text/plain\r\n" - ."\r\n" - ."bodyterm" - ."\r\n--123456789abcdef\r\n" - ."Content-Type: application/pdf\r\n" - ."\r\n" - ."attach2" - ."\r\n--123456789abcdef--\r\n" - ) || die; + xlog "Append emails with PDF attachments to trigger extractor"; + $self->make_message( + "msg1", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/pdf\r\n" . "\r\n" + . "attach1" + . "\r\n--123456789abcdef--\r\n" + ) || die; + $self->make_message( + "msg2", + mime_type => "multipart/related", + mime_boundary => "123456789abcdef", + body => "" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" + . "bodyterm" + . "\r\n--123456789abcdef\r\n" + . "Content-Type: application/pdf\r\n" . "\r\n" + . "attach2" + . "\r\n--123456789abcdef--\r\n" + ) || die; - xlog "Run squatter and allow partials"; - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-p', '-Z'); + xlog "Run squatter and allow partials"; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-p', '-Z'); - xlog "Assert text bodies of both messages are indexed"; - my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); - $self->assert_deep_equals([1,2], $uids); + xlog "Assert text bodies of both messages are indexed"; + my $uids = $imap->search('fuzzy', 'body', 'bodyterm'); + $self->assert_deep_equals([ 1, 2 ], $uids); - xlog "Assert attachment of first message is not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach1'); - $self->assert_deep_equals([], $uids); + xlog "Assert attachment of first message is not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach1'); + $self->assert_deep_equals([], $uids); - xlog "Assert attachment of second message is not indexed"; - $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach2'); - $self->assert_deep_equals([], $uids); + xlog "Assert attachment of second message is not indexed"; + $uids = $imap->search('fuzzy', 'xattachmentbody', 'attach2'); + $self->assert_deep_equals([], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/stem_any b/cassandane/tiny-tests/SearchFuzzy/stem_any index b31a8e6753..4e8179477b 100644 --- a/cassandane/tiny-tests/SearchFuzzy/stem_any +++ b/cassandane/tiny-tests/SearchFuzzy/stem_any @@ -2,29 +2,25 @@ use Cassandane::Tiny; sub test_stem_any - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; - $self->create_testmessages(); + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; + $self->create_testmessages(); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - xlog $self, "Select INBOX"; - $talk->select("INBOX") || die; + xlog $self, "Select INBOX"; + $talk->select("INBOX") || die; - my $r; - xlog $self, 'SEARCH for body "connection"'; - $r = $talk->search('body', { Quote => "connection" }) || die; - if ($self->{fuzzyalways}) { - $self->assert_num_equals(3, scalar @$r); - } else { - $self->assert_num_equals(1, scalar @$r); - } - - - xlog $self, "SEARCH for FUZZY body \"connection\""; - $r = $talk->search( - "fuzzy", ["body", { Quote => "connection" }], - ) || die; + my $r; + xlog $self, 'SEARCH for body "connection"'; + $r = $talk->search('body', { Quote => "connection" }) || die; + if ($self->{fuzzyalways}) { $self->assert_num_equals(3, scalar @$r); + } else { + $self->assert_num_equals(1, scalar @$r); + } + + xlog $self, "SEARCH for FUZZY body \"connection\""; + $r = $talk->search("fuzzy", [ "body", { Quote => "connection" } ],) || die; + $self->assert_num_equals(3, scalar @$r); } diff --git a/cassandane/tiny-tests/SearchFuzzy/stem_verbs b/cassandane/tiny-tests/SearchFuzzy/stem_verbs index 8812ffa588..c00069ca62 100644 --- a/cassandane/tiny-tests/SearchFuzzy/stem_verbs +++ b/cassandane/tiny-tests/SearchFuzzy/stem_verbs @@ -2,32 +2,31 @@ use Cassandane::Tiny; sub test_stem_verbs - :min_version_3_0 :needs_search_xapian :JMAPExtensions -{ - my ($self) = @_; - $self->create_testmessages(); + : min_version_3_0 : needs_search_xapian : JMAPExtensions { + my ($self) = @_; + $self->create_testmessages(); - my $talk = $self->{store}->get_client(); - $self->assert_not_null($self->{jmap}); + my $talk = $self->{store}->get_client(); + $self->assert_not_null($self->{jmap}); - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - xlog $self, 'SEARCH for subject "runs"'; - $r = $talk->search('subject', { Quote => "runs" }) || die; - if ($self->{fuzzyalways}) { - $self->assert_num_equals(3, scalar @$r); - } else { - $self->assert_num_equals(1, scalar @$r); - } - - xlog $self, 'SEARCH for FUZZY subject "runs"'; - $r = $talk->search('fuzzy', ['subject', { Quote => "runs" }]) || die; + xlog $self, 'SEARCH for subject "runs"'; + $r = $talk->search('subject', { Quote => "runs" }) || die; + if ($self->{fuzzyalways}) { $self->assert_num_equals(3, scalar @$r); + } else { + $self->assert_num_equals(1, scalar @$r); + } + + xlog $self, 'SEARCH for FUZZY subject "runs"'; + $r = $talk->search('fuzzy', [ 'subject', { Quote => "runs" } ]) || die; + $self->assert_num_equals(3, scalar @$r); - xlog $self, 'Get snippets for FUZZY subject "runs"'; - $r = $self->get_snippets('INBOX', $uids, { subject => 'runs' }); - $self->assert_num_equals(3, scalar @{$r->{snippets}}); + xlog $self, 'Get snippets for FUZZY subject "runs"'; + $r = $self->get_snippets('INBOX', $uids, { subject => 'runs' }); + $self->assert_num_equals(3, scalar @{ $r->{snippets} }); } diff --git a/cassandane/tiny-tests/SearchFuzzy/stopwords b/cassandane/tiny-tests/SearchFuzzy/stopwords index 85c3f7a042..7b8a39cc73 100644 --- a/cassandane/tiny-tests/SearchFuzzy/stopwords +++ b/cassandane/tiny-tests/SearchFuzzy/stopwords @@ -2,59 +2,53 @@ use Cassandane::Tiny; sub test_stopwords - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; - - # This test assumes that "the" is a stopword and is configured with - # the search_stopword_path in cassandane.ini. If the option is not - # set it tests legacy behaviour. - - my $talk = $self->{store}->get_client(); - - # Set up Xapian database - xlog $self, "Generate and index test messages."; - my %params = ( - mime_charset => "utf-8", - ); - my $subject; - my $body; - - $subject = "1"; - $body = "In my opinion the soup smells tasty"; - $params{body} = $body; - $self->make_message($subject, %params) || die; - - $subject = "2"; - $body = "The funny thing is that this isn't funny"; - $params{body} = $body; - $self->make_message($subject, %params) || die; - - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - - # Connect via IMAP - xlog $self, "Select INBOX"; - $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - - my $term; - my $r; - - # Search for stopword only - $r = $talk->search( - "charset", "utf-8", "fuzzy", "text", "the", - ) || die; - $self->assert_num_equals(2, scalar @$r); - - # Search for stopword plus significant term - $r = $talk->search( - "charset", "utf-8", "fuzzy", "text", "the soup", - ) || die; - $self->assert_num_equals(1, scalar @$r); - - $r = $talk->search( - "charset", "utf-8", "fuzzy", "text", "the", "fuzzy", "text", "soup", - ) || die; - $self->assert_num_equals(1, scalar @$r); + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; + + # This test assumes that "the" is a stopword and is configured with + # the search_stopword_path in cassandane.ini. If the option is not + # set it tests legacy behaviour. + + my $talk = $self->{store}->get_client(); + + # Set up Xapian database + xlog $self, "Generate and index test messages."; + my %params = (mime_charset => "utf-8",); + my $subject; + my $body; + + $subject = "1"; + $body = "In my opinion the soup smells tasty"; + $params{body} = $body; + $self->make_message($subject, %params) || die; + + $subject = "2"; + $body = "The funny thing is that this isn't funny"; + $params{body} = $body; + $self->make_message($subject, %params) || die; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); + + # Connect via IMAP + xlog $self, "Select INBOX"; + $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + + my $term; + my $r; + + # Search for stopword only + $r = $talk->search("charset", "utf-8", "fuzzy", "text", "the",) || die; + $self->assert_num_equals(2, scalar @$r); + + # Search for stopword plus significant term + $r = $talk->search("charset", "utf-8", "fuzzy", "text", "the soup",) || die; + $self->assert_num_equals(1, scalar @$r); + + $r + = $talk->search("charset", "utf-8", "fuzzy", "text", "the", "fuzzy", + "text", "soup",) + || die; + $self->assert_num_equals(1, scalar @$r); } diff --git a/cassandane/tiny-tests/SearchFuzzy/striphtml_alternative b/cassandane/tiny-tests/SearchFuzzy/striphtml_alternative index 81a45c22eb..9ad1f1ab18 100644 --- a/cassandane/tiny-tests/SearchFuzzy/striphtml_alternative +++ b/cassandane/tiny-tests/SearchFuzzy/striphtml_alternative @@ -2,32 +2,30 @@ use Cassandane::Tiny; sub test_striphtml_alternative - :min_version_3_3 :needs_search_xapian -{ - my ($self) = @_; - my $talk = $self->{store}->get_client(); + : min_version_3_3 : needs_search_xapian { + my ($self) = @_; + my $talk = $self->{store}->get_client(); - xlog "Index message with both html and plain text part"; - $self->make_message("test", - mime_type => "multipart/alternative", - mime_boundary => "boundary_1", - body => "" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/plain; charset=\"UTF-8\"\r\n" - . "\r\n" - . "
This is a plain text body with html.
\r\n" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/html; charset=\"UTF-8\"\r\n" - . "\r\n" - . "
This is an html body.
\r\n" - . "\r\n--boundary_1--\r\n" - ) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog "Index message with both html and plain text part"; + $self->make_message( + "test", + mime_type => "multipart/alternative", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain; charset=\"UTF-8\"\r\n" . "\r\n" + . "
This is a plain text body with html.
\r\n" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/html; charset=\"UTF-8\"\r\n" . "\r\n" + . "
This is an html body.
\r\n" + . "\r\n--boundary_1--\r\n" + ) || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Assert that HTML in plain text is stripped"; - my $uids = $talk->search('fuzzy', 'body', 'html') || die; - $self->assert_deep_equals([1], $uids); + xlog "Assert that HTML in plain text is stripped"; + my $uids = $talk->search('fuzzy', 'body', 'html') || die; + $self->assert_deep_equals([1], $uids); - $uids = $talk->search('fuzzy', 'body', 'div') || die; - $self->assert_deep_equals([], $uids); + $uids = $talk->search('fuzzy', 'body', 'div') || die; + $self->assert_deep_equals([], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/striphtml_plain b/cassandane/tiny-tests/SearchFuzzy/striphtml_plain index dfd78834d6..7d7c7ad4bb 100644 --- a/cassandane/tiny-tests/SearchFuzzy/striphtml_plain +++ b/cassandane/tiny-tests/SearchFuzzy/striphtml_plain @@ -2,22 +2,20 @@ use Cassandane::Tiny; sub test_striphtml_plain - :min_version_3_3 :needs_search_xapian -{ - my ($self) = @_; - my $talk = $self->{store}->get_client(); + : min_version_3_3 : needs_search_xapian { + my ($self) = @_; + my $talk = $self->{store}->get_client(); - xlog "Index message with only plain text part"; - $self->make_message("test", - body => "" - . "
This is a plain text body with html.
\r\n" - ) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog "Index message with only plain text part"; + $self->make_message("test", + body => "" . "
This is a plain text body with html.
\r\n") + || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Assert that HTML in plain-text only isn't stripped"; - my $uids = $talk->search('fuzzy', 'body', 'html') || die; - $self->assert_deep_equals([1], $uids); + xlog "Assert that HTML in plain-text only isn't stripped"; + my $uids = $talk->search('fuzzy', 'body', 'html') || die; + $self->assert_deep_equals([1], $uids); - $uids = $talk->search('fuzzy', 'body', 'div') || die; - $self->assert_deep_equals([1], $uids); + $uids = $talk->search('fuzzy', 'body', 'div') || die; + $self->assert_deep_equals([1], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/striphtml_rfc822 b/cassandane/tiny-tests/SearchFuzzy/striphtml_rfc822 index 7124a68a79..78a890a513 100644 --- a/cassandane/tiny-tests/SearchFuzzy/striphtml_rfc822 +++ b/cassandane/tiny-tests/SearchFuzzy/striphtml_rfc822 @@ -2,49 +2,45 @@ use Cassandane::Tiny; sub test_striphtml_rfc822 - :min_version_3_3 :needs_search_xapian -{ - my ($self) = @_; - my $talk = $self->{store}->get_client(); + : min_version_3_3 : needs_search_xapian { + my ($self) = @_; + my $talk = $self->{store}->get_client(); - xlog "Index message with attached rfc822 message"; - $self->make_message("test", - mime_type => "multipart/mixed", - mime_boundary => "boundary_1", - body => "" - . "\r\n--boundary_1\r\n" - . "Content-Type: text/plain; charset=\"UTF-8\"\r\n" - . "\r\n" - . "
plain
\r\n" - . "\r\n--boundary_1\r\n" - . "Content-Type: message/rfc822\r\n" - . "\r\n" - . "Subject: bar\r\n" - . "From: from\@local\r\n" - . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" - . "To: to\@local\r\n" - . "Mime-Version: 1.0\r\n" - . "Content-Type: multipart/alternative; boundary=boundary_2\r\n" - . "\r\n" - . "\r\n--boundary_2\r\n" - . "Content-Type: text/plain; charset=\"UTF-8\"\r\n" - . "\r\n" - . "
embeddedplain with html.
\r\n" - . "\r\n--boundary_2\r\n" - . "Content-Type: text/html; charset=\"UTF-8\"\r\n" - . "\r\n" - . "
embeddedhtml.
\r\n" - . "\r\n--boundary_2--\r\n" - ) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog "Index message with attached rfc822 message"; + $self->make_message( + "test", + mime_type => "multipart/mixed", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain; charset=\"UTF-8\"\r\n" . "\r\n" + . "
plain
\r\n" + . "\r\n--boundary_1\r\n" + . "Content-Type: message/rfc822\r\n" . "\r\n" + . "Subject: bar\r\n" + . "From: from\@local\r\n" + . "Date: Wed, 05 Oct 2016 14:59:07 +1100\r\n" + . "To: to\@local\r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: multipart/alternative; boundary=boundary_2\r\n" + . "\r\n" + . "\r\n--boundary_2\r\n" + . "Content-Type: text/plain; charset=\"UTF-8\"\r\n" . "\r\n" + . "
embeddedplain with html.
\r\n" + . "\r\n--boundary_2\r\n" + . "Content-Type: text/html; charset=\"UTF-8\"\r\n" . "\r\n" + . "
embeddedhtml.
\r\n" + . "\r\n--boundary_2--\r\n" + ) || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - xlog "Assert that HTML in top-level message isn't stripped"; - my $uids = $talk->search('fuzzy', 'body', 'main') || die; - $self->assert_deep_equals([1], $uids); + xlog "Assert that HTML in top-level message isn't stripped"; + my $uids = $talk->search('fuzzy', 'body', 'main') || die; + $self->assert_deep_equals([1], $uids); - xlog "Assert that HTML in embedded message plain text is stripped"; - $uids = $talk->search('fuzzy', 'body', 'div') || die; - $self->assert_deep_equals([], $uids); - $uids = $talk->search('fuzzy', 'body', 'html') || die; - $self->assert_deep_equals([1], $uids); + xlog "Assert that HTML in embedded message plain text is stripped"; + $uids = $talk->search('fuzzy', 'body', 'div') || die; + $self->assert_deep_equals([], $uids); + $uids = $talk->search('fuzzy', 'body', 'html') || die; + $self->assert_deep_equals([1], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/subject_and_body_match b/cassandane/tiny-tests/SearchFuzzy/subject_and_body_match index a42d4db4af..ce9cf45495 100644 --- a/cassandane/tiny-tests/SearchFuzzy/subject_and_body_match +++ b/cassandane/tiny-tests/SearchFuzzy/subject_and_body_match @@ -2,16 +2,15 @@ use Cassandane::Tiny; sub test_subject_and_body_match - :min_version_3_0 :needs_search_xapian :needs_dependency_cld2 -{ - my ($self) = @_; + : min_version_3_0 : needs_search_xapian : needs_dependency_cld2 { + my ($self) = @_; - $self->make_message('fwd subject', body => 'a schenectady body'); + $self->make_message('fwd subject', body => 'a schenectady body'); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $uids = $talk->search('fuzzy', 'text', 'fwd', 'text', 'schenectady'); - $self->assert_deep_equals([1], $uids); + my $uids = $talk->search('fuzzy', 'text', 'fwd', 'text', 'schenectady'); + $self->assert_deep_equals([1], $uids); } diff --git a/cassandane/tiny-tests/SearchFuzzy/subject_isutf8 b/cassandane/tiny-tests/SearchFuzzy/subject_isutf8 index 731b8563fd..f4e7a221b9 100644 --- a/cassandane/tiny-tests/SearchFuzzy/subject_isutf8 +++ b/cassandane/tiny-tests/SearchFuzzy/subject_isutf8 @@ -2,64 +2,64 @@ use Cassandane::Tiny; sub test_subject_isutf8 - :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; + : min_version_3_0 : needs_search_xapian { + my ($self) = @_; - xlog $self, "Generate and index test messages."; - # that's: "nuff réunion critères duff" - my $subject = "=?utf-8?q?nuff_r=C3=A9union_crit=C3=A8res_duff?="; - my $body = "empty"; - my %params = ( - mime_charset => "utf-8", - body => $body - ); - $self->make_message($subject, %params) || die; - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + xlog $self, "Generate and index test messages."; + # that's: "nuff réunion critères duff" + my $subject = "=?utf-8?q?nuff_r=C3=A9union_crit=C3=A8res_duff?="; + my $body = "empty"; + my %params = ( + mime_charset => "utf-8", + body => $body + ); + $self->make_message($subject, %params) || die; + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - # Connect to IMAP - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; + # Connect to IMAP + xlog $self, "Select INBOX"; + my $r = $talk->select("INBOX") || die; - # Search subject without accents - # my $term = "réunion critères"; - my %searches; + # Search subject without accents + # my $term = "réunion critères"; + my %searches; - if ($self->{skipdiacrit}) { - # Diacritics are stripped before indexing and search. That's a sane - # choice as long as there is no language-specific stemming applied - # during indexing and search. - %searches = ( - "reunion criteres" => 1, - "réunion critères" => 1, - "reunion critères" => 1, - "réunion criter" => 1, - "réunion crit" => 0, - "union critères" => 0, - ); - my $term = "naive"; - } else { - # Diacritics are not stripped from search. This currently is very - # restrictive: until Cyrus can stem by language, this is basically - # a whole-word match. - %searches = ( - "reunion criteres" => 0, - "réunion critères" => 1, - "reunion critères" => 0, - "réunion criter" => 0, - "réunion crit" => 0, - "union critères" => 0, - ); - } + if ($self->{skipdiacrit}) { + # Diacritics are stripped before indexing and search. That's a sane + # choice as long as there is no language-specific stemming applied + # during indexing and search. + %searches = ( + "reunion criteres" => 1, + "réunion critères" => 1, + "reunion critères" => 1, + "réunion criter" => 1, + "réunion crit" => 0, + "union critères" => 0, + ); + my $term = "naive"; + } else { + # Diacritics are not stripped from search. This currently is very + # restrictive: until Cyrus can stem by language, this is basically + # a whole-word match. + %searches = ( + "reunion criteres" => 0, + "réunion critères" => 1, + "reunion critères" => 0, + "réunion criter" => 0, + "réunion crit" => 0, + "union critères" => 0, + ); + } - while (my($term, $expectedCnt) = each %searches) { - xlog $self, "SEARCH for FUZZY text \"$term\""; - $r = $talk->search( - "charset", "utf-8", "fuzzy", ["text", { Quote => $term }], - ) || die; - $self->assert_num_equals($expectedCnt, scalar @$r); - } + while (my ($term, $expectedCnt) = each %searches) { + xlog $self, "SEARCH for FUZZY text \"$term\""; + $r + = $talk->search("charset", "utf-8", "fuzzy", + [ "text", { Quote => $term } ], + ) || die; + $self->assert_num_equals($expectedCnt, scalar @$r); + } } diff --git a/cassandane/tiny-tests/SearchFuzzy/weird_crasher b/cassandane/tiny-tests/SearchFuzzy/weird_crasher index 42f7547330..32f98b5a69 100644 --- a/cassandane/tiny-tests/SearchFuzzy/weird_crasher +++ b/cassandane/tiny-tests/SearchFuzzy/weird_crasher @@ -2,18 +2,20 @@ use Cassandane::Tiny; sub test_weird_crasher - :Conversations :min_version_3_0 :needs_search_xapian -{ - my ($self) = @_; - return if not $self->{test_fuzzy_search}; - $self->create_testmessages(); + : Conversations : min_version_3_0 : needs_search_xapian { + my ($self) = @_; + return if not $self->{test_fuzzy_search}; + $self->create_testmessages(); - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - xlog $self, "Select INBOX"; - $talk->select("INBOX") || die; + xlog $self, "Select INBOX"; + $talk->select("INBOX") || die; - xlog $self, "SEARCH for 'A 李 A'"; - my $r = $talk->xconvmultisort( [ qw(reverse arrival) ], [ 'conversations', position => [1,10] ], 'utf-8', 'fuzzy', 'text', { Quote => "A 李 A" }); - $self->assert_not_null($r); + xlog $self, "SEARCH for 'A 李 A'"; + my $r + = $talk->xconvmultisort([qw(reverse arrival)], + [ 'conversations', position => [ 1, 10 ] ], + 'utf-8', 'fuzzy', 'text', { Quote => "A 李 A" }); + $self->assert_not_null($r); } diff --git a/cassandane/tiny-tests/SearchFuzzy/xapian_index_partid b/cassandane/tiny-tests/SearchFuzzy/xapian_index_partid index 0ab1ed6b5c..9f28eb95a0 100644 --- a/cassandane/tiny-tests/SearchFuzzy/xapian_index_partid +++ b/cassandane/tiny-tests/SearchFuzzy/xapian_index_partid @@ -2,59 +2,68 @@ use Cassandane::Tiny; sub test_xapian_index_partid - :min_version_3_0 :needs_search_xapian :needs_component_jmap -{ - my ($self) = @_; - - # UID 1: match - $self->make_message("xtext", body => "xbody", - from => Cassandane::Address->new( - localpart => "xfrom", - domain => "example.com" - ) - ) || die; - - # UID 2: no match - $self->make_message("xtext", body => "xtext", - from => Cassandane::Address->new( - localpart => "xfrom", - domain => "example.com" - ) - ) || die; - - # UID 3: no match - $self->make_message("xbody", body => "xtext", - from => Cassandane::Address->new( - localpart => "xfrom", - domain => "example.com" - ) - ) || die; - - # UID 4: match - $self->make_message("nomatch", body => "xbody xtext", - from => Cassandane::Address->new( - localpart => "xfrom", - domain => "example.com" - ) - ) || die; - - # UID 5: no match - $self->make_message("xtext", body => "xbody xtext", - from => Cassandane::Address->new( - localpart => "nomatch", - domain => "example.com" - ) - ) || die; - - - $self->{instance}->run_command({cyrus => 1}, 'squatter', '-v'); - - my $talk = $self->{store}->get_client(); - $talk->select("INBOX") || die; - my $uids = $talk->search('fuzzy', 'from', 'xfrom', - 'fuzzy', 'body', 'xbody', - 'fuzzy', 'text', 'xtext') || die; - $self->assert_num_equals(2, scalar @$uids); - $self->assert_num_equals(1, @$uids[0]); - $self->assert_num_equals(4, @$uids[1]); + : min_version_3_0 : needs_search_xapian : needs_component_jmap { + my ($self) = @_; + + # UID 1: match + $self->make_message( + "xtext", + body => "xbody", + from => Cassandane::Address->new( + localpart => "xfrom", + domain => "example.com" + ) + ) || die; + + # UID 2: no match + $self->make_message( + "xtext", + body => "xtext", + from => Cassandane::Address->new( + localpart => "xfrom", + domain => "example.com" + ) + ) || die; + + # UID 3: no match + $self->make_message( + "xbody", + body => "xtext", + from => Cassandane::Address->new( + localpart => "xfrom", + domain => "example.com" + ) + ) || die; + + # UID 4: match + $self->make_message( + "nomatch", + body => "xbody xtext", + from => Cassandane::Address->new( + localpart => "xfrom", + domain => "example.com" + ) + ) || die; + + # UID 5: no match + $self->make_message( + "xtext", + body => "xbody xtext", + from => Cassandane::Address->new( + localpart => "nomatch", + domain => "example.com" + ) + ) || die; + + $self->{instance}->run_command({ cyrus => 1 }, 'squatter', '-v'); + + my $talk = $self->{store}->get_client(); + $talk->select("INBOX") || die; + my $uids = $talk->search( + 'fuzzy', 'from', 'xfrom', 'fuzzy', 'body', 'xbody', + 'fuzzy', 'text', 'xtext' + ) || die; + $self->assert_num_equals(2, scalar @$uids); + $self->assert_num_equals(1, @$uids[0]); + $self->assert_num_equals(4, @$uids[1]); } diff --git a/cassandane/tiny-tests/SearchFuzzy/xattachmentname b/cassandane/tiny-tests/SearchFuzzy/xattachmentname index a0b0653c3b..7dd4ff6934 100644 --- a/cassandane/tiny-tests/SearchFuzzy/xattachmentname +++ b/cassandane/tiny-tests/SearchFuzzy/xattachmentname @@ -2,54 +2,42 @@ use Cassandane::Tiny; sub test_xattachmentname - :needs_search_xapian -{ - my ($self) = @_; + : needs_search_xapian { + my ($self) = @_; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $body = "" + my $body + = "" . "--boundary\r\n" - . "Content-Type: text/plain\r\n" - . "\r\n" - . "body" - . "\r\n" + . "Content-Type: text/plain\r\n" . "\r\n" . "body" . "\r\n" . "--boundary\r\n" . "Content-Type: application/x-excel; name=\"blah\"\r\n" . "Content-Transfer-Encoding: base64\r\n" - . "Content-Disposition: attachment; filename=\"stuff.xls\"\r\n" - . "\r\n" - . "SGVsbG8sIFdvcmxkIQ==" - . "\r\n" + . "Content-Disposition: attachment; filename=\"stuff.xls\"\r\n" . "\r\n" + . "SGVsbG8sIFdvcmxkIQ==" . "\r\n" . "--boundary--\r\n"; - $self->make_message("foo", - mime_type => "multipart/mixed", - mime_boundary => "boundary", - body => $body - ); + $self->make_message( + "foo", + mime_type => "multipart/mixed", + mime_boundary => "boundary", + body => $body + ); - $self->{instance}->run_command({cyrus => 1}, 'squatter'); + $self->{instance}->run_command({ cyrus => 1 }, 'squatter'); - my $r; + my $r; - $r = $talk->search( - "fuzzy", "xattachmentname", { Quote => "stuff" } - ) || die; - $self->assert_num_equals(1, scalar @$r); + $r = $talk->search("fuzzy", "xattachmentname", { Quote => "stuff" }) || die; + $self->assert_num_equals(1, scalar @$r); - $r = $talk->search( - "fuzzy", "xattachmentname", { Quote => "nope" } - ) || die; - $self->assert_num_equals(0, scalar @$r); + $r = $talk->search("fuzzy", "xattachmentname", { Quote => "nope" }) || die; + $self->assert_num_equals(0, scalar @$r); - $r = $talk->search( - "fuzzy", "text", { Quote => "stuff.xls" } - ) || die; - $self->assert_num_equals(1, scalar @$r); + $r = $talk->search("fuzzy", "text", { Quote => "stuff.xls" }) || die; + $self->assert_num_equals(1, scalar @$r); - $r = $talk->search( - "fuzzy", "xattachmentname", { Quote => "blah" }, - ) || die; - $self->assert_num_equals(1, scalar @$r); + $r = $talk->search("fuzzy", "xattachmentname", { Quote => "blah" },) || die; + $self->assert_num_equals(1, scalar @$r); } diff --git a/cassandane/tiny-tests/Sieve/badscript_sievec b/cassandane/tiny-tests/Sieve/badscript_sievec index 18974a7a9b..48848fa84e 100644 --- a/cassandane/tiny-tests/Sieve/badscript_sievec +++ b/cassandane/tiny-tests/Sieve/badscript_sievec @@ -2,11 +2,10 @@ use Cassandane::Tiny; sub test_badscript_sievec - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing sieve script compile failures, via sievec"; - $self->{compile_method} = 'sievec'; - $self->badscript_common(); + xlog $self, "Testing sieve script compile failures, via sievec"; + $self->{compile_method} = 'sievec'; + $self->badscript_common(); } diff --git a/cassandane/tiny-tests/Sieve/badscript_timsieved b/cassandane/tiny-tests/Sieve/badscript_timsieved index 41b6c571bf..ca9fd6e960 100644 --- a/cassandane/tiny-tests/Sieve/badscript_timsieved +++ b/cassandane/tiny-tests/Sieve/badscript_timsieved @@ -2,11 +2,10 @@ use Cassandane::Tiny; sub test_badscript_timsieved - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing sieve script compile failures, via timsieved"; - $self->{compile_method} = 'timsieved'; - $self->badscript_common(); + xlog $self, "Testing sieve script compile failures, via timsieved"; + $self->{compile_method} = 'timsieved'; + $self->badscript_common(); } diff --git a/cassandane/tiny-tests/Sieve/create_inherit_color b/cassandane/tiny-tests/Sieve/create_inherit_color index 10b548e1b0..00de60d2de 100644 --- a/cassandane/tiny-tests/Sieve/create_inherit_color +++ b/cassandane/tiny-tests/Sieve/create_inherit_color @@ -2,46 +2,51 @@ use Cassandane::Tiny; sub test_create_inherit_color - :min_version_3_9 :AltNameSpace :needs_component_sieve :needs_component_jmap - :want_service_http -{ - my ($self) = @_; - - my $jmap = $self->{jmap}; - - xlog $self, "Create mailbox with color"; - my $res = $jmap->CallMethods([['Mailbox/set', { - create => { - 1 => { - parentId => JSON::null, - name => 'foo', - color => "coral", - }, + : min_version_3_9 : AltNameSpace : needs_component_sieve : + needs_component_jmap + : want_service_http { + my ($self) = @_; + + my $jmap = $self->{jmap}; + + xlog $self, "Create mailbox with color"; + my $res = $jmap->CallMethods([ [ + 'Mailbox/set', + { + create => { + 1 => { + parentId => JSON::null, + name => 'foo', + color => "coral", }, - }, "R1"]]); - $self->assert_not_null($res->[0][1]{created}{1}); - - my $hitfolder = "foo.bar"; - - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<assert_not_null($res->[0][1]{created}{1}); + + my $hitfolder = "foo.bar"; + + xlog $self, "Install the sieve script"; + my $scriptname = 'flatPack'; + $self->{instance}->install_sieve_script( + <{gen}->generate(subject => "msg1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "msg1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it"; - my $talk = $self->{store}->get_client(); - $talk->select($hitfolder); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + xlog $self, "Check that the message made it"; + my $talk = $self->{store}->get_client(); + $talk->select($hitfolder); + $self->assert_num_equals(1, $talk->get_response_code('exists')); - xlog $self, "Check that :created mailbox inherited color"; - $res = $jmap->CallMethods([['Mailbox/get', {}, "R1"]]); - my %m = map { $_->{name} => $_ } @{$res->[0][1]{list}}; - $self->assert_str_equals("coral", $m{"bar"}->{color}); + xlog $self, "Check that :created mailbox inherited color"; + $res = $jmap->CallMethods([ [ 'Mailbox/get', {}, "R1" ] ]); + my %m = map { $_->{name} => $_ } @{ $res->[0][1]{list} }; + $self->assert_str_equals("coral", $m{"bar"}->{color}); } diff --git a/cassandane/tiny-tests/Sieve/date b/cassandane/tiny-tests/Sieve/date index cb99bd15d4..f6dcc860e4 100644 --- a/cassandane/tiny-tests/Sieve/date +++ b/cassandane/tiny-tests/Sieve/date @@ -2,11 +2,11 @@ use Cassandane::Tiny; sub test_date - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["date", "variables", "imap4flags", "regex", "relational"]; if date :originalzone "date" "date" [ "2018-05-16", "2018-12-16" ] { @@ -30,9 +30,9 @@ if date :originalzone "date" "zone" "-0700" { addflag "Test5"; } EOF - ); + ); - my $raw1 = << 'EOF'; + my $raw1 = << 'EOF'; Date: Wed, 16 May 2018 22:06:18 -0700 From: Some Person To: foo/bar @@ -43,7 +43,7 @@ X-Cassandane-Unique: foo foo bar EOF - my $raw2 = << 'EOF'; + my $raw2 = << 'EOF'; Date: Sun, 16 Dec 2018 22:06:18 -0700 From: Some Person To: foo/bar @@ -53,19 +53,25 @@ X-Cassandane-Unique: foo foo bar EOF - xlog $self, "Deliver messages"; - my $msg1 = Cassandane::Message->new(raw => $raw1); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver messages"; + my $msg1 = Cassandane::Message->new(raw => $raw1); + $self->{instance}->deliver($msg1); - my $msg2 = Cassandane::Message->new(raw => $raw2); - $self->{instance}->deliver($msg2); + my $msg2 = Cassandane::Message->new(raw => $raw2); + $self->{instance}->deliver($msg2); - my $imaptalk = $self->{store}->get_client(); - $self->{store}->set_fetch_attributes(qw(uid flags)); - $self->{store}->set_folder('INBOX'); - $msg1->set_attribute(uid => 1); - $msg1->set_attribute(flags => [ '\\Recent', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5' ]); - $msg2->set_attribute(uid => 2); - $msg2->set_attribute(flags => [ '\\Recent', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5' ]); - $self->check_messages({ 1 => $msg1, 2 => $msg2 }, keyed_on => 'uid', check_guid => 0); + my $imaptalk = $self->{store}->get_client(); + $self->{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->set_folder('INBOX'); + $msg1->set_attribute(uid => 1); + $msg1->set_attribute( + flags => [ '\\Recent', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5' ]); + $msg2->set_attribute(uid => 2); + $msg2->set_attribute( + flags => [ '\\Recent', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5' ]); + $self->check_messages( + { 1 => $msg1, 2 => $msg2 }, + keyed_on => 'uid', + check_guid => 0 + ); } diff --git a/cassandane/tiny-tests/Sieve/date_iana_tzid b/cassandane/tiny-tests/Sieve/date_iana_tzid index 184b61c292..1fd625f7e4 100644 --- a/cassandane/tiny-tests/Sieve/date_iana_tzid +++ b/cassandane/tiny-tests/Sieve/date_iana_tzid @@ -2,12 +2,12 @@ use Cassandane::Tiny; sub test_date_iana_tzid - :needs_component_sieve :min_version_3_7 - :needs_dependency_ical -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_7 + : needs_dependency_ical { + my ($self) = @_; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["date", "variables", "imap4flags", "regex", "relational"]; if date :zone "-1000" "date" "hour" "19" { @@ -18,9 +18,9 @@ if date :zone "Pacific/Honolulu" "date" "hour" "19" { addflag "Test2"; } EOF - ); + ); - my $raw = << 'EOF'; + my $raw = << 'EOF'; Date: Wed, 16 May 2018 22:06:18 -0700 From: Some Person To: foo/bar @@ -30,14 +30,14 @@ X-Cassandane-Unique: foo foo bar EOF - xlog $self, "Deliver a message"; - my $msg1 = Cassandane::Message->new(raw => $raw); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = Cassandane::Message->new(raw => $raw); + $self->{instance}->deliver($msg1); - my $imaptalk = $self->{store}->get_client(); - $self->{store}->set_fetch_attributes(qw(uid flags)); - $self->{store}->set_folder('INBOX'); - $msg1->set_attribute(uid => 1); - $msg1->set_attribute(flags => [ '\\Recent', 'Test1', 'Test2' ]); - $self->check_messages({ 1 => $msg1 }, keyed_on => 'uid', check_guid => 0); + my $imaptalk = $self->{store}->get_client(); + $self->{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->set_folder('INBOX'); + $msg1->set_attribute(uid => 1); + $msg1->set_attribute(flags => [ '\\Recent', 'Test1', 'Test2' ]); + $self->check_messages({ 1 => $msg1 }, keyed_on => 'uid', check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/date_local_zone b/cassandane/tiny-tests/Sieve/date_local_zone index 95aac4c196..955bc513c9 100644 --- a/cassandane/tiny-tests/Sieve/date_local_zone +++ b/cassandane/tiny-tests/Sieve/date_local_zone @@ -2,26 +2,26 @@ use Cassandane::Tiny; sub test_date_local_zone - :needs_component_sieve :min_version_3_9 -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_9 { + my ($self) = @_; - my $date = "Wed, 16 May 2018 22:06:18 -0700"; - my $dt = DateTime->from_epoch(epoch => str2time($date), time_zone => 'local'); + my $date = "Wed, 16 May 2018 22:06:18 -0700"; + my $dt = DateTime->from_epoch(epoch => str2time($date), time_zone => 'local'); - my $date_hour = $dt->strftime("%H"); + my $date_hour = $dt->strftime("%H"); - my $now = DateTime->now(); - my $cur_utc_hour = $now->strftime("%H"); + my $now = DateTime->now(); + my $cur_utc_hour = $now->strftime("%H"); - $now->set_time_zone('local'); + $now->set_time_zone('local'); - my $cur_hour = $now->strftime("%H"); - my $cur_zone = $now->strftime("%z"); - my $cur_std11 = $now->strftime("%a, %d %b %Y %H:[0-9]{2}:[0-9]{2} %z"); - $cur_std11 =~ s/\+/[+]/g; # escape any '+' from %z + my $cur_hour = $now->strftime("%H"); + my $cur_zone = $now->strftime("%z"); + my $cur_std11 = $now->strftime("%a, %d %b %Y %H:[0-9]{2}:[0-9]{2} %z"); + $cur_std11 =~ s/\+/[+]/g; # escape any '+' from %z - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: foo/bar @@ -56,14 +56,15 @@ X-Cassandane-Unique: foo foo bar EOF - xlog $self, "Deliver a message"; - my $msg1 = Cassandane::Message->new(raw => $raw); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = Cassandane::Message->new(raw => $raw); + $self->{instance}->deliver($msg1); - my $imaptalk = $self->{store}->get_client(); - $self->{store}->set_fetch_attributes(qw(uid flags)); - $self->{store}->set_folder('INBOX'); - $msg1->set_attribute(uid => 1); - $msg1->set_attribute(flags => [ '\\Recent', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5' ]); - $self->check_messages({ 1 => $msg1 }, keyed_on => 'uid', check_guid => 0); + my $imaptalk = $self->{store}->get_client(); + $self->{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->set_folder('INBOX'); + $msg1->set_attribute(uid => 1); + $msg1->set_attribute( + flags => [ '\\Recent', 'Test1', 'Test2', 'Test3', 'Test4', 'Test5' ]); + $self->check_messages({ 1 => $msg1 }, keyed_on => 'uid', check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver b/cassandane/tiny-tests/Sieve/deliver index 3c072a76cc..b9d55717b2 100644 --- a/cassandane/tiny-tests/Sieve/deliver +++ b/cassandane/tiny-tests/Sieve/deliver @@ -2,40 +2,41 @@ use Cassandane::Tiny; sub test_deliver - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - my $target = "INBOX.target"; + my $target = "INBOX.target"; - xlog $self, "Install a sieve script filing all mail into a nonexistant folder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Actually create the target folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Actually create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; - $self->{store}->set_fetch_attributes('uid'); + $imaptalk->create($target) + or die "Cannot create $target: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver another message"; - my $msg2 = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg2); - $msg2->set_attribute(uid => 1); + xlog $self, "Deliver another message"; + my $msg2 = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg2); + $msg2->set_attribute(uid => 1); - xlog $self, "Check that only the 1st message made it to INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that only the 1st message made it to INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Check that only the 2nd message made it to the target"; - $self->{store}->set_folder($target); - $self->check_messages({ 1 => $msg2 }, check_guid => 0); + xlog $self, "Check that only the 2nd message made it to the target"; + $self->{store}->set_folder($target); + $self->check_messages({ 1 => $msg2 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_compile b/cassandane/tiny-tests/Sieve/deliver_compile index d2fd271149..3b711de8f0 100644 --- a/cassandane/tiny-tests/Sieve/deliver_compile +++ b/cassandane/tiny-tests/Sieve/deliver_compile @@ -2,42 +2,43 @@ use Cassandane::Tiny; sub test_deliver_compile - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - my $target = "INBOX.target"; + my $target = "INBOX.target"; - xlog $self, "Create the target folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; - $self->{store}->set_fetch_attributes('uid'); + xlog $self, "Create the target folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($target) + or die "Cannot create $target: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Install a sieve script filing all mail into the target folder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Delete the compiled bytecode"; - my $sieve_dir = $self->{instance}->get_sieve_script_dir('cassandane'); - my $fname = "$sieve_dir/test1.bc"; - unlink $fname or die "Cannot unlink $fname: $!"; + xlog $self, "Delete the compiled bytecode"; + my $sieve_dir = $self->{instance}->get_sieve_script_dir('cassandane'); + my $fname = "$sieve_dir/test1.bc"; + unlink $fname or die "Cannot unlink $fname: $!"; - sleep 1; # so the two deliveries get different syslog timestamps + sleep 1; # so the two deliveries get different syslog timestamps - xlog $self, "Deliver another message - lmtpd should rebuild the missing bytecode"; - my $msg2 = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg2); + xlog $self, + "Deliver another message - lmtpd should rebuild the missing bytecode"; + my $msg2 = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg2); - xlog $self, "Check that both messages made it to the target"; - $self->{store}->set_folder($target); - $self->check_messages({ 1 => $msg1, 2 => $msg2 }, check_guid => 0); + xlog $self, "Check that both messages made it to the target"; + $self->{store}->set_folder($target); + $self->check_messages({ 1 => $msg1, 2 => $msg2 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_globalshared b/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_globalshared index 5f6cd47016..7649be1b81 100644 --- a/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_globalshared +++ b/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_globalshared @@ -2,35 +2,36 @@ use Cassandane::Tiny; sub test_deliver_fileinto_autocreate_globalshared - :needs_component_sieve :NoStartInstances :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : NoStartInstances : NoAltNameSpace { + my ($self) = @_; - $self->{instance}->{config}->set('anysievefolder' => 'yes'); - $self->_start_instances(); + $self->{instance}->{config}->set('anysievefolder' => 'yes'); + $self->_start_instances(); - # sieve script should not be able to create a new global shared mailbox - my $target = "TopLevel"; + # sieve script should not be able to create a new global shared mailbox + my $target = "TopLevel"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'cassandane'); + , username => 'cassandane' + ); - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, users => [ 'cassandane' ]); + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1, users => ['cassandane']); - # autosievefolder should have failed to create the target, because the - # user doesn't have permission to create a folder in the global shared - # namespace - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->select($target); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); + # autosievefolder should have failed to create the target, because the + # user doesn't have permission to create a folder in the global shared + # namespace + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->select($target); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); - # then the fileinto should fail, and the message be delivered to inbox - # instead - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + # then the fileinto should fail, and the message be delivered to inbox + # instead + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_newuser b/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_newuser index 451ab10cbd..284ef98f96 100644 --- a/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_newuser +++ b/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_newuser @@ -2,34 +2,35 @@ use Cassandane::Tiny; sub test_deliver_fileinto_autocreate_newuser - :needs_component_sieve :NoStartInstances :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : NoStartInstances : NoAltNameSpace { + my ($self) = @_; - $self->{instance}->{config}->set('anysievefolder' => 'yes'); - $self->_start_instances(); + $self->{instance}->{config}->set('anysievefolder' => 'yes'); + $self->_start_instances(); - # sieve script should not be able to create a new user account - my $target = "user.other"; + # sieve script should not be able to create a new user account + my $target = "user.other"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'cassandane'); + , username => 'cassandane' + ); - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, users => [ 'cassandane' ]); + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1, users => ['cassandane']); - # autosievefolder should have failed to create the target, because the - # user doesn't have permission to create a mailbox under user. - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->select($target); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); + # autosievefolder should have failed to create the target, because the + # user doesn't have permission to create a mailbox under user. + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->select($target); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); - # then the fileinto should fail, and the message be delivered to inbox - # instead - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + # then the fileinto should fail, and the message be delivered to inbox + # instead + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_otheruser b/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_otheruser index ce883d94b2..30ae029644 100644 --- a/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_otheruser +++ b/cassandane/tiny-tests/Sieve/deliver_fileinto_autocreate_otheruser @@ -2,38 +2,39 @@ use Cassandane::Tiny; sub test_deliver_fileinto_autocreate_otheruser - :needs_component_sieve :NoStartInstances :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : NoStartInstances : NoAltNameSpace { + my ($self) = @_; - $self->{instance}->{config}->set('anysievefolder' => 'yes'); - $self->_start_instances(); + $self->{instance}->{config}->set('anysievefolder' => 'yes'); + $self->_start_instances(); - $self->{instance}->create_user('other'); + $self->{instance}->create_user('other'); - # sieve script should not be able to create a mailbox in some other - # user's account - my $target = "user.other.SomeFolder"; + # sieve script should not be able to create a mailbox in some other + # user's account + my $target = "user.other.SomeFolder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'cassandane'); - - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, users => [ 'cassandane' ]); - - # autosievefolder should have failed to create the target, because the - # user doesn't have permission to create a folder in another user's - # account - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->select($target); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); - - # then the fileinto should fail, and the message be delivered to inbox - # instead - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + , username => 'cassandane' + ); + + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1, users => ['cassandane']); + + # autosievefolder should have failed to create the target, because the + # user doesn't have permission to create a folder in another user's + # account + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->select($target); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); + + # then the fileinto should fail, and the message be delivered to inbox + # instead + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_fileinto_create_globalshared b/cassandane/tiny-tests/Sieve/deliver_fileinto_create_globalshared index 0a3345129e..b6affde7e1 100644 --- a/cassandane/tiny-tests/Sieve/deliver_fileinto_create_globalshared +++ b/cassandane/tiny-tests/Sieve/deliver_fileinto_create_globalshared @@ -2,32 +2,33 @@ use Cassandane::Tiny; sub test_deliver_fileinto_create_globalshared - :needs_component_sieve :min_version_3_0 :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_0 : NoAltNameSpace { + my ($self) = @_; - # sieve script should not be able to create a new global shared mailbox - my $target = "TopLevel"; + # sieve script should not be able to create a new global shared mailbox + my $target = "TopLevel"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'cassandane'); + , username => 'cassandane' + ); - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, users => [ 'cassandane' ]); + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1, users => ['cassandane']); - # autosievefolder should have failed to create the target, because the - # user doesn't have permission to create a folder in the global shared - # namespace - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->select($target); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); + # autosievefolder should have failed to create the target, because the + # user doesn't have permission to create a folder in the global shared + # namespace + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->select($target); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); - # then the fileinto should fail, and the message be delivered to inbox - # instead - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + # then the fileinto should fail, and the message be delivered to inbox + # instead + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_fileinto_create_newuser b/cassandane/tiny-tests/Sieve/deliver_fileinto_create_newuser index c8d8e971ba..7dba745643 100644 --- a/cassandane/tiny-tests/Sieve/deliver_fileinto_create_newuser +++ b/cassandane/tiny-tests/Sieve/deliver_fileinto_create_newuser @@ -2,31 +2,32 @@ use Cassandane::Tiny; sub test_deliver_fileinto_create_newuser - :needs_component_sieve :min_version_3_0 :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_0 : NoAltNameSpace { + my ($self) = @_; - # sieve script should not be able to create a new user - my $target = "user.other"; + # sieve script should not be able to create a new user + my $target = "user.other"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'cassandane'); + , username => 'cassandane' + ); - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, users => [ 'cassandane' ]); + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1, users => ['cassandane']); - # autosievefolder should have failed to create the target, because the - # user doesn't have permission to create a mailbox under user. - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->select($target); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); + # autosievefolder should have failed to create the target, because the + # user doesn't have permission to create a mailbox under user. + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->select($target); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); - # then the fileinto should fail, and the message be delivered to inbox - # instead - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + # then the fileinto should fail, and the message be delivered to inbox + # instead + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_fileinto_create_nonimap b/cassandane/tiny-tests/Sieve/deliver_fileinto_create_nonimap index ad659cb4e1..24798ef94c 100644 --- a/cassandane/tiny-tests/Sieve/deliver_fileinto_create_nonimap +++ b/cassandane/tiny-tests/Sieve/deliver_fileinto_create_nonimap @@ -2,31 +2,32 @@ use Cassandane::Tiny; sub test_deliver_fileinto_create_nonimap - :needs_component_sieve :min_version_3_0 :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_0 : NoAltNameSpace { + my ($self) = @_; - # sieve script should not be able to create a non-IMAP mailbox - my $target = "INBOX.#calendars.target"; + # sieve script should not be able to create a non-IMAP mailbox + my $target = "INBOX.#calendars.target"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'cassandane'); + , username => 'cassandane' + ); - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, users => [ 'cassandane' ]); + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1, users => ['cassandane']); - # autosievefolder should have failed to create the target, - # because the the target is in a non-IMAP namespace - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->select($target); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); + # autosievefolder should have failed to create the target, + # because the the target is in a non-IMAP namespace + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->select($target); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); - # then the fileinto should fail, - # and the message be delivered to inbox instead - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + # then the fileinto should fail, + # and the message be delivered to inbox instead + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_fileinto_create_otheruser b/cassandane/tiny-tests/Sieve/deliver_fileinto_create_otheruser index f741cd4e30..ee0631a6e2 100644 --- a/cassandane/tiny-tests/Sieve/deliver_fileinto_create_otheruser +++ b/cassandane/tiny-tests/Sieve/deliver_fileinto_create_otheruser @@ -2,35 +2,36 @@ use Cassandane::Tiny; sub test_deliver_fileinto_create_otheruser - :needs_component_sieve :min_version_3_0 :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_0 : NoAltNameSpace { + my ($self) = @_; - $self->{instance}->create_user('other'); + $self->{instance}->create_user('other'); - # sieve script should not be able to create a mailbox in some other - # user's account - my $target = "user.other.SomeFolder"; + # sieve script should not be able to create a mailbox in some other + # user's account + my $target = "user.other.SomeFolder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'cassandane'); + , username => 'cassandane' + ); - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, users => [ 'cassandane' ]); + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1, users => ['cassandane']); - # autosievefolder should have failed to create the target, because the - # user doesn't have permission to create a folder in another user's - # account - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->select($target); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); + # autosievefolder should have failed to create the target, because the + # user doesn't have permission to create a folder in another user's + # account + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->select($target); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + $self->assert_matches(qr/does not exist/i, $admintalk->get_last_error()); - # then the fileinto should fail, and the message be delivered to inbox - # instead - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + # then the fileinto should fail, and the message be delivered to inbox + # instead + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_fileinto_dot b/cassandane/tiny-tests/Sieve/deliver_fileinto_dot index 994ffd8f52..cb6246cba5 100644 --- a/cassandane/tiny-tests/Sieve/deliver_fileinto_dot +++ b/cassandane/tiny-tests/Sieve/deliver_fileinto_dot @@ -2,46 +2,49 @@ use Cassandane::Tiny; sub test_deliver_fileinto_dot - :UnixHierarchySep - :needs_component_sieve -{ - my ($self) = @_; - - xlog $self, "Testing a sieve script which does a 'fileinto' a mailbox"; - xlog $self, "when the user has a dot in their name. Bug 3664"; - # NOTE: The commit https://github.com/cyrusimap/cyrus-imapd/commit/73af8e19546f235f6286cc9147a3ea74bde19ebb - # in Cyrus-imapd changes this behaviour where in we don't do a '.' -> '^' anymore. - - xlog $self, "Create the dotted user"; - my $user = 'betty.boop'; - $self->{instance}->create_user($user); - - xlog $self, "Connect as the new user"; - my $svc = $self->{instance}->get_service('imap'); - $self->{store} = $svc->create_store(username => $user, folder => 'INBOX'); - $self->{store}->set_fetch_attributes('uid'); - my $imaptalk = $self->{store}->get_client(); - - xlog $self, "Create the target folder"; - - my $target = Cassandane::Mboxname->new(config => $self->{instance}->{config}, - userid => $user, - box => 'target')->to_external(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; - - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(< '^' anymore. + + xlog $self, "Create the dotted user"; + my $user = 'betty.boop'; + $self->{instance}->create_user($user); + + xlog $self, "Connect as the new user"; + my $svc = $self->{instance}->get_service('imap'); + $self->{store} = $svc->create_store(username => $user, folder => 'INBOX'); + $self->{store}->set_fetch_attributes('uid'); + my $imaptalk = $self->{store}->get_client(); + + xlog $self, "Create the target folder"; + + my $target = Cassandane::Mboxname->new( + config => $self->{instance}->{config}, + userid => $user, + box => 'target' + )->to_external(); + $imaptalk->create($target) + or die "Cannot create $target: $@"; + + xlog $self, "Install the sieve script"; + $self->{instance}->install_sieve_script( + < 'betty.boop'); + , username => 'betty.boop' + ); - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, users => [ $user ]); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1, users => [$user]); - xlog $self, "Check that the message made it to target"; - $self->{store}->set_folder($target); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to target"; + $self->{store}->set_folder($target); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/deliver_multiple_users b/cassandane/tiny-tests/Sieve/deliver_multiple_users index 42203f2117..6b9cc8f22c 100644 --- a/cassandane/tiny-tests/Sieve/deliver_multiple_users +++ b/cassandane/tiny-tests/Sieve/deliver_multiple_users @@ -2,53 +2,56 @@ use Cassandane::Tiny; sub test_deliver_multiple_users - :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - # create 2 other users - $self->{instance}->create_user('other1'); - $self->{instance}->create_user('other2'); + # create 2 other users + $self->{instance}->create_user('other1'); + $self->{instance}->create_user('other2'); - # install redirect script for cassandane - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'cassandane'); + , username => 'cassandane' + ); - # install fileinto script for other2 - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'other2'); - - # deliver a message to all 3 users - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1, - users => [ 'cassandane', 'other1', 'other2' ]); - - # message should NOT appear in cassandane INBOX - my $admintalk = $self->{adminstore}->get_client(); - $admintalk->examine('user.cassandane'); - $admintalk->fetch('1', '(flags)'); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - - # message should appear in other1 INBOX - $admintalk->examine('user.other1'); - my $res = $admintalk->fetch('1', '(flags)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals($res, { '1' => { 'flags' => [ '\\Recent'] }}); - - # message should NOT appear in other2 INBOX - $admintalk->examine('user.other2'); - $admintalk->fetch('1', '(flags)'); - $self->assert_str_equals('no', $admintalk->get_last_completion_response()); - - # message should appear in other2 INBOX.sub - $admintalk->examine('user.other2.sub'); - $res = $admintalk->fetch('1', '(flags)'); - $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); - $self->assert_deep_equals($res, - { '1' => { 'flags' => [ '\\Recent', '\\Flagged'] }}); + , username => 'other2' + ); + + # deliver a message to all 3 users + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance} + ->deliver($msg1, users => [ 'cassandane', 'other1', 'other2' ]); + + # message should NOT appear in cassandane INBOX + my $admintalk = $self->{adminstore}->get_client(); + $admintalk->examine('user.cassandane'); + $admintalk->fetch('1', '(flags)'); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + + # message should appear in other1 INBOX + $admintalk->examine('user.other1'); + my $res = $admintalk->fetch('1', '(flags)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals($res, { '1' => { 'flags' => ['\\Recent'] } }); + + # message should NOT appear in other2 INBOX + $admintalk->examine('user.other2'); + $admintalk->fetch('1', '(flags)'); + $self->assert_str_equals('no', $admintalk->get_last_completion_response()); + + # message should appear in other2 INBOX.sub + $admintalk->examine('user.other2.sub'); + $res = $admintalk->fetch('1', '(flags)'); + $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); + $self->assert_deep_equals($res, + { '1' => { 'flags' => [ '\\Recent', '\\Flagged' ] } }); } diff --git a/cassandane/tiny-tests/Sieve/deliver_specialuse b/cassandane/tiny-tests/Sieve/deliver_specialuse index c4250bde4c..b3a014ee4d 100644 --- a/cassandane/tiny-tests/Sieve/deliver_specialuse +++ b/cassandane/tiny-tests/Sieve/deliver_specialuse @@ -2,38 +2,38 @@ use Cassandane::Tiny; sub test_deliver_specialuse - :min_version_3_0 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - my $target = "INBOX.target"; + my $target = "INBOX.target"; - xlog $self, "create the target folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target, "(use (\\Trash))") - or die "Cannot create $target: $@"; - $self->{store}->set_fetch_attributes('uid'); + $imaptalk->create($target, "(use (\\Trash))") + or die "Cannot create $target: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Install a sieve script filing all mail into the Trash role"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); - $msg->set_attribute(uid => 1); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); + $msg->set_attribute(uid => 1); - xlog $self, "Check that no messages are in INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({}, check_guid => 0); + xlog $self, "Check that no messages are in INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({}, check_guid => 0); - xlog $self, "Check that the message made it into the target folder"; - $self->{store}->set_folder($target); - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it into the target folder"; + $self->{store}->set_folder($target); + $self->check_messages({ 1 => $msg }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/discard_match_on_body_raw b/cassandane/tiny-tests/Sieve/discard_match_on_body_raw index 31d2c36826..db6feec48d 100644 --- a/cassandane/tiny-tests/Sieve/discard_match_on_body_raw +++ b/cassandane/tiny-tests/Sieve/discard_match_on_body_raw @@ -2,12 +2,12 @@ use Cassandane::Tiny; sub test_discard_match_on_body_raw - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: foo/bar @@ -132,12 +132,12 @@ END:VEVENT END:VCALENDAR ------=_Part_91374_1856076643.1527870431792-- EOF - xlog $self, "Deliver a message"; - my $msg1 = Cassandane::Message->new(raw => $raw); - $self->{instance}->deliver($msg1); - - # should fail to deliver and NOT appear in INBOX - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - $self->assert_num_equals(0, $imaptalk->get_response_code('exists')); + xlog $self, "Deliver a message"; + my $msg1 = Cassandane::Message->new(raw => $raw); + $self->{instance}->deliver($msg1); + + # should fail to deliver and NOT appear in INBOX + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + $self->assert_num_equals(0, $imaptalk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/discard_match_on_body_text b/cassandane/tiny-tests/Sieve/discard_match_on_body_text index c942dfab10..6c0019aadd 100644 --- a/cassandane/tiny-tests/Sieve/discard_match_on_body_text +++ b/cassandane/tiny-tests/Sieve/discard_match_on_body_text @@ -2,12 +2,12 @@ use Cassandane::Tiny; sub test_discard_match_on_body_text - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: foo/bar @@ -132,12 +132,12 @@ END:VEVENT END:VCALENDAR ------=_Part_91374_1856076643.1527870431792-- EOF - xlog $self, "Deliver a message"; - my $msg1 = Cassandane::Message->new(raw => $raw); - $self->{instance}->deliver($msg1); - - # should fail to deliver and NOT appear in INBOX - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - $self->assert_num_equals(0, $imaptalk->get_response_code('exists')); + xlog $self, "Deliver a message"; + my $msg1 = Cassandane::Message->new(raw => $raw); + $self->{instance}->deliver($msg1); + + # should fail to deliver and NOT appear in INBOX + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + $self->assert_num_equals(0, $imaptalk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/double_require b/cassandane/tiny-tests/Sieve/double_require index 237e874d8a..9ab9bbdccf 100644 --- a/cassandane/tiny-tests/Sieve/double_require +++ b/cassandane/tiny-tests/Sieve/double_require @@ -2,42 +2,43 @@ use Cassandane::Tiny; sub test_double_require - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - my $target = "INBOX.target"; + my $target = "INBOX.target"; - xlog $self, "Install a sieve script filing all mail into a nonexistant folder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Actually create the target folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Actually create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; - $self->{store}->set_fetch_attributes('uid'); + $imaptalk->create($target) + or die "Cannot create $target: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Deliver another message"; - my $msg2 = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg2); - $msg2->set_attribute(uid => 1); + xlog $self, "Deliver another message"; + my $msg2 = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg2); + $msg2->set_attribute(uid => 1); - xlog $self, "Check that only the 1st message made it to INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that only the 1st message made it to INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Check that only the 2nd message made it to the target"; - $self->{store}->set_folder($target); - $self->check_messages({ 1 => $msg2 }, check_guid => 0); + xlog $self, "Check that only the 2nd message made it to the target"; + $self->{store}->set_folder($target); + $self->check_messages({ 1 => $msg2 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/dup_fileinto_implicit_keep_flags b/cassandane/tiny-tests/Sieve/dup_fileinto_implicit_keep_flags index 47a73c329b..be2be03b03 100644 --- a/cassandane/tiny-tests/Sieve/dup_fileinto_implicit_keep_flags +++ b/cassandane/tiny-tests/Sieve/dup_fileinto_implicit_keep_flags @@ -2,26 +2,26 @@ use Cassandane::Tiny; sub test_dup_fileinto_implicit_keep_flags - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - $self->{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Testing duplicate suppression between 'fileinto' & 'keep'"; + xlog $self, "Testing duplicate suppression between 'fileinto' & 'keep'"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that only last copy of the message made it to INBOX"; - $self->{store}->set_folder('INBOX'); - $msg1->set_attribute(flags => [ '\\Recent' ]); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that only last copy of the message made it to INBOX"; + $self->{store}->set_folder('INBOX'); + $msg1->set_attribute(flags => ['\\Recent']); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/dup_keep_fileinto b/cassandane/tiny-tests/Sieve/dup_keep_fileinto index a1d7639c41..fe13bcab7d 100644 --- a/cassandane/tiny-tests/Sieve/dup_keep_fileinto +++ b/cassandane/tiny-tests/Sieve/dup_keep_fileinto @@ -8,24 +8,24 @@ use Cassandane::Tiny; # tested for here. sub test_dup_keep_fileinto - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing duplicate suppression between 'keep' & 'fileinto'"; + xlog $self, "Testing duplicate suppression between 'keep' & 'fileinto'"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that only one copy of the message made it to INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that only one copy of the message made it to INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/dup_keep_keep b/cassandane/tiny-tests/Sieve/dup_keep_keep index 929deae15c..aff709f728 100644 --- a/cassandane/tiny-tests/Sieve/dup_keep_keep +++ b/cassandane/tiny-tests/Sieve/dup_keep_keep @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_dup_keep_keep - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing duplicate suppression between 'keep' & 'keep'"; + xlog $self, "Testing duplicate suppression between 'keep' & 'keep'"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that only one copy of the message made it to INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that only one copy of the message made it to INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/duplicate b/cassandane/tiny-tests/Sieve/duplicate index e385d5deb7..90a8fad3cc 100644 --- a/cassandane/tiny-tests/Sieve/duplicate +++ b/cassandane/tiny-tests/Sieve/duplicate @@ -2,49 +2,49 @@ use Cassandane::Tiny; sub test_duplicate - :min_version_3_1 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install a sieve script with a duplicate check"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "ALERT: server down"); - $self->{instance}->deliver($msg1); - - xlog $self, "Deliver second message"; - # This message should be discarded - my $msg2 = $self->{gen}->generate(subject => "ALERT: server down"); - $self->{instance}->deliver($msg2); - - xlog $self, "Deliver third message"; - # This message should be discarded - my $msg3 = $self->{gen}->generate(subject => "ALERT: server down"); - $self->{instance}->deliver($msg3); - - sleep 3; - xlog $self, "Deliver fourth message"; - # This message should be delivered (after the expire time) - my $msg4 = $self->{gen}->generate(subject => "ALERT: server down"); - $self->{instance}->deliver($msg4); - - xlog $self, "Deliver fifth message"; - # This message should be discarded - my $msg5 = $self->{gen}->generate(subject => "ALERT: server down"); - $self->{instance}->deliver($msg5); - - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - - $self->assert_num_equals(2, $imaptalk->get_response_code('exists')); + ); + + xlog $self, "Deliver a message"; + # This message sets the duplicate tracking entry + my $msg1 = $self->{gen}->generate(subject => "ALERT: server down"); + $self->{instance}->deliver($msg1); + + xlog $self, "Deliver second message"; + # This message should be discarded + my $msg2 = $self->{gen}->generate(subject => "ALERT: server down"); + $self->{instance}->deliver($msg2); + + xlog $self, "Deliver third message"; + # This message should be discarded + my $msg3 = $self->{gen}->generate(subject => "ALERT: server down"); + $self->{instance}->deliver($msg3); + + sleep 3; + xlog $self, "Deliver fourth message"; + # This message should be delivered (after the expire time) + my $msg4 = $self->{gen}->generate(subject => "ALERT: server down"); + $self->{instance}->deliver($msg4); + + xlog $self, "Deliver fifth message"; + # This message should be discarded + my $msg5 = $self->{gen}->generate(subject => "ALERT: server down"); + $self->{instance}->deliver($msg5); + + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + + $self->assert_num_equals(2, $imaptalk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/editheader_basic b/cassandane/tiny-tests/Sieve/editheader_basic index f1fca9b2bd..c12134a0bc 100644 --- a/cassandane/tiny-tests/Sieve/editheader_basic +++ b/cassandane/tiny-tests/Sieve/editheader_basic @@ -2,15 +2,15 @@ use Cassandane::Tiny; sub test_editheader_basic - :min_version_3_1 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve { + my ($self) = @_; - my $target = "INBOX.target"; + my $target = "INBOX.target"; - xlog $self, "Install a sieve script with editheader actions"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{store}->get_client(); + xlog $self, "Create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; + $imaptalk->create($target) + or die "Cannot create $target: $@"; - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - $imaptalk->select("INBOX"); - my $res = $imaptalk->fetch(1, 'rfc822'); + $imaptalk->select("INBOX"); + my $res = $imaptalk->fetch(1, 'rfc822'); - $msg1 = $res->{1}->{rfc822}; + $msg1 = $res->{1}->{rfc822}; - $self->assert_matches(qr/^X-Cassandane-Test: prepend1\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: append1\r\nX-Cassandane-Test: append3\r\nX-Cassandane-Test: append5\r\n\r\n/, $msg1); + $self->assert_matches(qr/^X-Cassandane-Test: prepend1\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: append1\r\nX-Cassandane-Test: append3\r\nX-Cassandane-Test: append5\r\n\r\n/, + $msg1 + ); - $imaptalk->select($target); - $res = $imaptalk->fetch(1, 'rfc822'); + $imaptalk->select($target); + $res = $imaptalk->fetch(1, 'rfc822'); - $msg1 = $res->{1}->{rfc822}; + $msg1 = $res->{1}->{rfc822}; - $self->assert_matches(qr/^Return-Path: /, $msg1); - $self->assert_matches(qr/X-Cassandane-Unique: .*\r\n\r\n/, $msg1); + $self->assert_matches(qr/^Return-Path: /, $msg1); + $self->assert_matches(qr/X-Cassandane-Unique: .*\r\n\r\n/, $msg1); } diff --git a/cassandane/tiny-tests/Sieve/editheader_complex b/cassandane/tiny-tests/Sieve/editheader_complex index 92c1f92fd1..572dc5d70c 100644 --- a/cassandane/tiny-tests/Sieve/editheader_complex +++ b/cassandane/tiny-tests/Sieve/editheader_complex @@ -2,15 +2,15 @@ use Cassandane::Tiny; sub test_editheader_complex - :min_version_3_3 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_3 + : needs_component_sieve { + my ($self) = @_; - my $target = "INBOX.target"; + my $target = "INBOX.target"; - xlog $self, "Install a sieve script with editheader actions"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{store}->get_client(); + xlog $self, "Create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; + $imaptalk->create($target) + or die "Cannot create $target: $@"; - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - $imaptalk->select("INBOX"); - my $res = $imaptalk->fetch(1, 'rfc822'); + $imaptalk->select("INBOX"); + my $res = $imaptalk->fetch(1, 'rfc822'); - $msg1 = $res->{1}->{rfc822}; + $msg1 = $res->{1}->{rfc822}; - $self->assert_matches(qr/^X-Cassandane-Test: =\?UTF-8\?Q\?prepend1=0A=0A\?=\r\nX-Hello: =\?UTF-8\?Q\?World=E2=88=97\?=\r\nReturn-Path: /, $msg1); - $self->assert_matches(qr/X-Cassandane-Unique: .*\r\nX-Cassandane-Test: append1\r\nX-Cassandane-Test: append3\r\nX-Cassandane-Test: append5\r\n\r\n/, $msg1); + $self->assert_matches( + qr/^X-Cassandane-Test: =\?UTF-8\?Q\?prepend1=0A=0A\?=\r\nX-Hello: =\?UTF-8\?Q\?World=E2=88=97\?=\r\nReturn-Path: /, + $msg1 + ); + $self->assert_matches( + qr/X-Cassandane-Unique: .*\r\nX-Cassandane-Test: append1\r\nX-Cassandane-Test: append3\r\nX-Cassandane-Test: append5\r\n\r\n/, + $msg1 + ); - $imaptalk->select($target); - $res = $imaptalk->fetch(1, 'rfc822'); + $imaptalk->select($target); + $res = $imaptalk->fetch(1, 'rfc822'); - $msg1 = $res->{1}->{rfc822}; + $msg1 = $res->{1}->{rfc822}; - $self->assert_matches(qr/^X-Hello: =\?UTF-8\?Q\?World=E2=88=97\?=\r\nReturn-Path: /, $msg1); - $self->assert_matches(qr/X-Cassandane-Unique: .*\r\n\r\n/, $msg1); + $self->assert_matches( + qr/^X-Hello: =\?UTF-8\?Q\?World=E2=88=97\?=\r\nReturn-Path: /, $msg1); + $self->assert_matches(qr/X-Cassandane-Unique: .*\r\n\r\n/, $msg1); } diff --git a/cassandane/tiny-tests/Sieve/editheader_encoded_address_list b/cassandane/tiny-tests/Sieve/editheader_encoded_address_list index fa7b5958d1..8456ae58cb 100644 --- a/cassandane/tiny-tests/Sieve/editheader_encoded_address_list +++ b/cassandane/tiny-tests/Sieve/editheader_encoded_address_list @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_editheader_encoded_address_list - :min_version_3_3 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_3 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install a sieve script with editheader actions"; - my $script = < in non-list \${unicode:2217}"; EOF - $script =~ s/\r?\n/\r\n/gs; - $script =~ s/\\/\\\\/gs; - - $self->{instance}->install_sieve_script($script); - - xlog $self, "Deliver a matching message"; - my $msg1 = $self->{gen}->generate( - subject => "Message 1", - extra_headers => [['To', '"=?UTF-8?Q?=E2=88=97?=" , bbb@example.com, ccc@example.com'] - ], - ); - $self->{instance}->deliver($msg1); - - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - my $res = $imaptalk->fetch(1, 'rfc822'); - - $msg1 = $res->{1}->{rfc822}; - - $self->assert_matches(qr/To: =\?UTF-8\?Q\?=22=E2=88=97=22\?= ,\s+"BBB" ,\s+ccc\@example.com\r\n/, $msg1); - $self->assert_matches(qr/X-Foo: =\?UTF-8\?Q\?must_encode_star_\(=E2=88=97\)\?=\r\n/, $msg1); - $self->assert_matches(qr/X-Bar: don't need to encode this\r\n/, $msg1); - $self->assert_matches(qr/X-Blah: =\?UTF-8\?Q\?can_encode__in_non-list_=E2=88=97\?=\r\n\r\n/, $msg1); + $script =~ s/\r?\n/\r\n/gs; + $script =~ s/\\/\\\\/gs; + + $self->{instance}->install_sieve_script($script); + + xlog $self, "Deliver a matching message"; + my $msg1 = $self->{gen}->generate( + subject => "Message 1", + extra_headers => [ [ + 'To', + '"=?UTF-8?Q?=E2=88=97?=" , bbb@example.com, ccc@example.com' + ] ], + ); + $self->{instance}->deliver($msg1); + + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + my $res = $imaptalk->fetch(1, 'rfc822'); + + $msg1 = $res->{1}->{rfc822}; + + $self->assert_matches( + qr/To: =\?UTF-8\?Q\?=22=E2=88=97=22\?= ,\s+"BBB" ,\s+ccc\@example.com\r\n/, + $msg1 + ); + $self->assert_matches( + qr/X-Foo: =\?UTF-8\?Q\?must_encode_star_\(=E2=88=97\)\?=\r\n/, $msg1); + $self->assert_matches(qr/X-Bar: don't need to encode this\r\n/, $msg1); + $self->assert_matches( + qr/X-Blah: =\?UTF-8\?Q\?can_encode__in_non-list_=E2=88=97\?=\r\n\r\n/, + $msg1 + ); } diff --git a/cassandane/tiny-tests/Sieve/encoded_char_variable_in_mboxname b/cassandane/tiny-tests/Sieve/encoded_char_variable_in_mboxname index b960806c70..9ed024dd99 100644 --- a/cassandane/tiny-tests/Sieve/encoded_char_variable_in_mboxname +++ b/cassandane/tiny-tests/Sieve/encoded_char_variable_in_mboxname @@ -2,34 +2,34 @@ use Cassandane::Tiny; sub test_encoded_char_variable_in_mboxname - :needs_component_sieve :min_version_3_1 :SieveUTF8Fileinto -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_1 : SieveUTF8Fileinto { + my ($self) = @_; - my $target = "INBOX.\N{U+2217}"; + my $target = "INBOX.\N{U+2217}"; - xlog $self, "Testing encoded-character in a mailbox name"; + xlog $self, "Testing encoded-character in a mailbox name"; - xlog $self, "Actually create the target folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Actually create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; - $self->{store}->set_fetch_attributes('uid'); + $imaptalk->create($target) + or die "Cannot create $target: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Install script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message made it to the target"; - $self->{store}->set_folder($target); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the target"; + $self->{store}->set_folder($target); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/enotify b/cassandane/tiny-tests/Sieve/enotify index bd120ee636..ba6a1199ed 100644 --- a/cassandane/tiny-tests/Sieve/enotify +++ b/cassandane/tiny-tests/Sieve/enotify @@ -2,27 +2,27 @@ use Cassandane::Tiny; sub test_enotify - :needs_component_sieve :min_version_3_2 -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_2 { + my ($self) = @_; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["enotify"]; notify "https://cyrusimap.org/notifiers/updatecal"; notify :message "Hello World!" "mailto:foo@example.com"; EOF - ); + ); - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - my $data = $self->{instance}->getnotify(); - my ($updatecal) = grep { $_->{METHOD} eq 'updatecal' } @$data; - my ($mailto) = grep { $_->{METHOD} eq 'mailto' } @$data; + my $data = $self->{instance}->getnotify(); + my ($updatecal) = grep { $_->{METHOD} eq 'updatecal' } @$data; + my ($mailto) = grep { $_->{METHOD} eq 'mailto' } @$data; - $self->assert_not_null($updatecal); - $self->assert_not_null($mailto); - $self->assert_matches(qr/Hello World!/, $mailto->{MESSAGE}); + $self->assert_not_null($updatecal); + $self->assert_not_null($mailto); + $self->assert_matches(qr/Hello World!/, $mailto->{MESSAGE}); } diff --git a/cassandane/tiny-tests/Sieve/ereject b/cassandane/tiny-tests/Sieve/ereject index 8f6b6d0ac0..472e5901a7 100644 --- a/cassandane/tiny-tests/Sieve/ereject +++ b/cassandane/tiny-tests/Sieve/ereject @@ -2,27 +2,27 @@ use Cassandane::Tiny; sub test_ereject - :min_version_3_1 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install a sieve script rejecting all mail"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - my $res = $self->{instance}->deliver($msg1); + xlog $self, "Attempt to deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + my $res = $self->{instance}->deliver($msg1); - # should fail to deliver - $self->assert_num_not_equals(0, $res); + # should fail to deliver + $self->assert_num_not_equals(0, $res); - # should NOT appear in INBOX - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - $self->assert_num_equals(0, $imaptalk->get_response_code('exists')); + # should NOT appear in INBOX + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + $self->assert_num_equals(0, $imaptalk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/error_flag b/cassandane/tiny-tests/Sieve/error_flag index 6443e08161..c24aa7e067 100644 --- a/cassandane/tiny-tests/Sieve/error_flag +++ b/cassandane/tiny-tests/Sieve/error_flag @@ -2,12 +2,12 @@ use Cassandane::Tiny; sub test_error_flag - :needs_component_sieve :min_version_3_3 -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_3 { + my ($self) = @_; - xlog $self, "Install a sieve script filing all mail into a nonexistant folder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script(<{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + # should NOT get flagged + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - # SHOULD get flagged - my $msg2 = $self->{gen}->generate(subject => "this will fail with an error"); - $self->{instance}->deliver($msg2); + # SHOULD get flagged + my $msg2 = $self->{gen}->generate(subject => "this will fail with an error"); + $self->{instance}->deliver($msg2); - # should NOT get flagged - my $msg3 = $self->{gen}->generate(subject => "Message 3"); - $self->{instance}->deliver($msg3); + # should NOT get flagged + my $msg3 = $self->{gen}->generate(subject => "Message 3"); + $self->{instance}->deliver($msg3); - # SHOULD get flagged - my $msg4 = $self->{gen}->generate(subject => "this fileinto won't succeed"); - $self->{instance}->deliver($msg4); + # SHOULD get flagged + my $msg4 = $self->{gen}->generate(subject => "this fileinto won't succeed"); + $self->{instance}->deliver($msg4); - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - $self->assert_num_equals(4, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX"); + $self->assert_num_equals(4, $imaptalk->get_response_code('exists')); - my $res = $imaptalk->fetch('1:*', 'flags'); + my $res = $imaptalk->fetch('1:*', 'flags'); - $self->assert_null(grep { $_ eq '$SieveFailed' } @{$res->{1}{flags}}); - $self->assert_not_null(grep { $_ eq '$SieveFailed' } @{$res->{2}{flags}}); - $self->assert_null(grep { $_ eq '$SieveFailed' } @{$res->{3}{flags}}); - $self->assert_not_null(grep { $_ eq '$SieveFailed' } @{$res->{4}{flags}}); + $self->assert_null(grep { $_ eq '$SieveFailed' } @{ $res->{1}{flags} }); + $self->assert_not_null(grep { $_ eq '$SieveFailed' } @{ $res->{2}{flags} }); + $self->assert_null(grep { $_ eq '$SieveFailed' } @{ $res->{3}{flags} }); + $self->assert_not_null(grep { $_ eq '$SieveFailed' } @{ $res->{4}{flags} }); } diff --git a/cassandane/tiny-tests/Sieve/fileinto_mailboxid b/cassandane/tiny-tests/Sieve/fileinto_mailboxid index e56a194882..f03a5a337b 100644 --- a/cassandane/tiny-tests/Sieve/fileinto_mailboxid +++ b/cassandane/tiny-tests/Sieve/fileinto_mailboxid @@ -2,79 +2,79 @@ use Cassandane::Tiny; sub test_fileinto_mailboxid - :min_version_3_1 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"mailboxid\" action"; + xlog $self, "Testing the \"mailboxid\" action"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX.testfolder"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX.testfolder"; - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); - $talk->create($missfolder); + $talk->create($hitfolder); + $talk->create($missfolder); - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } - my $res = $talk->status($hitfolder, ['mailboxid']); - my $id = $res->{mailboxid}[0]; + my $res = $talk->status($hitfolder, ['mailboxid']); + my $id = $res->{mailboxid}[0]; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } - xlog $self, "Delete the target folder"; - $talk->delete($hitfolder); + xlog $self, "Delete the target folder"; + $talk->delete($hitfolder); - xlog $self, "Deliver a message now that the folder doesn't exist"; - { - my $msg = $self->{gen}->generate(subject => "msg3"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg3"} = $msg; - } + xlog $self, "Deliver a message now that the folder doesn't exist"; + { + my $msg = $self->{gen}->generate(subject => "msg3"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg3"} = $msg; + } - xlog $self, "Check that the message made it to miss folder"; - $self->{store}->set_folder($missfolder); - $self->check_messages($exp{$missfolder}, check_guid => 0); + xlog $self, "Check that the message made it to miss folder"; + $self->{store}->set_folder($missfolder); + $self->check_messages($exp{$missfolder}, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/fileinto_mailboxid_variable b/cassandane/tiny-tests/Sieve/fileinto_mailboxid_variable index fc638231b7..3cde7faf33 100644 --- a/cassandane/tiny-tests/Sieve/fileinto_mailboxid_variable +++ b/cassandane/tiny-tests/Sieve/fileinto_mailboxid_variable @@ -2,80 +2,80 @@ use Cassandane::Tiny; sub test_fileinto_mailboxid_variable - :min_version_3_5 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_5 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"mailboxid\" action"; + xlog $self, "Testing the \"mailboxid\" action"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX.testfolder"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX.testfolder"; - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); - $talk->create($missfolder); + $talk->create($hitfolder); + $talk->create($missfolder); - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } - my $res = $talk->status($hitfolder, ['mailboxid']); - my $id = $res->{mailboxid}[0]; + my $res = $talk->status($hitfolder, ['mailboxid']); + my $id = $res->{mailboxid}[0]; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } - xlog $self, "Delete the target folder"; - $talk->delete($hitfolder); + xlog $self, "Delete the target folder"; + $talk->delete($hitfolder); - xlog $self, "Deliver a message now that the folder doesn't exist"; - { - my $msg = $self->{gen}->generate(subject => "msg3"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg3"} = $msg; - } + xlog $self, "Deliver a message now that the folder doesn't exist"; + { + my $msg = $self->{gen}->generate(subject => "msg3"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg3"} = $msg; + } - xlog $self, "Check that the message made it to miss folder"; - $self->{store}->set_folder($missfolder); - $self->check_messages($exp{$missfolder}, check_guid => 0); + xlog $self, "Check that the message made it to miss folder"; + $self->{store}->set_folder($missfolder); + $self->check_messages($exp{$missfolder}, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/fileinto_mailboxidexists b/cassandane/tiny-tests/Sieve/fileinto_mailboxidexists index 12e5fa1b23..46531d1925 100644 --- a/cassandane/tiny-tests/Sieve/fileinto_mailboxidexists +++ b/cassandane/tiny-tests/Sieve/fileinto_mailboxidexists @@ -2,81 +2,81 @@ use Cassandane::Tiny; sub test_fileinto_mailboxidexists - :min_version_3_1 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"mailboxidexists\" test"; + xlog $self, "Testing the \"mailboxidexists\" test"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX"; - my $testfolder = "INBOX.testfolder"; + my $testfolder = "INBOX.testfolder"; - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); + $talk->create($hitfolder); - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } - xlog $self, "Create the test folder"; - $talk->create($testfolder); - my $res = $talk->status($testfolder, ['mailboxid']); - my $id = $res->{mailboxid}[0]; + xlog $self, "Create the test folder"; + $talk->create($testfolder); + my $res = $talk->status($testfolder, ['mailboxid']); + my $id = $res->{mailboxid}[0]; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } - xlog $self, "Delete the test folder"; - $talk->delete($testfolder); + xlog $self, "Delete the test folder"; + $talk->delete($testfolder); - xlog $self, "Deliver a message now that the folder doesn't exist"; - { - my $msg = $self->{gen}->generate(subject => "msg3"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg3"} = $msg; - } + xlog $self, "Deliver a message now that the folder doesn't exist"; + { + my $msg = $self->{gen}->generate(subject => "msg3"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg3"} = $msg; + } - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/github_issue_complex_variables b/cassandane/tiny-tests/Sieve/github_issue_complex_variables index 37768264d1..7b889b2847 100644 --- a/cassandane/tiny-tests/Sieve/github_issue_complex_variables +++ b/cassandane/tiny-tests/Sieve/github_issue_complex_variables @@ -2,13 +2,12 @@ use Cassandane::Tiny; sub test_github_issue_complex_variables - :min_version_3_1 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install a sieve script with complex variable work"; - $self->{instance}->install_sieve_script(<<'EOF'); + xlog $self, "Install a sieve script with complex variable work"; + $self->{instance}->install_sieve_script(<<'EOF'); require ["fileinto", "reject", "vacation", "envelope", "body", "relational", "regex", "subaddress", "copy", "mailbox", "mboxmetadata", "servermetadata", "date", "index", "comparator-i;ascii-numeric", "variables", "imap4flags", "editheader", "duplicate", "vacation-seconds"]; ### BEGIN USER SIEVE @@ -80,7 +79,7 @@ if allof ( } EOF - my $raw = << 'EOF'; + my $raw = << 'EOF'; Date: Wed, 16 May 2018 22:06:18 -0700 From: Some Person To: foo/bar @@ -90,17 +89,17 @@ X-GitHub-Reason: subscribed foo bar EOF - xlog $self, "Deliver a message"; - my $msg1 = Cassandane::Message->new(raw => $raw); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = Cassandane::Message->new(raw => $raw); + $self->{instance}->deliver($msg1); - # if there's a delivery failure, it will be in the Inbox - xlog $self, "Check there there are no messages in the Inbox"; - my $talk = $self->{store}->get_client(); - $talk->select("INBOX"); - $self->assert_num_equals(0, $talk->get_response_code('exists')); + # if there's a delivery failure, it will be in the Inbox + xlog $self, "Check there there are no messages in the Inbox"; + my $talk = $self->{store}->get_client(); + $talk->select("INBOX"); + $self->assert_num_equals(0, $talk->get_response_code('exists')); - # if there's no delivery failure, this folder will be created! - $talk->select("INBOX.GitHub.foo.bar.pulls.replies"); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + # if there's no delivery failure, this folder will be created! + $talk->select("INBOX.GitHub.foo.bar.pulls.replies"); + $self->assert_num_equals(1, $talk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/imip_add b/cassandane/tiny-tests/Sieve/imip_add index f263318928..d654487435 100644 --- a/cassandane/tiny-tests/Sieve/imip_add +++ b/cassandane/tiny-tests/Sieve/imip_add @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_add - :needs_component_sieve :needs_component_httpd :min_version_3_7 - :want_service_http -{ - my ($self) = @_; - - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -57,28 +57,29 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Expunge the message"; - $IMAP->store('1', '+flags', '(\\Deleted)'); - $IMAP->expunge(); - - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('An Event', $events->[0]{title}); - $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); - - - $imip = <new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Expunge the message"; + $IMAP->store('1', '+flags', '(\\Deleted)'); + $IMAP->expunge(); + + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('An Event', $events->[0]{title}); + $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); + + $imip = < To: Cassandane @@ -103,18 +104,21 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP update"; - $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Check that the event was updated on calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_not_null($events->[0]{recurrenceOverrides}{'2021-09-24T15:30:00'}); + xlog $self, "Deliver iMIP update"; + $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Check that the event was updated on calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_not_null( + $events->[0]{recurrenceOverrides}{'2021-09-24T15:30:00'}); } diff --git a/cassandane/tiny-tests/Sieve/imip_cancel b/cassandane/tiny-tests/Sieve/imip_cancel index c436ebe684..4536a80148 100644 --- a/cassandane/tiny-tests/Sieve/imip_cancel +++ b/cassandane/tiny-tests/Sieve/imip_cancel @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_cancel - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; - - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -58,27 +58,28 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Expunge the message"; - $IMAP->store('1', '+flags', '(\\Deleted)'); - $IMAP->expunge(); + xlog $self, "Expunge the message"; + $IMAP->store('1', '+flags', '(\\Deleted)'); + $IMAP->expunge(); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('tentative', $events->[0]{status}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('tentative', $events->[0]{status}); - - $imip = < To: Cassandane @@ -102,18 +103,20 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP cancel"; - $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Check that the event was removed from calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('cancelled', $events->[0]{status}); + xlog $self, "Deliver iMIP cancel"; + $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Check that the event was removed from calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('cancelled', $events->[0]{status}); } diff --git a/cassandane/tiny-tests/Sieve/imip_cancel_delete b/cassandane/tiny-tests/Sieve/imip_cancel_delete index 1d281421ee..a9942ef964 100644 --- a/cassandane/tiny-tests/Sieve/imip_cancel_delete +++ b/cassandane/tiny-tests/Sieve/imip_cancel_delete @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_cancel_delete - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -56,26 +56,27 @@ ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:cassandane\@example.com END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Expunge the message"; - $IMAP->store('1', '+flags', '(\\Deleted)'); - $IMAP->expunge(); + xlog $self, "Expunge the message"; + $IMAP->store('1', '+flags', '(\\Deleted)'); + $IMAP->expunge(); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); - - $imip = < To: Cassandane @@ -99,16 +100,18 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP cancel"; - $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP cancel"; + $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event was removed from calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_deep_equals($events, []); + xlog $self, "Check that the event was removed from calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_deep_equals($events, []); } diff --git a/cassandane/tiny-tests/Sieve/imip_cancel_instance b/cassandane/tiny-tests/Sieve/imip_cancel_instance index 3f3bec5cb3..b5506dd6b7 100644 --- a/cassandane/tiny-tests/Sieve/imip_cancel_instance +++ b/cassandane/tiny-tests/Sieve/imip_cancel_instance @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_cancel_instance - :needs_component_sieve :needs_component_httpd :min_version_3_7 - :want_service_http -{ - my ($self) = @_; - - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -58,28 +58,30 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Expunge the message"; - $IMAP->store('1', '+flags', '(\\Deleted)'); - $IMAP->expunge(); - - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('An Event', $events->[0]{title}); - $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); - my $href = $events->[0]{href}; - - $imip = <new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Expunge the message"; + $IMAP->store('1', '+flags', '(\\Deleted)'); + $IMAP->expunge(); + + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('An Event', $events->[0]{title}); + $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); + my $href = $events->[0]{href}; + + $imip = < To: Cassandane @@ -104,20 +106,22 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP cancel"; - $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP cancel"; + $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the updated event made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); + xlog $self, "Check that the updated event made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_equals(JSON::null, - $events->[0]{recurrenceOverrides}{'2021-09-23T15:30:00'}); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_equals(JSON::null, + $events->[0]{recurrenceOverrides}{'2021-09-23T15:30:00'}); } diff --git a/cassandane/tiny-tests/Sieve/imip_cancel_to_organizer b/cassandane/tiny-tests/Sieve/imip_cancel_to_organizer index af5e0bb905..da6a647b2a 100644 --- a/cassandane/tiny-tests/Sieve/imip_cancel_to_organizer +++ b/cassandane/tiny-tests/Sieve/imip_cancel_to_organizer @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_cancel_to_organizer - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); + my $href = "/dav/calendars/user/cassandane/$CalendarId/$uuid.ics"; + $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('tentative', $events->[0]{status}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('tentative', $events->[0]{status}); - - my $imip = < To: Cassandane @@ -83,16 +82,18 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP cancel"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP cancel"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Make sure that the event was NOT canceled"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('tentative', $events->[0]{status}); - $self->assert_equals(0, $events->[0]{sequence}); + xlog $self, "Make sure that the event was NOT canceled"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('tentative', $events->[0]{status}); + $self->assert_equals(0, $events->[0]{sequence}); } diff --git a/cassandane/tiny-tests/Sieve/imip_invite b/cassandane/tiny-tests/Sieve/imip_invite index 72bff7e9ee..961ee71efa 100644 --- a/cassandane/tiny-tests/Sieve/imip_invite +++ b/cassandane/tiny-tests/Sieve/imip_invite @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_invite - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -57,17 +57,19 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); } diff --git a/cassandane/tiny-tests/Sieve/imip_invite_base64 b/cassandane/tiny-tests/Sieve/imip_invite_base64 index fcef054a03..02592fd616 100644 --- a/cassandane/tiny-tests/Sieve/imip_invite_base64 +++ b/cassandane/tiny-tests/Sieve/imip_invite_base64 @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_invite_base64 - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -64,17 +64,19 @@ X-Cassandane-Unique: $uuid $itip_b64 EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); } diff --git a/cassandane/tiny-tests/Sieve/imip_invite_calendarid b/cassandane/tiny-tests/Sieve/imip_invite_calendarid index a9f807d1a3..6a66e883ba 100644 --- a/cassandane/tiny-tests/Sieve/imip_invite_calendarid +++ b/cassandane/tiny-tests/Sieve/imip_invite_calendarid @@ -2,25 +2,25 @@ use Cassandane::Tiny; sub test_imip_invite_calendarid - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user and second calendar"; - my $CalDAV = $self->{caldav}; - my $CalendarId = $CalDAV->NewCalendar({name => 'foo'}); - $self->assert_not_null($CalendarId); + xlog $self, "Create calendar user and second calendar"; + my $CalDAV = $self->{caldav}; + my $CalendarId = $CalDAV->NewCalendar({ name => 'foo' }); + $self->assert_not_null($CalendarId); - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -59,17 +59,19 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); } diff --git a/cassandane/tiny-tests/Sieve/imip_invite_funky_uid b/cassandane/tiny-tests/Sieve/imip_invite_funky_uid index 03b346c64f..4d81a3a720 100644 --- a/cassandane/tiny-tests/Sieve/imip_invite_funky_uid +++ b/cassandane/tiny-tests/Sieve/imip_invite_funky_uid @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_invite_funky_uid - :needs_component_sieve :needs_component_httpd :min_version_3_7 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_7 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -66,17 +66,19 @@ X-Cassandane-Unique: $uuid $itip_b64 EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); } diff --git a/cassandane/tiny-tests/Sieve/imip_invite_multipart b/cassandane/tiny-tests/Sieve/imip_invite_multipart index 9d20122c25..0fbf7656c6 100644 --- a/cassandane/tiny-tests/Sieve/imip_invite_multipart +++ b/cassandane/tiny-tests/Sieve/imip_invite_multipart @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_invite_multipart - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -70,17 +70,19 @@ END:VCALENDAR --$uuid-- EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); } diff --git a/cassandane/tiny-tests/Sieve/imip_invite_single_then_master b/cassandane/tiny-tests/Sieve/imip_invite_single_then_master index 087808f4c5..efde9bb891 100644 --- a/cassandane/tiny-tests/Sieve/imip_invite_single_then_master +++ b/cassandane/tiny-tests/Sieve/imip_invite_single_then_master @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_invite_single_then_master - :needs_component_sieve :needs_component_httpd :min_version_3_7 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_7 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -58,26 +58,26 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_null($events->[0]{recurrenceRule}); - $self->assert_not_null($events->[0]{recurrenceOverrides}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_null($events->[0]{recurrenceRule}); + $self->assert_not_null($events->[0]{recurrenceOverrides}); - xlog $self, "Get and accept the event"; - my $href = $events->[0]{href}; - my $response = $CalDAV->Request('GET', $href); - my $ical = $response->{content}; - $ical =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; + xlog $self, "Get and accept the event"; + my $href = $events->[0]{href}; + my $response = $CalDAV->Request('GET', $href); + my $ical = $response->{content}; + $ical =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; - $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); - $imip = < To: Cassandane @@ -106,16 +106,18 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP update"; - $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP update"; + $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - xlog $self, "Check that the standalone instance was dropped from the calendar"; - $events = $CalDAV->GetEvents($CalendarId); + xlog $self, + "Check that the standalone instance was dropped from the calendar"; + $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_not_null($events->[0]{recurrenceRule}); - $self->assert_null($events->[0]{recurrenceOverrides}); - $self->assert_str_equals('needs-action', $events->[0]{participants}{'cassandane@example.com'}{scheduleStatus}); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_not_null($events->[0]{recurrenceRule}); + $self->assert_null($events->[0]{recurrenceOverrides}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'cassandane@example.com'}{scheduleStatus}); } diff --git a/cassandane/tiny-tests/Sieve/imip_invite_updatesonly b/cassandane/tiny-tests/Sieve/imip_invite_updatesonly index f831ea8892..40d4d884ac 100644 --- a/cassandane/tiny-tests/Sieve/imip_invite_updatesonly +++ b/cassandane/tiny-tests/Sieve/imip_invite_updatesonly @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_invite_updatesonly - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -57,16 +57,18 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => ['\\Recent'] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event did NOT make it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_deep_equals([], $events); + xlog $self, "Check that the event did NOT make it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_deep_equals([], $events); } diff --git a/cassandane/tiny-tests/Sieve/imip_move_event b/cassandane/tiny-tests/Sieve/imip_move_event index 114a57424b..3fafde05f3 100644 --- a/cassandane/tiny-tests/Sieve/imip_move_event +++ b/cassandane/tiny-tests/Sieve/imip_move_event @@ -2,64 +2,76 @@ use Cassandane::Tiny; sub test_imip_move_event - :min_version_3_7 :needs_component_jmap :needs_component_sieve - :want_service_http -{ - my ($self) = @_; + : min_version_3_7 : needs_component_jmap : needs_component_sieve + : want_service_http { + my ($self) = @_; - my $jmap = $self->{jmap}; - my $caldav = $self->{caldav}; - my $admin = $self->{adminstore}->get_client(); + my $jmap = $self->{jmap}; + my $caldav = $self->{caldav}; + my $admin = $self->{adminstore}->get_client(); - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <CallMethods([ - ['Calendar/set', { - create => { - calendarX => { - name => 'X', - }, - calendarY => { - name => 'Y', - }, - }, - }, 'R1'], - ]); - my $calendarX = $res->[0][1]{created}{calendarX}{id}; - $self->assert_not_null($calendarX); - my $calendarY = $res->[0][1]{created}{calendarY}{id}; - $self->assert_not_null($calendarY); - - $res = $jmap->CallMethods([ - ['CalendarPreferences/set', { - update => { - singleton => { - defaultCalendarId => $calendarX, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{singleton}); - - xlog "Get CalendarEvent state"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [], - }, 'R1'], - ]); - my $state = $res->[0][1]{state}; - $self->assert_not_null($state); - - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $imip = <CallMethods([ + [ + 'Calendar/set', + { + create => { + calendarX => { + name => 'X', + }, + calendarY => { + name => 'Y', + }, + }, + }, + 'R1' + ], + ]); + my $calendarX = $res->[0][1]{created}{calendarX}{id}; + $self->assert_not_null($calendarX); + my $calendarY = $res->[0][1]{created}{calendarY}{id}; + $self->assert_not_null($calendarY); + + $res = $jmap->CallMethods([ + [ + 'CalendarPreferences/set', + { + update => { + singleton => { + defaultCalendarId => $calendarX, + }, + }, + }, + 'R1' + ], + ]); + $self->assert(exists $res->[0][1]{updated}{singleton}); + + xlog "Get CalendarEvent state"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [], + }, + 'R1' + ], + ]); + my $state = $res->[0][1]{state}; + $self->assert_not_null($state); + + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $imip = < To: Cassandane @@ -87,44 +99,59 @@ END:VEVENT END:VCALENDAR EOF - xlog "Deliver iMIP invite for instance1"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - - xlog "Assert instance1 got into calendar X"; - $res = $jmap->CallMethods([ - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R1'], - ['CalendarEvent/get', { - '#ids' => { - resultOf => 'R1', - name => 'CalendarEvent/changes', - path => '/created' + xlog "Deliver iMIP invite for instance1"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + + xlog "Assert instance1 got into calendar X"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R1' + ], + [ + 'CalendarEvent/get', + { + '#ids' => { + resultOf => 'R1', + name => 'CalendarEvent/changes', + path => '/created' + }, + properties => ['calendarIds'], + }, + 'R2' + ], + ]); + $self->assert_deep_equals( + { + $calendarX => JSON::true, + }, + $res->[1][1]{list}[0]{calendarIds} + ); + my $instance1 = $res->[1][1]{list}[0]{id}; + + xlog "Move instance1 to calendar Y"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/set', + { + update => { + $instance1 => { + calendarIds => { + $calendarY => JSON::true, }, - properties => [ 'calendarIds' ], - }, 'R2'], - ]); - $self->assert_deep_equals({ - $calendarX => JSON::true, - }, $res->[1][1]{list}[0]{calendarIds}); - my $instance1 = $res->[1][1]{list}[0]{id}; - - xlog "Move instance1 to calendar Y"; - $res = $jmap->CallMethods([ - ['CalendarEvent/set', { - update => { - $instance1 => { - calendarIds => { - $calendarY => JSON::true, - }, - }, - }, - }, 'R1'], - ]); - $self->assert(exists $res->[0][1]{updated}{$instance1}); - $state = $res->[0][1]{newState}; - - $imip = <assert(exists $res->[0][1]{updated}{$instance1}); + $state = $res->[0][1]{newState}; + + $imip = < To: Cassandane @@ -152,30 +179,44 @@ END:VEVENT END:VCALENDAR EOF - xlog "Deliver iMIP invite for instance2"; - $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); - - $res = $jmap->CallMethods([ - ['CalendarEvent/changes', { - sinceState => $state, - }, 'R1'], - ]); - $self->assert_num_equals(1, scalar @{$res->[0][1]{created}}); - my $instance2 = $res->[0][1]{created}[0]; - $self->assert_str_not_equals($instance1, $instance2); - - xlog "Assert both instance1 and instance2 are in calendar Y"; - $res = $jmap->CallMethods([ - ['CalendarEvent/get', { - ids => [ $instance1, $instance2 ], - properties => [ 'calendarIds' ], - }, 'R1'], - ]); - $self->assert_num_equals(2, scalar @{$res->[0][1]{list}}); - $self->assert_deep_equals({ - $calendarY => JSON::true, - }, $res->[0][1]{list}[0]{calendarIds}); - $self->assert_deep_equals({ - $calendarY => JSON::true, - }, $res->[0][1]{list}[1]{calendarIds}); + xlog "Deliver iMIP invite for instance2"; + $self->{instance}->deliver(Cassandane::Message->new(raw => $imip)); + + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/changes', + { + sinceState => $state, + }, + 'R1' + ], + ]); + $self->assert_num_equals(1, scalar @{ $res->[0][1]{created} }); + my $instance2 = $res->[0][1]{created}[0]; + $self->assert_str_not_equals($instance1, $instance2); + + xlog "Assert both instance1 and instance2 are in calendar Y"; + $res = $jmap->CallMethods([ + [ + 'CalendarEvent/get', + { + ids => [ $instance1, $instance2 ], + properties => ['calendarIds'], + }, + 'R1' + ], + ]); + $self->assert_num_equals(2, scalar @{ $res->[0][1]{list} }); + $self->assert_deep_equals( + { + $calendarY => JSON::true, + }, + $res->[0][1]{list}[0]{calendarIds} + ); + $self->assert_deep_equals( + { + $calendarY => JSON::true, + }, + $res->[0][1]{list}[1]{calendarIds} + ); } diff --git a/cassandane/tiny-tests/Sieve/imip_override b/cassandane/tiny-tests/Sieve/imip_override index b80d208a3b..7369385e9d 100644 --- a/cassandane/tiny-tests/Sieve/imip_override +++ b/cassandane/tiny-tests/Sieve/imip_override @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_override - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; - - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -58,28 +58,30 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Expunge the message"; - $IMAP->store('1', '+flags', '(\\Deleted)'); - $IMAP->expunge(); - - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('An Event', $events->[0]{title}); - $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); - - xlog $self, "Get the event, set per-user data, and add an alarm"; - my $alarm = <new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Expunge the message"; + $IMAP->store('1', '+flags', '(\\Deleted)'); + $IMAP->expunge(); + + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('An Event', $events->[0]{title}); + $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); + + xlog $self, "Get the event, set per-user data, and add an alarm"; + my $alarm = <[0]{href}; - my $response = $CalDAV->Request('GET', $href); - my $ical = $response->{content}; - $ical =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; - $ical =~ s/END:VEVENT/TRANSP:TRANSPARENT\n${alarm}END:VEVENT/; + my $href = $events->[0]{href}; + my $response = $CalDAV->Request('GET', $href); + my $ical = $response->{content}; + $ical =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; + $ical =~ s/END:VEVENT/TRANSP:TRANSPARENT\n${alarm}END:VEVENT/; - $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); - $imip = < To: Cassandane @@ -125,32 +127,42 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP update"; - $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Check that the updated event made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('An Overridden Event', $events->[0]{recurrenceOverrides}{'2021-09-23T15:30:00'}{title}); - $self->assert_str_equals('location2', $events->[0]{recurrenceOverrides}{'2021-09-23T15:30:00'}{locations}{location}{name}); - - xlog $self, "Make sure that per-user data remains"; - $self->assert_equals(JSON::true, $events->[0]{showAsFree}); - $self->assert_not_null($events->[0]{alerts}{myalarm}); - $self->assert_str_equals('accepted', $events->[0]{participants}{'cassandane@example.com'}{scheduleStatus}); - - $response = $CalDAV->Request('GET', $href); - $ical = Data::ICal->new(data => $response->{content}); - $self->assert_str_equals('TRANSPARENT', $ical->{entries}[0]{properties}{transp}[0]{value}); - $self->assert_str_equals('DISPLAY', $ical->{entries}[0]{entries}[0]{properties}{action}[0]{value}); - $self->assert_str_equals('TRANSPARENT', $ical->{entries}[1]{properties}{transp}[0]{value}); - $self->assert_str_equals('DISPLAY', $ical->{entries}[1]{entries}[0]{properties}{action}[0]{value}); + xlog $self, "Deliver iMIP update"; + $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Check that the updated event made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('An Overridden Event', + $events->[0]{recurrenceOverrides}{'2021-09-23T15:30:00'}{title}); + $self->assert_str_equals('location2', + $events->[0]{recurrenceOverrides}{'2021-09-23T15:30:00'}{locations} + {location}{name}); + + xlog $self, "Make sure that per-user data remains"; + $self->assert_equals(JSON::true, $events->[0]{showAsFree}); + $self->assert_not_null($events->[0]{alerts}{myalarm}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'cassandane@example.com'}{scheduleStatus}); + + $response = $CalDAV->Request('GET', $href); + $ical = Data::ICal->new(data => $response->{content}); + $self->assert_str_equals('TRANSPARENT', + $ical->{entries}[0]{properties}{transp}[0]{value}); + $self->assert_str_equals('DISPLAY', + $ical->{entries}[0]{entries}[0]{properties}{action}[0]{value}); + $self->assert_str_equals('TRANSPARENT', + $ical->{entries}[1]{properties}{transp}[0]{value}); + $self->assert_str_equals('DISPLAY', + $ical->{entries}[1]{entries}[0]{properties}{action}[0]{value}); } diff --git a/cassandane/tiny-tests/Sieve/imip_preserve_alerts b/cassandane/tiny-tests/Sieve/imip_preserve_alerts index 6a0aca03e0..70244be685 100644 --- a/cassandane/tiny-tests/Sieve/imip_preserve_alerts +++ b/cassandane/tiny-tests/Sieve/imip_preserve_alerts @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_preserve_alerts - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; - - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -58,11 +58,11 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver initial iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Deliver initial iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - $imip = < To: Cassandane @@ -93,12 +93,12 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Add another attendee to a single instance"; - $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Add another attendee to a single instance"; + $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - xlog $self, "Get the event and add an alarm"; - my $alarm = <GetEvents($CalendarId); - my $href = $events->[0]{href}; - my $response = $CalDAV->Request('GET', $href); - my $ical = $response->{content}; - $ical =~ s/END:VEVENT/${alarm}END:VEVENT/g; + my $events = $CalDAV->GetEvents($CalendarId); + my $href = $events->[0]{href}; + my $response = $CalDAV->Request('GET', $href); + my $ical = $response->{content}; + $ical =~ s/END:VEVENT/${alarm}END:VEVENT/g; - $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); - $imip = < To: Cassandane @@ -161,15 +161,17 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Update the master to include the other user"; - $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Update the master to include the other user"; + $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - xlog $self, "Make sure that alarms remain"; - $events = $CalDAV->GetEvents($CalendarId); + xlog $self, "Make sure that alarms remain"; + $events = $CalDAV->GetEvents($CalendarId); - $response = $CalDAV->Request('GET', $href); - $ical = Data::ICal->new(data => $response->{content}); - $self->assert_str_equals('DISPLAY', $ical->{entries}[0]{entries}[0]{properties}{action}[0]{value}); - $self->assert_str_equals('DISPLAY', $ical->{entries}[1]{entries}[0]{properties}{action}[0]{value}); + $response = $CalDAV->Request('GET', $href); + $ical = Data::ICal->new(data => $response->{content}); + $self->assert_str_equals('DISPLAY', + $ical->{entries}[0]{entries}[0]{properties}{action}[0]{value}); + $self->assert_str_equals('DISPLAY', + $ical->{entries}[1]{entries}[0]{properties}{action}[0]{value}); } diff --git a/cassandane/tiny-tests/Sieve/imip_publish b/cassandane/tiny-tests/Sieve/imip_publish index 7936ccb1da..9370b541b3 100644 --- a/cassandane/tiny-tests/Sieve/imip_publish +++ b/cassandane/tiny-tests/Sieve/imip_publish @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_publish - :needs_component_sieve :needs_component_httpd :min_version_3_7 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_7 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -55,17 +55,19 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); } diff --git a/cassandane/tiny-tests/Sieve/imip_publish_no_from_no_organizer b/cassandane/tiny-tests/Sieve/imip_publish_no_from_no_organizer index 3e3f71b8f1..6170687606 100644 --- a/cassandane/tiny-tests/Sieve/imip_publish_no_from_no_organizer +++ b/cassandane/tiny-tests/Sieve/imip_publish_no_from_no_organizer @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_publish_no_from_no_organizer - :needs_component_sieve :needs_component_httpd :min_version_3_9 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_9 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < Message-ID: <$uuid\@example.net> @@ -53,17 +53,19 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); } diff --git a/cassandane/tiny-tests/Sieve/imip_reply b/cassandane/tiny-tests/Sieve/imip_reply index 3ca2aa9f91..0751afcc6a 100644 --- a/cassandane/tiny-tests/Sieve/imip_reply +++ b/cassandane/tiny-tests/Sieve/imip_reply @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_imip_reply - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$CalendarId/$uuid.ics"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Create an event on calendar"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - - my $imip = < To: Cassandane @@ -85,21 +84,23 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP reply"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the reply made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('Test User', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the reply made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('Test User', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); } diff --git a/cassandane/tiny-tests/Sieve/imip_reply_no_organizer b/cassandane/tiny-tests/Sieve/imip_reply_no_organizer index fe91d0ee8b..40d490bc2b 100644 --- a/cassandane/tiny-tests/Sieve/imip_reply_no_organizer +++ b/cassandane/tiny-tests/Sieve/imip_reply_no_organizer @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_imip_reply_no_organizer - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$CalendarId/$uuid.ics"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Create an event on calendar"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - - my $imip = < To: Cassandane @@ -87,21 +86,23 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP reply"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the reply made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('Test User', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the reply made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('Test User', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); } diff --git a/cassandane/tiny-tests/Sieve/imip_reply_one_recurrence b/cassandane/tiny-tests/Sieve/imip_reply_one_recurrence index b1e8b03ea9..c1f8f2be9c 100644 --- a/cassandane/tiny-tests/Sieve/imip_reply_one_recurrence +++ b/cassandane/tiny-tests/Sieve/imip_reply_one_recurrence @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_imip_reply_one_recurrence - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "09b59913-30b2-4f90-982a-7ce6e2a56655"; - my $href = "$CalendarId/$uuid.ics"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "09b59913-30b2-4f90-982a-7ce6e2a56655"; + my $href = "$CalendarId/$uuid.ics"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Create an event on calendar"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('Emerson Leaf', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('Emerson Leaf', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - - my $imip = <<'EOF'; + my $imip = <<'EOF'; Date: Thu, 23 Sep 2021 09:06:18 -0400 From: Foo To: Cassandane @@ -159,29 +158,33 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP reply"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the reply made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - # top level is not updated - $self->assert_str_equals('Emerson Leaf', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - # particular recurrence is - my $recur = '2023-06-29T09:00:00'; + xlog $self, "Check that the reply made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + # top level is not updated + $self->assert_str_equals('Emerson Leaf', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + # particular recurrence is + my $recur = '2023-06-29T09:00:00'; - $self->assert_str_equals('Emerson Leaf', - $events->[0]{recurrenceOverrides}{$recur}{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('tentative', - $events->[0]{recurrenceOverrides}{$recur}{participants}{'foo@example.net'}{scheduleStatus}); + $self->assert_str_equals('Emerson Leaf', + $events->[0]{recurrenceOverrides}{$recur}{participants}{'foo@example.net'} + {name}); + $self->assert_str_equals('tentative', + $events->[0]{recurrenceOverrides}{$recur}{participants}{'foo@example.net'} + {scheduleStatus}); } diff --git a/cassandane/tiny-tests/Sieve/imip_reply_override b/cassandane/tiny-tests/Sieve/imip_reply_override index 919d2f4a55..012577b806 100644 --- a/cassandane/tiny-tests/Sieve/imip_reply_override +++ b/cassandane/tiny-tests/Sieve/imip_reply_override @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_imip_reply_override - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$CalendarId/$uuid.ics"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Create an event on calendar"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - - my $imip = < To: Cassandane @@ -95,26 +94,30 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP reply"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the reply made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('Test User', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the reply made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('Test User', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - $self->assert_str_equals('accepted', - $events->[0]{recurrenceOverrides}{'2021-10-21T15:30:00'}{participants}{'cassandane@example.com'}{scheduleStatus}); - $self->assert_str_equals('declined', - $events->[0]{recurrenceOverrides}{'2021-10-21T15:30:00'}{participants}{'foo@example.net'}{scheduleStatus}); + $self->assert_str_equals('accepted', + $events->[0]{recurrenceOverrides}{'2021-10-21T15:30:00'}{participants} + {'cassandane@example.com'}{scheduleStatus}); + $self->assert_str_equals('declined', + $events->[0]{recurrenceOverrides}{'2021-10-21T15:30:00'}{participants} + {'foo@example.net'}{scheduleStatus}); } diff --git a/cassandane/tiny-tests/Sieve/imip_reply_override_google b/cassandane/tiny-tests/Sieve/imip_reply_override_google index 3449504845..dce9b4a7c2 100644 --- a/cassandane/tiny-tests/Sieve/imip_reply_override_google +++ b/cassandane/tiny-tests/Sieve/imip_reply_override_google @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_imip_reply_override_google - :needs_component_sieve :needs_component_httpd :min_version_3_7 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_7 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$CalendarId/$uuid.ics"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Create an event on calendar"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - - my $imip = < To: Cassandane @@ -116,33 +115,39 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP reply"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the reply made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('Test User', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the reply made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('Test User', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - $self->assert_str_equals('2021-07-22T15:30:00', - $events->[0]{recurrenceOverrides}{'2021-07-21T15:30:00'}{start}); - $self->assert_str_equals('accepted', - $events->[0]{recurrenceOverrides}{'2021-07-21T15:30:00'}{participants}{'cassandane@example.com'}{scheduleStatus}); - $self->assert_str_equals('tentative', - $events->[0]{recurrenceOverrides}{'2021-07-21T15:30:00'}{participants}{'foo@example.net'}{scheduleStatus}); + $self->assert_str_equals('2021-07-22T15:30:00', + $events->[0]{recurrenceOverrides}{'2021-07-21T15:30:00'}{start}); + $self->assert_str_equals('accepted', + $events->[0]{recurrenceOverrides}{'2021-07-21T15:30:00'}{participants} + {'cassandane@example.com'}{scheduleStatus}); + $self->assert_str_equals('tentative', + $events->[0]{recurrenceOverrides}{'2021-07-21T15:30:00'}{participants} + {'foo@example.net'}{scheduleStatus}); - $self->assert_str_equals('accepted', - $events->[0]{recurrenceOverrides}{'2021-07-28T15:30:00'}{participants}{'cassandane@example.com'}{scheduleStatus}); - $self->assert_str_equals('declined', - $events->[0]{recurrenceOverrides}{'2021-07-28T15:30:00'}{participants}{'foo@example.net'}{scheduleStatus}); + $self->assert_str_equals('accepted', + $events->[0]{recurrenceOverrides}{'2021-07-28T15:30:00'}{participants} + {'cassandane@example.com'}{scheduleStatus}); + $self->assert_str_equals('declined', + $events->[0]{recurrenceOverrides}{'2021-07-28T15:30:00'}{participants} + {'foo@example.net'}{scheduleStatus}); } diff --git a/cassandane/tiny-tests/Sieve/imip_reply_override_invalid b/cassandane/tiny-tests/Sieve/imip_reply_override_invalid index 834921e7c0..a506f6be40 100644 --- a/cassandane/tiny-tests/Sieve/imip_reply_override_invalid +++ b/cassandane/tiny-tests/Sieve/imip_reply_override_invalid @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_imip_reply_override_invalid - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; - - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$CalendarId/$uuid.ics"; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Create an event on calendar"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - $self->assert_null($events->[0]{recurrenceRule}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + $self->assert_null($events->[0]{recurrenceRule}); - - my $imip = < To: Cassandane @@ -87,30 +86,31 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Expunge the message"; - $IMAP->store('1', '+flags', '(\\Deleted)'); - $IMAP->expunge(); - - xlog $self, "Check that the reply was ignored"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - $self->assert_null($events->[0]{recurrenceRule}); - $self->assert_null($events->[0]{recurrenceOverrides}); - - - $event = <new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => ['\\Recent'] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Expunge the message"; + $IMAP->store('1', '+flags', '(\\Deleted)'); + $IMAP->expunge(); + + xlog $self, "Check that the reply was ignored"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + $self->assert_null($events->[0]{recurrenceRule}); + $self->assert_null($events->[0]{recurrenceOverrides}); + + $event = <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - - xlog $self, "Check that the event made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - $self->assert_not_null($events->[0]{recurrenceRule}); - $self->assert_null($events->[0]{recurrenceOverrides}); - - - $imip = <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + + xlog $self, "Check that the event made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + $self->assert_not_null($events->[0]{recurrenceRule}); + $self->assert_null($events->[0]{recurrenceOverrides}); + + $imip = < To: Cassandane @@ -176,24 +175,26 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Check that the reply (master only) made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('Test User', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - - $self->assert_not_null($events->[0]{recurrenceRule}); - $self->assert_null($events->[0]{recurrenceOverrides}); + xlog $self, "Deliver iMIP reply"; + $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Check that the reply (master only) made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('Test User', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + + $self->assert_not_null($events->[0]{recurrenceRule}); + $self->assert_null($events->[0]{recurrenceOverrides}); } diff --git a/cassandane/tiny-tests/Sieve/imip_reply_override_rdate b/cassandane/tiny-tests/Sieve/imip_reply_override_rdate index 8d976366f2..3362206c78 100644 --- a/cassandane/tiny-tests/Sieve/imip_reply_override_rdate +++ b/cassandane/tiny-tests/Sieve/imip_reply_override_rdate @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_imip_reply_override_rdate - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_5 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$CalendarId/$uuid.ics"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Create an event on calendar"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - my $imip = < To: Cassandane @@ -93,26 +93,30 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP reply"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the reply made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('Test User', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the reply made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('Test User', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - $self->assert_str_equals('accepted', - $events->[0]{recurrenceOverrides}{'2021-10-21T15:30:00'}{participants}{'cassandane@example.com'}{scheduleStatus}); - $self->assert_str_equals('declined', - $events->[0]{recurrenceOverrides}{'2021-10-21T15:30:00'}{participants}{'foo@example.net'}{scheduleStatus}); + $self->assert_str_equals('accepted', + $events->[0]{recurrenceOverrides}{'2021-10-21T15:30:00'}{participants} + {'cassandane@example.com'}{scheduleStatus}); + $self->assert_str_equals('declined', + $events->[0]{recurrenceOverrides}{'2021-10-21T15:30:00'}{participants} + {'foo@example.net'}{scheduleStatus}); } diff --git a/cassandane/tiny-tests/Sieve/imip_reply_with_alarm b/cassandane/tiny-tests/Sieve/imip_reply_with_alarm index 893985311b..4960d1d843 100644 --- a/cassandane/tiny-tests/Sieve/imip_reply_with_alarm +++ b/cassandane/tiny-tests/Sieve/imip_reply_with_alarm @@ -2,24 +2,24 @@ use Cassandane::Tiny; sub test_imip_reply_with_alarm - :needs_component_sieve :needs_component_httpd :min_version_3_9 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_9 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - my $href = "$CalendarId/$uuid.ics"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + my $href = "$CalendarId/$uuid.ics"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); + xlog $self, "Create an event on calendar"; + $CalDAV->Request('PUT', $href, $event, 'Content-Type' => 'text/calendar'); - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('needs-action', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('needs-action', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); - - my $imip = < To: Cassandane @@ -91,21 +90,23 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP reply"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP reply"; + my $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); - xlog $self, "Check that the reply made it to calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('Test User', - $events->[0]{participants}{'foo@example.net'}{name}); - $self->assert_str_equals('accepted', - $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); + xlog $self, "Check that the reply made it to calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('Test User', + $events->[0]{participants}{'foo@example.net'}{name}); + $self->assert_str_equals('accepted', + $events->[0]{participants}{'foo@example.net'}{scheduleStatus}); } diff --git a/cassandane/tiny-tests/Sieve/imip_strip_personal_data b/cassandane/tiny-tests/Sieve/imip_strip_personal_data index 3cc4ccdffc..2542133d44 100644 --- a/cassandane/tiny-tests/Sieve/imip_strip_personal_data +++ b/cassandane/tiny-tests/Sieve/imip_strip_personal_data @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_strip_personal_data - :needs_component_sieve :needs_component_httpd :min_version_3_7 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : min_version_3_7 + : want_service_http { + my ($self) = @_; - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); + my $IMAP = $self->{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -68,18 +68,18 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - xlog $self, "Get the event and verify that personal data has been stripped"; - my $events = $CalDAV->GetEvents($CalendarId); - my $response = $CalDAV->Request('GET', $events->[0]{href}); - my $ical = $response->{content}; - $self->assert_does_not_match(qr/\r\nBEGIN:VALARM/, $ical); - $self->assert_does_not_match(qr/\r\nCOLOR:/, $ical); - $self->assert_does_not_match(qr/\r\nTRANSP:/, $ical); - $self->assert_does_not_match(qr/\r\nCATEGORIES:#0000FF/, $ical); - $self->assert_does_not_match(qr/\r\nCATEGORIES:blue/, $ical); - $self->assert_matches(qr/\r\nCATEGORIES:foo/, $ical); + xlog $self, "Get the event and verify that personal data has been stripped"; + my $events = $CalDAV->GetEvents($CalendarId); + my $response = $CalDAV->Request('GET', $events->[0]{href}); + my $ical = $response->{content}; + $self->assert_does_not_match(qr/\r\nBEGIN:VALARM/, $ical); + $self->assert_does_not_match(qr/\r\nCOLOR:/, $ical); + $self->assert_does_not_match(qr/\r\nTRANSP:/, $ical); + $self->assert_does_not_match(qr/\r\nCATEGORIES:#0000FF/, $ical); + $self->assert_does_not_match(qr/\r\nCATEGORIES:blue/, $ical); + $self->assert_matches(qr/\r\nCATEGORIES:foo/, $ical); } diff --git a/cassandane/tiny-tests/Sieve/imip_update b/cassandane/tiny-tests/Sieve/imip_update index 2cb400813c..4b66c3ae67 100644 --- a/cassandane/tiny-tests/Sieve/imip_update +++ b/cassandane/tiny-tests/Sieve/imip_update @@ -2,23 +2,23 @@ use Cassandane::Tiny; sub test_imip_update - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - my ($self) = @_; - - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -57,28 +57,29 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Expunge the message"; - $IMAP->store('1', '+flags', '(\\Deleted)'); - $IMAP->expunge(); - - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('An Event', $events->[0]{title}); - $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); - - - $imip = <new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Expunge the message"; + $IMAP->store('1', '+flags', '(\\Deleted)'); + $IMAP->expunge(); + + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('An Event', $events->[0]{title}); + $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); + + $imip = < To: Cassandane @@ -106,19 +107,21 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP update"; - $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Check that the event was updated on calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('An Updated Event', $events->[0]{title}); - $self->assert_str_equals('2021-09-24T14:00:00', $events->[0]{start}); + xlog $self, "Deliver iMIP update"; + $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Check that the event was updated on calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('An Updated Event', $events->[0]{title}); + $self->assert_str_equals('2021-09-24T14:00:00', $events->[0]{start}); } diff --git a/cassandane/tiny-tests/Sieve/imip_update_master_and_add_override b/cassandane/tiny-tests/Sieve/imip_update_master_and_add_override index 5a9a65e655..6a692da4ca 100644 --- a/cassandane/tiny-tests/Sieve/imip_update_master_and_add_override +++ b/cassandane/tiny-tests/Sieve/imip_update_master_and_add_override @@ -2,25 +2,25 @@ use Cassandane::Tiny; sub test_imip_update_master_and_add_override - :needs_component_sieve :needs_component_httpd :min_version_3_5 - :want_service_http -{ - # this test is mainly here to test that a crasher caused by Cyrus using - # a libical component after it was freed - my ($self) = @_; - - my $IMAP = $self->{store}->get_client(); - $self->{store}->_select(); - $self->assert_num_equals(1, $IMAP->uid()); - $self->{store}->set_fetch_attributes(qw(uid flags)); - - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $CalendarId = 'Default'; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{store}->get_client(); + $self->{store}->_select(); + $self->assert_num_equals(1, $IMAP->uid()); + $self->{store}->set_fetch_attributes(qw(uid flags)); + + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $CalendarId = 'Default'; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + + xlog $self, "Install a sieve script to process iMIP"; + $self->{instance}->install_sieve_script( + < To: Cassandane @@ -59,29 +59,30 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 1, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Expunge the message"; - $IMAP->store('1', '+flags', '(\\Deleted)'); - $IMAP->expunge(); - - xlog $self, "Check that the event made it to calendar"; - my $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('An Event', $events->[0]{title}); - $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); - - - xlog $self, "Get the event, set per-user data, and add an alarm"; - my $alarm = <new(raw => $imip); + $msg->set_attribute( + uid => 1, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Expunge the message"; + $IMAP->store('1', '+flags', '(\\Deleted)'); + $IMAP->expunge(); + + xlog $self, "Check that the event made it to calendar"; + my $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('An Event', $events->[0]{title}); + $self->assert_str_equals('2021-09-23T15:30:00', $events->[0]{start}); + + xlog $self, "Get the event, set per-user data, and add an alarm"; + my $alarm = <[0]{href}; - my $response = $CalDAV->Request('GET', $href); - my $ical = $response->{content}; - $ical =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; - $ical =~ s/END:VEVENT/TRANSP:TRANSPARENT\n${alarm}END:VEVENT/; + my $href = $events->[0]{href}; + my $response = $CalDAV->Request('GET', $href); + my $ical = $response->{content}; + $ical =~ s/PARTSTAT=NEEDS-ACTION/PARTSTAT=ACCEPTED/; + $ical =~ s/END:VEVENT/TRANSP:TRANSPARENT\n${alarm}END:VEVENT/; - $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); + $CalDAV->Request('PUT', $href, $ical, 'Content-Type' => 'text/calendar'); - $imip = < To: Cassandane @@ -140,19 +141,21 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP update"; - $msg = Cassandane::Message->new(raw => $imip); - $msg->set_attribute(uid => 2, - flags => [ '\\Recent', '\\Flagged' ]); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Check that the event was updated on calendar"; - $events = $CalDAV->GetEvents($CalendarId); - $self->assert_equals(1, scalar @$events); - $self->assert_str_equals($uuid, $events->[0]{uid}); - $self->assert_str_equals('An Updated Event', $events->[0]{title}); - $self->assert_str_equals('2021-09-24T14:00:00', $events->[0]{start}); + xlog $self, "Deliver iMIP update"; + $msg = Cassandane::Message->new(raw => $imip); + $msg->set_attribute( + uid => 2, + flags => [ '\\Recent', '\\Flagged' ] + ); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Check that the event was updated on calendar"; + $events = $CalDAV->GetEvents($CalendarId); + $self->assert_equals(1, scalar @$events); + $self->assert_str_equals($uuid, $events->[0]{uid}); + $self->assert_str_equals('An Updated Event', $events->[0]{title}); + $self->assert_str_equals('2021-09-24T14:00:00', $events->[0]{start}); } diff --git a/cassandane/tiny-tests/Sieve/implicit_keep_target_bad b/cassandane/tiny-tests/Sieve/implicit_keep_target_bad index 9b49bd913a..febc417022 100644 --- a/cassandane/tiny-tests/Sieve/implicit_keep_target_bad +++ b/cassandane/tiny-tests/Sieve/implicit_keep_target_bad @@ -2,36 +2,40 @@ use Cassandane::Tiny; sub test_implicit_keep_target_bad - :min_version_3_9 :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_9 : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - my ($res, $errs) = $self->compile_sievec('norequire', <compile_sievec( + 'norequire', <assert_str_equals('failure', $res); - $self->assert_matches(qr/vnd.cyrus.implicit_keep_target extension MUST be enabled/, $errs); - $self->assert_matches(qr/mailboxid extension MUST be enabled/, $errs); - $self->assert_matches(qr/special-use extension MUST be enabled/, $errs); + $self->assert_str_equals('failure', $res); + $self->assert_matches( + qr/vnd.cyrus.implicit_keep_target extension MUST be enabled/, $errs); + $self->assert_matches(qr/mailboxid extension MUST be enabled/, $errs); + $self->assert_matches(qr/special-use extension MUST be enabled/, $errs); - ($res, $errs) = $self->compile_sievec('conflict', <compile_sievec( + 'conflict', <assert_str_equals('failure', $res); - $self->assert_matches(qr/tag :specialuse MUST NOT be used with tag :mailboxid/, $errs); + $self->assert_str_equals('failure', $res); + $self->assert_matches( + qr/tag :specialuse MUST NOT be used with tag :mailboxid/, $errs); - ($res, $errs) = $self->compile_sievec('badsyntax', <compile_sievec( + 'badsyntax', <assert_str_equals('failure', $res); - $self->assert_matches(qr/syntax error/, $errs); + $self->assert_str_equals('failure', $res); + $self->assert_matches(qr/syntax error/, $errs); } diff --git a/cassandane/tiny-tests/Sieve/implicit_keep_target_conditional b/cassandane/tiny-tests/Sieve/implicit_keep_target_conditional index 2b0948b649..98891ed7e4 100644 --- a/cassandane/tiny-tests/Sieve/implicit_keep_target_conditional +++ b/cassandane/tiny-tests/Sieve/implicit_keep_target_conditional @@ -2,22 +2,22 @@ use Cassandane::Tiny; sub test_implicit_keep_target_conditional - :min_version_3_9 :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_9 : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - my $folder1 = "INBOX.foo"; - my $folder2 = "INBOX.bar"; + my $folder1 = "INBOX.foo"; + my $folder2 = "INBOX.bar"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder1) - or die "Cannot create $folder1: $@"; - $imaptalk->create($folder2) - or die "Cannot create $folder2: $@"; + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder1) + or die "Cannot create $folder1: $@"; + $imaptalk->create($folder2) + or die "Cannot create $folder2: $@"; - xlog $self, "Install a script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); - $msg1->set_attribute(uid => 1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); + $msg1->set_attribute(uid => 1); - xlog $self, "Check that the message made it to $folder1"; - $self->{store}->set_folder($folder1); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to $folder1"; + $self->{store}->set_folder($folder1); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Deliver a message"; - my $msg2 = $self->{gen}->generate(subject => "bar"); - $self->{instance}->deliver($msg2); - $msg2->set_attribute(uid => 1); + xlog $self, "Deliver a message"; + my $msg2 = $self->{gen}->generate(subject => "bar"); + $self->{instance}->deliver($msg2); + $msg2->set_attribute(uid => 1); - xlog $self, "Check that the message made it to $folder2"; - $self->{store}->set_folder($folder2); - $self->check_messages({ 1 => $msg2 }, check_guid => 0); + xlog $self, "Check that the message made it to $folder2"; + $self->{store}->set_folder($folder2); + $self->check_messages({ 1 => $msg2 }, check_guid => 0); - xlog $self, "Deliver a message"; - $msg1 = $self->{gen}->generate(subject => "keep"); - $self->{instance}->deliver($msg1); - $msg1->set_attribute(uid => 1); + xlog $self, "Deliver a message"; + $msg1 = $self->{gen}->generate(subject => "keep"); + $self->{instance}->deliver($msg1); + $msg1->set_attribute(uid => 1); - xlog $self, "Deliver a message"; - $msg2 = $self->{gen}->generate(subject => "error"); - $self->{instance}->deliver($msg2); - $msg2->set_attribute(uid => 2); + xlog $self, "Deliver a message"; + $msg2 = $self->{gen}->generate(subject => "error"); + $self->{instance}->deliver($msg2); + $msg2->set_attribute(uid => 2); - xlog $self, "Check that the messages made it to INBOX"; - $self->{store}->set_folder("INBOX"); - $self->check_messages({ 1 => $msg1, 2 => $msg2 }, check_guid => 0); + xlog $self, "Check that the messages made it to INBOX"; + $self->{store}->set_folder("INBOX"); + $self->check_messages({ 1 => $msg1, 2 => $msg2 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/implicit_keep_target_mailboxid b/cassandane/tiny-tests/Sieve/implicit_keep_target_mailboxid index a39feb9ba8..eb4e3487eb 100644 --- a/cassandane/tiny-tests/Sieve/implicit_keep_target_mailboxid +++ b/cassandane/tiny-tests/Sieve/implicit_keep_target_mailboxid @@ -2,34 +2,34 @@ use Cassandane::Tiny; sub test_implicit_keep_target_mailboxid - :min_version_3_9 :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_9 : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - my $folder = "INBOX.foo"; + my $folder = "INBOX.foo"; - xlog $self, "Create folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; + xlog $self, "Create folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; - xlog $self, "Get folder id"; - my $res = $imaptalk->status($folder, ['mailboxid']); - my $folderid = $res->{mailboxid}[0]; + xlog $self, "Get folder id"; + my $res = $imaptalk->status($folder, ['mailboxid']); + my $folderid = $res->{mailboxid}[0]; - xlog $self, "Install a script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to $folder"; - $self->{store}->set_folder($folder); - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to $folder"; + $self->{store}->set_folder($folder); + $self->check_messages({ 1 => $msg }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/implicit_keep_target_mailboxname b/cassandane/tiny-tests/Sieve/implicit_keep_target_mailboxname index 06f7e7aed3..08f0a263b8 100644 --- a/cassandane/tiny-tests/Sieve/implicit_keep_target_mailboxname +++ b/cassandane/tiny-tests/Sieve/implicit_keep_target_mailboxname @@ -2,30 +2,30 @@ use Cassandane::Tiny; sub test_implicit_keep_target_mailboxname - :min_version_3_9 :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_9 : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - my $folder = "INBOX.foo"; + my $folder = "INBOX.foo"; - xlog $self, "Create folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; + xlog $self, "Create folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; - xlog $self, "Install a script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to $folder"; - $self->{store}->set_folder($folder); - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to $folder"; + $self->{store}->set_folder($folder); + $self->check_messages({ 1 => $msg }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/implicit_keep_target_none b/cassandane/tiny-tests/Sieve/implicit_keep_target_none index 709a4e7a62..06151adf62 100644 --- a/cassandane/tiny-tests/Sieve/implicit_keep_target_none +++ b/cassandane/tiny-tests/Sieve/implicit_keep_target_none @@ -2,50 +2,50 @@ use Cassandane::Tiny; sub test_implicit_keep_target_none - :min_version_3_9 :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_9 : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - my $folder = "INBOX.foo"; + my $folder = "INBOX.foo"; - xlog $self, "Create folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; + xlog $self, "Create folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; - xlog $self, "Allow plus address delivery"; - $imaptalk->setacl($folder, 'anyone' => 'p'); - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Allow plus address delivery"; + $imaptalk->setacl($folder, 'anyone' => 'p'); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "Install a script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Implicit"); - $self->{instance}->deliver($msg); - - xlog $self, "Check that the message made it to INBOX"; - $self->{store}->set_folder('INBOX'); - $self->{store}->set_fetch_attributes(qw(uid flags)); - $msg->set_attribute(uid => 1); - $msg->set_attribute(flags => [ '\\Recent' ]); - $self->check_messages({ 1 => $msg }, check_guid => 0); - - xlog $self, "Deliver a message to plus address"; - $msg = $self->{gen}->generate(subject => "Explicit"); - $self->{instance}->deliver($msg, user => "cassandane+foo"); - - xlog $self, "Check that the message made it to $folder"; - $self->{store}->set_folder($folder); - $msg->set_attribute(uid => 1); - $msg->set_attribute(flags => [ '\\Recent', '\\Flagged']); - $self->check_messages({ 1 => $msg }, check_guid => 0); + ); + + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Implicit"); + $self->{instance}->deliver($msg); + + xlog $self, "Check that the message made it to INBOX"; + $self->{store}->set_folder('INBOX'); + $self->{store}->set_fetch_attributes(qw(uid flags)); + $msg->set_attribute(uid => 1); + $msg->set_attribute(flags => ['\\Recent']); + $self->check_messages({ 1 => $msg }, check_guid => 0); + + xlog $self, "Deliver a message to plus address"; + $msg = $self->{gen}->generate(subject => "Explicit"); + $self->{instance}->deliver($msg, user => "cassandane+foo"); + + xlog $self, "Check that the message made it to $folder"; + $self->{store}->set_folder($folder); + $msg->set_attribute(uid => 1); + $msg->set_attribute(flags => [ '\\Recent', '\\Flagged' ]); + $self->check_messages({ 1 => $msg }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/implicit_keep_target_specialuse b/cassandane/tiny-tests/Sieve/implicit_keep_target_specialuse index 0bd253c07c..eb43e402b6 100644 --- a/cassandane/tiny-tests/Sieve/implicit_keep_target_specialuse +++ b/cassandane/tiny-tests/Sieve/implicit_keep_target_specialuse @@ -2,30 +2,30 @@ use Cassandane::Tiny; sub test_implicit_keep_target_specialuse - :min_version_3_9 :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_9 : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - my $folder = "INBOX.foo"; + my $folder = "INBOX.foo"; - xlog $self, "Create folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder, "(use (\\Important))") - or die "Cannot create $folder: $@"; + xlog $self, "Create folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder, "(use (\\Important))") + or die "Cannot create $folder: $@"; - xlog $self, "Install a script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to $folder"; - $self->{store}->set_folder($folder); - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to $folder"; + $self->{store}->set_folder($folder); + $self->check_messages({ 1 => $msg }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/implicit_keep_target_stop b/cassandane/tiny-tests/Sieve/implicit_keep_target_stop index 8d9c209309..dbaf5d3ed3 100644 --- a/cassandane/tiny-tests/Sieve/implicit_keep_target_stop +++ b/cassandane/tiny-tests/Sieve/implicit_keep_target_stop @@ -2,31 +2,31 @@ use Cassandane::Tiny; sub test_implicit_keep_target_stop - :min_version_3_9 :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_9 : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - my $folder = "INBOX.foo"; + my $folder = "INBOX.foo"; - xlog $self, "Create folder"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; + xlog $self, "Create folder"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; - xlog $self, "Install a script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it to $folder"; - $self->{store}->set_folder($folder); - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to $folder"; + $self->{store}->set_folder($folder); + $self->check_messages({ 1 => $msg }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/include_cancel_implicit_keep b/cassandane/tiny-tests/Sieve/include_cancel_implicit_keep index 88bc997ed9..3ebb997e0b 100644 --- a/cassandane/tiny-tests/Sieve/include_cancel_implicit_keep +++ b/cassandane/tiny-tests/Sieve/include_cancel_implicit_keep @@ -2,29 +2,31 @@ use Cassandane::Tiny; sub test_include_cancel_implicit_keep - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install a script which includes another"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{instance}->install_sieve_script(<{instance}->install_sieve_script( + <'foo'); + , name => 'foo' + ); - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that no messages are in INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({}, check_guid => 0); + xlog $self, "Check that no messages are in INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({}, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/include_fileinto_implicit_keep_flags b/cassandane/tiny-tests/Sieve/include_fileinto_implicit_keep_flags index bc2bb5d648..f818828618 100644 --- a/cassandane/tiny-tests/Sieve/include_fileinto_implicit_keep_flags +++ b/cassandane/tiny-tests/Sieve/include_fileinto_implicit_keep_flags @@ -2,35 +2,37 @@ use Cassandane::Tiny; sub test_include_fileinto_implicit_keep_flags - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - $self->{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Install a script which includes another"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{instance}->install_sieve_script(<{instance}->install_sieve_script( + <'foo'); + , name => 'foo' + ); - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that only last copy of the message made it to INBOX"; - $self->{store}->set_folder('INBOX'); - $msg->set_attribute(flags => [ '\\Recent', '\\Flagged' ]); - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that only last copy of the message made it to INBOX"; + $self->{store}->set_folder('INBOX'); + $msg->set_attribute(flags => [ '\\Recent', '\\Flagged' ]); + $self->check_messages({ 1 => $msg }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/jmapquery b/cassandane/tiny-tests/Sieve/jmapquery index f2f3f77177..267d71709d 100644 --- a/cassandane/tiny-tests/Sieve/jmapquery +++ b/cassandane/tiny-tests/Sieve/jmapquery @@ -2,14 +2,14 @@ use Cassandane::Tiny; sub test_jmapquery - :min_version_3_3 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $imap = $self->{store}->get_client(); - $imap->create("INBOX.matches") or die; + my $imap = $self->{store}->get_client(); + $imap->create("INBOX.matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -29,9 +29,9 @@ if fileinto "INBOX.matches"; } EOF - ); + ); - my $body = << 'EOF'; + my $body = << 'EOF'; --047d7b33dd729737fe04d3bde348 Content-Type: text/plain; charset=UTF-8 @@ -45,30 +45,30 @@ abc= --047d7b33dd729737fe04d3bde348-- EOF - $body =~ s/\r?\n/\r\n/gs; + $body =~ s/\r?\n/\r\n/gs; - xlog $self, "Deliver a matching message"; - my $msg1 = $self->{gen}->generate( - subject => "Message 1", - extra_headers => [['X-Delivered-To', 'xxx@yyy.zzz']], - mime_type => "multipart/mixed", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body, - ); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a matching message"; + my $msg1 = $self->{gen}->generate( + subject => "Message 1", + extra_headers => [ [ 'X-Delivered-To', 'xxx@yyy.zzz' ] ], + mime_type => "multipart/mixed", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body, + ); + $self->{instance}->deliver($msg1); - $self->{store}->set_fetch_attributes('uid'); + $self->{store}->set_fetch_attributes('uid'); - xlog "Assert that message got moved into INBOX.matches"; - $self->{store}->set_folder('INBOX.matches'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog "Assert that message got moved into INBOX.matches"; + $self->{store}->set_folder('INBOX.matches'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Deliver a non-matching message"; - my $msg2 = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg2); - $msg2->set_attribute(uid => 1); + xlog $self, "Deliver a non-matching message"; + my $msg2 = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg2); + $msg2->set_attribute(uid => 1); - xlog "Assert that message got moved into INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg2 }, check_guid => 0); + xlog "Assert that message got moved into INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg2 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/jmapquery_attachmentindexing_body b/cassandane/tiny-tests/Sieve/jmapquery_attachmentindexing_body index 31651ea83d..a3966ffd60 100644 --- a/cassandane/tiny-tests/Sieve/jmapquery_attachmentindexing_body +++ b/cassandane/tiny-tests/Sieve/jmapquery_attachmentindexing_body @@ -2,42 +2,46 @@ use Cassandane::Tiny; sub test_jmapquery_attachmentindexing_body - :min_version_3_3 :needs_component_sieve :needs_component_jmap - :needs_search_xapian :SearchAttachmentExtractor :JMAPExtensions -{ - # Assert that a 'body' filter in a Sieve script does NOT - # cause the attachment indexer to get called. + : min_version_3_3 : needs_component_sieve : needs_component_jmap + : needs_search_xapian : SearchAttachmentExtractor : JMAPExtensions { + # Assert that a 'body' filter in a Sieve script does NOT + # cause the attachment indexer to get called. - my ($self) = @_; + my ($self) = @_; - my $imap = $self->{store}->get_client(); - my $instance = $self->{instance}; + my $imap = $self->{store}->get_client(); + my $instance = $self->{instance}; - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); - my (undef, $filename) = tempfile('tmpXXXXXX', OPEN => 0, - DIR => $instance->{basedir} . "/tmp"); + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); + my (undef, $filename) = tempfile( + 'tmpXXXXXX', + OPEN => 0, + DIR => $instance->{basedir} . "/tmp" + ); - xlog "Start a dummy extractor server"; - my $handler = sub { - my ($conn, $req) = @_; - open HANDLE, ">$filename" || die; - print HANDLE "$req->method"; - close HANDLE; - if ($req->method eq 'HEAD') { - my $res = HTTP::Response->new(204); - $res->content(""); - $conn->send_response($res); - } else { - my $res = HTTP::Response->new(200); - $res->content("data"); - $conn->send_response($res); - } - }; - $instance->start_httpd($handler, $uri->port()); + xlog "Start a dummy extractor server"; + my $handler = sub { + my ($conn, $req) = @_; + open HANDLE, ">$filename" || die; + print HANDLE "$req->method"; + close HANDLE; + if ($req->method eq 'HEAD') { + my $res = HTTP::Response->new(204); + $res->content(""); + $conn->send_response($res); + } else { + my $res = HTTP::Response->new(200); + $res->content("data"); + $conn->send_response($res); + } + }; + $instance->start_httpd($handler, $uri->port()); - xlog "Install JMAP sieve script"; - $imap->create("INBOX.matches") or die; - $instance->install_sieve_script(<<'EOF' + xlog "Install JMAP sieve script"; + $imap->create("INBOX.matches") or die; + $instance->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -51,10 +55,10 @@ if fileinto "INBOX.matches"; } EOF - ); + ); - xlog "Deliver a message with attachment"; - my $body = << 'EOF'; + xlog "Deliver a message with attachment"; + my $body = << 'EOF'; --047d7b33dd729737fe04d3bde348 Content-Type: text/plain; charset=UTF-8 @@ -67,19 +71,19 @@ data --047d7b33dd729737fe04d3bde348-- EOF - $body =~ s/\r?\n/\r\n/gs; - my $msg1 = $self->{gen}->generate( - subject => "Message 1", - mime_type => "multipart/mixed", - mime_boundary => "047d7b33dd729737fe04d3bde348", - body => $body, - ); - $instance->deliver($msg1); + $body =~ s/\r?\n/\r\n/gs; + my $msg1 = $self->{gen}->generate( + subject => "Message 1", + mime_type => "multipart/mixed", + mime_boundary => "047d7b33dd729737fe04d3bde348", + body => $body, + ); + $instance->deliver($msg1); - xlog "Assert that extractor did NOT get called"; - $self->assert_not_file_test($filename); + xlog "Assert that extractor did NOT get called"; + $self->assert_not_file_test($filename); - xlog "Assert that message got moved into INBOX.matches"; - $self->{store}->set_folder('INBOX.matches'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog "Assert that message got moved into INBOX.matches"; + $self->{store}->set_folder('INBOX.matches'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/jmapquery_attachmentindexing_text b/cassandane/tiny-tests/Sieve/jmapquery_attachmentindexing_text index ea63e8fb3e..0ebdfd832b 100644 --- a/cassandane/tiny-tests/Sieve/jmapquery_attachmentindexing_text +++ b/cassandane/tiny-tests/Sieve/jmapquery_attachmentindexing_text @@ -2,37 +2,38 @@ use Cassandane::Tiny; sub test_jmapquery_attachmentindexing_text - :min_version_3_9 :needs_component_sieve :needs_component_jmap - :needs_search_xapian :SearchAttachmentExtractor :JMAPExtensions -{ - # Assert that a 'text' filter in a Sieve script DOES - # cause the attachment indexer to get called. + : min_version_3_9 : needs_component_sieve : needs_component_jmap + : needs_search_xapian : SearchAttachmentExtractor : JMAPExtensions { + # Assert that a 'text' filter in a Sieve script DOES + # cause the attachment indexer to get called. - my ($self) = @_; - my $jmap = $self->{jmap}; - my $imap = $self->{store}->get_client(); - my $instance = $self->{instance}; + my ($self) = @_; + my $jmap = $self->{jmap}; + my $imap = $self->{store}->get_client(); + my $instance = $self->{instance}; - my $uri = URI->new($instance->{config}->get('search_attachment_extractor_url')); + my $uri + = URI->new($instance->{config}->get('search_attachment_extractor_url')); - # Start a dummy extractor server. - my $handler = sub { - my ($conn, $req) = @_; - if ($req->method eq 'HEAD') { - my $res = HTTP::Response->new(204); - $res->content(""); - $conn->send_response($res); - } else { - my $res = HTTP::Response->new(200); - $res->content("testattach"); - $conn->send_response($res); - } - }; - $instance->start_httpd($handler, $uri->port()); + # Start a dummy extractor server. + my $handler = sub { + my ($conn, $req) = @_; + if ($req->method eq 'HEAD') { + my $res = HTTP::Response->new(204); + $res->content(""); + $conn->send_response($res); + } else { + my $res = HTTP::Response->new(200); + $res->content("testattach"); + $conn->send_response($res); + } + }; + $instance->start_httpd($handler, $uri->port()); - $imap->create("matches") or die; + $imap->create("matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -58,9 +59,9 @@ if fileinto "matches"; } EOF - ); + ); - my $mime = <<'EOF'; + my $mime = <<'EOF'; From: from@local To: to@local Subject: testsubject @@ -83,14 +84,14 @@ dGVzdGF0dGFjaG1lbnQK --c4683f7a320d4d20902b000486fbdf9b-- EOF - $mime =~ s/\r?\n/\r\n/gs; + $mime =~ s/\r?\n/\r\n/gs; - my $msg = Cassandane::Message->new(); - $msg->set_lines(split /\n/, $mime); - $self->{instance}->deliver($msg); + my $msg = Cassandane::Message->new(); + $msg->set_lines(split /\n/, $mime); + $self->{instance}->deliver($msg); - xlog "Assert that message got moved into INBOX.matches"; - $imap->select('matches'); - $self->assert_num_equals(1, $imap->get_response_code('exists')); - $imap->unselect(); + xlog "Assert that message got moved into INBOX.matches"; + $imap->select('matches'); + $self->assert_num_equals(1, $imap->get_response_code('exists')); + $imap->unselect(); } diff --git a/cassandane/tiny-tests/Sieve/jmapquery_missing_in_reply_to b/cassandane/tiny-tests/Sieve/jmapquery_missing_in_reply_to index 1b05bb46e5..59cd9a91f5 100644 --- a/cassandane/tiny-tests/Sieve/jmapquery_missing_in_reply_to +++ b/cassandane/tiny-tests/Sieve/jmapquery_missing_in_reply_to @@ -2,14 +2,14 @@ use Cassandane::Tiny; sub test_jmapquery_missing_in_reply_to - :min_version_3_9 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_9 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $imap = $self->{store}->get_client(); - $imap->create("INBOX.matches") or die; + my $imap = $self->{store}->get_client(); + $imap->create("INBOX.matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if jmapquery text: @@ -21,9 +21,9 @@ if fileinto "INBOX.matches"; } EOF - ); + ); - my $body = << 'EOF'; + my $body = << 'EOF'; --047d7b33dd729737fe04d3bde348 Content-Type: text/plain; charset=UTF-8 @@ -37,16 +37,16 @@ abc= --047d7b33dd729737fe04d3bde348-- EOF - $body =~ s/\r?\n/\r\n/gs; + $body =~ s/\r?\n/\r\n/gs; - xlog $self, "Deliver a message without in-reply-to"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); - $msg1->set_attribute(uid => 1); + xlog $self, "Deliver a message without in-reply-to"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); + $msg1->set_attribute(uid => 1); - # better not have just crashed! + # better not have just crashed! - xlog "Assert that message got moved into INBOX.matches"; - $self->{store}->set_folder('INBOX.matches'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog "Assert that message got moved into INBOX.matches"; + $self->{store}->set_folder('INBOX.matches'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/jmapquery_multiple_to_cross_domain b/cassandane/tiny-tests/Sieve/jmapquery_multiple_to_cross_domain index cd56cfa9bb..bdc555b60f 100644 --- a/cassandane/tiny-tests/Sieve/jmapquery_multiple_to_cross_domain +++ b/cassandane/tiny-tests/Sieve/jmapquery_multiple_to_cross_domain @@ -2,14 +2,14 @@ use Cassandane::Tiny; sub test_jmapquery_multiple_to_cross_domain - :min_version_3_3 :needs_component_sieve :needs_component_jmap -{ - my ($self) = @_; + : min_version_3_3 : needs_component_sieve : needs_component_jmap { + my ($self) = @_; - my $imap = $self->{store}->get_client(); - $imap->create("INBOX.matches") or die; + my $imap = $self->{store}->get_client(); + $imap->create("INBOX.matches") or die; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["x-cyrus-jmapquery", "x-cyrus-log", "variables", "fileinto"]; if allof( not string :is "${stop}" "Y", @@ -24,34 +24,31 @@ if fileinto "INBOX.matches"; } EOF - ); - - xlog $self, "Deliver a matching message"; - my $msg1 = $self->{gen}->generate( - subject => "Message 1", - extra_headers => [['To', 'foo@example.net'], - ['X-Foo', 'bar'] - ], - ); - $self->{instance}->deliver($msg1); - - $self->{store}->set_fetch_attributes('uid'); - - xlog "Assert that message got moved into INBOX.matches"; - $self->{store}->set_folder('INBOX.matches'); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); - - xlog $self, "Deliver a non-matching message"; - my $msg2 = $self->{gen}->generate( - subject => "Message 2", - extra_headers => [['To', 'foo@example.com, bar@example.net'], - ['X-Foo', 'bar'] - ], - ); - $self->{instance}->deliver($msg2); - $msg2->set_attribute(uid => 1); - - xlog "Assert that message got moved into INBOX"; - $self->{store}->set_folder('INBOX'); - $self->check_messages({ 1 => $msg2 }, check_guid => 0); + ); + + xlog $self, "Deliver a matching message"; + my $msg1 = $self->{gen}->generate( + subject => "Message 1", + extra_headers => [ [ 'To', 'foo@example.net' ], [ 'X-Foo', 'bar' ] ], + ); + $self->{instance}->deliver($msg1); + + $self->{store}->set_fetch_attributes('uid'); + + xlog "Assert that message got moved into INBOX.matches"; + $self->{store}->set_folder('INBOX.matches'); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); + + xlog $self, "Deliver a non-matching message"; + my $msg2 = $self->{gen}->generate( + subject => "Message 2", + extra_headers => + [ [ 'To', 'foo@example.com, bar@example.net' ], [ 'X-Foo', 'bar' ] ], + ); + $self->{instance}->deliver($msg2); + $msg2->set_attribute(uid => 1); + + xlog "Assert that message got moved into INBOX"; + $self->{store}->set_folder('INBOX'); + $self->check_messages({ 1 => $msg2 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/nested_tests_and_discard b/cassandane/tiny-tests/Sieve/nested_tests_and_discard index 62c6be2e0f..6ac184e529 100644 --- a/cassandane/tiny-tests/Sieve/nested_tests_and_discard +++ b/cassandane/tiny-tests/Sieve/nested_tests_and_discard @@ -2,12 +2,12 @@ use Cassandane::Tiny; sub test_nested_tests_and_discard - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install a sieve script discarding all mail"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Attempt to deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - # should fail to deliver and NOT appear in INBOX - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - $self->assert_num_equals(0, $imaptalk->get_response_code('exists')); + # should fail to deliver and NOT appear in INBOX + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + $self->assert_num_equals(0, $imaptalk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/plus_address_mark_read b/cassandane/tiny-tests/Sieve/plus_address_mark_read index 5b37503df8..57115dbb62 100644 --- a/cassandane/tiny-tests/Sieve/plus_address_mark_read +++ b/cassandane/tiny-tests/Sieve/plus_address_mark_read @@ -2,33 +2,33 @@ use Cassandane::Tiny; sub test_plus_address_mark_read - :needs_component_sieve :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : NoAltNameSpace { + my ($self) = @_; - my $folder = "INBOX.foo"; + my $folder = "INBOX.foo"; - xlog $self, "Create folders"; - my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($folder) - or die "Cannot create $folder: $@"; + xlog $self, "Create folders"; + my $imaptalk = $self->{store}->get_client(); + $imaptalk->create($folder) + or die "Cannot create $folder: $@"; - $imaptalk->setacl($folder, 'anyone' => 'p'); + $imaptalk->setacl($folder, 'anyone' => 'p'); - xlog $self, "Install a script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg, user => "cassandane+foo"); + xlog $self, "Deliver a message to plus address"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg, user => "cassandane+foo"); - xlog $self, "Check that the message made it to $folder"; - $self->{store}->set_folder($folder); - $self->{store}->set_fetch_attributes(qw(uid flags)); - $msg->set_attribute(flags => [ '\\Recent', '\\Seen']); - $self->check_messages({ 1 => $msg }, check_guid => 0); + xlog $self, "Check that the message made it to $folder"; + $self->{store}->set_folder($folder); + $self->{store}->set_fetch_attributes(qw(uid flags)); + $msg->set_attribute(flags => [ '\\Recent', '\\Seen' ]); + $self->check_messages({ 1 => $msg }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/redirect_address_with_phrase b/cassandane/tiny-tests/Sieve/redirect_address_with_phrase index 5b9269ae44..2f96928d4f 100644 --- a/cassandane/tiny-tests/Sieve/redirect_address_with_phrase +++ b/cassandane/tiny-tests/Sieve/redirect_address_with_phrase @@ -2,26 +2,26 @@ use Cassandane::Tiny; sub test_redirect_address_with_phrase - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Install a script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <"; EOF - ); + ); - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); - # Verify that message was redirected (no RCPT TO error) - $self->assert_syslog_does_not_match($self->{instance}, - qr/RCPT TO: code=553 text=5.1.1/); + # Verify that message was redirected (no RCPT TO error) + $self->assert_syslog_does_not_match($self->{instance}, + qr/RCPT TO: code=553 text=5.1.1/); - xlog $self, "Make sure that message is NOT in INBOX (due to runtime error)"; - my $talk = $self->{store}->get_client(); - $talk->select("INBOX"); - $self->assert_num_equals(0, $talk->get_response_code('exists')); + xlog $self, "Make sure that message is NOT in INBOX (due to runtime error)"; + my $talk = $self->{store}->get_client(); + $talk->select("INBOX"); + $self->assert_num_equals(0, $talk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/remove_itip_on_jmap_update b/cassandane/tiny-tests/Sieve/remove_itip_on_jmap_update index aad0e2fe23..ac8ae1165f 100644 --- a/cassandane/tiny-tests/Sieve/remove_itip_on_jmap_update +++ b/cassandane/tiny-tests/Sieve/remove_itip_on_jmap_update @@ -2,19 +2,20 @@ use Cassandane::Tiny; sub test_remove_itip_on_jmap_update - :needs_component_sieve :needs_component_httpd :needs_component_jmap :min_version_3_5 - :want_service_http -{ - my ($self) = @_; + : needs_component_sieve : needs_component_httpd : needs_component_jmap : + min_version_3_5 + : want_service_http { + my ($self) = @_; - my $jmap = $self->{jmap}; + my $jmap = $self->{jmap}; - xlog $self, "Create calendar user"; - my $CalDAV = $self->{caldav}; - my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; + xlog $self, "Create calendar user"; + my $CalDAV = $self->{caldav}; + my $uuid = "6de280c9-edff-4019-8ebd-cfebc73f8201"; - xlog $self, "Install a sieve script to process iMIP"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < To: Cassandane @@ -54,12 +55,11 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite"; - my $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite"; + my $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - - $imip = < To: Cassandane @@ -88,31 +88,34 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP update"; - $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); - - xlog $self, "Check that 2 iTIP messages made it to Scheduling Inbox"; - my $events = $CalDAV->GetEvents('Inbox'); - $self->assert_equals(2, scalar @$events); - - my $res = $jmap->CallMethods([['CalendarEvent/get', {}, "R1"]]); - my $id = $res->[0][1]{list}[0]{id}; - - xlog $self, "Update participationStatus"; - $res = $jmap->CallMethods([['CalendarEvent/set', - {update => - {$id => - { "participants/cassandane/participationStatus" => "accepted"}}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "Check that iTIP messages were removed from Scheduling Inbox"; - $events = $CalDAV->GetEvents('Inbox'); - $self->assert_equals(0, scalar @$events); - - - $imip = <new(raw => $imip); + $self->{instance}->deliver($msg); + + xlog $self, "Check that 2 iTIP messages made it to Scheduling Inbox"; + my $events = $CalDAV->GetEvents('Inbox'); + $self->assert_equals(2, scalar @$events); + + my $res = $jmap->CallMethods([ [ 'CalendarEvent/get', {}, "R1" ] ]); + my $id = $res->[0][1]{list}[0]{id}; + + xlog $self, "Update participationStatus"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id => { "participants/cassandane/participationStatus" => "accepted" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "Check that iTIP messages were removed from Scheduling Inbox"; + $events = $CalDAV->GetEvents('Inbox'); + $self->assert_equals(0, scalar @$events); + + $imip = < To: Cassandane @@ -138,18 +141,19 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP cancel"; - $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP cancel"; + $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - xlog $self, "Delete canceled event"; - $res = $jmap->CallMethods([['CalendarEvent/set', {destroy => [$id]}, "R2"]]); + xlog $self, "Delete canceled event"; + $res = $jmap->CallMethods( + [ [ 'CalendarEvent/set', { destroy => [$id] }, "R2" ] ]); - xlog $self, "Check that iTIP messages were removed from Scheduling Inbox"; - $events = $CalDAV->GetEvents('Inbox'); - $self->assert_equals(0, scalar @$events); + xlog $self, "Check that iTIP messages were removed from Scheduling Inbox"; + $events = $CalDAV->GetEvents('Inbox'); + $self->assert_equals(0, scalar @$events); - $imip = < To: Cassandane @@ -179,11 +183,11 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite for one instance"; - $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); + xlog $self, "Deliver iMIP invite for one instance"; + $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); - $imip = < To: Cassandane @@ -213,39 +217,47 @@ END:VEVENT END:VCALENDAR EOF - xlog $self, "Deliver iMIP invite for another instance"; - $msg = Cassandane::Message->new(raw => $imip); - $self->{instance}->deliver($msg); - - xlog $self, "Check that 2 iTIP messages made it to Scheduling Inbox"; - $events = $CalDAV->GetEvents('Inbox'); - $self->assert_equals(2, scalar @$events); - - $res = $jmap->CallMethods([['CalendarEvent/get', {}, "R1"]]); - $id = $res->[0][1]{list}[0]{id}; - my $id2 = $res->[0][1]{list}[1]{id}; - - xlog $self, "Update participationStatus on one instance"; - $res = $jmap->CallMethods([['CalendarEvent/set', - {update => - {$id => - { "participants/cassandane/participationStatus" => "accepted"}}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "Check that one iTIP message was removed from Scheduling Inbox"; - $events = $CalDAV->GetEvents('Inbox'); - $self->assert_equals(1, scalar @$events); - - xlog $self, "Update participationStatus on the other instance"; - $res = $jmap->CallMethods([['CalendarEvent/set', - {update => - {$id2 => - { "participants/cassandane/participationStatus" => "accepted"}}}, - "R1"]]); - $self->assert_not_null($res->[0][1]{updated}); - - xlog $self, "Check that one iTIP message was removed from Scheduling Inbox"; - $events = $CalDAV->GetEvents('Inbox'); - $self->assert_equals(0, scalar @$events); + xlog $self, "Deliver iMIP invite for another instance"; + $msg = Cassandane::Message->new(raw => $imip); + $self->{instance}->deliver($msg); + + xlog $self, "Check that 2 iTIP messages made it to Scheduling Inbox"; + $events = $CalDAV->GetEvents('Inbox'); + $self->assert_equals(2, scalar @$events); + + $res = $jmap->CallMethods([ [ 'CalendarEvent/get', {}, "R1" ] ]); + $id = $res->[0][1]{list}[0]{id}; + my $id2 = $res->[0][1]{list}[1]{id}; + + xlog $self, "Update participationStatus on one instance"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id => { "participants/cassandane/participationStatus" => "accepted" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "Check that one iTIP message was removed from Scheduling Inbox"; + $events = $CalDAV->GetEvents('Inbox'); + $self->assert_equals(1, scalar @$events); + + xlog $self, "Update participationStatus on the other instance"; + $res = $jmap->CallMethods([ [ + 'CalendarEvent/set', + { + update => { + $id2 => { "participants/cassandane/participationStatus" => "accepted" } + } + }, + "R1" + ] ]); + $self->assert_not_null($res->[0][1]{updated}); + + xlog $self, "Check that one iTIP message was removed from Scheduling Inbox"; + $events = $CalDAV->GetEvents('Inbox'); + $self->assert_equals(0, scalar @$events); } diff --git a/cassandane/tiny-tests/Sieve/rfc5490_create b/cassandane/tiny-tests/Sieve/rfc5490_create index f8e5b429a8..d784332073 100644 --- a/cassandane/tiny-tests/Sieve/rfc5490_create +++ b/cassandane/tiny-tests/Sieve/rfc5490_create @@ -2,53 +2,51 @@ use Cassandane::Tiny; sub test_rfc5490_create - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"fileinto :create\" syntax"; + xlog $self, "Testing the \"fileinto :create\" syntax"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - my $scriptname = 'lazySusan'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'quinoa', filedto => $hitfolder }, - { subject => 'QUINOA', filedto => $hitfolder }, - { subject => 'Quinoa', filedto => $hitfolder }, - { subject => 'QuinoA', filedto => $hitfolder }, - { subject => 'qUINOa', filedto => $hitfolder }, - { subject => 'selvage', filedto => $missfolder }, - ); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - foreach my $case (@cases) - { - xlog $self, "Deliver a message with subject \"$case->{subject}\""; - my $msg = $self->{gen}->generate(subject => $case->{subject}); - $msg->set_attribute(uid => $uid{$case->{filedto}}); - $uid{$case->{filedto}}++; - $self->{instance}->deliver($msg); - $exp{$case->{filedto}}->{$case->{subject}} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + my @cases = ( + { subject => 'quinoa', filedto => $hitfolder }, + { subject => 'QUINOA', filedto => $hitfolder }, + { subject => 'Quinoa', filedto => $hitfolder }, + { subject => 'QuinoA', filedto => $hitfolder }, + { subject => 'qUINOa', filedto => $hitfolder }, + { subject => 'selvage', filedto => $missfolder }, + ); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + foreach my $case (@cases) { + xlog $self, "Deliver a message with subject \"$case->{subject}\""; + my $msg = $self->{gen}->generate(subject => $case->{subject}); + $msg->set_attribute(uid => $uid{ $case->{filedto} }); + $uid{ $case->{filedto} }++; + $self->{instance}->deliver($msg); + $exp{ $case->{filedto} }->{ $case->{subject} } = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/rfc5490_mailboxexists b/cassandane/tiny-tests/Sieve/rfc5490_mailboxexists index 05aaf99249..f95534f5e8 100644 --- a/cassandane/tiny-tests/Sieve/rfc5490_mailboxexists +++ b/cassandane/tiny-tests/Sieve/rfc5490_mailboxexists @@ -2,58 +2,57 @@ use Cassandane::Tiny; sub test_rfc5490_mailboxexists - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"mailboxexists\" test"; + xlog $self, "Testing the \"mailboxexists\" test"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $testfolder = "INBOX.testfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $testfolder = "INBOX.testfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } - - xlog $self, "Create the test folder"; - $talk->create($testfolder); - - xlog $self, "Deliver a message now that the folder exists"; - { - my $msg = $self->{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + $talk->create($hitfolder); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } + + xlog $self, "Create the test folder"; + $talk->create($testfolder); + + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/rfc5490_mailboxexists_variables b/cassandane/tiny-tests/Sieve/rfc5490_mailboxexists_variables index 54b6f41252..f94fdf1caf 100644 --- a/cassandane/tiny-tests/Sieve/rfc5490_mailboxexists_variables +++ b/cassandane/tiny-tests/Sieve/rfc5490_mailboxexists_variables @@ -2,22 +2,22 @@ use Cassandane::Tiny; sub test_rfc5490_mailboxexists_variables - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"mailboxexists\" test with variables"; + xlog $self, "Testing the \"mailboxexists\" test with variables"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $testfolder = "INBOX.testfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $testfolder = "INBOX.testfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } - - xlog $self, "Create the test folder"; - $talk->create($testfolder); - - xlog $self, "Deliver a message now that the folder exists"; - { - my $msg = $self->{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + $talk->create($hitfolder); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } + + xlog $self, "Create the test folder"; + $talk->create($testfolder); + + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/rfc5490_metadata b/cassandane/tiny-tests/Sieve/rfc5490_metadata index 9a5a5b1394..3d69e41299 100644 --- a/cassandane/tiny-tests/Sieve/rfc5490_metadata +++ b/cassandane/tiny-tests/Sieve/rfc5490_metadata @@ -2,56 +2,55 @@ use Cassandane::Tiny; sub test_rfc5490_metadata - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"metadata\" test"; + xlog $self, "Testing the \"metadata\" test"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } - - xlog $self, "Create the annotation"; - $talk->setmetadata("INBOX", "/private/comment", "awesome"); - - xlog $self, "Deliver a message now that the folder exists"; - { - my $msg = $self->{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + $talk->create($hitfolder); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } + + xlog $self, "Create the annotation"; + $talk->setmetadata("INBOX", "/private/comment", "awesome"); + + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/rfc5490_metadata_matches b/cassandane/tiny-tests/Sieve/rfc5490_metadata_matches index 8bb47f79e2..b0aa0ed36b 100644 --- a/cassandane/tiny-tests/Sieve/rfc5490_metadata_matches +++ b/cassandane/tiny-tests/Sieve/rfc5490_metadata_matches @@ -2,59 +2,58 @@ use Cassandane::Tiny; sub test_rfc5490_metadata_matches - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"metadata\" test"; + xlog $self, "Testing the \"metadata\" test"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <setmetadata("INBOX", "/private/comment", "awesomesauce"); - - $talk->create($hitfolder); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg1"} = $msg; - } - - xlog $self, "Create the annotation"; - $talk->setmetadata("INBOX", "/private/comment", "awesome"); - - xlog $self, "Deliver a message now that the folder exists"; - { - my $msg = $self->{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + xlog $self, "Set the initial annotation"; + $talk->setmetadata("INBOX", "/private/comment", "awesomesauce"); + + $talk->create($hitfolder); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg1"} = $msg; + } + + xlog $self, "Create the annotation"; + $talk->setmetadata("INBOX", "/private/comment", "awesome"); + + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/rfc5490_metadataexists b/cassandane/tiny-tests/Sieve/rfc5490_metadataexists index 8cb3d83eda..1839d71f04 100644 --- a/cassandane/tiny-tests/Sieve/rfc5490_metadataexists +++ b/cassandane/tiny-tests/Sieve/rfc5490_metadataexists @@ -2,56 +2,55 @@ use Cassandane::Tiny; sub test_rfc5490_metadataexists - :min_version_3_0 :AnnotationAllowUndefined - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 : AnnotationAllowUndefined + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"metadataexists\" test"; + xlog $self, "Testing the \"metadataexists\" test"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } - - xlog $self, "Create the annotation"; - $talk->setmetadata("INBOX", "/private/magic", "hello"); - - xlog $self, "Deliver a message now that the folder exists"; - { - my $msg = $self->{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + $talk->create($hitfolder); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } + + xlog $self, "Create the annotation"; + $talk->setmetadata("INBOX", "/private/magic", "hello"); + + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/rfc5490_servermetadata b/cassandane/tiny-tests/Sieve/rfc5490_servermetadata index 34e5686e3e..b4ffe290de 100644 --- a/cassandane/tiny-tests/Sieve/rfc5490_servermetadata +++ b/cassandane/tiny-tests/Sieve/rfc5490_servermetadata @@ -2,60 +2,59 @@ use Cassandane::Tiny; sub test_rfc5490_servermetadata - :min_version_3_0 :AnnotationAllowUndefined - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 : AnnotationAllowUndefined + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"metadata\" test"; + xlog $self, "Testing the \"metadata\" test"; - my $talk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); - - # have a value - $admintalk->setmetadata("", "/shared/magic", "awesomesauce"); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } - - xlog $self, "Create the annotation"; - $admintalk->setmetadata("", "/shared/magic", "awesome"); - - xlog $self, "Deliver a message now that the folder exists"; - { - my $msg = $self->{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + $talk->create($hitfolder); + + # have a value + $admintalk->setmetadata("", "/shared/magic", "awesomesauce"); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } + + xlog $self, "Create the annotation"; + $admintalk->setmetadata("", "/shared/magic", "awesome"); + + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/rfc5490_servermetadataexists b/cassandane/tiny-tests/Sieve/rfc5490_servermetadataexists index 98150979b7..ee3a3f0fb9 100644 --- a/cassandane/tiny-tests/Sieve/rfc5490_servermetadataexists +++ b/cassandane/tiny-tests/Sieve/rfc5490_servermetadataexists @@ -2,58 +2,57 @@ use Cassandane::Tiny; sub test_rfc5490_servermetadataexists - :min_version_3_0 :AnnotationAllowUndefined - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 : AnnotationAllowUndefined + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \"servermetadataexists\" test"; + xlog $self, "Testing the \"servermetadataexists\" test"; - my $talk = $self->{store}->get_client(); - my $admintalk = $self->{adminstore}->get_client(); + my $talk = $self->{store}->get_client(); + my $admintalk = $self->{adminstore}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <setmetadata("", "/shared/magic", "foo"); - $talk->create($hitfolder); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } - - xlog $self, "Create the annotation"; - $admintalk->setmetadata("", "/shared/moo", "hello"); - - xlog $self, "Deliver a message now that the folder exists"; - { - my $msg = $self->{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + $admintalk->setmetadata("", "/shared/magic", "foo"); + $talk->create($hitfolder); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } + + xlog $self, "Create the annotation"; + $admintalk->setmetadata("", "/shared/moo", "hello"); + + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/sieve_setflag b/cassandane/tiny-tests/Sieve/sieve_setflag index 913ca1375e..984a89130b 100644 --- a/cassandane/tiny-tests/Sieve/sieve_setflag +++ b/cassandane/tiny-tests/Sieve/sieve_setflag @@ -2,42 +2,43 @@ use Cassandane::Tiny; sub test_sieve_setflag - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Actually create the target folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Actually create the target folder"; + my $imaptalk = $self->{store}->get_client(); - xlog $self, "Install a sieve script filing all mail into a nonexistant folder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + # should go in Folder1 + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - # should go in Folder2 - my $msg2 = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg2); + # should go in Folder2 + my $msg2 = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg2); - # should fail to deliver and wind up in INBOX - my $msg3 = $self->{gen}->generate(subject => "Message 3"); - $self->{instance}->deliver($msg3); + # should fail to deliver and wind up in INBOX + my $msg3 = $self->{gen}->generate(subject => "Message 3"); + $self->{instance}->deliver($msg3); - $imaptalk->unselect(); - $imaptalk->select("INBOX"); - $self->assert_num_equals(3, $imaptalk->get_response_code('exists')); + $imaptalk->unselect(); + $imaptalk->select("INBOX"); + $self->assert_num_equals(3, $imaptalk->get_response_code('exists')); - my @uids = $imaptalk->search('1:*', 'NOT', 'FLAGGED'); + my @uids = $imaptalk->search('1:*', 'NOT', 'FLAGGED'); - $self->assert_num_equals(2, scalar(@uids)); + $self->assert_num_equals(2, scalar(@uids)); } diff --git a/cassandane/tiny-tests/Sieve/snooze b/cassandane/tiny-tests/Sieve/snooze index a59a9f565f..801e1fea92 100644 --- a/cassandane/tiny-tests/Sieve/snooze +++ b/cassandane/tiny-tests/Sieve/snooze @@ -2,60 +2,60 @@ use Cassandane::Tiny; sub test_snooze - :needs_component_sieve :needs_component_calalarmd - :needs_component_jmap - :min_version_3_1 - :NoAltNameSpace -{ - my ($self) = @_; - - my $snoozed = "INBOX.snoozed"; - my $awakened = "INBOX.awakened"; - - my $localtz = DateTime::TimeZone->new( name => 'local' ); - my $maildate = DateTime->now(time_zone => $localtz); - $maildate->add(DateTime::Duration->new(minutes => 1)); - my $timestr = $maildate->strftime('%T'); - - xlog $self, "Install script"; - $self->{instance}->install_sieve_script(<new(name => 'local'); + my $maildate = DateTime->now(time_zone => $localtz); + $maildate->add(DateTime::Duration->new(minutes => 1)); + my $timestr = $maildate->strftime('%T'); + + xlog $self, "Install script"; + $self->{instance}->install_sieve_script( + <{store}->get_client(); + xlog $self, "Create the awakened folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($awakened) - or die "Cannot create $awakened: $@"; - $self->{store}->set_fetch_attributes(qw(uid flags)); + $imaptalk->create($awakened) + or die "Cannot create $awakened: $@"; + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Deliver a message without having a snoozed folder"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message without having a snoozed folder"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message was delivered to INBOX"; - $self->{store}->set_folder("INBOX"); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message was delivered to INBOX"; + $self->{store}->set_folder("INBOX"); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Create the snoozed folder"; - $imaptalk->create($snoozed, "(USE (\\Snoozed))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "Create the snoozed folder"; + $imaptalk->create($snoozed, "(USE (\\Snoozed))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "Deliver a message"; - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message made it to the snoozed folder"; - $self->{store}->set_folder($snoozed); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the snoozed folder"; + $self->{store}->set_folder($snoozed); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Trigger re-delivery of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 90 ); + xlog $self, "Trigger re-delivery of snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 90); - xlog $self, "Check that the message made it to the awakened folder"; - $self->{store}->set_folder($awakened); - $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged', '$awakened' ]); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the awakened folder"; + $self->{store}->set_folder($awakened); + $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged', '$awakened' ]); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/snooze_mailboxid b/cassandane/tiny-tests/Sieve/snooze_mailboxid index 8e25dc8fbe..48b577eb8f 100644 --- a/cassandane/tiny-tests/Sieve/snooze_mailboxid +++ b/cassandane/tiny-tests/Sieve/snooze_mailboxid @@ -2,63 +2,63 @@ use Cassandane::Tiny; sub test_snooze_mailboxid - :needs_component_sieve :needs_component_calalarmd - :needs_component_jmap - :min_version_3_1 - :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : needs_component_calalarmd + : needs_component_jmap + : min_version_3_1 + : NoAltNameSpace { + my ($self) = @_; - my $snoozed = "INBOX.snoozed"; - my $awakened = "INBOX.awakened"; + my $snoozed = "INBOX.snoozed"; + my $awakened = "INBOX.awakened"; - xlog $self, "Create the awakened folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Create the awakened folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($awakened) - or die "Cannot create $awakened: $@"; - my $res = $imaptalk->status($awakened, ['mailboxid']); - my $awakenedid = $res->{mailboxid}[0]; + $imaptalk->create($awakened) + or die "Cannot create $awakened: $@"; + my $res = $imaptalk->status($awakened, ['mailboxid']); + my $awakenedid = $res->{mailboxid}[0]; - my $localtz = DateTime::TimeZone->new( name => 'local' ); - my $maildate = DateTime->now(time_zone => $localtz); - $maildate->add(DateTime::Duration->new(minutes => 1)); - my $timestr = $maildate->strftime('%T'); + my $localtz = DateTime::TimeZone->new(name => 'local'); + my $maildate = DateTime->now(time_zone => $localtz); + $maildate->add(DateTime::Duration->new(minutes => 1)); + my $timestr = $maildate->strftime('%T'); - xlog $self, "Install script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Deliver a message without having a snoozed folder"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message without having a snoozed folder"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message was delivered to INBOX"; - $self->{store}->set_folder("INBOX"); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message was delivered to INBOX"; + $self->{store}->set_folder("INBOX"); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Create the snoozed folder"; - $imaptalk->create($snoozed, "(USE (\\Snoozed))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "Create the snoozed folder"; + $imaptalk->create($snoozed, "(USE (\\Snoozed))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "Deliver a message"; - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message made it to the snoozed folder"; - $self->{store}->set_folder($snoozed); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the snoozed folder"; + $self->{store}->set_folder($snoozed); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Trigger re-delivery of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 90 ); + xlog $self, "Trigger re-delivery of snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 90); - xlog $self, "Check that the message made it to the awakened folder"; - $self->{store}->set_folder($awakened); - $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged', '$awakened' ]); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the awakened folder"; + $self->{store}->set_folder($awakened); + $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged', '$awakened' ]); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/snooze_specialuse b/cassandane/tiny-tests/Sieve/snooze_specialuse index 96012946a8..620a568ac3 100644 --- a/cassandane/tiny-tests/Sieve/snooze_specialuse +++ b/cassandane/tiny-tests/Sieve/snooze_specialuse @@ -2,61 +2,61 @@ use Cassandane::Tiny; sub test_snooze_specialuse - :needs_component_sieve :needs_component_calalarmd - :needs_component_jmap - :min_version_3_3 - :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : needs_component_calalarmd + : needs_component_jmap + : min_version_3_3 + : NoAltNameSpace { + my ($self) = @_; - my $snoozed = "INBOX.snoozed"; - my $awakened = "INBOX.awakened"; + my $snoozed = "INBOX.snoozed"; + my $awakened = "INBOX.awakened"; - xlog $self, "Create the awakened folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Create the awakened folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($awakened, "(USE (\\Important))") - or die "Cannot create $awakened: $@"; + $imaptalk->create($awakened, "(USE (\\Important))") + or die "Cannot create $awakened: $@"; - my $localtz = DateTime::TimeZone->new( name => 'local' ); - my $maildate = DateTime->now(time_zone => $localtz); - $maildate->add(DateTime::Duration->new(minutes => 1)); - my $timestr = $maildate->strftime('%T'); + my $localtz = DateTime::TimeZone->new(name => 'local'); + my $maildate = DateTime->now(time_zone => $localtz); + $maildate->add(DateTime::Duration->new(minutes => 1)); + my $timestr = $maildate->strftime('%T'); - xlog $self, "Install script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Deliver a message without having a snoozed folder"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message without having a snoozed folder"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message was delivered to INBOX"; - $self->{store}->set_folder("INBOX"); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message was delivered to INBOX"; + $self->{store}->set_folder("INBOX"); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Create the snoozed folder"; - $imaptalk->create($snoozed, "(USE (\\Snoozed))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "Create the snoozed folder"; + $imaptalk->create($snoozed, "(USE (\\Snoozed))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "Deliver a message"; - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message made it to the snoozed folder"; - $self->{store}->set_folder($snoozed); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the snoozed folder"; + $self->{store}->set_folder($snoozed); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Trigger re-delivery of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 90 ); + xlog $self, "Trigger re-delivery of snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 90); - xlog $self, "Check that the message made it to the awakened folder"; - $self->{store}->set_folder($awakened); - $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged', '$awakened' ]); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the awakened folder"; + $self->{store}->set_folder($awakened); + $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged', '$awakened' ]); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/snooze_specialuse_create b/cassandane/tiny-tests/Sieve/snooze_specialuse_create index 0bcd75ec9a..b74db9cce9 100644 --- a/cassandane/tiny-tests/Sieve/snooze_specialuse_create +++ b/cassandane/tiny-tests/Sieve/snooze_specialuse_create @@ -2,57 +2,57 @@ use Cassandane::Tiny; sub test_snooze_specialuse_create - :needs_component_sieve :needs_component_calalarmd - :needs_component_jmap - :min_version_3_3 - :NoAltNameSpace -{ - my ($self) = @_; + : needs_component_sieve : needs_component_calalarmd + : needs_component_jmap + : min_version_3_3 + : NoAltNameSpace { + my ($self) = @_; - my $snoozed = "INBOX.snoozed"; - my $awakened = "INBOX.awakened"; + my $snoozed = "INBOX.snoozed"; + my $awakened = "INBOX.awakened"; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - my $localtz = DateTime::TimeZone->new( name => 'local' ); - my $maildate = DateTime->now(time_zone => $localtz); - $maildate->add(DateTime::Duration->new(minutes => 1)); - my $timestr = $maildate->strftime('%T'); + my $localtz = DateTime::TimeZone->new(name => 'local'); + my $maildate = DateTime->now(time_zone => $localtz); + $maildate->add(DateTime::Duration->new(minutes => 1)); + my $timestr = $maildate->strftime('%T'); - xlog $self, "Install script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{store}->set_fetch_attributes(qw(uid flags)); + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Deliver a message without having a snoozed folder"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message without having a snoozed folder"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message was delivered to INBOX"; - $self->{store}->set_folder("INBOX"); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message was delivered to INBOX"; + $self->{store}->set_folder("INBOX"); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Create the snoozed folder"; - $imaptalk->create($snoozed, "(USE (\\Snoozed))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "Create the snoozed folder"; + $imaptalk->create($snoozed, "(USE (\\Snoozed))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "Deliver a message"; - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message made it to the snoozed folder"; - $self->{store}->set_folder($snoozed); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the snoozed folder"; + $self->{store}->set_folder($snoozed); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Trigger re-delivery of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 90 ); + xlog $self, "Trigger re-delivery of snoozed email"; + $self->{instance} + ->run_command({ cyrus => 1 }, 'calalarmd', '-t' => $maildate->epoch() + 90); - xlog $self, "Check that the message made it to the awakened folder"; - $self->{store}->set_folder($awakened); - $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged', '$awakened' ]); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the awakened folder"; + $self->{store}->set_folder($awakened); + $msg1->set_attribute(flags => [ '\\Recent', '\\Flagged', '$awakened' ]); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/snooze_tzid b/cassandane/tiny-tests/Sieve/snooze_tzid index 805dcb33fb..0b657701fc 100644 --- a/cassandane/tiny-tests/Sieve/snooze_tzid +++ b/cassandane/tiny-tests/Sieve/snooze_tzid @@ -2,54 +2,55 @@ use Cassandane::Tiny; sub test_snooze_tzid - :needs_component_sieve :needs_component_calalarmd - :needs_component_jmap - :min_version_3_3 - :NoAltNamespace -{ - my ($self) = @_; - - my $snoozed = "INBOX.snoozed"; - my $awakened = "INBOX.awakened"; - - my $localtz = DateTime::TimeZone->new( name => 'Australia/Melbourne' ); - xlog $self, "using local timezone: " . $localtz->name(); - my $maildate = DateTime->now(time_zone => $localtz); - $maildate->add(DateTime::Duration->new(minutes => 1)); - my $timestr = $maildate->strftime('%T'); - - xlog $self, "Install script with tzid"; - $self->{instance}->install_sieve_script(<new(name => 'Australia/Melbourne'); + xlog $self, "using local timezone: " . $localtz->name(); + my $maildate = DateTime->now(time_zone => $localtz); + $maildate->add(DateTime::Duration->new(minutes => 1)); + my $timestr = $maildate->strftime('%T'); + + xlog $self, "Install script with tzid"; + $self->{instance}->install_sieve_script( + <{store}->get_client(); + xlog $self, "Create the awakened folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($awakened) - or die "Cannot create $awakened: $@"; - $self->{store}->set_fetch_attributes(qw(uid flags)); + $imaptalk->create($awakened) + or die "Cannot create $awakened: $@"; + $self->{store}->set_fetch_attributes(qw(uid flags)); - xlog $self, "Create the snoozed folder"; - $imaptalk->create($snoozed, "(USE (\\Snoozed))"); - $self->assert_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "Create the snoozed folder"; + $imaptalk->create($snoozed, "(USE (\\Snoozed))"); + $self->assert_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message made it to the snoozed folder"; - $self->{store}->set_folder($snoozed); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the snoozed folder"; + $self->{store}->set_folder($snoozed); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); - xlog $self, "Trigger re-delivery of snoozed email"; - $self->{instance}->run_command({ cyrus => 1 }, - 'calalarmd', '-t' => $maildate->epoch() + 39600 + 90 ); # 11h + 90s to account for NY/Mel time diff + xlog $self, "Trigger re-delivery of snoozed email"; + $self->{instance}->run_command({ cyrus => 1 }, + 'calalarmd', '-t' => $maildate->epoch() + 39600 + 90) + ; # 11h + 90s to account for NY/Mel time diff - xlog $self, "Check that the message made it to the awakened folder"; - $self->{store}->set_folder($awakened); - $msg1->set_attribute(flags => [ '\\Recent', '$awakened' ]); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the awakened folder"; + $self->{store}->set_folder($awakened); + $msg1->set_attribute(flags => [ '\\Recent', '$awakened' ]); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/specialuse b/cassandane/tiny-tests/Sieve/specialuse index bce256cad3..43279c0ae4 100644 --- a/cassandane/tiny-tests/Sieve/specialuse +++ b/cassandane/tiny-tests/Sieve/specialuse @@ -2,34 +2,34 @@ use Cassandane::Tiny; sub test_specialuse - :min_version_3_1 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing the \":specialuse\" argument"; + xlog $self, "Testing the \":specialuse\" argument"; - my $hitfolder = "INBOX.newfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{store}->get_client(); - $talk->create($hitfolder, "(USE (\\Junk))"); + xlog $self, "Create the hit folder"; + my $talk = $self->{store}->get_client(); + $talk->create($hitfolder, "(USE (\\Junk))"); - xlog $self, "Deliver a message now that the folder exists"; - my $msg = $self->{gen}->generate(subject => "msg1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message now that the folder exists"; + my $msg = $self->{gen}->generate(subject => "msg1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it"; - $talk->select($hitfolder); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + xlog $self, "Check that the message made it"; + $talk->select($hitfolder); + $self->assert_num_equals(1, $talk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/specialuse_create b/cassandane/tiny-tests/Sieve/specialuse_create index b6ca73d2c1..0458901ed0 100644 --- a/cassandane/tiny-tests/Sieve/specialuse_create +++ b/cassandane/tiny-tests/Sieve/specialuse_create @@ -2,29 +2,29 @@ use Cassandane::Tiny; sub test_specialuse_create - :min_version_3_1 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Testing the \":specialuse\" + \":create\" arguments"; + xlog $self, "Testing the \":specialuse\" + \":create\" arguments"; - my $hitfolder = "INBOX.newfolder"; + my $hitfolder = "INBOX.newfolder"; - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "msg1"); - $self->{instance}->deliver($msg); + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "msg1"); + $self->{instance}->deliver($msg); - xlog $self, "Check that the message made it"; - my $talk = $self->{store}->get_client(); - $talk->select($hitfolder); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + xlog $self, "Check that the message made it"; + my $talk = $self->{store}->get_client(); + $talk->select($hitfolder); + $self->assert_num_equals(1, $talk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/specialuse_exists b/cassandane/tiny-tests/Sieve/specialuse_exists index 2afad6c4ad..dd9ea337b4 100644 --- a/cassandane/tiny-tests/Sieve/specialuse_exists +++ b/cassandane/tiny-tests/Sieve/specialuse_exists @@ -2,59 +2,58 @@ use Cassandane::Tiny; sub test_specialuse_exists - :min_version_3_1 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - xlog $self, "Testing the \"specialuse_exists\" test"; + xlog $self, "Testing the \"specialuse_exists\" test"; - my $talk = $self->{store}->get_client(); + my $talk = $self->{store}->get_client(); - my $hitfolder = "INBOX.newfolder"; - my $testfolder = "INBOX.testfolder"; - my $missfolder = "INBOX"; + my $hitfolder = "INBOX.newfolder"; + my $testfolder = "INBOX.testfolder"; + my $missfolder = "INBOX"; - xlog $self, "Install the sieve script"; - my $scriptname = 'flatPack'; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <create($hitfolder); - - my %uid = ($hitfolder => 1, $missfolder => 1); - my %exp; - xlog $self, "Deliver a message"; - { - my $msg = $self->{gen}->generate(subject => "msg1"); - $msg->set_attribute(uid => $uid{$missfolder}); - $uid{$missfolder}++; - $self->{instance}->deliver($msg); - $exp{$missfolder}->{"msg1"} = $msg; - } - - xlog $self, "Create the test folder"; - $talk->create($testfolder, "(USE (\\Junk))"); - - xlog $self, "Deliver a message now that the folder exists"; - { - my $msg = $self->{gen}->generate(subject => "msg2"); - $msg->set_attribute(uid => $uid{$hitfolder}); - $uid{$hitfolder}++; - $self->{instance}->deliver($msg); - $exp{$hitfolder}->{"msg2"} = $msg; - } - - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + ); + + $talk->create($hitfolder); + + my %uid = ($hitfolder => 1, $missfolder => 1); + my %exp; + xlog $self, "Deliver a message"; + { + my $msg = $self->{gen}->generate(subject => "msg1"); + $msg->set_attribute(uid => $uid{$missfolder}); + $uid{$missfolder}++; + $self->{instance}->deliver($msg); + $exp{$missfolder}->{"msg1"} = $msg; + } + + xlog $self, "Create the test folder"; + $talk->create($testfolder, "(USE (\\Junk))"); + + xlog $self, "Deliver a message now that the folder exists"; + { + my $msg = $self->{gen}->generate(subject => "msg2"); + $msg->set_attribute(uid => $uid{$hitfolder}); + $uid{$hitfolder}++; + $self->{instance}->deliver($msg); + $exp{$hitfolder}->{"msg2"} = $msg; + } + + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/unicode_casemap b/cassandane/tiny-tests/Sieve/unicode_casemap index 54d82f9ce3..5b94ba4e6c 100644 --- a/cassandane/tiny-tests/Sieve/unicode_casemap +++ b/cassandane/tiny-tests/Sieve/unicode_casemap @@ -2,33 +2,34 @@ use Cassandane::Tiny; sub test_unicode_casemap - :min_version_3_9 - :needs_component_sieve :needs_component_sieve :NoAltNameSpace :NoMunge8bit -{ - my ($self) = @_; + : min_version_3_9 + : needs_component_sieve : needs_component_sieve : NoAltNameSpace : + NoMunge8bit { + my ($self) = @_; - xlog $self, "Testing the \"i;unicode-casemap\" collation"; + xlog $self, "Testing the \"i;unicode-casemap\" collation"; - my $miss = "INBOX"; - my $is = "INBOX.is"; - my $contains = "INBOX.contains"; - my $matches = "INBOX.matches"; - my $regex = "INBOX.regex"; + my $miss = "INBOX"; + my $is = "INBOX.is"; + my $contains = "INBOX.contains"; + my $matches = "INBOX.matches"; + my $regex = "INBOX.regex"; - xlog $self, "Actually create the target folders"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Actually create the target folders"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($is) - or die "Cannot create $is: $@"; - $imaptalk->create($contains) - or die "Cannot create $contains: $@"; - $imaptalk->create($matches) - or die "Cannot create $match: $@"; - $imaptalk->create($regex) - or die "Cannot create $regex: $@"; + $imaptalk->create($is) + or die "Cannot create $is: $@"; + $imaptalk->create($contains) + or die "Cannot create $contains: $@"; + $imaptalk->create($matches) + or die "Cannot create $match: $@"; + $imaptalk->create($regex) + or die "Cannot create $regex: $@"; - xlog $self, "Install the sieve script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + < 'hello world!', filedto => $is }, - { subject => 'pÂTé', filedto => $is }, - { subject => 'pate', filedto => $miss }, - { subject => 'I like pâTé', filedto => $contains }, - { subject => 'I like pâTé a lot', filedto => $matches }, - { subject => "I don't like pâTé very much", filedto => $regex }, - { subject => "I won't eat pâTé", filedto => $regex }, - ); + my @cases = ( + { subject => 'hello world!', filedto => $is }, + { subject => 'pÂTé', filedto => $is }, + { subject => 'pate', filedto => $miss }, + { subject => 'I like pâTé', filedto => $contains }, + { subject => 'I like pâTé a lot', filedto => $matches }, + { subject => "I don't like pâTé very much", filedto => $regex }, + { subject => "I won't eat pâTé", filedto => $regex }, + ); - my %uid = ($is => 1, $contains => 1, $matches => 1, $regex => 1, $miss => 1); - my %exp; - foreach my $case (@cases) - { - xlog $self, "Deliver a message with subject \"$case->{subject}\""; - my $msg = $self->{gen}->generate(subject => $case->{subject}); - $msg->set_attribute(uid => $uid{$case->{filedto}}); - $uid{$case->{filedto}}++; - $self->{instance}->deliver($msg); - $exp{$case->{filedto}}->{$case->{subject}} = $msg; - } + my %uid = ($is => 1, $contains => 1, $matches => 1, $regex => 1, $miss => 1); + my %exp; + foreach my $case (@cases) { + xlog $self, "Deliver a message with subject \"$case->{subject}\""; + my $msg = $self->{gen}->generate(subject => $case->{subject}); + $msg->set_attribute(uid => $uid{ $case->{filedto} }); + $uid{ $case->{filedto} }++; + $self->{instance}->deliver($msg); + $exp{ $case->{filedto} }->{ $case->{subject} } = $msg; + } - xlog $self, "Check that the messages made it"; - foreach my $folder (keys %exp) - { - $self->{store}->set_folder($folder); - $self->check_messages($exp{$folder}, check_guid => 0); - } + xlog $self, "Check that the messages made it"; + foreach my $folder (keys %exp) { + $self->{store}->set_folder($folder); + $self->check_messages($exp{$folder}, check_guid => 0); + } } diff --git a/cassandane/tiny-tests/Sieve/utf8_mboxname b/cassandane/tiny-tests/Sieve/utf8_mboxname index 507c1c302b..4d804f05f8 100644 --- a/cassandane/tiny-tests/Sieve/utf8_mboxname +++ b/cassandane/tiny-tests/Sieve/utf8_mboxname @@ -2,33 +2,33 @@ use Cassandane::Tiny; sub test_utf8_mboxname - :needs_component_sieve :min_version_3_1 :SieveUTF8Fileinto -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_1 : SieveUTF8Fileinto { + my ($self) = @_; - my $target = "INBOX.A & B"; + my $target = "INBOX.A & B"; - xlog $self, "Testing '&' in a mailbox name"; + xlog $self, "Testing '&' in a mailbox name"; - xlog $self, "Actually create the target folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Actually create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create($target) - or die "Cannot create $target: $@"; - $self->{store}->set_fetch_attributes('uid'); + $imaptalk->create($target) + or die "Cannot create $target: $@"; + $self->{store}->set_fetch_attributes('uid'); - xlog $self, "Install script"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - xlog $self, "Check that the message made it to the target"; - $self->{store}->set_folder($target); - $self->check_messages({ 1 => $msg1 }, check_guid => 0); + xlog $self, "Check that the message made it to the target"; + $self->{store}->set_folder($target); + $self->check_messages({ 1 => $msg1 }, check_guid => 0); } diff --git a/cassandane/tiny-tests/Sieve/utf8_subject_encoded b/cassandane/tiny-tests/Sieve/utf8_subject_encoded index e50847f32e..8e297fca38 100644 --- a/cassandane/tiny-tests/Sieve/utf8_subject_encoded +++ b/cassandane/tiny-tests/Sieve/utf8_subject_encoded @@ -2,41 +2,42 @@ use Cassandane::Tiny; sub test_utf8_subject_encoded - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "Install a sieve script flagging messages that match utf8 snowman"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + # should NOT get flagged + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - # SHOULD get flagged - my $msg2 = $self->{gen}->generate(subject => "=?UTF-8?B?4piD?="); - $self->{instance}->deliver($msg2); + # SHOULD get flagged + my $msg2 = $self->{gen}->generate(subject => "=?UTF-8?B?4piD?="); + $self->{instance}->deliver($msg2); - # should NOT get flagged - my $msg3 = $self->{gen}->generate(subject => "Message 3"); - $self->{instance}->deliver($msg3); + # should NOT get flagged + my $msg3 = $self->{gen}->generate(subject => "Message 3"); + $self->{instance}->deliver($msg3); - $imaptalk->unselect(); - $imaptalk->select("INBOX"); - $self->assert_num_equals(3, $imaptalk->get_response_code('exists')); + $imaptalk->unselect(); + $imaptalk->select("INBOX"); + $self->assert_num_equals(3, $imaptalk->get_response_code('exists')); - my @uids = $imaptalk->search('1:*', 'NOT', 'FLAGGED'); + my @uids = $imaptalk->search('1:*', 'NOT', 'FLAGGED'); - $self->assert_num_equals(2, scalar(@uids)); + $self->assert_num_equals(2, scalar(@uids)); } diff --git a/cassandane/tiny-tests/Sieve/utf8_subject_raw b/cassandane/tiny-tests/Sieve/utf8_subject_raw index e3f01740e2..ffeef37977 100644 --- a/cassandane/tiny-tests/Sieve/utf8_subject_raw +++ b/cassandane/tiny-tests/Sieve/utf8_subject_raw @@ -2,41 +2,42 @@ use Cassandane::Tiny; sub test_utf8_subject_raw - :min_version_3_0 - :needs_component_sieve :NoMunge8bit -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve : NoMunge8bit { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "Install a sieve script flagging messages that match utf8 snowman"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + # should NOT get flagged + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - # SHOULD get flagged - my $msg2 = $self->{gen}->generate(subject => "☃"); - $self->{instance}->deliver($msg2); + # SHOULD get flagged + my $msg2 = $self->{gen}->generate(subject => "☃"); + $self->{instance}->deliver($msg2); - # should NOT get flagged - my $msg3 = $self->{gen}->generate(subject => "Message 3"); - $self->{instance}->deliver($msg3); + # should NOT get flagged + my $msg3 = $self->{gen}->generate(subject => "Message 3"); + $self->{instance}->deliver($msg3); - $imaptalk->unselect(); - $imaptalk->select("INBOX"); - $self->assert_num_equals(3, $imaptalk->get_response_code('exists')); + $imaptalk->unselect(); + $imaptalk->select("INBOX"); + $self->assert_num_equals(3, $imaptalk->get_response_code('exists')); - my @uids = $imaptalk->search('1:*', 'NOT', 'FLAGGED'); + my @uids = $imaptalk->search('1:*', 'NOT', 'FLAGGED'); - $self->assert_num_equals(2, scalar(@uids)); + $self->assert_num_equals(2, scalar(@uids)); } diff --git a/cassandane/tiny-tests/Sieve/vacation_multiple b/cassandane/tiny-tests/Sieve/vacation_multiple index 7385a4acab..f5b5691380 100644 --- a/cassandane/tiny-tests/Sieve/vacation_multiple +++ b/cassandane/tiny-tests/Sieve/vacation_multiple @@ -2,19 +2,19 @@ use Cassandane::Tiny; sub test_vacation_multiple - :min_version_3_1 - :needs_component_sieve -{ - my ($self) = @_; - - # can't do anything without captured syslog - if (!$self->{instance}->{have_syslog_replacement}) { - xlog $self, "can't examine syslog, test is useless"; - return; - } - - xlog $self, "Install a sieve script with vacation action"; - $self->{instance}->install_sieve_script(<<'EOF' + : min_version_3_1 + : needs_component_sieve { + my ($self) = @_; + + # can't do anything without captured syslog + if (!$self->{instance}->{have_syslog_replacement}) { + xlog $self, "can't examine syslog, test is useless"; + return; + } + + xlog $self, "Install a sieve script with vacation action"; + $self->{instance}->install_sieve_script( + <<'EOF' require ["vacation"]; vacation :days 3 :addresses ["cassandane@example.com"] text: @@ -22,21 +22,31 @@ I am out of the office today. I will answer your email as soon as I can. . ; EOF - ); - - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1", - to => Cassandane::Address->new(localpart => 'cassandane', domain => 'example.com')); - $self->{instance}->deliver($msg1); - - sleep 1; - - xlog $self, "Deliver another message"; - my $msg2 = $self->{gen}->generate(subject => "Message 2", - to => Cassandane::Address->new(localpart => 'cassandane', domain => 'example.com')); - $self->{instance}->deliver($msg2); - - # Make sure that we only sent one response - my @resp = $self->{instance}->getsyslog(qr/smtpclient_open:/); - $self->assert_num_equals(1, scalar @resp); + ); + + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate( + subject => "Message 1", + to => Cassandane::Address->new( + localpart => 'cassandane', + domain => 'example.com' + ) + ); + $self->{instance}->deliver($msg1); + + sleep 1; + + xlog $self, "Deliver another message"; + my $msg2 = $self->{gen}->generate( + subject => "Message 2", + to => Cassandane::Address->new( + localpart => 'cassandane', + domain => 'example.com' + ) + ); + $self->{instance}->deliver($msg2); + + # Make sure that we only sent one response + my @resp = $self->{instance}->getsyslog(qr/smtpclient_open:/); + $self->assert_num_equals(1, scalar @resp); } diff --git a/cassandane/tiny-tests/Sieve/vacation_with_explicit_subject b/cassandane/tiny-tests/Sieve/vacation_with_explicit_subject index 4609f649dc..882c9c280c 100644 --- a/cassandane/tiny-tests/Sieve/vacation_with_explicit_subject +++ b/cassandane/tiny-tests/Sieve/vacation_with_explicit_subject @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_vacation_with_explicit_subject - :min_version_3_1 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - my $target = "INBOX.Sent"; + my $target = "INBOX.Sent"; - xlog $self, "Install a sieve script with explicit vacation subject"; - $self->{instance}->install_sieve_script(<<'EOF' + xlog $self, "Install a sieve script with explicit vacation subject"; + $self->{instance}->install_sieve_script( + <<'EOF' require ["vacation", "fcc"]; vacation :fcc "INBOX.Sent" :days 1 :addresses ["cassandane@example.com"] :subject "Boo" text: @@ -19,29 +19,35 @@ I am out of the office today. I will answer your email as soon as I can. . ; EOF - ); - - xlog $self, "Create the target folder"; - my $talk = $self->{store}->get_client(); - $talk->create($target, "(USE (\\Sent))"); - - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1", - to => Cassandane::Address->new(localpart => 'cassandane', domain => 'example.com')); - $self->{instance}->deliver($msg1); - - xlog $self, "Check that a copy of the auto-reply message made it"; - $talk->select($target); - $self->assert_num_equals(1, $talk->get_response_code('exists')); - - xlog $self, "Check that the message is an auto-reply"; - my $res = $talk->fetch(1, 'rfc822'); - my $msg2 = $res->{1}->{rfc822}; - - $self->assert_matches(qr/Subject: Boo\r\n/ms, $msg2); - $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, $msg2); - $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); - -# use Data::Dumper; -# warn Dumper($msg2); + ); + + xlog $self, "Create the target folder"; + my $talk = $self->{store}->get_client(); + $talk->create($target, "(USE (\\Sent))"); + + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate( + subject => "Message 1", + to => Cassandane::Address->new( + localpart => 'cassandane', + domain => 'example.com' + ) + ); + $self->{instance}->deliver($msg1); + + xlog $self, "Check that a copy of the auto-reply message made it"; + $talk->select($target); + $self->assert_num_equals(1, $talk->get_response_code('exists')); + + xlog $self, "Check that the message is an auto-reply"; + my $res = $talk->fetch(1, 'rfc822'); + my $msg2 = $res->{1}->{rfc822}; + + $self->assert_matches(qr/Subject: Boo\r\n/ms, $msg2); + $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, + $msg2); + $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); + + # use Data::Dumper; + # warn Dumper($msg2); } diff --git a/cassandane/tiny-tests/Sieve/vacation_with_fcc b/cassandane/tiny-tests/Sieve/vacation_with_fcc index 571abc93b4..9677c3269f 100644 --- a/cassandane/tiny-tests/Sieve/vacation_with_fcc +++ b/cassandane/tiny-tests/Sieve/vacation_with_fcc @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_vacation_with_fcc - :min_version_3_1 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - my $target = "INBOX.Sent"; + my $target = "INBOX.Sent"; - xlog $self, "Install a sieve script with vacation action that uses :fcc"; - $self->{instance}->install_sieve_script(<<'EOF' + xlog $self, "Install a sieve script with vacation action that uses :fcc"; + $self->{instance}->install_sieve_script( + <<'EOF' require ["vacation", "fcc"]; vacation :fcc "INBOX.Sent" :days 1 :addresses ["cassandane@example.com"] text: @@ -19,26 +19,32 @@ I am out of the office today. I will answer your email as soon as I can. . ; EOF - ); - - xlog $self, "Create the target folder"; - my $talk = $self->{store}->get_client(); - $talk->create($target, "(USE (\\Sent))"); - - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1", - to => Cassandane::Address->new(localpart => 'cassandane', domain => 'example.com')); - $self->{instance}->deliver($msg1); - - xlog $self, "Check that a copy of the auto-reply message made it"; - $talk->select($target); - $self->assert_num_equals(1, $talk->get_response_code('exists')); - - xlog $self, "Check that the message is an auto-reply"; - my $res = $talk->fetch(1, 'rfc822'); - my $msg2 = $res->{1}->{rfc822}; - - $self->assert_matches(qr/Subject: Auto:(?:\r\n)? Message 1\r\n/ms, $msg2); - $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, $msg2); - $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); + ); + + xlog $self, "Create the target folder"; + my $talk = $self->{store}->get_client(); + $talk->create($target, "(USE (\\Sent))"); + + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate( + subject => "Message 1", + to => Cassandane::Address->new( + localpart => 'cassandane', + domain => 'example.com' + ) + ); + $self->{instance}->deliver($msg1); + + xlog $self, "Check that a copy of the auto-reply message made it"; + $talk->select($target); + $self->assert_num_equals(1, $talk->get_response_code('exists')); + + xlog $self, "Check that the message is an auto-reply"; + my $res = $talk->fetch(1, 'rfc822'); + my $msg2 = $res->{1}->{rfc822}; + + $self->assert_matches(qr/Subject: Auto:(?:\r\n)? Message 1\r\n/ms, $msg2); + $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, + $msg2); + $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); } diff --git a/cassandane/tiny-tests/Sieve/vacation_with_fcc_specialuse b/cassandane/tiny-tests/Sieve/vacation_with_fcc_specialuse index 873769e782..ebb8f58717 100644 --- a/cassandane/tiny-tests/Sieve/vacation_with_fcc_specialuse +++ b/cassandane/tiny-tests/Sieve/vacation_with_fcc_specialuse @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_vacation_with_fcc_specialuse - :min_version_3_1 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - my $target = "INBOX.Sent"; + my $target = "INBOX.Sent"; - xlog $self, "Install a sieve script with vacation action that uses :fcc"; - $self->{instance}->install_sieve_script(<<'EOF' + xlog $self, "Install a sieve script with vacation action that uses :fcc"; + $self->{instance}->install_sieve_script( + <<'EOF' require ["vacation", "fcc", "special-use"]; vacation :fcc "INBOX" :specialuse "\\Sent" :days 1 :addresses ["cassandane@example.com"] text: @@ -19,26 +19,32 @@ I am out of the office today. I will answer your email as soon as I can. . ; EOF - ); - - xlog $self, "Create the target folder"; - my $talk = $self->{store}->get_client(); - $talk->create($target, "(USE (\\Sent))"); - - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1", - to => Cassandane::Address->new(localpart => 'cassandane', domain => 'example.com')); - $self->{instance}->deliver($msg1); - - xlog $self, "Check that a copy of the auto-reply message made it"; - $talk->select($target); - $self->assert_num_equals(1, $talk->get_response_code('exists')); - - xlog $self, "Check that the message is an auto-reply"; - my $res = $talk->fetch(1, 'rfc822'); - my $msg2 = $res->{1}->{rfc822}; - - $self->assert_matches(qr/Subject: Auto:(?:\r\n)? Message 1\r\n/ms, $msg2); - $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, $msg2); - $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); + ); + + xlog $self, "Create the target folder"; + my $talk = $self->{store}->get_client(); + $talk->create($target, "(USE (\\Sent))"); + + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate( + subject => "Message 1", + to => Cassandane::Address->new( + localpart => 'cassandane', + domain => 'example.com' + ) + ); + $self->{instance}->deliver($msg1); + + xlog $self, "Check that a copy of the auto-reply message made it"; + $talk->select($target); + $self->assert_num_equals(1, $talk->get_response_code('exists')); + + xlog $self, "Check that the message is an auto-reply"; + my $res = $talk->fetch(1, 'rfc822'); + my $msg2 = $res->{1}->{rfc822}; + + $self->assert_matches(qr/Subject: Auto:(?:\r\n)? Message 1\r\n/ms, $msg2); + $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, + $msg2); + $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); } diff --git a/cassandane/tiny-tests/Sieve/vacation_with_following_rules b/cassandane/tiny-tests/Sieve/vacation_with_following_rules index 573e5ec292..9d8175d0d6 100644 --- a/cassandane/tiny-tests/Sieve/vacation_with_following_rules +++ b/cassandane/tiny-tests/Sieve/vacation_with_following_rules @@ -2,14 +2,15 @@ use Cassandane::Tiny; sub test_vacation_with_following_rules - :needs_component_sieve :min_version_3_0 -{ - my ($self) = @_; + : needs_component_sieve : min_version_3_0 { + my ($self) = @_; - my $target = "INBOX.target"; + my $target = "INBOX.target"; - xlog $self, "Install a sieve script filing all mail into a nonexistant folder"; - $self->{instance}->install_sieve_script(<<'EOF' + xlog $self, + "Install a sieve script filing all mail into a nonexistant folder"; + $self->{instance}->install_sieve_script( + <<'EOF' require ["fileinto", "reject", "vacation", "imap4flags", "envelope", "relational", "regex", "subaddress", "copy", "mailbox", "mboxmetadata", "servermetadata", "date", "index", "comparator-i;ascii-numeric", "variables"]; @@ -34,11 +35,11 @@ if header :contains ["To","Cc","From","Subject","Date","Content-Type","Delivered } EOF - ); + ); - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - # This will crash if we have broken parsing of vacation + # This will crash if we have broken parsing of vacation } diff --git a/cassandane/tiny-tests/Sieve/vacation_with_long_encoded_origsubject b/cassandane/tiny-tests/Sieve/vacation_with_long_encoded_origsubject index bea7e5bb54..346c8f82b1 100644 --- a/cassandane/tiny-tests/Sieve/vacation_with_long_encoded_origsubject +++ b/cassandane/tiny-tests/Sieve/vacation_with_long_encoded_origsubject @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_vacation_with_long_encoded_origsubject - :min_version_3_1 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - my $target = 'INBOX.Sent'; + my $target = 'INBOX.Sent'; - xlog $self, "Install a sieve script with vacation action that uses :fcc"; - $self->{instance}->install_sieve_script(<<"EOF" + xlog $self, "Install a sieve script with vacation action that uses :fcc"; + $self->{instance}->install_sieve_script( + <<"EOF" require ["vacation", "fcc"]; vacation :fcc "$target" :days 1 :addresses ["cassandane\@example.com"] text: @@ -19,62 +19,63 @@ I am out of the office today. I will answer your email as soon as I can. . ; EOF - ); + ); - xlog $self, "Create the target folder"; - my $talk = $self->{store}->get_client(); - $talk->create($target, "(USE (\\Sent))"); + xlog $self, "Create the target folder"; + my $talk = $self->{store}->get_client(); + $talk->create($target, "(USE (\\Sent))"); - xlog $self, "Deliver a message"; - # should end up refolding a couple of times - my $subject = "=?UTF-8?Q?=E3=83=86=E3=82=B9=E3=83=88=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?=\r\n" - . " =?UTF-8?Q?=E3=82=B8=E3=80=81=E7=84=A1=E8=A6=96=E3=81=97=E3=81=A6=E3=81=8F?=\r\n" - . " =?UTF-8?Q?=E3=81=A0=E3=81=95=E3=81=84?="; + xlog $self, "Deliver a message"; + # should end up refolding a couple of times + my $subject + = "=?UTF-8?Q?=E3=83=86=E3=82=B9=E3=83=88=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?=\r\n" + . " =?UTF-8?Q?=E3=82=B8=E3=80=81=E7=84=A1=E8=A6=96=E3=81=97=E3=81=A6=E3=81=8F?=\r\n" + . " =?UTF-8?Q?=E3=81=A0=E3=81=95=E3=81=84?="; - my $msg1 = $self->{gen}->generate( - subject => $subject, - to => Cassandane::Address->new(localpart => 'cassandane', - domain => 'example.com')); - $self->{instance}->deliver($msg1); + my $msg1 = $self->{gen}->generate( + subject => $subject, + to => Cassandane::Address->new( + localpart => 'cassandane', + domain => 'example.com' + ) + ); + $self->{instance}->deliver($msg1); - xlog $self, "Check that a copy of the auto-reply message made it"; - $talk->select($target); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + xlog $self, "Check that a copy of the auto-reply message made it"; + $talk->select($target); + $self->assert_num_equals(1, $talk->get_response_code('exists')); - xlog $self, "Check that the message is an auto-reply"; - my $res = $talk->fetch(1, 'rfc822'); - my $msg2 = $res->{1}->{rfc822}; + xlog $self, "Check that the message is an auto-reply"; + my $res = $talk->fetch(1, 'rfc822'); + my $msg2 = $res->{1}->{rfc822}; - # check we folded a reasonable number of times - my $actual_subject; - if ($msg2 =~ m/^(Subject:.*?\r\n)(?!\s)/ms) { - $actual_subject = $1; - } - $self->assert_matches(qr/^Subject:/, $actual_subject); - my $fold_count = () = $actual_subject =~ m/\r\n /g; - xlog "fold count: $fold_count"; - $self->assert_num_gte(2, $fold_count); - $self->assert_num_lte(4, $fold_count); + # check we folded a reasonable number of times + my $actual_subject; + if ($msg2 =~ m/^(Subject:.*?\r\n)(?!\s)/ms) { + $actual_subject = $1; + } + $self->assert_matches(qr/^Subject:/, $actual_subject); + my $fold_count = () = $actual_subject =~ m/\r\n /g; + xlog "fold count: $fold_count"; + $self->assert_num_gte(2, $fold_count); + $self->assert_num_lte(4, $fold_count); - # subject should be the original subject plus "Auto: " and CRLF - if (version->parse($Encode::MIME::Header::VERSION) - < version->parse("2.28")) { - # XXX Work around a bug in older Encode::MIME::Header - # XXX (https://rt.cpan.org/Public/Bug/Display.html?id=42902) - # XXX that loses the space between 'Subject:' and 'Auto:', - # XXX by allowing it to be optional - my $subjpat = "Auto: " . decode("MIME-Header", $subject) . "\r\n"; - my $subjre = qr/Subject:\s?$subjpat/; - $self->assert_matches($subjre, decode("MIME-Header", $actual_subject)); - } - else { - my $subjpat = "Subject: Auto: " - . decode("MIME-Header", $subject) . "\r\n"; - $self->assert_str_equals($subjpat, - decode("MIME-Header", $actual_subject)); - } + # subject should be the original subject plus "Auto: " and CRLF + if (version->parse($Encode::MIME::Header::VERSION) < version->parse("2.28")) { + # XXX Work around a bug in older Encode::MIME::Header + # XXX (https://rt.cpan.org/Public/Bug/Display.html?id=42902) + # XXX that loses the space between 'Subject:' and 'Auto:', + # XXX by allowing it to be optional + my $subjpat = "Auto: " . decode("MIME-Header", $subject) . "\r\n"; + my $subjre = qr/Subject:\s?$subjpat/; + $self->assert_matches($subjre, decode("MIME-Header", $actual_subject)); + } else { + my $subjpat = "Subject: Auto: " . decode("MIME-Header", $subject) . "\r\n"; + $self->assert_str_equals($subjpat, decode("MIME-Header", $actual_subject)); + } - # check for auto-submitted header - $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, $msg2); - $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); + # check for auto-submitted header + $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, + $msg2); + $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); } diff --git a/cassandane/tiny-tests/Sieve/vacation_with_long_origsubject b/cassandane/tiny-tests/Sieve/vacation_with_long_origsubject index 11cedafda2..ff58030f44 100644 --- a/cassandane/tiny-tests/Sieve/vacation_with_long_origsubject +++ b/cassandane/tiny-tests/Sieve/vacation_with_long_origsubject @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_vacation_with_long_origsubject - :min_version_3_1 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_1 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - my $target = 'INBOX.Sent'; + my $target = 'INBOX.Sent'; - xlog $self, "Install a sieve script with vacation action that uses :fcc"; - $self->{instance}->install_sieve_script(<<"EOF" + xlog $self, "Install a sieve script with vacation action that uses :fcc"; + $self->{instance}->install_sieve_script( + <<"EOF" require ["vacation", "fcc"]; vacation :fcc "$target" :days 1 :addresses ["cassandane\@example.com"] text: @@ -19,51 +19,56 @@ I am out of the office today. I will answer your email as soon as I can. . ; EOF - ); + ); - xlog $self, "Create the target folder"; - my $talk = $self->{store}->get_client(); - $talk->create($target, "(USE (\\Sent))"); + xlog $self, "Create the target folder"; + my $talk = $self->{store}->get_client(); + $talk->create($target, "(USE (\\Sent))"); - xlog $self, "Deliver a message"; - # should end up folding a couple of times - my $subject = "volutpat diam ut venenatis tellus in metus " - . "vulputate eu scelerisque felis imperdiet proin " - . "fermentum leo vel orci portad non pulvinar neque " - . "laoreet suspendisse interdum consectetur"; + xlog $self, "Deliver a message"; + # should end up folding a couple of times + my $subject + = "volutpat diam ut venenatis tellus in metus " + . "vulputate eu scelerisque felis imperdiet proin " + . "fermentum leo vel orci portad non pulvinar neque " + . "laoreet suspendisse interdum consectetur"; - my $msg1 = $self->{gen}->generate( - subject => $subject, - to => Cassandane::Address->new(localpart => 'cassandane', - domain => 'example.com')); - $self->{instance}->deliver($msg1); + my $msg1 = $self->{gen}->generate( + subject => $subject, + to => Cassandane::Address->new( + localpart => 'cassandane', + domain => 'example.com' + ) + ); + $self->{instance}->deliver($msg1); - xlog $self, "Check that a copy of the auto-reply message made it"; - $talk->select($target); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + xlog $self, "Check that a copy of the auto-reply message made it"; + $talk->select($target); + $self->assert_num_equals(1, $talk->get_response_code('exists')); - xlog $self, "Check that the message is an auto-reply"; - my $res = $talk->fetch(1, 'rfc822'); - my $msg2 = $res->{1}->{rfc822}; + xlog $self, "Check that the message is an auto-reply"; + my $res = $talk->fetch(1, 'rfc822'); + my $msg2 = $res->{1}->{rfc822}; - my $subjpat = $subject =~ s/ /(?:\r\n)? /gr; - my $subjre = qr{Subject:\r\n Auto: $subjpat}; + my $subjpat = $subject =~ s/ /(?:\r\n)? /gr; + my $subjre = qr{Subject:\r\n Auto: $subjpat}; - # subject should be the original subject plus "\r\n Auto: " and folding - $self->assert_matches($subjre, $msg2); + # subject should be the original subject plus "\r\n Auto: " and folding + $self->assert_matches($subjre, $msg2); - # check we folded a reasonable number of times - my $actual_subject; - if ($msg2 =~ m/^(Subject:.*?\r\n)(?!\s)/ms) { - $actual_subject = $1; - } - $self->assert_matches(qr/^Subject:/, $actual_subject); - my $fold_count = () = $actual_subject =~ m/\r\n /g; - xlog "fold count: $fold_count"; - $self->assert_num_gte(2, $fold_count); - $self->assert_num_lte(4, $fold_count); + # check we folded a reasonable number of times + my $actual_subject; + if ($msg2 =~ m/^(Subject:.*?\r\n)(?!\s)/ms) { + $actual_subject = $1; + } + $self->assert_matches(qr/^Subject:/, $actual_subject); + my $fold_count = () = $actual_subject =~ m/\r\n /g; + xlog "fold count: $fold_count"; + $self->assert_num_gte(2, $fold_count); + $self->assert_num_lte(4, $fold_count); - # check for auto-submitted header - $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, $msg2); - $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); + # check for auto-submitted header + $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, + $msg2); + $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); } diff --git a/cassandane/tiny-tests/Sieve/vacation_with_nonfoldable_origsubject b/cassandane/tiny-tests/Sieve/vacation_with_nonfoldable_origsubject index d1f1d0ff8a..b8f5494d9b 100644 --- a/cassandane/tiny-tests/Sieve/vacation_with_nonfoldable_origsubject +++ b/cassandane/tiny-tests/Sieve/vacation_with_nonfoldable_origsubject @@ -2,16 +2,16 @@ use Cassandane::Tiny; sub test_vacation_with_nonfoldable_origsubject - :min_version_3_5 - :needs_component_sieve - :NoAltNameSpace -{ - my ($self) = @_; + : min_version_3_5 + : needs_component_sieve + : NoAltNameSpace { + my ($self) = @_; - my $target = 'INBOX.Sent'; + my $target = 'INBOX.Sent'; - xlog $self, "Install a sieve script with vacation action that uses :fcc"; - $self->{instance}->install_sieve_script(<<"EOF" + xlog $self, "Install a sieve script with vacation action that uses :fcc"; + $self->{instance}->install_sieve_script( + <<"EOF" require ["vacation", "fcc"]; vacation :fcc "$target" :days 1 :addresses ["cassandane\@example.com"] text: @@ -19,63 +19,64 @@ I am out of the office today. I will answer your email as soon as I can. . ; EOF - ); + ); - xlog $self, "Create the target folder"; - my $talk = $self->{store}->get_client(); - $talk->create($target, "(USE (\\Sent))"); + xlog $self, "Create the target folder"; + my $talk = $self->{store}->get_client(); + $talk->create($target, "(USE (\\Sent))"); - xlog $self, "Deliver a message"; - # runs more than 75c without whitespace! - my $subject = "volutpatdiamutvenenatistellusinmetus" - . "vulputateeuscelerisquefelisimperdietproin" - . "fermentumleovelorciportanonpulvinarneque" - . "laoreetsuspendisseinterdumconsectetur"; + xlog $self, "Deliver a message"; + # runs more than 75c without whitespace! + my $subject + = "volutpatdiamutvenenatistellusinmetus" + . "vulputateeuscelerisquefelisimperdietproin" + . "fermentumleovelorciportanonpulvinarneque" + . "laoreetsuspendisseinterdumconsectetur"; - my $msg1 = $self->{gen}->generate( - subject => $subject, - to => Cassandane::Address->new(localpart => 'cassandane', - domain => 'example.com')); - $self->{instance}->deliver($msg1); + my $msg1 = $self->{gen}->generate( + subject => $subject, + to => Cassandane::Address->new( + localpart => 'cassandane', + domain => 'example.com' + ) + ); + $self->{instance}->deliver($msg1); - xlog $self, "Check that a copy of the auto-reply message made it"; - $talk->select($target); - $self->assert_num_equals(1, $talk->get_response_code('exists')); + xlog $self, "Check that a copy of the auto-reply message made it"; + $talk->select($target); + $self->assert_num_equals(1, $talk->get_response_code('exists')); - xlog $self, "Check that the message is an auto-reply"; - my $res = $talk->fetch(1, 'rfc822'); - my $msg2 = $res->{1}->{rfc822}; + xlog $self, "Check that the message is an auto-reply"; + my $res = $talk->fetch(1, 'rfc822'); + my $msg2 = $res->{1}->{rfc822}; - # check we folded a reasonable number of times - my $actual_subject; - if ($msg2 =~ m/^(Subject:.*?\r\n)(?!\s)/ms) { - $actual_subject = $1; - } - $self->assert_matches(qr/^Subject:/, $actual_subject); - my $fold_count = () = $actual_subject =~ m/\r\n /g; - xlog "fold count: $fold_count"; - $self->assert_num_gte(2, $fold_count); - $self->assert_num_lte(4, $fold_count); + # check we folded a reasonable number of times + my $actual_subject; + if ($msg2 =~ m/^(Subject:.*?\r\n)(?!\s)/ms) { + $actual_subject = $1; + } + $self->assert_matches(qr/^Subject:/, $actual_subject); + my $fold_count = () = $actual_subject =~ m/\r\n /g; + xlog "fold count: $fold_count"; + $self->assert_num_gte(2, $fold_count); + $self->assert_num_lte(4, $fold_count); - # subject should be the original subject plus "Auto: " and CRLF - if (version->parse($Encode::MIME::Header::VERSION) - < version->parse("2.28")) { - # XXX Work around a bug in older Encode::MIME::Header - # XXX (https://rt.cpan.org/Public/Bug/Display.html?id=42902) - # XXX that loses the space between 'Subject:' and 'Auto:', - # XXX by allowing it to be optional - my $subjpat = "Auto: " . decode("MIME-Header", $subject) . "\r\n"; - my $subjre = qr/Subject:\s?$subjpat/; - $self->assert_matches($subjre, decode("MIME-Header", $actual_subject)); - } - else { - my $subjpat = "Subject: Auto: " - . decode("MIME-Header", $subject) . "\r\n"; - $self->assert_str_equals($subjpat, - decode("MIME-Header", $actual_subject)); - } + # subject should be the original subject plus "Auto: " and CRLF + if (version->parse($Encode::MIME::Header::VERSION) < version->parse("2.28")) { + # XXX Work around a bug in older Encode::MIME::Header + # XXX (https://rt.cpan.org/Public/Bug/Display.html?id=42902) + # XXX that loses the space between 'Subject:' and 'Auto:', + # XXX by allowing it to be optional + my $subjpat = "Auto: " . decode("MIME-Header", $subject) . "\r\n"; + my $subjre = qr/Subject:\s?$subjpat/; + $self->assert_matches($subjre, decode("MIME-Header", $actual_subject)); + } else { + my $subjpat = "Subject: Auto: " . decode("MIME-Header", $subject) . "\r\n"; + $self->assert_str_equals($subjpat, decode("MIME-Header", $actual_subject)); + } - # check for auto-submitted header - $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, $msg2); - $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); + # check for auto-submitted header + $self->assert_matches(qr/Auto-Submitted: auto-replied \(vacation\)\r\n/, + $msg2); + $self->assert_matches(qr/\r\n\r\nI am out of the office today./, $msg2); } diff --git a/cassandane/tiny-tests/Sieve/variable_modifiers b/cassandane/tiny-tests/Sieve/variable_modifiers index e284417e2f..917e7ac772 100644 --- a/cassandane/tiny-tests/Sieve/variable_modifiers +++ b/cassandane/tiny-tests/Sieve/variable_modifiers @@ -2,11 +2,11 @@ use Cassandane::Tiny; sub test_variable_modifiers - :needs_component_sieve -{ - my ($self) = @_; + : needs_component_sieve { + my ($self) = @_; - $self->{instance}->install_sieve_script(<<'EOF' + $self->{instance}->install_sieve_script( + <<'EOF' require ["variables", "editheader", "regex", "enotify"]; set "a" "juMBlEd?lETteRS=.*"; @@ -44,31 +44,51 @@ addheader "X-Cassandane-Test" "regex+upper = \"${n}\""; addheader "X-Cassandane-Test" "regex+url+upperfirst+lower = \"${o}\""; addheader "X-Cassandane-Test" "regex+url+upper+len = \"${p}\""; EOF - ); + ); - xlog $self, "Deliver a message"; - my $msg1 = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + xlog $self, "Deliver a message"; + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - my $imaptalk = $self->{store}->get_client(); - $imaptalk->select("INBOX"); - my $res = $imaptalk->fetch(1, 'rfc822'); + my $imaptalk = $self->{store}->get_client(); + $imaptalk->select("INBOX"); + my $res = $imaptalk->fetch(1, 'rfc822'); - $msg1 = $res->{1}->{rfc822}; + $msg1 = $res->{1}->{rfc822}; - $self->assert_matches(qr/X-Cassandane-Test: len = "18"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: lower = "jumbled\?letters=\.\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: upper = "JUMBLED\?LETTERS=\.\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: lowerfirst = "juMBlEd\?lETteRS=\.\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: lowerfirst\+upper = "jUMBLED\?LETTERS=\.\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: upperfirst = "JuMBlEd\?lETteRS=\.\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: upperfirst\+lower = "Jumbled\?letters=\.\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: wild = "juMBlEd\\\?lETteRS=\.\\\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: regex = "juMBlEd\\\?lETteRS=\\\.\\\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: url = "juMBlEd%3FlETteRS%3D\.%2A"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: wild\+upper = "JUMBLED\\\?LETTERS=\.\\\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: regex\+upper = "JUMBLED\\\?LETTERS=\\\.\\\*"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: url\+upper = "JUMBLED%3FLETTERS%3D\.%2A"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: regex\+url\+upperfirst\+lower = "Jumbled%5C%3Fletters%3D%5C.%5C%2A"\r\n/, $msg1); - $self->assert_matches(qr/X-Cassandane-Test: regex\+url\+upper\+len = "33"\r\n/, $msg1); + $self->assert_matches(qr/X-Cassandane-Test: len = "18"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: lower = "jumbled\?letters=\.\*"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: upper = "JUMBLED\?LETTERS=\.\*"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: lowerfirst = "juMBlEd\?lETteRS=\.\*"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: lowerfirst\+upper = "jUMBLED\?LETTERS=\.\*"\r\n/, + $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: upperfirst = "JuMBlEd\?lETteRS=\.\*"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: upperfirst\+lower = "Jumbled\?letters=\.\*"\r\n/, + $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: wild = "juMBlEd\\\?lETteRS=\.\\\*"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: regex = "juMBlEd\\\?lETteRS=\\\.\\\*"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: url = "juMBlEd%3FlETteRS%3D\.%2A"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: wild\+upper = "JUMBLED\\\?LETTERS=\.\\\*"\r\n/, + $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: regex\+upper = "JUMBLED\\\?LETTERS=\\\.\\\*"\r\n/, + $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: url\+upper = "JUMBLED%3FLETTERS%3D\.%2A"\r\n/, $msg1); + $self->assert_matches( + qr/X-Cassandane-Test: regex\+url\+upperfirst\+lower = "Jumbled%5C%3Fletters%3D%5C.%5C%2A"\r\n/, + $msg1 + ); + $self->assert_matches( + qr/X-Cassandane-Test: regex\+url\+upper\+len = "33"\r\n/, $msg1); } diff --git a/cassandane/tiny-tests/Sieve/variables_basic b/cassandane/tiny-tests/Sieve/variables_basic index a15aba5caa..6faa7a0d6b 100644 --- a/cassandane/tiny-tests/Sieve/variables_basic +++ b/cassandane/tiny-tests/Sieve/variables_basic @@ -2,20 +2,21 @@ use Cassandane::Tiny; sub test_variables_basic - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Actually create the target folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Actually create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.target"); - $imaptalk->create("INBOX.target.Folder1"); - $imaptalk->create("INBOX.target.Folder2"); + $imaptalk->create("INBOX.target"); + $imaptalk->create("INBOX.target.Folder1"); + $imaptalk->create("INBOX.target.Folder2"); - xlog $self, "Install a sieve script filing all mail into a nonexistant folder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => " \r\n Message\r\n 1 "); - $self->{instance}->deliver($msg1); + # should go in Folder1 + my $msg1 = $self->{gen}->generate(subject => " \r\n Message\r\n 1 "); + $self->{instance}->deliver($msg1); - # should go in Folder2 - my $msg2 = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg2); + # should go in Folder2 + my $msg2 = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg2); - # should fail to deliver and wind up in INBOX - my $msg3 = $self->{gen}->generate(subject => "Message 3"); - $self->{instance}->deliver($msg3); + # should fail to deliver and wind up in INBOX + my $msg3 = $self->{gen}->generate(subject => "Message 3"); + $self->{instance}->deliver($msg3); - # should not match the if, and file into target - my $msg4 = $self->{gen}->generate(subject => "Totally different"); - $self->{instance}->deliver($msg4); + # should not match the if, and file into target + my $msg4 = $self->{gen}->generate(subject => "Totally different"); + $self->{instance}->deliver($msg4); - $imaptalk->select("INBOX.target.Folder1"); - $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX.target.Folder1"); + $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); - $imaptalk->select("INBOX.target.Folder2"); - $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX.target.Folder2"); + $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); - $imaptalk->select("INBOX"); - $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX"); + $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); - $imaptalk->select("INBOX.target"); - $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX.target"); + $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/Sieve/variables_regex b/cassandane/tiny-tests/Sieve/variables_regex index 3f6fbdb805..de1e4e6428 100644 --- a/cassandane/tiny-tests/Sieve/variables_regex +++ b/cassandane/tiny-tests/Sieve/variables_regex @@ -2,20 +2,21 @@ use Cassandane::Tiny; sub test_variables_regex - :min_version_3_0 - :needs_component_sieve -{ - my ($self) = @_; + : min_version_3_0 + : needs_component_sieve { + my ($self) = @_; - xlog $self, "Actually create the target folder"; - my $imaptalk = $self->{store}->get_client(); + xlog $self, "Actually create the target folder"; + my $imaptalk = $self->{store}->get_client(); - $imaptalk->create("INBOX.target"); - $imaptalk->create("INBOX.target.Folder1"); - $imaptalk->create("INBOX.target.Folder2"); + $imaptalk->create("INBOX.target"); + $imaptalk->create("INBOX.target.Folder1"); + $imaptalk->create("INBOX.target.Folder2"); - xlog $self, "Install a sieve script filing all mail into a nonexistant folder"; - $self->{instance}->install_sieve_script(<{instance}->install_sieve_script( + <{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg1); + # should go in Folder1 + my $msg1 = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg1); - # should go in Folder2 - my $msg2 = $self->{gen}->generate(subject => "Message x2"); - $self->{instance}->deliver($msg2); + # should go in Folder2 + my $msg2 = $self->{gen}->generate(subject => "Message x2"); + $self->{instance}->deliver($msg2); - # should fail to deliver and wind up in INBOX - my $msg3 = $self->{gen}->generate(subject => "Message 3"); - $self->{instance}->deliver($msg3); + # should fail to deliver and wind up in INBOX + my $msg3 = $self->{gen}->generate(subject => "Message 3"); + $self->{instance}->deliver($msg3); - # should not match the if, and file into target - my $msg4 = $self->{gen}->generate(subject => "Totally different"); - $self->{instance}->deliver($msg4); + # should not match the if, and file into target + my $msg4 = $self->{gen}->generate(subject => "Totally different"); + $self->{instance}->deliver($msg4); - $imaptalk->select("INBOX.target.Folder1"); - $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX.target.Folder1"); + $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); - $imaptalk->select("INBOX.target.Folder2"); - $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX.target.Folder2"); + $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); - $imaptalk->select("INBOX"); - $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX"); + $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); - $imaptalk->select("INBOX.target"); - $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); + $imaptalk->select("INBOX.target"); + $self->assert_num_equals(1, $imaptalk->get_response_code('exists')); } diff --git a/cassandane/tiny-tests/UIDonly/copy_move b/cassandane/tiny-tests/UIDonly/copy_move index 9e8626f0c4..dbfac7c8a6 100644 --- a/cassandane/tiny-tests/UIDonly/copy_move +++ b/cassandane/tiny-tests/UIDonly/copy_move @@ -2,48 +2,46 @@ use Cassandane::Tiny; sub test_copy_move - :min_version_3_9 :NoAltNameSpace -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - my $folder = 'INBOX.foo'; - - xlog $self, "append some messages"; - my %exp; - my $N = 10; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); - - xlog $self, "create a second mailbox"; - my $res =$imaptalk->create($folder); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - - xlog $self, "ENABLE UIDONLY"; - $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); - $self->assert_num_equals(1, $res->{uidonly}); - - xlog $self, "attempt COPY"; - $res = $imaptalk->copy(1, $folder); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # get_response_code() doesn't (yet) handle [UIDREQUIRED] - $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); - - xlog $self, "attempt MOVE"; - $res = $imaptalk->move(1, $folder); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # get_response_code() doesn't (yet) handle [UIDREQUIRED] - $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); - - xlog $self, "UID MOVE"; - $res = $imaptalk->_imap_cmd('UID MOVE', 1, 'vanished', '1', $folder); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('1', $res->[0]); + : min_version_3_9 : NoAltNameSpace { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + my $folder = 'INBOX.foo'; + + xlog $self, "append some messages"; + my %exp; + my $N = 10; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); + + xlog $self, "create a second mailbox"; + my $res = $imaptalk->create($folder); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + + xlog $self, "ENABLE UIDONLY"; + $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); + $self->assert_num_equals(1, $res->{uidonly}); + + xlog $self, "attempt COPY"; + $res = $imaptalk->copy(1, $folder); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # get_response_code() doesn't (yet) handle [UIDREQUIRED] + $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); + + xlog $self, "attempt MOVE"; + $res = $imaptalk->move(1, $folder); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # get_response_code() doesn't (yet) handle [UIDREQUIRED] + $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); + + xlog $self, "UID MOVE"; + $res = $imaptalk->_imap_cmd('UID MOVE', 1, 'vanished', '1', $folder); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('1', $res->[0]); } 1; diff --git a/cassandane/tiny-tests/UIDonly/expunge b/cassandane/tiny-tests/UIDonly/expunge index c0699e8986..5c09a64f1d 100644 --- a/cassandane/tiny-tests/UIDonly/expunge +++ b/cassandane/tiny-tests/UIDonly/expunge @@ -2,35 +2,33 @@ use Cassandane::Tiny; sub test_expunge - :min_version_3_9 -{ - my ($self) = @_; + : min_version_3_9 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "append some messages"; - my %exp; - my $N = 10; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); + xlog $self, "append some messages"; + my %exp; + my $N = 10; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); - xlog $self, "delete the 1st and 6th"; - $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "delete the 1st and 6th"; + $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "ENABLE UIDONLY"; - $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); - $self->assert_num_equals(1, $res->{uidonly}); + xlog $self, "ENABLE UIDONLY"; + $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); + $self->assert_num_equals(1, $res->{uidonly}); - xlog $self, "EXPUNGE"; - $res = $imaptalk->_imap_cmd('EXPUNGE', 0, 'vanished'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals('1,6', $res->[0]); + xlog $self, "EXPUNGE"; + $res = $imaptalk->_imap_cmd('EXPUNGE', 0, 'vanished'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals('1,6', $res->[0]); } 1; diff --git a/cassandane/tiny-tests/UIDonly/fetch b/cassandane/tiny-tests/UIDonly/fetch index 453b5d4399..b057512d50 100644 --- a/cassandane/tiny-tests/UIDonly/fetch +++ b/cassandane/tiny-tests/UIDonly/fetch @@ -2,70 +2,68 @@ use Cassandane::Tiny; sub test_fetch - :min_version_3_9 -{ - my ($self) = @_; + : min_version_3_9 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "append some messages"; - my %exp; - my $N = 10; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); + xlog $self, "append some messages"; + my %exp; + my $N = 10; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); - xlog $self, "EXPUNGE the 1st and 6th"; - $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $imaptalk->expunge(); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "EXPUNGE the 1st and 6th"; + $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $imaptalk->expunge(); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "FETCH all UID + FLAGS"; - my $res = $imaptalk->fetch('1:*', '(UID FLAGS)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_str_equals($res->{'1'}->{uid}, "2"); - $self->assert_str_equals($res->{'2'}->{uid}, "3"); - $self->assert_str_equals($res->{'3'}->{uid}, "4"); - $self->assert_str_equals($res->{'4'}->{uid}, "5"); - $self->assert_str_equals($res->{'5'}->{uid}, "7"); - $self->assert_str_equals($res->{'6'}->{uid}, "8"); - $self->assert_str_equals($res->{'7'}->{uid}, "9"); - $self->assert_str_equals($res->{'8'}->{uid}, "10"); + xlog $self, "FETCH all UID + FLAGS"; + my $res = $imaptalk->fetch('1:*', '(UID FLAGS)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_str_equals($res->{'1'}->{uid}, "2"); + $self->assert_str_equals($res->{'2'}->{uid}, "3"); + $self->assert_str_equals($res->{'3'}->{uid}, "4"); + $self->assert_str_equals($res->{'4'}->{uid}, "5"); + $self->assert_str_equals($res->{'5'}->{uid}, "7"); + $self->assert_str_equals($res->{'6'}->{uid}, "8"); + $self->assert_str_equals($res->{'7'}->{uid}, "9"); + $self->assert_str_equals($res->{'8'}->{uid}, "10"); - xlog $self, "ENABLE UIDONLY"; - $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); - $self->assert_num_equals(1, $res->{uidonly}); + xlog $self, "ENABLE UIDONLY"; + $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); + $self->assert_num_equals(1, $res->{uidonly}); - xlog $self, "attempt FETCH again"; - $res = $imaptalk->fetch('1:*', '(UID FLAGS)'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # get_response_code() doesn't (yet) handle [UIDREQUIRED] - $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); + xlog $self, "attempt FETCH again"; + $res = $imaptalk->fetch('1:*', '(UID FLAGS)'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # get_response_code() doesn't (yet) handle [UIDREQUIRED] + $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); - xlog $self, "UID FETCH all FLAGS"; - my %fetched = $self->uidonly_cmd($imaptalk, 'UID FETCH', '1:10', '(FLAGS)'); - $self->assert(exists $fetched{'2'}); - # make sure UID isn't in the response - $self->assert(not exists $fetched{'2'}->{uid}); - $self->assert(exists $fetched{'2'}->{flags}); - $self->assert(exists $fetched{'3'}); - $self->assert(exists $fetched{'4'}); - $self->assert(exists $fetched{'5'}); - $self->assert(exists $fetched{'7'}); - $self->assert(exists $fetched{'8'}); - $self->assert(exists $fetched{'9'}); - $self->assert(exists $fetched{'10'}); + xlog $self, "UID FETCH all FLAGS"; + my %fetched = $self->uidonly_cmd($imaptalk, 'UID FETCH', '1:10', '(FLAGS)'); + $self->assert(exists $fetched{'2'}); + # make sure UID isn't in the response + $self->assert(not exists $fetched{'2'}->{uid}); + $self->assert(exists $fetched{'2'}->{flags}); + $self->assert(exists $fetched{'3'}); + $self->assert(exists $fetched{'4'}); + $self->assert(exists $fetched{'5'}); + $self->assert(exists $fetched{'7'}); + $self->assert(exists $fetched{'8'}); + $self->assert(exists $fetched{'9'}); + $self->assert(exists $fetched{'10'}); - xlog $self, "UID FETCH 2 UID"; - %fetched = $self->uidonly_cmd($imaptalk, 'UID FETCH', '2', '(UID)'); - $self->assert(exists $fetched{'2'}); - # make sure UID is in the response - $self->assert_num_equals(2, $fetched{'2'}->{uid}); + xlog $self, "UID FETCH 2 UID"; + %fetched = $self->uidonly_cmd($imaptalk, 'UID FETCH', '2', '(UID)'); + $self->assert(exists $fetched{'2'}); + # make sure UID is in the response + $self->assert_num_equals(2, $fetched{'2'}->{uid}); } 1; diff --git a/cassandane/tiny-tests/UIDonly/qresync b/cassandane/tiny-tests/UIDonly/qresync index 08393a483f..f84753df25 100644 --- a/cassandane/tiny-tests/UIDonly/qresync +++ b/cassandane/tiny-tests/UIDonly/qresync @@ -2,50 +2,49 @@ use Cassandane::Tiny; sub test_qresync - :min_version_3_9 -{ - my ($self) = @_; - - my $imaptalk = $self->{store}->get_client(); - - xlog $self, "Deliver a message"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); - - $imaptalk->select("INBOX"); - my $uidvalidity = $imaptalk->get_response_code('uidvalidity'); - - xlog $self, "ENABLE QRESYNC"; - my $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'QRESYNC'); - $self->assert_num_equals(1, $res->{qresync}); - - xlog "QResync mailbox with message sequence map"; - $imaptalk->unselect(); - $imaptalk->select("INBOX", "(QRESYNC ($uidvalidity 0 1 (1 1)))" => 1); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $res = $imaptalk->get_response_code('fetch'); - $self->assert_not_null($res); - $self->assert(exists $res->{'1'}{'flags'}); - $self->assert(exists $res->{'1'}{'modseq'}); - $self->assert(exists $res->{'1'}{'uid'}); - - xlog $self, "ENABLE UIDONLY"; - $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); - $self->assert_num_equals(1, $res->{uidonly}); - - xlog "attempt to QResync mailbox with message sequence map"; - $imaptalk->unselect(); - $imaptalk->select("INBOX", "(QRESYNC ($uidvalidity 0 1 (1 1)))" => 1); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - - xlog "QResync mailbox"; - my %fetched = $self->uidonly_cmd($imaptalk, 'SELECT', - "INBOX", "(QRESYNC ($uidvalidity 0 1))"); - $self->assert(exists $fetched{'1'}); - # make sure UID isn't in the response - $self->assert(not exists $fetched{'1'}->{uid}); - $self->assert(exists $fetched{'1'}->{flags}); - $self->assert(exists $fetched{'1'}->{modseq}); + : min_version_3_9 { + my ($self) = @_; + + my $imaptalk = $self->{store}->get_client(); + + xlog $self, "Deliver a message"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); + + $imaptalk->select("INBOX"); + my $uidvalidity = $imaptalk->get_response_code('uidvalidity'); + + xlog $self, "ENABLE QRESYNC"; + my $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'QRESYNC'); + $self->assert_num_equals(1, $res->{qresync}); + + xlog "QResync mailbox with message sequence map"; + $imaptalk->unselect(); + $imaptalk->select("INBOX", "(QRESYNC ($uidvalidity 0 1 (1 1)))" => 1); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $res = $imaptalk->get_response_code('fetch'); + $self->assert_not_null($res); + $self->assert(exists $res->{'1'}{'flags'}); + $self->assert(exists $res->{'1'}{'modseq'}); + $self->assert(exists $res->{'1'}{'uid'}); + + xlog $self, "ENABLE UIDONLY"; + $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); + $self->assert_num_equals(1, $res->{uidonly}); + + xlog "attempt to QResync mailbox with message sequence map"; + $imaptalk->unselect(); + $imaptalk->select("INBOX", "(QRESYNC ($uidvalidity 0 1 (1 1)))" => 1); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + + xlog "QResync mailbox"; + my %fetched = $self->uidonly_cmd($imaptalk, 'SELECT', + "INBOX", "(QRESYNC ($uidvalidity 0 1))"); + $self->assert(exists $fetched{'1'}); + # make sure UID isn't in the response + $self->assert(not exists $fetched{'1'}->{uid}); + $self->assert(exists $fetched{'1'}->{flags}); + $self->assert(exists $fetched{'1'}->{modseq}); } 1; diff --git a/cassandane/tiny-tests/UIDonly/search b/cassandane/tiny-tests/UIDonly/search index 623fde8415..a210823504 100644 --- a/cassandane/tiny-tests/UIDonly/search +++ b/cassandane/tiny-tests/UIDonly/search @@ -2,71 +2,69 @@ use Cassandane::Tiny; sub test_search - :min_version_3_9 -{ - my ($self) = @_; + : min_version_3_9 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "append some messages"; - my %exp; - my $N = 10; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); + xlog $self, "append some messages"; + my %exp; + my $N = 10; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); - xlog $self, "delete the 1st and 6th"; - $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + xlog $self, "delete the 1st and 6th"; + $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - xlog $self, "SEARCH"; - $res = $imaptalk->search('not', 'deleted'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(8, scalar @{$res}); - $self->assert_num_equals(2, $res->[0]); - $self->assert_num_equals(3, $res->[1]); - $self->assert_num_equals(4, $res->[2]); - $self->assert_num_equals(5, $res->[3]); - $self->assert_num_equals(7, $res->[4]); - $self->assert_num_equals(8, $res->[5]); - $self->assert_num_equals(9, $res->[6]); - $self->assert_num_equals(10, $res->[7]); + xlog $self, "SEARCH"; + $res = $imaptalk->search('not', 'deleted'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(8, scalar @{$res}); + $self->assert_num_equals(2, $res->[0]); + $self->assert_num_equals(3, $res->[1]); + $self->assert_num_equals(4, $res->[2]); + $self->assert_num_equals(5, $res->[3]); + $self->assert_num_equals(7, $res->[4]); + $self->assert_num_equals(8, $res->[5]); + $self->assert_num_equals(9, $res->[6]); + $self->assert_num_equals(10, $res->[7]); - xlog $self, "ENABLE UIDONLY"; - $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); - $self->assert_num_equals(1, $res->{uidonly}); + xlog $self, "ENABLE UIDONLY"; + $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY'); + $self->assert_num_equals(1, $res->{uidonly}); - xlog $self, "attempt SEARCH"; - $res = $imaptalk->search('not', 'deleted'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # get_response_code() doesn't (yet) handle [UIDREQUIRED] - $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); + xlog $self, "attempt SEARCH"; + $res = $imaptalk->search('not', 'deleted'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # get_response_code() doesn't (yet) handle [UIDREQUIRED] + $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); - xlog $self, "attempt UID SEARCH with msgnos"; - $res = $imaptalk->_imap_cmd('UID SEARCH', 1, 'search', '1:10'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + xlog $self, "attempt UID SEARCH with msgnos"; + $res = $imaptalk->_imap_cmd('UID SEARCH', 1, 'search', '1:10'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - xlog $self, "UID SEARCH"; - $res = $imaptalk->_imap_cmd('UID SEARCH', 1, 'search', 'not', 'deleted'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(8, scalar @{$res}); - $self->assert_num_equals(2, $res->[0]); - $self->assert_num_equals(3, $res->[1]); - $self->assert_num_equals(4, $res->[2]); - $self->assert_num_equals(5, $res->[3]); - $self->assert_num_equals(7, $res->[4]); - $self->assert_num_equals(8, $res->[5]); - $self->assert_num_equals(9, $res->[6]); - $self->assert_num_equals(10, $res->[7]); + xlog $self, "UID SEARCH"; + $res = $imaptalk->_imap_cmd('UID SEARCH', 1, 'search', 'not', 'deleted'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(8, scalar @{$res}); + $self->assert_num_equals(2, $res->[0]); + $self->assert_num_equals(3, $res->[1]); + $self->assert_num_equals(4, $res->[2]); + $self->assert_num_equals(5, $res->[3]); + $self->assert_num_equals(7, $res->[4]); + $self->assert_num_equals(8, $res->[5]); + $self->assert_num_equals(9, $res->[6]); + $self->assert_num_equals(10, $res->[7]); - xlog $self, "UID SEARCH with UIDs"; - $res = $imaptalk->_imap_cmd('UID SEARCH', 1, 'search', 'uid', '1:10'); - $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); - $self->assert_num_equals(10, scalar @{$res}); + xlog $self, "UID SEARCH with UIDs"; + $res = $imaptalk->_imap_cmd('UID SEARCH', 1, 'search', 'uid', '1:10'); + $self->assert_str_equals('ok', $imaptalk->get_last_completion_response()); + $self->assert_num_equals(10, scalar @{$res}); } 1; diff --git a/cassandane/tiny-tests/UIDonly/store b/cassandane/tiny-tests/UIDonly/store index 1bf25a96d6..09ed6124e6 100644 --- a/cassandane/tiny-tests/UIDonly/store +++ b/cassandane/tiny-tests/UIDonly/store @@ -2,44 +2,42 @@ use Cassandane::Tiny; sub test_store - :min_version_3_9 -{ - my ($self) = @_; + : min_version_3_9 { + my ($self) = @_; - my $imaptalk = $self->{store}->get_client(); + my $imaptalk = $self->{store}->get_client(); - xlog $self, "append some messages"; - my %exp; - my $N = 10; - for (1..$N) - { - my $msg = $self->make_message("Message $_"); - $exp{$_} = $msg; - } - xlog $self, "check the messages got there"; - $self->check_messages(\%exp); + xlog $self, "append some messages"; + my %exp; + my $N = 10; + for (1 .. $N) { + my $msg = $self->make_message("Message $_"); + $exp{$_} = $msg; + } + xlog $self, "check the messages got there"; + $self->check_messages(\%exp); - xlog $self, "ENABLE UIDONLY & CONDSTORE"; - $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY', 'CONDSTORE'); - $self->assert_num_equals(1, $res->{uidonly}); - $self->assert_num_equals(1, $res->{condstore}); + xlog $self, "ENABLE UIDONLY & CONDSTORE"; + $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY', 'CONDSTORE'); + $self->assert_num_equals(1, $res->{uidonly}); + $self->assert_num_equals(1, $res->{condstore}); - xlog $self, "attempt STORE"; - $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); - $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); - # get_response_code() doesn't (yet) handle [UIDREQUIRED] - $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); + xlog $self, "attempt STORE"; + $imaptalk->store('1,6', '+FLAGS', '(\\Deleted)'); + $self->assert_str_equals('bad', $imaptalk->get_last_completion_response()); + # get_response_code() doesn't (yet) handle [UIDREQUIRED] + $self->assert_matches(qr/\[UIDREQUIRED\]/, $imaptalk->get_last_error()); - xlog $self, "UID STORE"; - my %fetched = $self->uidonly_cmd($imaptalk, 'UID STORE', - '1,6', '+FLAGS', '(\\Deleted)'); - $self->assert(exists $fetched{'1'}); - $self->assert_str_equals('\\Deleted', $fetched{'1'}->{flags}[0]); - # make sure that MODSEQ is also in the response - $self->assert(exists $fetched{'1'}->{modseq}); - $self->assert(exists $fetched{'6'}); - $self->assert_str_equals('\\Deleted', $fetched{'6'}->{flags}[0]); - $self->assert(exists $fetched{'6'}->{modseq}); + xlog $self, "UID STORE"; + my %fetched = $self->uidonly_cmd($imaptalk, 'UID STORE', + '1,6', '+FLAGS', '(\\Deleted)'); + $self->assert(exists $fetched{'1'}); + $self->assert_str_equals('\\Deleted', $fetched{'1'}->{flags}[0]); + # make sure that MODSEQ is also in the response + $self->assert(exists $fetched{'1'}->{modseq}); + $self->assert(exists $fetched{'6'}); + $self->assert_str_equals('\\Deleted', $fetched{'6'}->{flags}[0]); + $self->assert(exists $fetched{'6'}->{modseq}); } 1; diff --git a/cassandane/tiny-tests/UIDonly/unsolicited b/cassandane/tiny-tests/UIDonly/unsolicited index d3f0ff8688..4b906c9f92 100644 --- a/cassandane/tiny-tests/UIDonly/unsolicited +++ b/cassandane/tiny-tests/UIDonly/unsolicited @@ -2,41 +2,40 @@ use Cassandane::Tiny; sub test_unsolicited - :min_version_3_9 :NoAltNameSpace -{ - my ($self) = @_; - - xlog $self, "Deliver some messages"; - my $msg = $self->{gen}->generate(subject => "Message 1"); - $self->{instance}->deliver($msg); - $msg = $self->{gen}->generate(subject => "Message 2"); - $self->{instance}->deliver($msg); - - my $imaptalk = $self->{store}->get_client(); - my $res = $imaptalk->select('INBOX'); - - xlog $self, "Expunge first message"; - $imaptalk->store('1', '+flags', '\\deleted'); - $imaptalk->expunge(); - - xlog $self, "ENABLE UIDONLY & CONDSTORE"; - $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY', 'CONDSTORE'); - $self->assert_num_equals(1, $res->{uidonly}); - $self->assert_num_equals(1, $res->{uidonly}); - - my $admintalk = $self->{adminstore}->get_client(); - $res = $admintalk->select('user.cassandane'); - - xlog $self, "set flag in another session"; - $admintalk->store('1', '+flags', '\\flagged'); - - xlog $self, "poll for changes"; - my %fetched = $self->uidonly_cmd($imaptalk, 'NOOP'); - $self->assert(exists $fetched{'2'}); - # make sure UID isn't in the response - $self->assert(not exists $fetched{'2'}->{uid}); - $self->assert(exists $fetched{'2'}->{flags}); - $self->assert(exists $fetched{'2'}->{modseq}); + : min_version_3_9 : NoAltNameSpace { + my ($self) = @_; + + xlog $self, "Deliver some messages"; + my $msg = $self->{gen}->generate(subject => "Message 1"); + $self->{instance}->deliver($msg); + $msg = $self->{gen}->generate(subject => "Message 2"); + $self->{instance}->deliver($msg); + + my $imaptalk = $self->{store}->get_client(); + my $res = $imaptalk->select('INBOX'); + + xlog $self, "Expunge first message"; + $imaptalk->store('1', '+flags', '\\deleted'); + $imaptalk->expunge(); + + xlog $self, "ENABLE UIDONLY & CONDSTORE"; + $res = $imaptalk->_imap_cmd('ENABLE', 0, 'enabled', 'UIDONLY', 'CONDSTORE'); + $self->assert_num_equals(1, $res->{uidonly}); + $self->assert_num_equals(1, $res->{uidonly}); + + my $admintalk = $self->{adminstore}->get_client(); + $res = $admintalk->select('user.cassandane'); + + xlog $self, "set flag in another session"; + $admintalk->store('1', '+flags', '\\flagged'); + + xlog $self, "poll for changes"; + my %fetched = $self->uidonly_cmd($imaptalk, 'NOOP'); + $self->assert(exists $fetched{'2'}); + # make sure UID isn't in the response + $self->assert(not exists $fetched{'2'}->{uid}); + $self->assert(exists $fetched{'2'}->{flags}); + $self->assert(exists $fetched{'2'}->{modseq}); } 1; diff --git a/perl/annotator/AnnotateInlinedCIDs.pm b/perl/annotator/AnnotateInlinedCIDs.pm index 061d16ed96..68df3da34c 100644 --- a/perl/annotator/AnnotateInlinedCIDs.pm +++ b/perl/annotator/AnnotateInlinedCIDs.pm @@ -1,7 +1,6 @@ use warnings; use strict; - package AnnotateInlinedCIDs; use Cyrus::Annotator::Daemon; use JSON; @@ -11,46 +10,46 @@ our @ISA = qw(Cyrus::Annotator::Daemon); use constant INLINEDCIDSNAME => "/vendor/jmap.io/inlinedcids"; sub MakeInlinedCIDs { - my ($message) = @_; - - my %CidData; - my $BS = $message->bodystructure(); - - # Keep looking into multipart types - my @Parts = ($BS); - do {{ # needed, see perlsyn - $BS = shift @Parts; - - # Multipart, search all sub-parts - if ($BS->{'MIME-Type'} eq 'multipart') { - push @Parts, @{$BS->{'MIME-Subparts'} || []}; - next; - - } elsif ($BS->{'MIME-TxtType'} eq "text/html") { - # A HTML body, check all cid: URLs in tags - my $Content = $message->read_part_content($BS); - - # Pick all tags with cid: URLs - while ( $Content =~ /]*src\s*=\s*"cid:(.+?)"[^>]*>/g ) { - # Format the URL as how it will show up as Content-ID - my $cid = "<" . uri_unescape($1) . ">"; - $CidData{$cid} = $BS->{'IMAP-Partnum'}; - } - + my ($message) = @_; + + my %CidData; + my $BS = $message->bodystructure(); + + # Keep looking into multipart types + my @Parts = ($BS); + do { { # needed, see perlsyn + $BS = shift @Parts; + + # Multipart, search all sub-parts + if ($BS->{'MIME-Type'} eq 'multipart') { + push @Parts, @{ $BS->{'MIME-Subparts'} || [] }; + next; + + } elsif ($BS->{'MIME-TxtType'} eq "text/html") { + # A HTML body, check all cid: URLs in tags + my $Content = $message->read_part_content($BS); + + # Pick all tags with cid: URLs + while ($Content =~ /]*src\s*=\s*"cid:(.+?)"[^>]*>/g) { + # Format the URL as how it will show up as Content-ID + my $cid = "<" . uri_unescape($1) . ">"; + $CidData{$cid} = $BS->{'IMAP-Partnum'}; } - }} while (@Parts); - return %CidData ? \%CidData : undef; + } + } } while (@Parts); + + return %CidData ? \%CidData : undef; } sub annotate_message { - my ( $self, $message ) = @_; + my ($self, $message) = @_; - my $InlinedCIDs = MakeInlinedCIDs($message); - $message->set_shared_annotation(INLINEDCIDSNAME, encode_json($InlinedCIDs)) - if defined $InlinedCIDs; + my $InlinedCIDs = MakeInlinedCIDs($message); + $message->set_shared_annotation(INLINEDCIDSNAME, encode_json($InlinedCIDs)) + if defined $InlinedCIDs; - return 0; + return 0; } AnnotateInlinedCIDs->run(); diff --git a/perl/annotator/Daemon.pm b/perl/annotator/Daemon.pm index 4c2382ace5..a7e1bf2fb8 100755 --- a/perl/annotator/Daemon.pm +++ b/perl/annotator/Daemon.pm @@ -42,6 +42,7 @@ use warnings; use strict; + package Cyrus::Annotator::Daemon; use base qw(Net::Server); # use Data::Dumper; @@ -52,12 +53,12 @@ use Encode qw(decode); our $VERSION = '1.00'; -use constant USER => 'cyrus'; -use constant GROUP => 'mail'; -use constant RUNPREFIX => '/var/run/annotatord'; -use constant APPNAME => 'annotatord'; -use constant PIDFILE => RUNPREFIX . '.pid'; -use constant SOCKPATH => RUNPREFIX . '.socket'; +use constant USER => 'cyrus'; +use constant GROUP => 'mail'; +use constant RUNPREFIX => '/var/run/annotatord'; +use constant APPNAME => 'annotatord'; +use constant PIDFILE => RUNPREFIX . '.pid'; +use constant SOCKPATH => RUNPREFIX . '.socket'; # Levels are: LOG_DEBUG (7), LOG_INFO (6), *LOG_NOTICE (5), LOG_WARNING (4), LOG_ERR (3) use constant LOG_LEVEL => LOG_INFO; @@ -107,30 +108,29 @@ Cyrus::Annotator::Daemon has the following methods. =cut my @default_args = ( - personality => 'Net::Server', - appname => APPNAME, - - user => USER, - group => GROUP, - pid_file => PIDFILE, - background => 1, - size_limit => 256, - - syslog_level => LOG_LEVEL, - syslog_facility => LOG_LOCAL6, - syslog_ident => APPNAME, - log_file => 'Sys::Syslog', - - proto => 'unix', - port => SOCKPATH . '|SOCK_STREAM|unix' + personality => 'Net::Server', + appname => APPNAME, + + user => USER, + group => GROUP, + pid_file => PIDFILE, + background => 1, + size_limit => 256, + + syslog_level => LOG_LEVEL, + syslog_facility => LOG_LOCAL6, + syslog_ident => APPNAME, + log_file => 'Sys::Syslog', + + proto => 'unix', + port => SOCKPATH . '|SOCK_STREAM|unix' ); -sub new -{ - my ($class, @args) = @_; - my $self = $class->SUPER::new(@args); +sub new { + my ($class, @args) = @_; + my $self = $class->SUPER::new(@args); - return $self; + return $self; } =item I @@ -162,14 +162,12 @@ socket bound to I. =cut -sub run -{ - my ($class, @args) = @_; - my %aa = (@default_args, @args); - return $class->SUPER::run(%aa); +sub run { + my ($class, @args) = @_; + my %aa = (@default_args, @args); + return $class->SUPER::run(%aa); } - # Can pass a file handle or string # Returns two item list of ($ParsedData, $Remainder) # All lines must be \r\n terminated @@ -279,13 +277,13 @@ sub _dlist_parse { die "Unexpected end of line in IMAP response : '$Line'"; } # Otherwise fine, we're about to exit anyway - } - else { - die "Error parsing atom in IMAP response : '" . substr($Line, pos($Line), 100) . "'"; + } else { + die "Error parsing atom in IMAP response : '" + . substr($Line, pos($Line), 100) . "'"; } - # Repeat while we're within brackets + # Repeat while we're within brackets } while (scalar @AtomStack); my $Remainder = substr($Line, pos($Line)); @@ -295,18 +293,18 @@ sub _dlist_parse { sub _parse_list_to_hash { my $ContentHashList = shift || []; - my $Recursive = shift; + my $Recursive = shift; - ref($ContentHashList) eq 'ARRAY' || return { }; + ref($ContentHashList) eq 'ARRAY' || return {}; my %Res; while (@$ContentHashList) { my ($Param, $Val) = (shift @$ContentHashList, shift @$ContentHashList); - $Val = _parse_list_to_hash($Val, $Recursive-1) + $Val = _parse_list_to_hash($Val, $Recursive - 1) if (ref($Val) && $Recursive); - $Res{lc($Param)} = $Val; + $Res{ lc($Param) } = $Val; } return \%Res; @@ -326,7 +324,8 @@ sub _parse_bodystructure { my ($Part, @SubParts); for ($Part = 1; ref($Bs->[0]); $Part++) { my $SubPartNum = ($PartNum ? $PartNum . "." : "") . $Part; - my $Res = _parse_bodystructure(shift(@$Bs), $IncludeRaw, $DecodeUTF8, $SubPartNum, 1); + my $Res = _parse_bodystructure(shift(@$Bs), $IncludeRaw, $DecodeUTF8, + $SubPartNum, 1); push @SubParts, $Res; } @@ -340,7 +339,7 @@ sub _parse_bodystructure { 'Content-Language', shift(@$Bs), 'Content-Location', shift(@$Bs), # Shouldn't be anything after this. Add as remainder if there is - 'Remainder', $Bs + 'Remainder', $Bs ); } @@ -357,36 +356,39 @@ sub _parse_bodystructure { # Pull out special fields for 'text' or 'message/rfc822' types if ($MimeType eq 'text') { - %Res = ( - 'Lines', splice(@$Bs, 5, 1) - ); + %Res = ('Lines', splice(@$Bs, 5, 1)); } elsif ($MimeType eq 'message' && $MimeSubtype eq 'rfc822') { # message/rfc822 includes the messages envelope and bodystructure my @MsgParts = splice(@$Bs, 5, 3); %Res = ( - 'Message-Envelope', _parse_envelope(shift(@MsgParts), $IncludeRaw, $DecodeUTF8), - 'Message-Bodystructure', _parse_bodystructure(shift(@MsgParts), $IncludeRaw, $DecodeUTF8, $PartNum), - 'Message-Lines', shift(@MsgParts) + 'Message-Envelope', + _parse_envelope(shift(@MsgParts), $IncludeRaw, $DecodeUTF8), + 'Message-Bodystructure', + _parse_bodystructure( + shift(@MsgParts), $IncludeRaw, $DecodeUTF8, $PartNum + ), + 'Message-Lines', + shift(@MsgParts) ); } # All normal mime-entities have these parts %Res = ( %Res, - 'MIME-Type', $MimeType, - 'MIME-Subtype', $MimeSubtype, - 'Content-Type', _parse_list_to_hash(shift(@$Bs)), - 'Content-ID', shift(@$Bs), - 'Content-Description', shift(@$Bs), - 'Content-Transfer-Encoding', shift(@$Bs), - 'Size', shift(@$Bs), - 'Content-MD5', shift(@$Bs), - 'Content-Disposition', _parse_list_to_hash(shift(@$Bs), 1), - 'Content-Language', shift(@$Bs), - 'Content-Location', shift(@$Bs), + 'MIME-Type', $MimeType, + 'MIME-Subtype', $MimeSubtype, + 'Content-Type', _parse_list_to_hash(shift(@$Bs)), + 'Content-ID', shift(@$Bs), + 'Content-Description', shift(@$Bs), + 'Content-Transfer-Encoding', shift(@$Bs), + 'Size', shift(@$Bs), + 'Content-MD5', shift(@$Bs), + 'Content-Disposition', _parse_list_to_hash(shift(@$Bs), 1), + 'Content-Language', shift(@$Bs), + 'Content-Location', shift(@$Bs), # Shouldn't be anything after this. Add as remainder if there is - 'Remainder', $Bs + 'Remainder', $Bs ); # Extra information for the annotation callout - gnb 20110420 @@ -394,7 +396,7 @@ sub _parse_bodystructure { if ($Extra) { $Extra = _parse_list_to_hash($Extra, 0); # Make casing consistent for users - $Res{Offset} = $Extra->{offset}; + $Res{Offset} = $Extra->{offset}; $Res{HeaderSize} = $Extra->{headersize}; } @@ -409,7 +411,7 @@ sub _parse_bodystructure { # Regexps used to determine if header is MIME encoded (we remove . from # especials because of dumb ANSI_X3.4-1968 encoding) -my $RFC2047Token = qr/[^\x00-\x1f\(\)\<\>\@\,\;\:\"\/\[\]\?\=\ ]+/; +my $RFC2047Token = qr/[^\x00-\x1f\(\)\<\>\@\,\;\:\"\/\[\]\?\=\ ]+/; my $NeedDecodeUTF8Regexp = qr/=\?$RFC2047Token\?$RFC2047Token\?[^\?]*\?=/; sub _parse_envelope { @@ -417,30 +419,43 @@ sub _parse_envelope { # Check envelope assumption scalar(@$Env) == 10 - || die "IMAPTalk: Wrong number of fields in envelope structure " . Dumper($Env); + || die "IMAPTalk: Wrong number of fields in envelope structure " + . Dumper($Env); - _decode_utf8($Env->[1]) if $DecodeUTF8 && defined($Env->[1]) && $Env->[1] =~ $NeedDecodeUTF8Regexp; + _decode_utf8($Env->[1]) + if $DecodeUTF8 && defined($Env->[1]) && $Env->[1] =~ $NeedDecodeUTF8Regexp; # Setup hash directly from envelope structure my %Res = ( - 'Date', $Env->[0], - 'Subject', $Env->[1], - 'From', _parse_email_address($Env->[2], $DecodeUTF8), - 'Sender', _parse_email_address($Env->[3], $DecodeUTF8), - 'Reply-To', _parse_email_address($Env->[4], $DecodeUTF8), - 'To', _parse_email_address($Env->[5], $DecodeUTF8), - 'Cc', _parse_email_address($Env->[6], $DecodeUTF8), - 'Bcc', _parse_email_address($Env->[7], $DecodeUTF8), - ($IncludeRaw ? ( - 'From-Raw', $Env->[2], - 'Sender-Raw', $Env->[3], - 'Reply-To-Raw',$Env->[4], - 'To-Raw', $Env->[5], - 'Cc-Raw', $Env->[6], - 'Bcc-Raw', $Env->[7], - ) : ()), - 'In-Reply-To', $Env->[8], - 'Message-ID', $Env->[9] + 'Date', + $Env->[0], + 'Subject', + $Env->[1], + 'From', + _parse_email_address($Env->[2], $DecodeUTF8), + 'Sender', + _parse_email_address($Env->[3], $DecodeUTF8), + 'Reply-To', + _parse_email_address($Env->[4], $DecodeUTF8), + 'To', + _parse_email_address($Env->[5], $DecodeUTF8), + 'Cc', + _parse_email_address($Env->[6], $DecodeUTF8), + 'Bcc', + _parse_email_address($Env->[7], $DecodeUTF8), + ( + $IncludeRaw + ? ( + 'From-Raw', $Env->[2], 'Sender-Raw', $Env->[3], + 'Reply-To-Raw', $Env->[4], 'To-Raw', $Env->[5], + 'Cc-Raw', $Env->[6], 'Bcc-Raw', $Env->[7], + ) + : () + ), + 'In-Reply-To', + $Env->[8], + 'Message-ID', + $Env->[9] ); return \%Res; @@ -448,15 +463,16 @@ sub _parse_envelope { sub _parse_email_address { my $EmailAddressList = shift || []; - my $DecodeUTF8 = shift; + my $DecodeUTF8 = shift; # Email addresses always come as a list of addresses (possibly in groups) - my @EmailGroups = ([ undef ]); + my @EmailGroups = ([undef]); foreach my $Adr (@$EmailAddressList) { # Check address assumption scalar(@$Adr) == 4 - || die "IMAPTalk: Wrong number of fields in email address structure " . Dumper($Adr); + || die "IMAPTalk: Wrong number of fields in email address structure " + . Dumper($Adr); # No hostname is start/end of group if (!defined $Adr->[0] && !defined $Adr->[3]) { @@ -465,26 +481,27 @@ sub _parse_email_address { } # Build 'ename@ecorp.com' part - my $EmailStr = (defined $Adr->[2] ? $Adr->[2] : '') - . '@' - . (defined $Adr->[3] ? $Adr->[3] : ''); - # If the email address has a name, add it at the start and put <> around address + my $EmailStr = (defined $Adr->[2] ? $Adr->[2] : '') . '@' + . (defined $Adr->[3] ? $Adr->[3] : ''); +# If the email address has a name, add it at the start and put <> around address if (defined $Adr->[0] and $Adr->[0] ne '') { - _decode_utf8($Adr->[0]) if $DecodeUTF8 && $Adr->[0] =~ $NeedDecodeUTF8Regexp; + _decode_utf8($Adr->[0]) + if $DecodeUTF8 && $Adr->[0] =~ $NeedDecodeUTF8Regexp; # Strip any existing \"'s $Adr->[0] =~ s/\"//g; $EmailStr = '"' . $Adr->[0] . '" <' . $EmailStr . '>'; } - push @{$EmailGroups[-1]}, $EmailStr; + push @{ $EmailGroups[-1] }, $EmailStr; } - # Join the results with commas between each address, and "groupname: adrs ;" for groups +# Join the results with commas between each address, and "groupname: adrs ;" for groups for (@EmailGroups) { my $GroupName = shift @$_; ($_ = undef), next if !defined $GroupName && !@$_; my $EmailAdrs = join ", ", @$_; - $_ = defined($GroupName) ? $GroupName . ': ' . $EmailAdrs . ';' : $EmailAdrs; + $_ + = defined($GroupName) ? $GroupName . ': ' . $EmailAdrs . ';' : $EmailAdrs; } return join " ", grep { defined $_ } @EmailGroups; @@ -498,91 +515,86 @@ sub _decode_utf8 { eval { $_[0] = decode('MIME-Header', $_[0]); }; } -sub _read_args -{ - my $Nbytes; - my $Data = ''; - - for (;;) { - $Nbytes = readline STDIN; - last unless defined $Nbytes; - chomp $Nbytes; - $Nbytes = 0 + $Nbytes; -# printf "nbytes=%d\n", $nbytes; - last if (!$Nbytes); - read STDIN, $Data, $Nbytes, length($Data); - } +sub _read_args { + my $Nbytes; + my $Data = ''; + + for (;;) { + $Nbytes = readline STDIN; + last unless defined $Nbytes; + chomp $Nbytes; + $Nbytes = 0 + $Nbytes; + # printf "nbytes=%d\n", $nbytes; + last if (!$Nbytes); + read STDIN, $Data, $Nbytes, length($Data); + } - return $Data; + return $Data; } -sub _format_string -{ - my ($s) = @_; +sub _format_string { + my ($s) = @_; - return "NIL" unless defined $s; + return "NIL" unless defined $s; - my $len = length($s); + my $len = length($s); - if ($len > 1024 || $s =~ m/[\\"\012\015\200-\377]/) { - # don't try to quote this, use a literal - return "{$len}\r\n$s"; - } - else { - return "\"$s\""; - } + if ($len > 1024 || $s =~ m/[\\"\012\015\200-\377]/) { + # don't try to quote this, use a literal + return "{$len}\r\n$s"; + } else { + return "\"$s\""; + } } -sub _emit_results -{ - my ($self, $message) = @_; - my @results; - my $sep = ''; +sub _emit_results { + my ($self, $message) = @_; + my @results; + my $sep = ''; - my ($flags, $annots) = $message->get_changed(); + my ($flags, $annots) = $message->get_changed(); - foreach my $a (@$annots) { - my ($entry, $type, $value) = @$a; - my $format_val = _format_string($value); - push @results, "ANNOTATION ($entry ($type $format_val))"; - } + foreach my $a (@$annots) { + my ($entry, $type, $value) = @$a; + my $format_val = _format_string($value); + push @results, "ANNOTATION ($entry ($type $format_val))"; + } - foreach my $f (@$flags) { - my ($name, $set) = @$f; - my $op = $set ? "+FLAGS" : "-FLAGS"; - push @results, "$op $name"; - } + foreach my $f (@$flags) { + my ($name, $set) = @$f; + my $op = $set ? "+FLAGS" : "-FLAGS"; + push @results, "$op $name"; + } - print "(" . join(' ', @results) . ")\n"; + print "(" . join(' ', @results) . ")\n"; } -sub process_request -{ - my ($self) = @_; +sub process_request { + my ($self) = @_; - eval { - $self->log(3, "Reading request"); - my $ArgsString = _read_args(); - die "Failed to read args" unless $ArgsString; + eval { + $self->log(3, "Reading request"); + my $ArgsString = _read_args(); + die "Failed to read args" unless $ArgsString; - my ($ArgsList, $Remainder) = _dlist_parse($ArgsString); - die "Failed to parse args $ArgsString" unless $ArgsList; + my ($ArgsList, $Remainder) = _dlist_parse($ArgsString); + die "Failed to parse args $ArgsString" unless $ArgsList; - my %ArgsHash = @$ArgsList; + my %ArgsHash = @$ArgsList; - # parse the argshash out here - $ArgsHash{BODYSTRUCTURE} = _parse_bodystructure(delete $ArgsHash{BODY}); + # parse the argshash out here + $ArgsHash{BODYSTRUCTURE} = _parse_bodystructure(delete $ArgsHash{BODY}); - my $message = Cyrus::Annotator::Message->new(%ArgsHash); + my $message = Cyrus::Annotator::Message->new(%ArgsHash); - $self->annotate_message($message); + $self->annotate_message($message); - $self->log(3, "Emitting result"); - $self->_emit_results($message); - }; - if ($@) { - $self->log(2, "Caught and ignored error: $@"); - } + $self->log(3, "Emitting result"); + $self->_emit_results($message); + }; + if ($@) { + $self->log(2, "Caught and ignored error: $@"); + } } =item I @@ -599,20 +611,18 @@ examined, and on which flags and annotations can be set. =cut -sub annotate_message -{ - my ($self, $message) = @_; +sub annotate_message { + my ($self, $message) = @_; - die "Please define an annotate_message() sub"; + die "Please define an annotate_message() sub"; } -sub post_configure -{ - my ($self) = @_; +sub post_configure { + my ($self) = @_; - unlink(SOCKPATH); + unlink(SOCKPATH); - $self->SUPER::post_configure(); + $self->SUPER::post_configure(); } =back diff --git a/perl/annotator/Message.pm b/perl/annotator/Message.pm index 418af474ea..53af27f830 100644 --- a/perl/annotator/Message.pm +++ b/perl/annotator/Message.pm @@ -44,9 +44,9 @@ use warnings; package Cyrus::Annotator::Message; -use MIME::Base64 qw(decode_base64); +use MIME::Base64 qw(decode_base64); use MIME::QuotedPrint qw(decode_qp); -use Encode qw(decode); +use Encode qw(decode); our $VERSION = '1.00'; @@ -102,42 +102,42 @@ Takes the following args: =cut sub new { - my $class = shift; - my %args = @_; + my $class = shift; + my %args = @_; - my %flags; - my %annots; + my %flags; + my %annots; - my $fs = $args{FLAGS} || []; - my $as = $args{ANNOTATIONS} || []; + my $fs = $args{FLAGS} || []; + my $as = $args{ANNOTATIONS} || []; - for my $name (@$fs) { - $flags{$name} = { - value => 1, - orig => 1, - }; - } + for my $name (@$fs) { + $flags{$name} = { + value => 1, + orig => 1, + }; + } - while (my $entry = shift @$as) { - my $rest = shift @$as; - my ($type, $value) = @$rest; - $annots{$entry}{$type} = { - value => $value, - orig => $value, - }; - } + while (my $entry = shift @$as) { + my $rest = shift @$as; + my ($type, $value) = @$rest; + $annots{$entry}{$type} = { + value => $value, + orig => $value, + }; + } - my $self = bless { - filename => $args{FILENAME}, - bodystructure => $args{BODYSTRUCTURE}, - guid => $args{GUID}, - header => $args{HEADER}, - flag => \%flags, - annot => \%annots, - }, ref($class) || $class; + my $self = bless { + filename => $args{FILENAME}, + bodystructure => $args{BODYSTRUCTURE}, + guid => $args{GUID}, + header => $args{HEADER}, + flag => \%flags, + annot => \%annots, + }, + ref($class) || $class; } - =item I returns a read-only filehandle to the raw (rfc822) representation @@ -146,18 +146,18 @@ of the full message. =cut sub fh { - my $self = shift; + my $self = shift; - unless ($self->{fh}) { - die "Need a filename" unless $self->{filename}; - require "IO/File.pm"; - $self->{fh} = IO::File->new($self->{filename}, 'r'); - } + unless ($self->{fh}) { + die "Need a filename" unless $self->{filename}; + require "IO/File.pm"; + $self->{fh} = IO::File->new($self->{filename}, 'r'); + } - # Move back to start of message - seek $self->{fh}, 0, 0; + # Move back to start of message + seek $self->{fh}, 0, 0; - return $self->{fh}; + return $self->{fh}; } =item I @@ -168,36 +168,35 @@ encoding and charset. =cut sub decode_part { - my $self = shift; - my ($Part, $Content) = @_; - - if (lc $Part->{'Content-Transfer-Encoding'} eq 'base64') { - # remove trailing partial value - $Content =~ tr{[A-Za-z0-9+/=]}{}cd; - my $extra = length($Content) % 4; - if ($extra) { - # warn "stripping $extra chars " . length($Content); - $Content = substr($Content, 0, -$extra); - } - $Content = decode_base64($Content); - } - elsif (lc $Part->{'Content-Transfer-Encoding'} eq 'quoted-printable') { - # remove trailing partial value - $Content =~ s/=.?$//; - $Content = decode_qp($Content); + my $self = shift; + my ($Part, $Content) = @_; + + if (lc $Part->{'Content-Transfer-Encoding'} eq 'base64') { + # remove trailing partial value + $Content =~ tr{[A-Za-z0-9+/=]}{}cd; + my $extra = length($Content) % 4; + if ($extra) { + # warn "stripping $extra chars " . length($Content); + $Content = substr($Content, 0, -$extra); } + $Content = decode_base64($Content); + } elsif (lc $Part->{'Content-Transfer-Encoding'} eq 'quoted-printable') { + # remove trailing partial value + $Content =~ s/=.?$//; + $Content = decode_qp($Content); + } - my $charset = lc($Part->{'Content-Type'}{charset} || 'iso-8859-1'); + my $charset = lc($Part->{'Content-Type'}{charset} || 'iso-8859-1'); - # If no charset is present, it defaults to ascii. But some systems - # send 8-bit data. For them, assume iso-8859-1, ascii is a subset anyway - $charset = 'iso-8859-1' - if $charset eq 'ascii' || $charset eq 'us-ascii'; + # If no charset is present, it defaults to ascii. But some systems + # send 8-bit data. For them, assume iso-8859-1, ascii is a subset anyway + $charset = 'iso-8859-1' + if $charset eq 'ascii' || $charset eq 'us-ascii'; - # Fix up some bogus formatted iso charsets - $charset =~ s/^(iso)[\-_]?(\d+)[\-_](\d+)[\-_]?\w*/$1-$2-$3/i; + # Fix up some bogus formatted iso charsets + $charset =~ s/^(iso)[\-_]?(\d+)[\-_](\d+)[\-_]?\w*/$1-$2-$3/i; - return eval { decode($charset, $Content) } || decode('iso-8859-1', $Content); + return eval { decode($charset, $Content) } || decode('iso-8859-1', $Content); } =item I @@ -210,34 +209,34 @@ If no 'nbytes' is passed, read the entire part. =cut sub read_part_content { - my $self = shift; - my ($Part, $nbytes) = @_; + my $self = shift; + my ($Part, $nbytes) = @_; - unless ($Part) { - $Part = $self->bodystructure(); - } + unless ($Part) { + $Part = $self->bodystructure(); + } - my $fh = $self->fh(); + my $fh = $self->fh(); - die "No Offset for part" - unless defined $Part->{Offset}; - die "No Size for part" - unless defined $Part->{Size}; + die "No Offset for part" + unless defined $Part->{Offset}; + die "No Size for part" + unless defined $Part->{Size}; - if (!defined($nbytes) || $Part->{Size} < $nbytes) { - $nbytes = $Part->{Size}; - } + if (!defined($nbytes) || $Part->{Size} < $nbytes) { + $nbytes = $Part->{Size}; + } - seek $fh, $Part->{Offset}, 0 - or die "Cannot seek: $!"; + seek $fh, $Part->{Offset}, 0 + or die "Cannot seek: $!"; - my $Content = ''; + my $Content = ''; - # Could be 0 length body, only die on undef (real error) - my $r = read($fh, $Content, $nbytes); - die "Cannot read: $!" if !defined $r; + # Could be 0 length body, only die on undef (real error) + my $r = read($fh, $Content, $nbytes); + die "Cannot read: $!" if !defined $r; - return $self->decode_part($Part, $Content); + return $self->decode_part($Part, $Content); } =item I @@ -247,14 +246,14 @@ returns a Mail::Header object containing all the headers of the message. =cut sub header { - my $self = shift; + my $self = shift; - unless ($self->{header}) { - require "Mail/Header.pm"; - $self->{header} = Mail::Header->new($self->fh()); - } + unless ($self->{header}) { + require "Mail/Header.pm"; + $self->{header} = Mail::Header->new($self->fh()); + } - return $self->{header}; + return $self->{header}; } =item I @@ -336,41 +335,40 @@ For example: =cut sub bodystructure { - my $self = shift; - return $self->{bodystructure}; + my $self = shift; + return $self->{bodystructure}; } - sub get_flag { - my $self = shift; - my ($name) = @_; + my $self = shift; + my ($name) = @_; - return $self->{flag}{$name}{value}; + return $self->{flag}{$name}{value}; } sub get_flags { - my $self = shift; - return grep { $self->{flag}{$_}{value} } keys %{$self->{flag}}; + my $self = shift; + return grep { $self->{flag}{$_}{value} } keys %{ $self->{flag} }; } sub set_flag_value { - my $self = shift; - my ($name, $value) = @_; - $self->{flag}{$name}{orig} = 0 - unless exists $self->{flag}{$name}{orig}; - $self->{flag}{$name}{value} = $value; + my $self = shift; + my ($name, $value) = @_; + $self->{flag}{$name}{orig} = 0 + unless exists $self->{flag}{$name}{orig}; + $self->{flag}{$name}{value} = $value; } sub set_flag { - my $self = shift; - my ($name) = @_; - $self->set_flag_value($name, 1); + my $self = shift; + my ($name) = @_; + $self->set_flag_value($name, 1); } sub clear_flag { - my $self = shift; - my ($name) = @_; - $self->set_flag_value($name, 0); + my $self = shift; + my ($name) = @_; + $self->set_flag_value($name, 0); } =item I @@ -396,55 +394,55 @@ For example: =cut sub get_annotation { - my $self = shift; - my ($entry, $type) = @_; + my $self = shift; + my ($entry, $type) = @_; - return $self->{annot}{$entry}{$type}{value}; + return $self->{annot}{$entry}{$type}{value}; } sub set_annotation { - my $self = shift; - my ($entry, $type, $value) = @_; - $value = '' unless defined $value; - $self->{annot}{$entry}{$type}{orig} = '' - unless exists $self->{annot}{$entry}{$type}{orig}; - $self->{annot}{$entry}{$type}{value} = $value; + my $self = shift; + my ($entry, $type, $value) = @_; + $value = '' unless defined $value; + $self->{annot}{$entry}{$type}{orig} = '' + unless exists $self->{annot}{$entry}{$type}{orig}; + $self->{annot}{$entry}{$type}{value} = $value; } sub get_shared_annotation { - my $self = shift; - my ($entry) = @_; - return $self->get_annotation($entry, 'value.shared'); + my $self = shift; + my ($entry) = @_; + return $self->get_annotation($entry, 'value.shared'); } sub set_shared_annotation { - my $self = shift; - my ($entry, $value) = @_; - return $self->set_annotation($entry, 'value.shared', $value); + my $self = shift; + my ($entry, $value) = @_; + return $self->set_annotation($entry, 'value.shared', $value); } sub clear_shared_annotation { - my $self = shift; - my ($entry) = @_; - return $self->set_annotation($entry, 'value.shared', ''); + my $self = shift; + my ($entry) = @_; + return $self->set_annotation($entry, 'value.shared', ''); } sub get_private_annotation { - my $self = shift; - my ($entry) = @_; - return $self->get_annotation($entry, 'value.private'); + my $self = shift; + my ($entry) = @_; + return $self->get_annotation($entry, 'value.private'); } sub set_private_annotation { - my $self = shift; - my ($entry, $value) = @_; - return $self->set_annotation($entry, 'value.private', $value); + my $self = shift; + my ($entry, $value) = @_; + return $self->set_annotation($entry, 'value.private', $value); } sub clear_private_annotation { - my $self = shift; - my ($entry) = @_; - return $self->set_annotation($entry, 'value.private', ''); + my $self = shift; + my ($entry) = @_; + return $self->set_annotation($entry, 'value.private', ''); } =item I @@ -456,35 +454,34 @@ returns two arrayrefs - [['flagname', 'bool']] and [['entry', 'type', 'value']], =cut sub get_changed { - my $self = shift; - my @flags; - my @annots; - - foreach my $name (sort keys %{$self->{flag}}) { - my $item = $self->{flag}{$name}; - push @flags, [$name, $item->{value}] - unless $item->{value} == $item->{orig}; - } + my $self = shift; + my @flags; + my @annots; + + foreach my $name (sort keys %{ $self->{flag} }) { + my $item = $self->{flag}{$name}; + push @flags, [ $name, $item->{value} ] + unless $item->{value} == $item->{orig}; + } - foreach my $entry (sort keys %{$self->{annot}}) { - foreach my $type (sort keys %{$self->{annot}{$entry}}) { - my $item = $self->{annot}{$entry}{$type}; - push @annots, [$entry, $type, $item->{value}] - unless is_eq($item->{value}, $item->{orig}); - } + foreach my $entry (sort keys %{ $self->{annot} }) { + foreach my $type (sort keys %{ $self->{annot}{$entry} }) { + my $item = $self->{annot}{$entry}{$type}; + push @annots, [ $entry, $type, $item->{value} ] + unless is_eq($item->{value}, $item->{orig}); } + } - return (\@flags, \@annots); + return (\@flags, \@annots); } sub is_eq { - my ($l, $r) = @_; - if (defined $l && defined $r) { - return $l eq $r; - } - else { - return !defined $l && !defined $r; - } + my ($l, $r) = @_; + if (defined $l && defined $r) { + return $l eq $r; + } else { + return !defined $l && !defined $r; + } } =back diff --git a/perl/imap/Cyrus/AccountSync.pm b/perl/imap/Cyrus/AccountSync.pm index bf63aef454..129f959d58 100644 --- a/perl/imap/Cyrus/AccountSync.pm +++ b/perl/imap/Cyrus/AccountSync.pm @@ -57,6 +57,7 @@ Cyrus::AccountSync - dump and undump entire accounts =head1 EXAMPLES =cut + =head1 PUBLIC API =over =item Cyrus::AccountSync->new($SyncProto); @@ -64,11 +65,9 @@ Cyrus::AccountSync - dump and undump entire accounts sub new { my $class = shift; - my $sync = shift; + my $sync = shift; - my $Self = bless { - sync => $sync, - }, ref($class) || $class; + my $Self = bless { sync => $sync, }, ref($class) || $class; return $Self; } @@ -86,46 +85,49 @@ sub dump_user { # no user? return unless $info->{MAILBOX}; - my %subs = map { $_ => 1 } @{$info->{LSUB}[0]||[]}; + my %subs = map { $_ => 1 } @{ $info->{LSUB}[0] || [] }; my @folders; - for my $folder (@{$info->{MAILBOX}}) { + for my $folder (@{ $info->{MAILBOX} }) { next if $folder->{MBOXTYPE}; # don't dump special folders my $fi = $self->{sync}->dlwrite("GET", "FULLMAILBOX", $folder->{MBOXNAME}); my @emails; - for my $record (@{$fi->{MAILBOX}[0]{RECORD}||[]}) { - my $res = $self->{sync}->dlwrite("GET", "FETCH", { - PARTITION => $folder->{PARTITION}, - MBOXNAME => $folder->{MBOXNAME}, - UNIQUEID => $folder->{UNIQUEID}, - GUID => $record->{GUID}, - UID => $record->{UID}, - }); - my $ref = $res->{MESSAGE}[0]; - my $data = $$ref->[3]; + for my $record (@{ $fi->{MAILBOX}[0]{RECORD} || [] }) { + my $res = $self->{sync}->dlwrite( + "GET", "FETCH", + { + PARTITION => $folder->{PARTITION}, + MBOXNAME => $folder->{MBOXNAME}, + UNIQUEID => $folder->{UNIQUEID}, + GUID => $record->{GUID}, + UID => $record->{UID}, + } + ); + my $ref = $res->{MESSAGE}[0]; + my $data = $$ref->[3]; my %email = ( - uid => $record->{UID} + 0, - flags => $record->{FLAGS}, - modseq => $record->{MODSEQ} + 0, + uid => $record->{UID} + 0, + flags => $record->{FLAGS}, + modseq => $record->{MODSEQ} + 0, internalDate => $record->{INTERNALDATE} + 0, - rawMessage => $data, + rawMessage => $data, ); if ($opts{objectid}) { $email{emailId} = "M" . substr($record->{GUID}, 0, 24); } push @emails, \%email; } - my $annots = _parseannots($fi->{MAILBOX}[0]{ANNOTATIONS}); - my $use = $annots->{$opts{username}}{"/specialuse"}; + my $annots = _parseannots($fi->{MAILBOX}[0]{ANNOTATIONS}); + my $use = $annots->{ $opts{username} }{"/specialuse"}; my %frecord = ( - name => _mkname($opts{username}, $folder->{MBOXNAME}), + name => _mkname($opts{username}, $folder->{MBOXNAME}), uidValidity => $folder->{UIDVALIDITY} + 0, - nextUid => $folder->{LAST_UID} + 1, + nextUid => $folder->{LAST_UID} + 1, highestModificationSequenceValue => $folder->{HIGHESTMODSEQ} + 0, - emails => \@emails, + emails => \@emails, ); - $frecord{subscribed} = JSON::true if $subs{$folder->{MBOXNAME}}; - $frecord{specialUse} = $use if $use; + $frecord{subscribed} = JSON::true if $subs{ $folder->{MBOXNAME} }; + $frecord{specialUse} = $use if $use; if ($opts{objectid}) { $frecord{mailboxId} = $folder->{UNIQUEID}; } @@ -149,7 +151,7 @@ sub undump_user { my %opts = @_; die "need username" unless $opts{username}; - die "need data" unless $opts{data}; + die "need data" unless $opts{data}; my $info = $self->{sync}->dlwrite("GET", "USER", $opts{username}); die "user $opts{username} exists" if keys %$info; @@ -158,69 +160,73 @@ sub undump_user { my $mailboxes = $opts{data}{mailboxes}; - my $acl = $opts{acl} || "$opts{username} lrswipkxtecdan admin lrswipkxtecdan anyone p ", + my $acl = $opts{acl} + || "$opts{username} lrswipkxtecdan admin lrswipkxtecdan anyone p ", - my $time = time(); + my $time = time(); my @subs; for my $mailbox (@$mailboxes) { # create the emails first my @upload; my @records; - for my $email (@{$mailbox->{emails}||[]}) { + for my $email (@{ $mailbox->{emails} || [] }) { my $data = $email->{rawMessage}; my $size = length($data); my $guid = sha1_hex($data); - push @upload, \[$partition, $guid, $size, $data]; + push @upload, \[ $partition, $guid, $size, $data ]; my $internaldate = $email->{internalDate} // $time; - my $modseq = $email->{modseq} || 1; - my $flags = $email->{flags} || []; - my %record = ( - ANNOTATIONS => [], # skip savedate and such for now - GUID => $guid, - FLAGS => $flags, + my $modseq = $email->{modseq} || 1; + my $flags = $email->{flags} || []; + my %record = ( + ANNOTATIONS => [], # skip savedate and such for now + GUID => $guid, + FLAGS => $flags, INTERNALDATE => $internaldate, LAST_UPDATED => $internaldate, - MODSEQ => $modseq, - SIZE => $size, - UID => $email->{uid}, + MODSEQ => $modseq, + SIZE => $size, + UID => $email->{uid}, ); push @records, \%record; } $self->{sync}->dlwrite("APPLY", "MESSAGE", \@upload) if @upload; - my $mbname = Cyrus::Mbname->new_extuserfolder($opts{username}, $mailbox->{name}); - my $intname = $mbname->intname; + my $mbname + = Cyrus::Mbname->new_extuserfolder($opts{username}, $mailbox->{name}); + my $intname = $mbname->intname; my $highestmodseq = $mailbox->{highestModificationSequenceValue} || 1; - my $uidvalidity = $mailbox->{uidValidity} || $time; - my $last_uid = $mailbox->{nextUid} ? $mailbox->{nextUid} - 1 : 0; - my $uniqueid = $mailbox->{mailboxId} || "$uuid"; + my $uidvalidity = $mailbox->{uidValidity} || $time; + my $last_uid = $mailbox->{nextUid} ? $mailbox->{nextUid} - 1 : 0; + my $uniqueid = $mailbox->{mailboxId} || "$uuid"; my @annotations; + if ($mailbox->{specialUse}) { - push @annotations, { - 'ENTRY' => '/specialuse', + push @annotations, + { + 'ENTRY' => '/specialuse', 'MODSEQ' => '0', 'USERID' => $opts{username}, - 'VALUE' => $mailbox->{specialUse}, - }; + 'VALUE' => $mailbox->{specialUse}, + }; } my %maildata = ( - 'ACL' => $acl, - 'ANNOTATIONS' => \@annotations, - 'CREATEDMODSEQ' => '1', - 'FOLDERMODSEQ' => '1', - 'HIGHESTMODSEQ' => "$highestmodseq", + 'ACL' => $acl, + 'ANNOTATIONS' => \@annotations, + 'CREATEDMODSEQ' => '1', + 'FOLDERMODSEQ' => '1', + 'HIGHESTMODSEQ' => "$highestmodseq", 'LAST_APPENDDATE' => '0', - 'LAST_UID' => "$last_uid", - 'MBOXNAME' => $intname, - 'OPTIONS' => 'P', - 'PARTITION' => $partition, + 'LAST_UID' => "$last_uid", + 'MBOXNAME' => $intname, + 'OPTIONS' => 'P', + 'PARTITION' => $partition, 'POP3_LAST_LOGIN' => '0', 'POP3_SHOW_AFTER' => '0', - 'RECENTTIME' => '0', - 'RECENTUID' => '0', - 'RECORD' => \@records, - 'UIDVALIDITY' => $uidvalidity, - 'UNIQUEID' => $uniqueid, + 'RECENTTIME' => '0', + 'RECENTUID' => '0', + 'RECORD' => \@records, + 'UIDVALIDITY' => $uidvalidity, + 'UNIQUEID' => $uniqueid, ); $self->{sync}->dlwrite('APPLY', 'MAILBOX', \%maildata); @@ -229,7 +235,9 @@ sub undump_user { } if (@subs) { - $self->{sync}->dlwrite('APPLY', 'SUB', { USERID => $opts{username}, MBOXNAME => $_ }) for @subs; + $self->{sync} + ->dlwrite('APPLY', 'SUB', { USERID => $opts{username}, MBOXNAME => $_ }) + for @subs; } } @@ -246,7 +254,7 @@ sub _parseannots { my $annots = shift || []; my %res; for my $item (@$annots) { - $res{$item->{USERID}//''}{$item->{ENTRY}} = $item->{VALUE}; + $res{ $item->{USERID} // '' }{ $item->{ENTRY} } = $item->{VALUE}; } return \%res; } diff --git a/perl/imap/Cyrus/CacheFile.pm b/perl/imap/Cyrus/CacheFile.pm index d9e870a022..94836e9fd8 100755 --- a/perl/imap/Cyrus/CacheFile.pm +++ b/perl/imap/Cyrus/CacheFile.pm @@ -39,7 +39,7 @@ See examples/index_uids.pl for some usage # #define NUM_CACHE_FIELDS 10 our $NUM_CACHE_FIELDS = 10; -our @NAMES = qw( +our @NAMES = qw( ENVELOPE BODYSTRUCTURE BODY @@ -55,7 +55,7 @@ our @NAMES = qw( # PUBLIC API sub new { - my $class = shift; + my $class = shift; my $handle = shift; # read header @@ -63,23 +63,24 @@ sub new { # XXX - check for success! sysread($handle, $buf, 4); my $version = unpack('N', $buf); - my $Self = bless { version => $version, handle => $handle, offset => 4 }, ref($class) || $class; + my $Self = bless { version => $version, handle => $handle, offset => 4 }, + ref($class) || $class; return $Self; } sub new_file { - my $class = shift; - my $file = shift; + my $class = shift; + my $file = shift; my $lockopts = shift; my $fh; if ($lockopts) { $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY'; - $fh = IO::File::fcntl->new($file, '+<', @$lockopts) - || die "Can't open $file for locked read: $!"; + $fh = IO::File::fcntl->new($file, '+<', @$lockopts) + || die "Can't open $file for locked read: $!"; } else { $fh = IO::File->new("< $file") - || die "Can't open $file for read: $!"; + || die "Can't open $file for read: $!"; } return $class->new($fh); @@ -91,19 +92,19 @@ sub next_record { my @record; my $size = 0; - for (1..$NUM_CACHE_FIELDS) { + for (1 .. $NUM_CACHE_FIELDS) { sysread($Self->{handle}, $buf, 4); return undef unless $buf; - my $num = unpack('N', $buf); + my $num = unpack('N', $buf); my $bytes = $num; $bytes += 4 - $num % 4 if $num % 4; # offsets are multiple of 4 bytes sysread($Self->{handle}, $buf, $bytes); - push @record, [$num, $bytes, $buf]; + push @record, [ $num, $bytes, $buf ]; $size += $bytes + 4; } my $ret = { - size => $size, + size => $size, records => \@record, }; @@ -114,7 +115,7 @@ sub next_record { } sub record { - my $Self = shift; + my $Self = shift; my $Field = shift; return undef unless ($Self->{record}); @@ -147,18 +148,18 @@ sub dump { sub dump_record { my $Self = shift; - my $rec = shift || $Self->{record}; + my $rec = shift || $Self->{record}; return unless $rec; print Dump($rec->{records}); } sub print_record { my $Self = shift; - my $rec = shift || $Self->{record}; + my $rec = shift || $Self->{record}; return unless $rec; - foreach my $rnum (0..$NUM_CACHE_FIELDS-1) { + foreach my $rnum (0 .. $NUM_CACHE_FIELDS - 1) { my $record = $rec->{records}[$rnum]; - my $str = substr($record->[2], 0, $record->[0]); + my $str = substr($record->[2], 0, $record->[0]); if ($rnum == 3) { # section my @items = unpack('N*', $str); $str = parse_section(0, \@items); @@ -168,14 +169,14 @@ sub print_record { } sub parse_section { - my $part = shift; - my $items = shift; + my $part = shift; + my $items = shift; my $num_parts = shift @$items; if ($num_parts == 0) { return "$part:()"; } my $ret = "$part:(" . parse_item($items); - my $n = 1; + my $n = 1; while ($n < $num_parts) { my $subpart = $part ? "$part.$n" : $n; $ret .= " " . parse_item($items); @@ -192,13 +193,14 @@ sub parse_section { } sub parse_item { - my $items = shift; - my $header_offset = shift @$items; - my $header_size = shift @$items; + my $items = shift; + my $header_offset = shift @$items; + my $header_size = shift @$items; my $content_offset = shift @$items; - my $content_size = shift @$items; - my $encoding = shift @$items; - return "($header_offset:$header_size $content_offset:$content_size $encoding)"; + my $content_size = shift @$items; + my $encoding = shift @$items; + return + "($header_offset:$header_size $content_offset:$content_size $encoding)"; } =head1 AUTHOR AND COPYRIGHT diff --git a/perl/imap/Cyrus/DList.pm b/perl/imap/Cyrus/DList.pm index 1aa737c452..8bb9357ce7 100644 --- a/perl/imap/Cyrus/DList.pm +++ b/perl/imap/Cyrus/DList.pm @@ -48,50 +48,50 @@ use File::Temp; sub new_kvlist { my $class = shift; - my $key = shift; + my $key = shift; return bless { type => 'kvlist', - key => $key, + key => $key, data => [], - }, ref($class) || $class; + }, + ref($class) || $class; } sub new_list { my $class = shift; - my $key = shift; + my $key = shift; return bless { type => 'list', - key => $key, + key => $key, data => [], - }, ref($class) || $class; + }, + ref($class) || $class; } sub new_perl { my $class = shift; - my $key = shift; - my $val = shift; - my $Self = $class->new_list(undef); + my $key = shift; + my $val = shift; + my $Self = $class->new_list(undef); $Self->add_perl($key, $val); return $Self->{data}[0]; } sub add_perl { my $Self = shift; - my $key = shift; - my $val = shift; + my $key = shift; + my $val = shift; if (not ref($val)) { $Self->add_atom($key, $val); - } - elsif (ref($val) eq 'ARRAY') { + } elsif (ref($val) eq 'ARRAY') { my $child = $Self->add_list($key); $child->add_perl(undef, $_) for @$val; - } - elsif (ref($val) eq 'HASH') { + } elsif (ref($val) eq 'HASH') { my $child = $Self->add_kvlist($key); my $order = delete $val->{__kvlist_order}; @@ -103,91 +103,88 @@ sub add_perl { } $child->add_perl($_, $val->{$_}) for sort keys %$val; - } - elsif (ref($val) eq 'REF') { + } elsif (ref($val) eq 'REF') { my $item = $$val; if (ref($item) eq 'ARRAY') { $Self->add_file($key, @$item); } else { die "Unknown file format " . ref($item); } - } - else { + } else { die "UNKNOWN $key " . ref($val); } } sub add_list { my $Self = shift; - my $key = shift; + my $key = shift; die unless $Self->{type} =~ m/list/; my $res = bless { type => 'list', - key => $key, + key => $key, data => [], }; - push @{$Self->{data}}, $res; + push @{ $Self->{data} }, $res; return $res; } - sub add_kvlist { my $Self = shift; - my $key = shift; + my $key = shift; die unless $Self->{type} =~ m/list/; my $res = bless { type => 'kvlist', - key => $key, + key => $key, data => [], }; - push @{$Self->{data}}, $res; + push @{ $Self->{data} }, $res; return $res; } sub add_file { - my $Self = shift; - my $key = shift; + my $Self = shift; + my $key = shift; my $partition = shift; - my $guid = shift; - my $size = shift; - my $value = shift; + my $guid = shift; + my $size = shift; + my $value = shift; die unless $Self->{type} =~ m/list/; my $res = bless { - type => 'file', - key => $key, + type => 'file', + key => $key, partition => $partition, - guid => $guid, - size => $size, - data => $value, + guid => $guid, + size => $size, + data => $value, }; - push @{$Self->{data}}, $res; + push @{ $Self->{data} }, $res; return $res; } sub add_atom { - my $Self = shift; - my $key = shift; + my $Self = shift; + my $key = shift; my $value = shift; die unless $Self->{type} =~ m/list/; my $res = bless { type => 'atom', - key => $key, + key => $key, data => $value, }; - push @{$Self->{data}}, $res; + push @{ $Self->{data} }, $res; return $res; } @@ -217,8 +214,8 @@ sub _getword { # Great - custom magic sub _parse_string { - my $Self = shift; - my $ref = shift; + my $Self = shift; + my $ref = shift; my $parsekey = shift; my $key = ''; @@ -237,19 +234,17 @@ sub _parse_string { $Child->_parse_string($ref, 0); $$ref =~ s/^\s+//; } - } - elsif ($$ref =~ s/^\%//) { + } elsif ($$ref =~ s/^\%//) { # kvlist if ($$ref =~ s/^\(//) { die unless $$ref; my $Child = $Self->add_kvlist($key); - while (not ($$ref =~ s/^\)//)) { + while (not($$ref =~ s/^\)//)) { $Child->_parse_string($ref, 1); $$ref =~ s/^\s+//; } - } - elsif ($$ref =~ s/^\{//) { + } elsif ($$ref =~ s/^\{//) { die unless $$ref; my $partition = _getword($ref); die "No partition" unless length($partition); @@ -263,18 +258,17 @@ sub _parse_string { my $content = substr($$ref, 0, $size, ''); $Self->add_file($key, $partition, $guid, $size, $content); } - } - else { + } else { my $content = _getastring($ref); $Self->add_atom($key, $content); } } sub parse_string { - my $class = shift; - my $string = shift; + my $class = shift; + my $string = shift; my $parsekey = shift; - my $base = $class->new_list(); + my $base = $class->new_list(); $base->_parse_string(\$string, $parsekey); return $base->{data}[0]; } @@ -299,18 +293,16 @@ sub as_string { my $Self = shift; if ($Self->{type} eq 'kvlist') { - my @items = map { _printastring($_->{key}) => $_->as_string() } @{$Self->{data}}; + my @items + = map { _printastring($_->{key}) => $_->as_string() } @{ $Self->{data} }; return '%(' . join(' ', @items) . ')'; - } - elsif ($Self->{type} eq 'list') { - my @items = map { $_->as_string() } @{$Self->{data}}; + } elsif ($Self->{type} eq 'list') { + my @items = map { $_->as_string() } @{ $Self->{data} }; return '(' . join(' ', @items) . ')'; - } - elsif ($Self->{type} eq 'file') { + } elsif ($Self->{type} eq 'file') { my @items = ($Self->{partition}, $Self->{guid}, $Self->{size}); - return '%{' . join (' ', @items) . "}\r\n" . $Self->{data}; - } - else { + return '%{' . join(' ', @items) . "}\r\n" . $Self->{data}; + } else { return _printastring($Self->{data}); } } @@ -322,21 +314,18 @@ sub as_perl { my $kvlist = {}; my @order; - foreach my $datum (@{$Self->{data}}) { + foreach my $datum (@{ $Self->{data} }) { push @order, $datum->{key}; - $kvlist->{$datum->{key}} = $datum->as_perl(); + $kvlist->{ $datum->{key} } = $datum->as_perl(); } - $kvlist->{__kvlist_order} = [ @order ]; + $kvlist->{__kvlist_order} = [@order]; return $kvlist; - } - elsif ($Self->{type} eq 'list') { - return [ map { $_->as_perl() } @{$Self->{data}} ]; - } - elsif ($Self->{type} eq 'file') { + } elsif ($Self->{type} eq 'list') { + return [ map { $_->as_perl() } @{ $Self->{data} } ]; + } elsif ($Self->{type} eq 'file') { return \[ $Self->{partition}, $Self->{guid}, $Self->{size}, $Self->{data} ]; - } - else { + } else { return $Self->{data}; } } @@ -346,33 +335,32 @@ sub anyevent_read_type { my %obj; %obj = ( - data => '', + data => '', getline => sub { if ($_[1] =~ m/(\d+)\+?\}$/) { my $length = $1; $obj{data} .= $_[1] . $_[2]; # compatible with both file literals and regular literals $_[0]->unshift_read(chunk => $length, $obj{getliteral}); - } - else { + } else { my $dlist = Cyrus::DList->parse_string($obj{data} . $_[1], $parsekey); $cb->($handle, $dlist); %obj = (); # drop refs } - 1 + 1; }, getliteral => sub { $obj{data} .= $_[1]; - $_[0]->unshift_read (line => $obj{getline}); - 1 + $_[0]->unshift_read(line => $obj{getline}); + 1; }, ); return sub { - $_[0]->unshift_read (line => $obj{getline}); - 1 + $_[0]->unshift_read(line => $obj{getline}); + 1; }; -}; +} sub anyevent_write_type { my ($handle, $dlist, $printkey) = @_; diff --git a/perl/imap/Cyrus/HeaderFile.pm b/perl/imap/Cyrus/HeaderFile.pm index 6535b56e17..dcf55916ab 100755 --- a/perl/imap/Cyrus/HeaderFile.pm +++ b/perl/imap/Cyrus/HeaderFile.pm @@ -34,7 +34,8 @@ XXX: see index_uids.pl =cut our $HL1 = qq{\241\002\213\015Cyrus mailbox header}; -our $HL2 = qq{"The best thing about this system was that it had lots of goals."}; +our $HL2 + = qq{"The best thing about this system was that it had lots of goals."}; our $HL3 = qq{\t--Jim Morris on Andrew}; =head1 PUBLIC API @@ -48,7 +49,7 @@ Read the header file in $fh =cut sub new { - my $class = shift; + my $class = shift; my $handle = shift; # read header @@ -56,9 +57,9 @@ sub new { my $body = <$handle>; my $Self = bless {}, ref($class) || $class; - $Self->{handle} = $handle; # keep for locking + $Self->{handle} = $handle; # keep for locking $Self->{rawheader} = $body; - $Self->{header} = $Self->parse_header($body); + $Self->{header} = $Self->parse_header($body); return $Self; } @@ -75,18 +76,18 @@ calls $class->new() with the filehandle. =cut sub new_file { - my $class = shift; - my $file = shift; + my $class = shift; + my $file = shift; my $lockopts = shift; my $fh; if ($lockopts) { $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY'; - $fh = IO::File::fcntl->new($file, '+<', @$lockopts) - || die "Can't open $file for locked read: $!"; + $fh = IO::File::fcntl->new($file, '+<', @$lockopts) + || die "Can't open $file for locked read: $!"; } else { $fh = IO::File->new("< $file") - || die "Can't open $file for read: $!"; + || die "Can't open $file for read: $!"; } return $class->new($fh); @@ -99,7 +100,7 @@ Return the entire header as a hash, or individual named field. =cut sub header { - my $Self = shift; + my $Self = shift; my $Field = shift; if ($Field) { @@ -117,8 +118,8 @@ to the given filehandle. =cut sub write_header { - my $Self = shift; - my $fh = shift; + my $Self = shift; + my $fh = shift; my $header = shift || $Self->header(); $fh->print($Self->make_header($header)); @@ -127,15 +128,15 @@ sub write_header { # XXX still writes old-style header! sub make_header { my $Self = shift; - my $ds = shift || $Self->header(); + my $ds = shift || $Self->header(); # NOTE: no tab separator if no uniqueid! my $qr_uuid = $ds->{QuotaRoot}; $qr_uuid .= "\t$ds->{UniqueId}" if $ds->{UniqueId}; # NOTE: acl and flags should have '' as the last element! - my $flags = join(" ", @{$ds->{Flags}}, ''); - my $acl = join("\t", @{$ds->{ACL}}, ''); + my $flags = join(" ", @{ $ds->{Flags} }, ''); + my $acl = join("\t", @{ $ds->{ACL} }, ''); my $buf = <{dlistheader}; + my $fh = shift; + my $dl = shift || $Self->{dlistheader}; my $buf = <{dlistheader} = $lines[3]; my $dlist = Cyrus::DList->parse_string($lines[3]); - my $hash = $dlist->as_perl(); + my $hash = $dlist->as_perl(); $quotaroot = $hash->{Q} // ''; - $uniqueid = $hash->{I}; - @flags = @{$hash->{U}} if ref $hash->{U}; + $uniqueid = $hash->{I}; + @flags = @{ $hash->{U} } if ref $hash->{U}; if (ref $hash->{A}) { - my $order = delete $hash->{A}->{__kvlist_order}; + my $order = delete $hash->{A}->{__kvlist_order}; - if ($order && ref $order eq 'ARRAY') { - foreach my $k (@{$order}) { - my $v = delete $hash->{A}->{$k}; - push @acl, $k, $v; - } + if ($order && ref $order eq 'ARRAY') { + foreach my $k (@{$order}) { + my $v = delete $hash->{A}->{$k}; + push @acl, $k, $v; } + } - push @acl, %{$hash->{A}}; + push @acl, %{ $hash->{A} }; } - } - else { + } else { ($quotaroot, $uniqueid) = split /\t/, $lines[3]; - @flags = split / /, $lines[4]; - @acl = split /\t/, $lines[5]; + @flags = split / /, $lines[4]; + @acl = split /\t/, $lines[5]; } return { QuotaRoot => $quotaroot, - UniqueId => $uniqueid, - Flags => \@flags, - ACL => \@acl, + UniqueId => $uniqueid, + Flags => \@flags, + ACL => \@acl, }; } @@ -228,5 +228,4 @@ Licenced under the same terms as Cyrus IMAPd. =cut - 1; diff --git a/perl/imap/Cyrus/ImapClone.pm b/perl/imap/Cyrus/ImapClone.pm index f8cd67c300..2bc15280c3 100644 --- a/perl/imap/Cyrus/ImapClone.pm +++ b/perl/imap/Cyrus/ImapClone.pm @@ -39,7 +39,6 @@ # AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING # OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - package Cyrus::ImapClone; use strict; @@ -48,7 +47,7 @@ use Date::Parse; use Cyrus::SyncProto; use Mail::IMAPTalk; use Data::Dumper; -use Digest::SHA qw(sha1_hex); +use Digest::SHA qw(sha1_hex); use Tie::DataUUID qw($uuid); use IO::Socket::SSL; use JSON::XS; @@ -62,6 +61,7 @@ Cyrus::ImapClone - A pure perl interface to clone Cyrus Mailbox. =head1 EXAMPLES =cut + =head1 PUBLIC API =over =item Cyrus::ImapClone->new() @@ -69,16 +69,16 @@ Cyrus::ImapClone - A pure perl interface to clone Cyrus Mailbox. sub new { my $class = shift; - my %args = @_; + my %args = @_; my $syncssl = $args{syncssl}; - my $st = Mail::IMAPTalk->new( - Server => $args{synchost}, - Port => $args{syncport}, - Username => $args{syncuser}, - Password => $args{syncpass}, - AuthzUser => $args{syncauthz}, - UseSSL => $syncssl, + my $st = Mail::IMAPTalk->new( + Server => $args{synchost}, + Port => $args{syncport}, + Username => $args{syncuser}, + Password => $args{syncpass}, + AuthzUser => $args{syncauthz}, + UseSSL => $syncssl, UseBlocking => $syncssl, UseCompress => 1, ); @@ -92,25 +92,26 @@ sub new { my $userdata = $sp->dlwrite('GET', 'USER', $args{synctarget}); my $usessl = $args{imapssl}; - my $it = Mail::IMAPTalk->new( - Server => $args{imaphost}, - Port => $args{imapport}, - Username => $args{imapuser}, - Password => $args{imappass}, - AuthzUser => $args{imapauthz}, + my $it = Mail::IMAPTalk->new( + Server => $args{imaphost}, + Port => $args{imapport}, + Username => $args{imapuser}, + Password => $args{imappass}, + AuthzUser => $args{imapauthz}, SSL_verify_mode => SSL_VERIFY_NONE, - UseSSL => $usessl, - UseBlocking => $usessl, - UseCompress => 1, + UseSSL => $usessl, + UseBlocking => $usessl, + UseCompress => 1, ); return bless { - syncer => $sp, - synctalk => $st, - imaptalk => $it, - userdata => $userdata, + syncer => $sp, + synctalk => $st, + imaptalk => $it, + userdata => $userdata, targetuser => $args{synctarget}, - }, ref($class) || $class; + }, + ref($class) || $class; } sub done { @@ -125,55 +126,71 @@ sub DESTROY { } sub batchfillrecords { - my $Self = shift; + my $Self = shift; my $mboxname = shift; - my $records = shift; + my $records = shift; - my %todo = %$records; + my %todo = %$records; my $total = scalar keys %todo; # batch in units of max 10 megabytes plus 1 message while (%todo) { my $size = 0; my %batch; - foreach my $uid (sort {$a <=> $b} keys %todo) { + foreach my $uid (sort { $a <=> $b } keys %todo) { $batch{$uid} = delete $todo{$uid}; $size += $batch{$uid}{SIZE}; last if $size > 1024 * 1024 * 10; # 10 megabytes } $Self->fillrecords($mboxname, \%batch); last unless %todo; - print "Batching - still " . scalar(keys %todo) . "/$total to go for $mboxname\n" if $Self->{verbose}; + print "Batching - still " + . scalar(keys %todo) + . "/$total to go for $mboxname\n" + if $Self->{verbose}; } } sub fillrecords { - my $Self = shift; + my $Self = shift; my $mboxname = shift; - my $records = shift; + my $records = shift; # XXX - smaller batch to control memory usage? - my $imap = $Self->{imaptalk}; - my $fetch = $imap->fetch([sort {$a <=> $b} keys %$records], '(rfc822)'); + my $imap = $Self->{imaptalk}; + my $fetch = $imap->fetch([ sort { $a <=> $b } keys %$records ], '(rfc822)'); my @apply; - foreach my $uid (sort {$a <=> $b} keys %$records) { + foreach my $uid (sort { $a <=> $b } keys %$records) { die "MISSING $uid" unless $fetch->{$uid}; - die "SIZE MISSMATCH $uid" unless $records->{$uid}{SIZE} == length($fetch->{$uid}{rfc822}); + die "SIZE MISSMATCH $uid" + unless $records->{$uid}{SIZE} == length($fetch->{$uid}{rfc822}); $records->{$uid}{GUID} = sha1_hex($fetch->{$uid}{rfc822}); } # let's try to reserve first - my @names = map { $_->{MBOXNAME} } @{$Self->{userdata}{MAILBOX}}; + my @names = map { $_->{MBOXNAME} } @{ $Self->{userdata}{MAILBOX} }; my %guids = map { $_->{GUID} => 1 } values %$records; - my $res = $Self->{syncer}->dlwrite('APPLY', 'RESERVE', {PARTITION => 'default', MBOXNAME => \@names, GUID => [sort keys %guids]}); - my %missing = map { $_ => 1 } @{$res->{MISSING}[0]}; + my $res = $Self->{syncer}->dlwrite( + 'APPLY', + 'RESERVE', + { + PARTITION => 'default', + MBOXNAME => \@names, + GUID => [ sort keys %guids ] + } + ); + my %missing = map { $_ => 1 } @{ $res->{MISSING}[0] }; return unless %missing; - foreach my $uid (sort {$a <=> $b} keys %$records) { - next unless $missing{$records->{$uid}{GUID}}; - push @apply, \['default', $records->{$uid}{GUID}, $records->{$uid}{SIZE}, $fetch->{$uid}{rfc822}]; + foreach my $uid (sort { $a <=> $b } keys %$records) { + next unless $missing{ $records->{$uid}{GUID} }; + push @apply, + \[ + 'default', $records->{$uid}{GUID}, + $records->{$uid}{SIZE}, $fetch->{$uid}{rfc822} + ]; } return unless @apply; @@ -182,38 +199,44 @@ sub fillrecords { } sub syncmailbox { - my $Self = shift; + my $Self = shift; my $mboxname = shift; my $existing = shift; if ($existing) { - my $status = $Self->{imaptalk}->status($Self->_sync_to_imap($mboxname), "(HIGHESTMODSEQ UIDVALIDITY)"); - die "UIDVALIDITY CHANGED" if ($existing->{UIDVALIDITY} != $status->{uidvalidity}); + my $status = $Self->{imaptalk} + ->status($Self->_sync_to_imap($mboxname), "(HIGHESTMODSEQ UIDVALIDITY)"); + die "UIDVALIDITY CHANGED" + if ($existing->{UIDVALIDITY} != $status->{uidvalidity}); return if ($existing->{HIGHESTMODSEQ} == $status->{highestmodseq}); } $Self->{imaptalk}->examine($Self->_sync_to_imap($mboxname)); - my $imap = $Self->{imaptalk}; + my $imap = $Self->{imaptalk}; my %idata = ( - UIDVALIDITY => $imap->get_response_code('uidvalidity') + 0, - LAST_UID => $imap->get_response_code('uidnext') - 1, + UIDVALIDITY => $imap->get_response_code('uidvalidity') + 0, + LAST_UID => $imap->get_response_code('uidnext') - 1, HIGHESTMODSEQ => $imap->get_response_code('highestmodseq') || 1, - EXISTS => $imap->get_response_code('exists') || 0, + EXISTS => $imap->get_response_code('exists') || 0, ); # basic sanity checks - die "UIDVALIDITY CHANGED" if ($existing and $existing->{UIDVALIDITY} != $idata{UIDVALIDITY}); + die "UIDVALIDITY CHANGED" + if ($existing and $existing->{UIDVALIDITY} != $idata{UIDVALIDITY}); return if ($existing and $existing->{HIGHESTMODSEQ} == $idata{HIGHESTMODSEQ}); my $sdata = $Self->readup($mboxname, $existing); # basic sanity checks again with latest data - die "UIDVALIDITY CHANGED " . Dumper($sdata) if ($sdata and $sdata->{UIDVALIDITY} != $idata{UIDVALIDITY}); + die "UIDVALIDITY CHANGED " . Dumper($sdata) + if ($sdata and $sdata->{UIDVALIDITY} != $idata{UIDVALIDITY}); return if ($existing and $existing->{HIGHESTMODSEQ} == $idata{HIGHESTMODSEQ}); # sanity range checks - die "FUTURE CHANGED MODSEQ $sdata->{HIGHESTMODSEQ} > $idata{HIGHESTMODSEQ}" if ($sdata and $sdata->{HIGHESTMODSEQ} > $idata{HIGHESTMODSEQ}); - die "FUTURE CHANGED UIDS $sdata->{LAST_UID} > $idata{LAST_UID}" if ($sdata and $sdata->{LAST_UID} > $idata{LAST_UID}); + die "FUTURE CHANGED MODSEQ $sdata->{HIGHESTMODSEQ} > $idata{HIGHESTMODSEQ}" + if ($sdata and $sdata->{HIGHESTMODSEQ} > $idata{HIGHESTMODSEQ}); + die "FUTURE CHANGED UIDS $sdata->{LAST_UID} > $idata{LAST_UID}" + if ($sdata and $sdata->{LAST_UID} > $idata{LAST_UID}); my $time = time(); @@ -221,26 +244,26 @@ sub syncmailbox { print "NEW MAILBOX $mboxname: $idata{EXISTS}\n" if $Self->{verbose}; my %mb = ( - ACL => Cyrus::SyncProto::user_acl($Self->{targetuser}), - HIGHESTMODSEQ => 0, + ACL => Cyrus::SyncProto::user_acl($Self->{targetuser}), + HIGHESTMODSEQ => 0, LAST_APPENDDATE => 0, - LAST_UID => 0, - MBOXNAME => $mboxname, - OPTIONS => 'P', - PARTITION => 'default', + LAST_UID => 0, + MBOXNAME => $mboxname, + OPTIONS => 'P', + PARTITION => 'default', POP3_LAST_LOGIN => 0, POP3_SHOW_AFTER => 0, - QUOTAROOT => $Self->_imap_to_sync('INBOX'), - RECENTTIME => 0, - RECENTUID => 0, - RECORD => [], - SYNC_CRC => 0, - SYNC_ANNOT_CRC => 0, - UIDVALIDITY => $idata{UIDVALIDITY}, - UNIQUEID => $uuid, + QUOTAROOT => $Self->_imap_to_sync('INBOX'), + RECENTTIME => 0, + RECENTUID => 0, + RECORD => [], + SYNC_CRC => 0, + SYNC_ANNOT_CRC => 0, + UIDVALIDITY => $idata{UIDVALIDITY}, + UNIQUEID => $uuid, ); - push @{$Self->{userdata}{MAILBOX}}, \%mb; + push @{ $Self->{userdata}{MAILBOX} }, \%mb; $sdata = { %mb, RECORD => [] }; } @@ -251,20 +274,24 @@ sub syncmailbox { # clever logic here.. if ($sdata->{LAST_UID}) { # re-fetch flags only - my $end = $sdata->{LAST_UID}; - my $fetch = $imap->fetch("1:$end", "(uid flags modseq)", "(changedsince $sdata->{HIGHESTMODSEQ})"); - foreach my $record (grep { _notexpunged($_) } @{$sdata->{RECORD}}) { + my $end = $sdata->{LAST_UID}; + my $fetch = $imap->fetch( + "1:$end", + "(uid flags modseq)", + "(changedsince $sdata->{HIGHESTMODSEQ})" + ); + foreach my $record (grep { _notexpunged($_) } @{ $sdata->{RECORD} }) { my $uid = $record->{UID}; next unless $fetch->{$uid}; - my @flags = @{$fetch->{$uid}{flags}}; + my @flags = @{ $fetch->{$uid}{flags} }; if (grep { lc $_ eq '\\recent' } @flags) { $recentuid = $uid if $recentuid > $uid; } # update the record and the CRC $sdata->{SYNC_CRC} ^= $Self->{syncer}->record_crc($record); - $record->{FLAGS} = _cleanflags(@flags); - $record->{MODSEQ} = $fetch->{$uid}{modseq}[0]; + $record->{FLAGS} = _cleanflags(@flags); + $record->{MODSEQ} = $fetch->{$uid}{modseq}[0]; $record->{LAST_UPDATED} = $time; $sdata->{SYNC_CRC} ^= $Self->{syncer}->record_crc($record); push @applyrecords, $record; @@ -272,12 +299,13 @@ sub syncmailbox { } my $first = $sdata->{LAST_UID} + 1; - my $last = $idata{LAST_UID}; + my $last = $idata{LAST_UID}; if ($last >= $first) { - my $fetch = $imap->fetch("$first:$last", "(uid flags modseq internaldate rfc822.size)"); + my $fetch = $imap->fetch("$first:$last", + "(uid flags modseq internaldate rfc822.size)"); my %records; - foreach my $uid (sort {$a <=> $b} keys %$fetch) { - my @flags = @{$fetch->{$uid}{flags}}; + foreach my $uid (sort { $a <=> $b } keys %$fetch) { + my @flags = @{ $fetch->{$uid}{flags} }; if (grep { lc $_ eq '\\recent' } @flags) { $recentuid = $uid if $recentuid > $uid; } @@ -288,66 +316,79 @@ sub syncmailbox { # GUID to be filled INTERNALDATE => _mkunixtime($fetch->{$uid}{internaldate}), LAST_UPDATED => $time, - MODSEQ => $fetch->{$uid}{modseq}[0], - SIZE => $fetch->{$uid}{'rfc822.size'}, - UID => $uid, + MODSEQ => $fetch->{$uid}{modseq}[0], + SIZE => $fetch->{$uid}{'rfc822.size'}, + UID => $uid, }; } $Self->batchfillrecords($mboxname, \%records); - foreach my $uid (sort {$a <=> $b} keys %records) { - push @applyrecords, $records{$uid}; - push @{$sdata->{RECORD}}, $records{$uid}; + foreach my $uid (sort { $a <=> $b } keys %records) { + push @applyrecords, $records{$uid}; + push @{ $sdata->{RECORD} }, $records{$uid}; $sdata->{SYNC_CRC} ^= $Self->{syncer}->record_crc($records{$uid}); } } - if ($idata{EXISTS} != scalar(grep { _notexpunged($_) } @{$sdata->{RECORD}})) { + if ($idata{EXISTS} != scalar(grep { _notexpunged($_) } @{ $sdata->{RECORD} })) + { # we need to expunge something - let's see what.. print "DOING EXPUNGE CHECK FOR $mboxname\n" if $Self->{verbose}; - my $uids = $imap->search('uid', "1:$last"); + my $uids = $imap->search('uid', "1:$last"); my %exists = map { $_ => 1 } @$uids; - foreach my $record (grep { _notexpunged($_) } @{$sdata->{RECORD}}) { - next if $exists{$record->{UID}}; + foreach my $record (grep { _notexpunged($_) } @{ $sdata->{RECORD} }) { + next if $exists{ $record->{UID} }; # update the record and the CRC $sdata->{SYNC_CRC} ^= $Self->{syncer}->record_crc($record); - push @{$record->{FLAGS}}, "\\Expunged"; - $record->{MODSEQ} = $idata{HIGHESTMODSEQ}; + push @{ $record->{FLAGS} }, "\\Expunged"; + $record->{MODSEQ} = $idata{HIGHESTMODSEQ}; $record->{LAST_UPDATED} = $time; push @applyrecords, $record; } } $sdata->{HIGHESTMODSEQ} = $idata{HIGHESTMODSEQ}; - $sdata->{LAST_UID} = $idata{LAST_UID}; - $sdata->{RECENTTIME} = $time; - $sdata->{RECENTUID} = $recentuid; + $sdata->{LAST_UID} = $idata{LAST_UID}; + $sdata->{RECENTTIME} = $time; + $sdata->{RECENTUID} = $recentuid; - $Self->{syncer}->dlwrite('APPLY', 'MAILBOX', {%$sdata, RECORD => [sort { $a->{UID} <=> $b->{UID} } @applyrecords]}); + $Self->{syncer}->dlwrite('APPLY', 'MAILBOX', + { %$sdata, RECORD => [ sort { $a->{UID} <=> $b->{UID} } @applyrecords ] }); $Self->writedown($sdata); } sub readup { - my $Self = shift; + my $Self = shift; my $mboxname = shift; my $existing = shift; - if ($existing and $Self->{cachedir} and $Self->cachepath($existing->{UNIQUEID})) { + if ( $existing + and $Self->{cachedir} + and $Self->cachepath($existing->{UNIQUEID})) + { my $file = IO::File->new($Self->cachepath($existing->{UNIQUEID}), "r"); my $data = eval { $file->getline() }; my $perl = eval { decode_json($data) }; - if ($perl and $perl->{UNIQUEID} eq $existing->{UNIQUEID} and $perl->{HIGHESTMODSEQ} eq $existing->{HIGHESTMODSEQ} and $perl->{UIDVALIDITY} eq $existing->{UIDVALIDITY} and $perl->{LAST_UID} eq $existing->{LAST_UID}) { + if ( $perl + and $perl->{UNIQUEID} eq $existing->{UNIQUEID} + and $perl->{HIGHESTMODSEQ} eq $existing->{HIGHESTMODSEQ} + and $perl->{UIDVALIDITY} eq $existing->{UIDVALIDITY} + and $perl->{LAST_UID} eq $existing->{LAST_UID}) + { print "READING $mboxname FROM CACHE\n" if $Self->{verbose}; return $perl; - } - else { + } else { use Data::Dumper; - my %check = map { $_ => $perl->{$_} } qw(UNIQUEID HIGHESTMODSEQ UIDVALIDITY LAST_UID); - print "INVALID $mboxname CACHE: " . Dumper(\%check, $existing) if $Self->{verbose}; + my %check = map { $_ => $perl->{$_} } + qw(UNIQUEID HIGHESTMODSEQ UIDVALIDITY LAST_UID); + print "INVALID $mboxname CACHE: " . Dumper(\%check, $existing) + if $Self->{verbose}; } } - my $res = eval { $Self->{syncer}->dlwrite('GET', 'FULLMAILBOX', $mboxname)->{MAILBOX}[0] }; + my $res = eval { + $Self->{syncer}->dlwrite('GET', 'FULLMAILBOX', $mboxname)->{MAILBOX}[0]; + }; $Self->writedown($res) if $res; @@ -358,7 +399,7 @@ sub writedown { my $Self = shift; my $data = shift; return unless $Self->{cachedir}; - my @records = sort { $a->{UID} <=> $b->{UID} } @{$data->{RECORD}}; + my @records = sort { $a->{UID} <=> $b->{UID} } @{ $data->{RECORD} }; $data->{RECORD} = \@records; eval { my $file = IO::File->new($Self->cachepath($data->{UNIQUEID}, 1), 'w'); @@ -367,20 +408,20 @@ sub writedown { } sub cachepath { - my $Self = shift; + my $Self = shift; my $uniqueid = shift; - my $make = shift; + my $make = shift; return unless $Self->{cachedir}; - my $dir = "$Self->{cachedir}/$Self->{targetuser}"; + my $dir = "$Self->{cachedir}/$Self->{targetuser}"; my $path = "$dir/$uniqueid.cache"; return (-f $path ? $path : undef) unless $make; - mkdir $dir unless -d $dir; + mkdir $dir unless -d $dir; return $path; } sub _notexpunged { - my $record = shift; - my @expunged = grep { lc $_ eq '\\expunged' } @{$record->{FLAGS}}; + my $record = shift; + my @expunged = grep { lc $_ eq '\\expunged' } @{ $record->{FLAGS} }; return not scalar @expunged; } @@ -396,14 +437,14 @@ sub _mkunixtime { } sub syncmailboxes { - my $Self = shift; + my $Self = shift; my $userdata = $Self->{userdata}; my $list = $Self->{imaptalk}->list('INBOX', '*'); my %mbox = map { $Self->_imap_to_sync($_->[2]) => 1 } @$list; - foreach my $mailbox (@{$userdata->{MAILBOX}}) { - if (delete $mbox{$mailbox->{MBOXNAME}}) { + foreach my $mailbox (@{ $userdata->{MAILBOX} }) { + if (delete $mbox{ $mailbox->{MBOXNAME} }) { $Self->syncmailbox($mailbox->{MBOXNAME}, $mailbox); } else { $Self->{syncer}->apply_unmailbox($mailbox->{MBOXNAME}); @@ -415,12 +456,12 @@ sub syncmailboxes { } sub syncsubs { - my $Self = shift; + my $Self = shift; my $userdata = $Self->{userdata}; - my $lsub = $Self->{imaptalk}->lsub('INBOX', '*'); - my %sub = map { $Self->_imap_to_sync($_->[2]) => 1 } @$lsub; + my $lsub = $Self->{imaptalk}->lsub('INBOX', '*'); + my %sub = map { $Self->_imap_to_sync($_->[2]) => 1 } @$lsub; - foreach my $existing (@{$userdata->{LSUB}[0]}) { + foreach my $existing (@{ $userdata->{LSUB}[0] }) { next if delete $sub{$existing}; $Self->{syncer}->apply_unsub($existing, $Self->{targetuser}); } @@ -430,11 +471,11 @@ sub syncsubs { } sub syncquota { - my $Self = shift; + my $Self = shift; my $userdata = $Self->{userdata}; - my $quota = $Self->{imaptalk}->getquotaroot('INBOX'); - my $name = $quota->{quotaroot}[1]; - my $amount = $quota->{$name}[2]; + my $quota = $Self->{imaptalk}->getquotaroot('INBOX'); + my $name = $quota->{quotaroot}[1]; + my $amount = $quota->{$name}[2]; my $existing = $Self->{userdata}{QUOTA}[0]; if ($existing and not $amount) { $Self->{syncer}->dlwrite('APPLY', 'UNQUOTA', $existing->{ROOT}); @@ -442,7 +483,8 @@ sub syncquota { } return if not $amount; return if ($existing and $amount == $existing->{STORAGE}); - $Self->{syncer}->dlwrite('APPLY', 'QUOTA', { ROOT => $Self->_imap_to_sync('INBOX'), STORAGE => $amount }); + $Self->{syncer}->dlwrite('APPLY', 'QUOTA', + { ROOT => $Self->_imap_to_sync('INBOX'), STORAGE => $amount }); } sub syncuser { diff --git a/perl/imap/Cyrus/IndexFile.pm b/perl/imap/Cyrus/IndexFile.pm index 9eba72fc83..f02e5d0a8e 100755 --- a/perl/imap/Cyrus/IndexFile.pm +++ b/perl/imap/Cyrus/IndexFile.pm @@ -602,7 +602,7 @@ SKIPPED VERSION 11 - Fastmail internal only my $VersionFormats = { 9 => { HeaderSize => 96, - _make_fields('Header',< { HeaderSize => 96, - _make_fields('Header',< { HeaderSize => 96, - _make_fields('Header',< { HeaderSize => 128, - _make_fields('Header',< { HeaderSize => 128, - _make_fields('Header',< { HeaderSize => 160, - _make_fields('Header',< { HeaderSize => 160, - _make_fields('Header',< { HeaderSize => 160, - _make_fields('Header',< { HeaderSize => 160, - _make_fields('Header',< { HeaderSize => 160, - _make_fields('Header',< { HeaderSize => 160, - _make_fields('Header',< "\\Answered", - 1 => "\\Flagged", - 2 => "\\Deleted", - 3 => "\\Draft", - 4 => "\\Seen", + 0 => "\\Answered", + 1 => "\\Flagged", + 2 => "\\Deleted", + 3 => "\\Draft", + 4 => "\\Seen", 26 => "\\Snoozed", 27 => "[SPLITCONVERSATION]", 28 => "[NEEDS-CLEANUP]", @@ -1241,8 +1241,8 @@ sub _make_fields { foreach my $line (@lines) { my ($Name, $Type, $Size) = split /\s+/, $line; - push @names, $Name; - push @items, [$Name, $Type, $Size, $Num, $Pos]; + push @names, $Name; + push @items, [ $Name, $Type, $Size, $Num, $Pos ]; push @packitems, _make_pack($Type, $Size); $Pos += $Size; @@ -1250,26 +1250,23 @@ sub _make_fields { } return ( - $prefix . 'Names' => { map { $names[$_] => $_ } 0..$#names }, + $prefix . 'Names' => { map { $names[$_] => $_ } 0 .. $#names }, $prefix . 'Fields' => \@items, - $prefix . 'Pack' => join("", @packitems), + $prefix . 'Pack' => join("", @packitems), ); } # build the pack/unpack expression for a single field sub _make_pack { my $format = shift; - my $size = shift; + my $size = shift; if ($format eq 'int32' or $format eq 'time_t') { return 'N'; - } - elsif ($format eq 'int64') { # ignore start.. + } elsif ($format eq 'int64') { # ignore start.. return 'x[N]N'; - } - elsif ($format eq 'bitmap') { + } elsif ($format eq 'bitmap') { return 'B' . (8 * $size); - } - elsif ($format eq 'hex') { + } elsif ($format eq 'hex') { return 'H' . (2 * $size); } } @@ -1302,7 +1299,7 @@ Causes of death: =cut sub new { - my $class = shift; + my $class = shift; my $handle = shift; my $buf; @@ -1323,12 +1320,13 @@ sub new { sysread($handle, $buf, $frm->{HeaderSize} - 12, 12); my $Self = bless { @_, - version => $version, - handle => $handle, - format => $frm, + version => $version, + handle => $handle, + format => $frm, rawheader => $buf, - recno => 0, - }, ref($class) || $class; + recno => 0, + }, + ref($class) || $class; $Self->{header} = $Self->_header_b2h($buf); die "Unable to parse header" unless $Self->{header}; @@ -1348,19 +1346,20 @@ calls $class->new() with the filehandle. =cut sub new_file { - my $class = shift; + my $class = shift; my $filename = shift; my $lockopts = shift; my $fh; if ($lockopts) { - require 'IO/File/fcntl.pm' || die "can't lock without IO::File::fcntl module"; + require 'IO/File/fcntl.pm' + || die "can't lock without IO::File::fcntl module"; $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY'; - $fh = IO::File::fcntl->new($filename, '+<', @$lockopts) - || die "Can't open $filename for locked read: $!"; + $fh = IO::File::fcntl->new($filename, '+<', @$lockopts) + || die "Can't open $filename for locked read: $!"; } else { $fh = IO::File->new("< $filename") - || die "Can't open $filename for read: $!"; + || die "Can't open $filename for read: $!"; } return $class->new($fh, @_); @@ -1375,7 +1374,7 @@ the write_record function and set header fields on the new object. =cut sub new_empty { - my $class = shift; + my $class = shift; my $version = shift; # check that the version is supported @@ -1385,8 +1384,9 @@ sub new_empty { my $Self = bless { @_, version => $version, - format => $frm, - }, ref($class) || $class; + format => $frm, + }, + ref($class) || $class; return $Self; } @@ -1402,16 +1402,16 @@ new LastUpdated. =cut sub stream_copy { - my $Self = shift; - my $outfh = shift; + my $Self = shift; + my $outfh = shift; my $decide = shift; - my %Opts = @_; + my %Opts = @_; my $out = $Self->new_empty($Opts{version} || $Self->{version}); my $newheader = $Self->header_copy(); if ($Opts{headerfields}) { - foreach my $field (keys %{$Opts{headerfields}}) { + foreach my $field (keys %{ $Opts{headerfields} }) { $newheader->{$field} = $Opts{headerfields}{$field}; } } @@ -1420,7 +1420,7 @@ sub stream_copy { $newheader->{NumRecords} = 0; # Important! Otherwise you get versions out of skew! $newheader->{MinorVersion} = $out->{version}; - $newheader->{RecordSize} = $out->{format}{RecordSize}; + $newheader->{RecordSize} = $out->{format}{RecordSize}; $out->write_header($outfh, $newheader); $Self->reset(); @@ -1460,7 +1460,7 @@ Returns the raw packed header as it is on disk. =cut sub header { - my $Self = shift; + my $Self = shift; my $Field = shift; if ($Field) { @@ -1496,7 +1496,7 @@ make destructive changes without affecting the original. sub header_copy { my $Self = shift; my $orig = $Self->{header}; - return { %$orig }; + return {%$orig}; } =item $index->reset($num) @@ -1510,10 +1510,12 @@ Requires the input filehandle to be seekable. sub reset { my $Self = shift; - my $num = shift || 0; + my $num = shift || 0; - my $NumRecords = $Self->{header}{MinorVersion} < 12 ? - $Self->{header}{Exists} : $Self->{header}{NumRecords}; + my $NumRecords + = $Self->{header}{MinorVersion} < 12 + ? $Self->{header}{Exists} + : $Self->{header}{NumRecords}; die "Invalid record $num (must be >= 0 and <= $NumRecords" unless ($num >= 0 and $num <= $NumRecords); @@ -1580,8 +1582,10 @@ sub next_record_raw { delete $Self->{checksum_failure}; # use direct access for speed - my $NumRecords = $Self->{header}{MinorVersion} < 12 ? - $Self->{header}{Exists} : $Self->{header}{NumRecords}; + my $NumRecords + = $Self->{header}{MinorVersion} < 12 + ? $Self->{header}{Exists} + : $Self->{header}{NumRecords}; my $RecordSize = $Self->{header}{RecordSize}; return undef unless $RecordSize; @@ -1592,8 +1596,7 @@ sub next_record_raw { # rewrite if passed so save the allocation cost $Self->{recno}++; return $Self->{rawrecord}; - } - else { + } else { delete $Self->{rawrecord}; return undef; # no more records! } @@ -1627,7 +1630,7 @@ returns undef, because there's no such concept in the datastructure. =cut sub record { - my $Self = shift; + my $Self = shift; my $Field = shift; my $record = $Self->record_hash(); @@ -1642,14 +1645,14 @@ sub record { } sub system_flags { - my $Self = shift; + my $Self = shift; my $Field = shift; my @sfdata = reverse split //, $Self->record('SystemFlags'); my %hash; - foreach my $key (0..$#sfdata) { + foreach my $key (0 .. $#sfdata) { next unless $sfdata[$key]; - $hash{$SystemFlagMap{$key} || $key} = $key; + $hash{ $SystemFlagMap{$key} || $key } = $key; } if ($Field) { @@ -1661,24 +1664,24 @@ sub system_flags { # arg is a Cyrus::HeaderFile to give us names for user flags sub flagslist { - my $Self = shift; + my $Self = shift; my $Header = shift; my @flags; # 32 bit sets my @sfdata = reverse split //, $Self->record('SystemFlags'); - foreach my $i (0..$#sfdata) { + foreach my $i (0 .. $#sfdata) { next unless $sfdata[$i]; push @flags, $SystemFlagMap{$i} || $i; } if ($Header) { my $userflags = $Header->header('Flags'); - my @ufdata = split //, $Self->record('UserFlags'); + my @ufdata = split //, $Self->record('UserFlags'); foreach my $base (0, 32, 64, 96) { - foreach my $i (0..31) { - my $f = $userflags->[$base+31-$i]; - push @flags, $f if ($f and $ufdata[$base+$i]); + foreach my $i (0 .. 31) { + my $f = $userflags->[ $base + 31 - $i ]; + push @flags, $f if ($f and $ufdata[ $base + $i ]); } } } @@ -1703,8 +1706,8 @@ sub record_array { } sub record_raw { - my $Self = shift; - return $Self->{rawrecord}; + my $Self = shift; + return $Self->{rawrecord}; } =item $index->field_number($Field) @@ -1715,7 +1718,7 @@ if there isn't one. =cut sub field_number { - my $Self = shift; + my $Self = shift; my $Field = shift; my $names = $Self->{format}{RecordNames}; die "No such record field $Field\n" unless exists $names->{$Field}; @@ -1731,8 +1734,8 @@ $header can be in array, hash or buffer format =cut sub write_header { - my $Self = shift; - my $fh = shift; + my $Self = shift; + my $fh = shift; my $header = shift; my $buf = $Self->_make_header($header); @@ -1749,11 +1752,13 @@ Also seeks back to the header and rewrites it with exists incremented by one. =cut sub append_record { - my $Self = shift; + my $Self = shift; my $record = shift; - my $NumRecords = $Self->{header}{MinorVersion} < 12 ? - $Self->{header}{Exists} : $Self->{header}{NumRecords}; + my $NumRecords + = $Self->{header}{MinorVersion} < 12 + ? $Self->{header}{Exists} + : $Self->{header}{NumRecords}; $Self->reset($NumRecords); $Self->write_record($Self->{handle}, $record); @@ -1766,7 +1771,7 @@ sub append_record { } sub rewrite_header { - my $Self = shift; + my $Self = shift; my $header = shift || $Self->header(); sysseek($Self->{handle}, 0, 0); @@ -1782,9 +1787,9 @@ Rewrite the record at position given by $num with the record (hash, array or buf =cut sub rewrite_record { - my $Self = shift; + my $Self = shift; my $record = shift; - my $num = @_ ? shift : ($Self->{recno} - 1); + my $num = @_ ? shift : ($Self->{recno} - 1); $Self->reset($num); @@ -1801,10 +1806,10 @@ XXX - $num support not done yet =cut sub write_record { - my $Self = shift; - my $fh = shift; + my $Self = shift; + my $fh = shift; my $record = shift; - my $num = shift; # XXX - seek? + my $num = shift; # XXX - seek? my $buf = $Self->_make_record($record); @@ -1818,22 +1823,22 @@ XXX - broken anyway. The purpose of this function is to allow multiple index fi =cut sub merge_indexes { - my $Self = shift; + my $Self = shift; my $target = shift; my @extras = shift; # copy the current header first my $targetpos = tell($target); - my $header = $Self->header(); + my $header = $Self->header(); # reset some stuff - $header->{NumRecords} = 0; + $header->{NumRecords} = 0; $header->{LastAppenddate} = 0; - $header->{LastUid} = 0; - $header->{QuotaUsed} = 0; - $header->{Deleted} = 0; - $header->{Answered} = 0; - $header->{Flagged} = 0; - $header->{HighestModseq} = 0; + $header->{LastUid} = 0; + $header->{QuotaUsed} = 0; + $header->{Deleted} = 0; + $header->{Answered} = 0; + $header->{Flagged} = 0; + $header->{HighestModseq} = 0; $Self->write_header($target, $header); my @all = ($Self, @extras); @@ -1847,7 +1852,7 @@ sub merge_indexes { my $higheruid; # read the first record of all lists - foreach my $n (0..$#all) { + foreach my $n (0 .. $#all) { next unless $records[$n]; if ($records[$n]{Uid} == $nextuid) { # algorithm: keep most recently modified @@ -1900,19 +1905,19 @@ process the records, and then re-parse them back into a binary index file. =cut sub header_dump { - my $Self = shift; + my $Self = shift; my $array = $Self->header_array(); return join(' ', @$array); } sub header_longdump { - my $Self = shift; + my $Self = shift; my $array = $Self->header_array(); my @data; my $frm = $Self->{format}{HeaderFields}; - foreach my $field (0..$#$frm) { + foreach my $field (0 .. $#$frm) { my $name = $frm->[$field][0]; - my $val = $array->[$field]; + my $val = $array->[$field]; $val = sprintf("%08x", $val) if $name =~ m/Crc$/; push @data, "$name: $val"; } @@ -1920,26 +1925,26 @@ sub header_longdump { } sub header_undump { - my $Self = shift; + my $Self = shift; my $string = shift; - my @items = split ' ', $string; + my @items = split ' ', $string; return \@items; } sub record_dump { - my $Self = shift; + my $Self = shift; my $array = $Self->record_array(); return join(' ', @$array); } sub record_longdump { - my $Self = shift; + my $Self = shift; my $array = $Self->record_array(); my @data; my $frm = $Self->{format}{RecordFields}; - foreach my $field (0..$#$frm) { + foreach my $field (0 .. $#$frm) { my $name = $frm->[$field][0]; - my $val = $array->[$field]; + my $val = $array->[$field]; $val = sprintf("%08x", $val) if $name =~ m/Crc$/; push @data, "$name: $val"; } @@ -1947,9 +1952,9 @@ sub record_longdump { } sub record_undump { - my $Self = shift; + my $Self = shift; my $string = shift; - my @items = split ' ', $string; + my @items = split ' ', $string; return \@items; } @@ -1957,7 +1962,7 @@ sub record_undump { sub _make_header { my $Self = shift; - my $ds = shift; + my $ds = shift; my $ref = ref($ds); @@ -1975,7 +1980,7 @@ sub _make_header { sub _make_record { my $Self = shift; - my $ds = shift; + my $ds = shift; my $ref = ref($ds); @@ -1996,27 +2001,28 @@ sub _make_record { sub _header_b2h { my $Self = shift; - my $buf = shift; + my $buf = shift; return undef unless $buf; my $array = $Self->_header_b2a($buf); - my $hash = $Self->_header_a2h($array); + my $hash = $Self->_header_a2h($array); return $hash; } sub _header_b2a { my $Self = shift; - my $buf = shift; + my $buf = shift; return undef unless $buf; my @array = unpack($Self->{format}{HeaderPack}, $buf); # check checksum match! if ($Self->{version} >= 11) { - my $Header = $Self->{format}{HeaderFields}[$Self->{format}{HeaderNames}{HeaderCrc}]; + my $Header = $Self->{format}{HeaderFields} + [ $Self->{format}{HeaderNames}{HeaderCrc} ]; my $crc = crc32(substr($buf, 0, $Header->[4])); - if ($array[$Header->[3]] != $crc) { + if ($array[ $Header->[3] ] != $crc) { $Self->{checksum_failure} = 1; warn "Header CRC Failure $array[$Header->[3]] != $crc"; die "Header CRC Failure $array[$Header->[3]] != $crc" @@ -2033,20 +2039,21 @@ sub _header_h2b { return undef unless $hash; my $array = $Self->_header_h2a($hash); - my $buf = $Self->_header_a2b($array); + my $buf = $Self->_header_a2b($array); return $buf; } sub _header_a2b { - my $Self = shift; + my $Self = shift; my $array = shift; return undef unless $array; my $buf = pack($Self->{format}{HeaderPack}, @$array); if ($Self->{version} >= 11) { - my $Header = $Self->{format}{HeaderFields}[$Self->{format}{HeaderNames}{HeaderCrc}]; + my $Header = $Self->{format}{HeaderFields} + [ $Self->{format}{HeaderNames}{HeaderCrc} ]; my $crc = crc32(substr($buf, 0, $Header->[4])); substr($buf, $Header->[4]) = pack('N', $crc); } @@ -2055,14 +2062,14 @@ sub _header_a2b { } sub _header_a2h { - my $Self = shift; + my $Self = shift; my $array = shift; return undef unless $array; my %res; my $frm = $Self->{format}{HeaderFields}; - for (0..$#$frm) { - $res{$frm->[$_][0]} = $array->[$_]; + for (0 .. $#$frm) { + $res{ $frm->[$_][0] } = $array->[$_]; } return \%res; @@ -2075,8 +2082,8 @@ sub _header_h2a { my @array; my $frm = $Self->{format}{HeaderFields}; - for (0..$#$frm) { - $array[$_] = $hash->{$frm->[$_][0]}; + for (0 .. $#$frm) { + $array[$_] = $hash->{ $frm->[$_][0] }; } return \@array; @@ -2091,20 +2098,21 @@ sub _record_h2b { return undef unless $hash; my $array = $Self->_record_h2a($hash); - my $buf = $Self->_record_a2b($array); + my $buf = $Self->_record_a2b($array); return $buf; } sub _record_a2b { - my $Self = shift; + my $Self = shift; my $array = shift; return undef unless $array; my $buf = pack($Self->{format}{RecordPack}, @$array); if ($Self->{version} >= 11) { - my $Record = $Self->{format}{RecordFields}[$Self->{format}{RecordNames}{RecordCrc}]; + my $Record = $Self->{format}{RecordFields} + [ $Self->{format}{RecordNames}{RecordCrc} ]; my $crc = crc32(substr($buf, 0, $Record->[4])); substr($buf, $Record->[4]) = pack('N', $crc); } @@ -2114,27 +2122,28 @@ sub _record_a2b { sub _record_b2h { my $Self = shift; - my $buf = shift; + my $buf = shift; return undef unless $buf; my $array = $Self->_record_b2a($buf); - my $hash = $Self->_record_a2h($array); + my $hash = $Self->_record_a2h($array); return $hash; } sub _record_b2a { my $Self = shift; - my $buf = shift; + my $buf = shift; return undef unless $buf; my @array = unpack($Self->{format}{RecordPack}, $buf); # check checksum match! if ($Self->{version} >= 11) { - my $Record = $Self->{format}{RecordFields}[$Self->{format}{RecordNames}{RecordCrc}]; + my $Record = $Self->{format}{RecordFields} + [ $Self->{format}{RecordNames}{RecordCrc} ]; my $crc = crc32(substr($buf, 0, $Record->[4])); - if ($array[$Record->[3]] != $crc) { + if ($array[ $Record->[3] ] != $crc) { $Self->{checksum_failure} = 1; warn "Record CRC Failure ($Self->{recno}) $array[$Record->[3]] != $crc"; die "Record CRC Failure ($Self->{recno}) $array[$Record->[3]] != $crc" @@ -2146,14 +2155,14 @@ sub _record_b2a { } sub _record_a2h { - my $Self = shift; + my $Self = shift; my $array = shift; return undef unless $array; my %res; my $frm = $Self->{format}{RecordFields}; - for (0..$#$frm) { - $res{$frm->[$_][0]} = $array->[$_]; + for (0 .. $#$frm) { + $res{ $frm->[$_][0] } = $array->[$_]; } return \%res; @@ -2166,8 +2175,8 @@ sub _record_h2a { my @array; my $frm = $Self->{format}{RecordFields}; - for (0..$#$frm) { - $array[$_] = $hash->{$frm->[$_][0]}; + for (0 .. $#$frm) { + $array[$_] = $hash->{ $frm->[$_][0] }; } return \@array; diff --git a/perl/imap/Cyrus/Mbentry.pm b/perl/imap/Cyrus/Mbentry.pm index 69c8ee24ab..aa76acac44 100755 --- a/perl/imap/Cyrus/Mbentry.pm +++ b/perl/imap/Cyrus/Mbentry.pm @@ -6,22 +6,27 @@ use Cyrus::Mbname; use Types::Standard qw(HashRef Value Int Bool Any); # XXX: unused so far -use constant USER_ACL => 'lrswipcdn'; +use constant USER_ACL => 'lrswipcdn'; use constant ADMIN_ACL => 'lrswipcdan'; -use constant ANY_ACL => 'p'; +use constant ANY_ACL => 'p'; has intname => (isa => Value, is => 'rw'); -has name => (isa => Any, is => 'ro', lazy => 1, default => sub { Cyrus::Mbname->new_dbname(shift->intname) }); - -has is_uuid => (isa => Bool, is => 'ro'); -has acls => (isa => HashRef, is => 'rw'); -has createdmodseq => (isa => Int, is => 'rw'); -has foldermodseq => (isa => Int, is => 'rw'); -has mtime => (isa => Int, is => 'rw'); -has partition => (isa => Value, is => 'rw'); -has type => (isa => Value, is => 'rw'); -has uniqueid => (isa => Value, is => 'rw'); -has uidvalidity => (isa => Int, is => 'rw'); +has name => ( + isa => Any, + is => 'ro', + lazy => 1, + default => sub { Cyrus::Mbname->new_dbname(shift->intname) } +); + +has is_uuid => (isa => Bool, is => 'ro'); +has acls => (isa => HashRef, is => 'rw'); +has createdmodseq => (isa => Int, is => 'rw'); +has foldermodseq => (isa => Int, is => 'rw'); +has mtime => (isa => Int, is => 'rw'); +has partition => (isa => Value, is => 'rw'); +has type => (isa => Value, is => 'rw'); +has uniqueid => (isa => Value, is => 'rw'); +has uidvalidity => (isa => Int, is => 'rw'); sub _parse_dlist { my ($self, $name, $details) = @_; @@ -32,15 +37,15 @@ sub _parse_dlist { } if ($name =~ m/^I/) { $self->{uniqueid} = substr($name, 1); - $self->{is_uuid} = 1; + $self->{is_uuid} = 1; } $self->{intname} = $name; - $self->{type} = 'e'; - foreach my $item (@{$dlist->{data}}) { + $self->{type} = 'e'; + foreach my $item (@{ $dlist->{data} }) { if ($item->{key} eq 'A') { my %acls; - foreach my $sub (@{$item->{data}}) { - $acls{$sub->{key}} = $sub->{data}; + foreach my $sub (@{ $item->{data} }) { + $acls{ $sub->{key} } = $sub->{data}; } $self->{acls} = \%acls; } @@ -51,9 +56,9 @@ sub _parse_dlist { $self->{foldermodseq} = $item->{data}; } if ($item->{key} eq 'H') { - for my $histitem (@{$item->{data}}) { + for my $histitem (@{ $item->{data} }) { my %hist; - for my $field (@{$histitem->{data}}) { + for my $field (@{ $histitem->{data} }) { if ($field->{key} eq 'F') { $hist{foldermodseq} = $field->{data}; } @@ -61,10 +66,11 @@ sub _parse_dlist { $hist{mtime} = $field->{data}; } if ($field->{key} eq 'N') { - $hist{name} = Cyrus::Mbname->new_dbname("N$field->{data}")->intname(); + $hist{name} + = Cyrus::Mbname->new_dbname("N$field->{data}")->intname(); } } - push @{$item->{name_history}}, \%hist; + push @{ $item->{name_history} }, \%hist; } } if ($item->{key} eq 'M') { @@ -92,7 +98,7 @@ sub _parse_dlist { sub parse { my $Proto = shift; my $Class = ref($Proto) || $Proto; - my $Self = {}; + my $Self = {}; bless($Self, $Class); my ($MailboxName, $MailboxDetails) = @_; @@ -105,18 +111,18 @@ sub parse { if ($MailboxDetails =~ s/^\((.*?)\) //) { my $named = $1; my %named = split / /, $named; - $Self->{uniqueid} = $named{uniqueid} if exists $named{uniqueid}; + $Self->{uniqueid} = $named{uniqueid} if exists $named{uniqueid}; $Self->{specialuse} = $named{specialuse} if exists $named{specialuse}; # XXX - the rest } # XXX - it's mbtype, not "flag" - though it's also always 0... my ($Flag, $Partition, $ACLs) = ($MailboxDetails =~ /^(\d) (\w+) (.*)/); $Flag == 0 || die "Unexpected flag: $Flag"; - my %ACLs = split("\t",$ACLs); - $Self->{type} = 'e'; + my %ACLs = split("\t", $ACLs); + $Self->{type} = 'e'; $Self->{partition} = $Partition; - $Self->{acls} = \%ACLs; - $Self->{name} = $MailboxName; + $Self->{acls} = \%ACLs; + $Self->{name} = $MailboxName; return $Self; } @@ -124,29 +130,32 @@ sub parse { sub FormatDBOld { my $Self = shift; my %named; - $named{uniqueid} = $Self->{uniqueid} if exists $Self->{uniqueid}; + $named{uniqueid} = $Self->{uniqueid} if exists $Self->{uniqueid}; $named{specialuse} = $Self->{specialuse} if exists $Self->{specialuse}; my $str = ''; if (%named) { $str = '(' . join(" ", map { "$_ $named{$_}" } sort keys %named) . ') '; } - return $str . "0 " . $Self->{partition} . " " . join("\t", %{$Self->{acls}}) . "\t"; + return + $str . "0 " + . $Self->{partition} . " " + . join("\t", %{ $Self->{acls} }) . "\t"; } sub has_type { my $self = shift; - my $arg = shift; + my $arg = shift; return $self->type =~ m/$arg/; } # convenience functions -sub is_tombstone { shift->has_type('d') } +sub is_tombstone { shift->has_type('d') } sub is_intermediate { shift->has_type('i') } # mbname wrappers -sub username { shift->name->username } -sub userfolder { shift->name->userfolder } +sub username { shift->name->username } +sub userfolder { shift->name->userfolder } sub adminfolder { shift->name->adminfolder } 1; diff --git a/perl/imap/Cyrus/Mbname.pm b/perl/imap/Cyrus/Mbname.pm index ce4fcc5edf..80b867201e 100755 --- a/perl/imap/Cyrus/Mbname.pm +++ b/perl/imap/Cyrus/Mbname.pm @@ -7,13 +7,13 @@ use Moo; use Types::Standard qw(ArrayRef Value Int); # internal data structure (real things) -has boxes => (is => 'rw', isa => ArrayRef); -has localpart => (is => 'rw', isa => Value); -has domain => (is => 'rw', isa => Value); +has boxes => (is => 'rw', isa => ArrayRef); +has localpart => (is => 'rw', isa => Value); +has domain => (is => 'rw', isa => Value); has is_deleted => (is => 'rw', isa => Int); sub new_intname { - my $class = shift; + my $class = shift; my $intname = shift; my %self; @@ -42,7 +42,7 @@ sub new_intname { } sub new_dbname { - my $class = shift; + my $class = shift; my $dbname = shift; # allow 'N' for new-style keys, an 'R' for 'RESTORED' domain @@ -74,9 +74,9 @@ sub new_dbname { } sub new_userfolder { - my $class = shift; + my $class = shift; my $username = shift; - my $folder = shift; + my $folder = shift; my %self; if ($username =~ s/\@(.*)//) { @@ -97,30 +97,27 @@ sub new_userfolder { if ($boxes[0] eq 'INBOX') { shift @boxes; - } - elsif($boxes[0] eq 'user') { + } elsif ($boxes[0] eq 'user') { shift @boxes; $self{localpart} = shift @boxes; - } - else { + } else { die "Unknown top-level $boxes[0]"; } $self{boxes} = \@boxes; - } - else { - $self{boxes} = []; # INBOX + } else { + $self{boxes} = []; # INBOX } return bless \%self, ref($class) || $class; } sub new_extuserfolder { - my $class = shift; + my $class = shift; my $username = shift; - my $folder = shift; + my $folder = shift; my %self; if ($username =~ s/\@(.*)//) { @@ -138,19 +135,16 @@ sub new_extuserfolder { if (@boxes == 1 and $boxes[0] eq 'INBOX') { shift @boxes; - } - elsif($boxes[0] eq 'user') { + } elsif ($boxes[0] eq 'user') { shift @boxes; $self{localpart} = shift @boxes; - } - else { + } else { $self{boxes} = \@boxes; } - } - else { - $self{boxes} = []; # INBOX + } else { + $self{boxes} = []; # INBOX } return bless \%self, ref($class) || $class; @@ -158,7 +152,7 @@ sub new_extuserfolder { sub new_adminfolder { # this is basically new_intname, but with the domain on the end... - my $class = shift; + my $class = shift; my $adminfolder = shift; my %self; @@ -191,12 +185,12 @@ sub new_adminfolder { sub intname { my $self = shift; - my $res = ''; + my $res = ''; if ($self->{domain}) { $res .= $self->{domain} . '!'; } - my @boxes = @{$self->{boxes}||[]}; + my @boxes = @{ $self->{boxes} || [] }; if ($self->{localpart}) { unshift @boxes, $self->{localpart}; @@ -215,12 +209,12 @@ sub intname { sub adminfolder { my $self = shift; - my $res = ''; + my $res = ''; if ($self->{domain}) { $res = '@' . $self->{domain}; } - my @boxes = @{$self->{boxes}||[]}; + my @boxes = @{ $self->{boxes} || [] }; if ($self->{localpart}) { unshift @boxes, $self->{localpart}; @@ -239,12 +233,12 @@ sub adminfolder { sub dbname { my $self = shift; - my $res = ''; + my $res = ''; if ($self->{domain}) { $res .= $self->{domain} . "\x1d"; } - my @boxes = @{$self->{boxes}||[]}; + my @boxes = @{ $self->{boxes} || [] }; if ($self->{localpart}) { unshift @boxes, $self->{localpart}; @@ -262,7 +256,7 @@ sub dbname { sub userfolder { my $self = shift; - my @boxes = @{$self->{boxes}||[]}; + my @boxes = @{ $self->{boxes} || [] }; s/\./\^/g for @boxes; @@ -279,8 +273,9 @@ sub userfolder { sub extuserfolder { my $self = shift; - die "Not a userfolder" unless ($self->{localpart} and not $self->{is_deleted}); - my @boxes = @{$self->{boxes}||[]}; + die "Not a userfolder" + unless ($self->{localpart} and not $self->{is_deleted}); + my @boxes = @{ $self->{boxes} || [] }; return 'INBOX' unless @boxes; @@ -292,7 +287,9 @@ sub extuserfolder { sub username { my $self = shift; return unless $self->{localpart}; - return $self->{domain} ? "$self->{localpart}\@$self->{domain}" : $self->{localpart}; + return $self->{domain} + ? "$self->{localpart}\@$self->{domain}" + : $self->{localpart}; } 1; diff --git a/perl/imap/Cyrus/SyncProto.pm b/perl/imap/Cyrus/SyncProto.pm index 88ef0fc376..6080939f50 100644 --- a/perl/imap/Cyrus/SyncProto.pm +++ b/perl/imap/Cyrus/SyncProto.pm @@ -57,6 +57,7 @@ Cyrus::SyncProto - =head1 EXAMPLES =cut + =head1 PUBLIC API =over =item Cyrus::SyncProto->new() @@ -66,39 +67,41 @@ my $digest = Digest::SHA->new(); sub new { my $class = shift; - my $talk = shift; + my $talk = shift; my $Self = bless { verbose => 1, - talk => $talk, - tag => 1, - }, ref($class) || $class; + talk => $talk, + tag => 1, + }, + ref($class) || $class; return $Self; } sub mailbox_crc { - my $Self = shift; + my $Self = shift; my $mailbox = shift; - my $crc = 0; - foreach my $record (@{$mailbox->{RECORD}}) { + my $crc = 0; + foreach my $record (@{ $mailbox->{RECORD} }) { $crc ^= $Self->record_crc($record); } return $crc; } sub record_crc { - my $Self = shift; + my $Self = shift; my $record = shift; my $flagcrc = 0; - foreach my $flag (@{$record->{FLAGS}}) { + foreach my $flag (@{ $record->{FLAGS} }) { my $item = lc($flag); return 0 if $item eq '\\expunged'; # specialcase $flagcrc ^= crc32($item); } - my $str = "$record->{UID} $record->{MODSEQ} $record->{LAST_UPDATED} ($flagcrc) $record->{INTERNALDATE} $record->{GUID}"; + my $str + = "$record->{UID} $record->{MODSEQ} $record->{LAST_UPDATED} ($flagcrc) $record->{INTERNALDATE} $record->{GUID}"; return crc32($str); } @@ -109,9 +112,9 @@ sub _dlitem { } sub dlwrite { - my $Self = shift; + my $Self = shift; my $command = join(' ', map { _dlitem($_) } @_); - my $tag = sprintf("S%08d", $Self->{tag}++); + my $tag = sprintf("S%08d", $Self->{tag}++); $Self->{talk}->_imap_socket_out("$tag SYNC$command\r\n"); my %data; my $onecmd = ''; @@ -119,13 +122,13 @@ sub dlwrite { if ($line =~ s/^(\* )// || $line =~ m/^( )/) { if ($onecmd && $1 eq '* ') { my $val = Cyrus::DList->parse_string($onecmd, 1); - push @{$data{$val->{key}}}, $val->as_perl(); + push @{ $data{ $val->{key} } }, $val->as_perl(); $onecmd = ''; } $onecmd .= $line; while ($onecmd =~ m/(\d+)\+?\}\s*$/s) { my $length = $1; - my $buf = $Self->{talk}->_imap_socket_read_bytes($length); + my $buf = $Self->{talk}->_imap_socket_read_bytes($length); $onecmd .= "\r\n" . $buf; $onecmd .= $Self->{talk}->_imap_socket_read_line(); } @@ -133,7 +136,7 @@ sub dlwrite { } if ($onecmd) { my $val = Cyrus::DList->parse_string($onecmd, 1); - push @{$data{$val->{key}}}, $val->as_perl(); + push @{ $data{ $val->{key} } }, $val->as_perl(); $onecmd = ''; } die "dlwrite failed @_ => $line" unless $line =~ m{^$tag OK }i; @@ -158,19 +161,21 @@ sub mailbox_user { } sub apply_sub { - my $Self = shift; + my $Self = shift; my $mailbox = shift; - my $user = shift; + my $user = shift; - return $Self->dlwrite('APPLY', 'SUB', { MBOXNAME => $mailbox, USERID => $user }); + return $Self->dlwrite('APPLY', 'SUB', + { MBOXNAME => $mailbox, USERID => $user }); } sub apply_unsub { - my $Self = shift; + my $Self = shift; my $mailbox = shift; - my $user = shift; + my $user = shift; - return $Self->dlwrite('APPLY', 'UNSUB', { MBOXNAME => $mailbox, USERID => $user }); + return $Self->dlwrite('APPLY', 'UNSUB', + { MBOXNAME => $mailbox, USERID => $user }); } sub apply_unuser { @@ -181,7 +186,7 @@ sub apply_unuser { } sub apply_unmailbox { - my $Self = shift; + my $Self = shift; my $mailbox = shift; return $Self->dlwrite('APPLY', 'UNMAILBOX', $mailbox); @@ -195,7 +200,7 @@ sub get_user { } sub get_mailboxes { - my $Self = shift; + my $Self = shift; my @mailboxes = shift; return $Self->dlwrite("GET", "MAILBOXES", [@mailboxes]); diff --git a/perl/imap/IMAP.pm b/perl/imap/IMAP.pm index 0a7e69f525..66cf6c5f7f 100644 --- a/perl/imap/IMAP.pm +++ b/perl/imap/IMAP.pm @@ -45,7 +45,7 @@ use vars qw($VERSION @ISA); require DynaLoader; -@ISA = qw(DynaLoader); +@ISA = qw(DynaLoader); $VERSION = '1.00'; bootstrap Cyrus::IMAP $VERSION; @@ -84,41 +84,37 @@ sub send { if ($2 eq 'a') { # atom $res .= scalar shift(@rest); - } - elsif ($2 eq 'q') { + } elsif ($2 eq 'q') { # qstring $res .= $self->_qstringize(shift(@rest)); - } - elsif ($2 eq 's') { + } elsif ($2 eq 's') { # astring $res .= $self->_stringize(shift(@rest)); - } - elsif ($2 eq 'd') { + } elsif ($2 eq 'd') { # decimal $res .= (0 + scalar shift(@rest)); - } - elsif ($2 eq 'u') { + } elsif ($2 eq 'u') { # unsigned decimal; perl cares not for C lossage... $res .= (0 + scalar shift(@rest)); - } - elsif ($2 eq 'v') { + } elsif ($2 eq 'v') { # #astring my $spc = ''; if (ref($rest[0]) =~ /(^|=)HASH($|\()/) { - my %vals = %{shift(@rest)}; + my %vals = %{ shift(@rest) }; foreach (keys %vals) { - $res .= $self->_stringize($_) . ' ' . - $self->_stringize($vals{$_}) . $spc; + $res + .= $self->_stringize($_) . ' ' + . $self->_stringize($vals{$_}) + . $spc; $spc = ' '; } } else { - foreach (@{shift(@rest)}) { + foreach (@{ shift(@rest) }) { $res .= $self->_stringize($_) . $spc; $spc = ' '; } } - } - else { + } else { # anything else (NB: we respect %B being labeled "internal only") # NB: unlike the C version, we do not fail when handed an unknown escape $res .= $2; @@ -131,15 +127,21 @@ sub send { sub _cc { my $res = 2; - local($^W) = 0; + local ($^W) = 0; if (length($_[0]) >= 1024) { 0; } else { - foreach (map {unpack 'C', $_} split(//, $_[0])) { - if ($_==0 || $_==10 || $_==13 || $_==34 || $_==92 || $_>=128) { + foreach (map { unpack 'C', $_ } split(//, $_[0])) { + if ($_ == 0 || $_ == 10 || $_ == 13 || $_ == 34 || $_ == 92 || $_ >= 128) + { $res = 0; - } - elsif ($_<33 || $_==37 || $_==40 || $_==41 || $_==42 || $_==123) { + } elsif ($_ < 33 + || $_ == 37 + || $_ == 40 + || $_ == 41 + || $_ == 42 + || $_ == 123) + { $res = 1 if $res == 2; } } @@ -153,15 +155,14 @@ sub _qstringize { my $cc = _cc($str); if ($cc) { - # would be needed except imclient devolves to a LITERAL in this case. - #$str =~ s/([\\\"])/\\$1/g; - '"' . $str . '"'; - } - else { - # right now we assume LITERAL+ on the part of the server, since - # we have no better way of dealing with literals. - # sorry! - "{" . length($str) . "+}\r\n" . $str; + # would be needed except imclient devolves to a LITERAL in this case. + #$str =~ s/([\\\"])/\\$1/g; + '"' . $str . '"'; + } else { + # right now we assume LITERAL+ on the part of the server, since + # we have no better way of dealing with literals. + # sorry! + "{" . length($str) . "+}\r\n" . $str; } } @@ -172,18 +173,16 @@ sub _stringize { my $nz = ($str ne ''); if ($nz && $cc == 2) { - $str; - } - elsif ($cc) { - # would be needed except imclient devolves to a LITERAL in this case. - #$str =~ s/([\\\"])/\\$1/g; - '"' . $str . '"'; - } - else { - # right now we assume LITERAL+ on the part of the server, since - # we have no better way of dealing with literals. - # sorry! - "{" . length($str) . "+}\r\n" . $str; + $str; + } elsif ($cc) { + # would be needed except imclient devolves to a LITERAL in this case. + #$str =~ s/([\\\"])/\\$1/g; + '"' . $str . '"'; + } else { + # right now we assume LITERAL+ on the part of the server, since + # we have no better way of dealing with literals. + # sorry! + "{" . length($str) . "+}\r\n" . $str; } } @@ -193,83 +192,97 @@ sub _stringize { # also take the opportunity to add a hash-based interface. # sub authenticate { - my ($self, $first) = @_; - my (%opts, $rc); + my ($self, $first) = @_; + my (%opts, $rc); my ($starttls, $logindisabled, $availmechs) = (0, 0, ""); - if (defined $first && - $first =~ /^-\w+|Mechanism|Service|Authz|User|Minssf|Maxssf|Password|Tlskey|Notls|CAfile|CApath$/) { + if (defined $first + && $first =~ + /^-\w+|Mechanism|Service|Authz|User|Minssf|Maxssf|Password|Tlskey|Notls|CAfile|CApath$/ + ) + { (undef, %opts) = @_; - foreach (qw(mechanism service authz user minssf maxssf password tlskey notls cafile capath)) { - $opts{'-' . $_} = $opts{ucfirst($_)} if !defined($opts{'-' . $_}); + foreach ( + qw(mechanism service authz user minssf maxssf password tlskey notls cafile capath) + ) + { + $opts{ '-' . $_ } = $opts{ ucfirst($_) } if !defined($opts{ '-' . $_ }); } } else { - (undef, $opts{-mechanism}, $opts{-service}, $opts{-authz}, $opts{-user}, - $opts{-minssf}, $opts{-maxssf}, $opts{-password}, - $opts{-tlskey}, $opts{-notls}, $opts{-cafile}, $opts{-capath}) = @_; + ( + undef, $opts{-mechanism}, $opts{-service}, $opts{-authz}, + $opts{-user}, $opts{-minssf}, $opts{-maxssf}, $opts{-password}, + $opts{-tlskey}, $opts{-notls}, $opts{-cafile}, $opts{-capath} + ) = @_; } - $opts{-cafile} = "" if !defined($opts{-cafile}); - $opts{-capath} = "" if !defined($opts{-capath}); + $opts{-cafile} = "" if !defined($opts{-cafile}); + $opts{-capath} = "" if !defined($opts{-capath}); $opts{-service} = "imap" if !defined($opts{-service}); - $opts{-minssf} = 0 if !defined($opts{-minssf}); - $opts{-maxssf} = 10000 if !defined($opts{-maxssf}); - $opts{-user} = $ENV{USER} || $ENV{LOGNAME} || (getpwuid($<))[0] + $opts{-minssf} = 0 if !defined($opts{-minssf}); + $opts{-maxssf} = 10000 if !defined($opts{-maxssf}); + $opts{-user} = $ENV{USER} || $ENV{LOGNAME} || (getpwuid($<))[0] if !defined($opts{-user}); $opts{-authz} = "" if (!defined($opts{-authz})); $rc = 0; # Fetch all relevant capabilities - $self->addcallback({-trigger => 'CAPABILITY', - -callback => sub {my %a = @_; - map { - $starttls = 1 - if /^STARTTLS$/i; - $logindisabled = 1 - if /^LOGINDISABLED$/i; - $availmechs .= $_ . ' ' - if s/^AUTH=//; - } - split(/ /, $a{-text})}}); + $self->addcallback({ + -trigger => 'CAPABILITY', + -callback => sub { + my %a = @_; + map { + $starttls = 1 + if /^STARTTLS$/i; + $logindisabled = 1 + if /^LOGINDISABLED$/i; + $availmechs .= $_ . ' ' + if s/^AUTH=//; + } + split(/ /, $a{-text}); + } + }); $self->send(undef, undef, 'CAPABILITY'); $opts{-mechanism} = $availmechs if !defined($opts{-mechanism}); # Do STARTTLS if given a TLS key, OR # if the specified SASL mech isn't available and LOGINDISABLED - if (defined($opts{-tlskey}) || - (!($availmechs =~ /(\b|^)$opts{-mechanism}($|\b)/i) && $logindisabled)) { - if (!havetls() || !$starttls) { - if ($logindisabled) { - warn "Login disabled.\n" - } else { - warn "TLS disabled.\n"; - } - return undef; + if (defined($opts{-tlskey}) + || (!($availmechs =~ /(\b|^)$opts{-mechanism}($|\b)/i) && $logindisabled)) + { + if (!havetls() || !$starttls) { + if ($logindisabled) { + warn "Login disabled.\n"; + } else { + warn "TLS disabled.\n"; } + return undef; + } - if (!defined($opts{-tlskey})) { - $opts{-tlskey} = ""; - } - if (!defined($opts{-cafile})) { - $opts{-cafile} = ""; - } - if (!defined($opts{-capath})) { - $opts{-capath} = ""; - } - if ($opts{-notls}) { - $opts{-tlskey} = undef; - } + if (!defined($opts{-tlskey})) { + $opts{-tlskey} = ""; + } + if (!defined($opts{-cafile})) { + $opts{-cafile} = ""; + } + if (!defined($opts{-capath})) { + $opts{-capath} = ""; + } + if ($opts{-notls}) { + $opts{-tlskey} = undef; + } - $self->_starttls($opts{-tlskey}, $opts{-tlskey}, $opts{-cafile}, $opts{-capath}); + $self->_starttls($opts{-tlskey}, $opts{-tlskey}, $opts{-cafile}, + $opts{-capath}); - # Refetch all relevant capabilities - ($starttls, $logindisabled, $availmechs) = (0, 0, ""); - $self->send(undef, undef, 'CAPABILITY'); - $opts{-mechanism} = $availmechs if ($opts{-mechanism} eq ''); + # Refetch all relevant capabilities + ($starttls, $logindisabled, $availmechs) = (0, 0, ""); + $self->send(undef, undef, 'CAPABILITY'); + $opts{-mechanism} = $availmechs if ($opts{-mechanism} eq ''); } - $self->addcallback({-trigger => 'CAPABILITY'}); + $self->addcallback({ -trigger => 'CAPABILITY' }); if (lc($opts{-mechanism}) ne 'login') { # This seems to be the only way to avoid a @@ -277,9 +290,10 @@ sub authenticate { # when $opts{-password} is uninitialized (which may well be ok for e.g. # the GSSAPI mechanism). no warnings 'uninitialized'; - $rc = $self->_authenticate($opts{-mechanism}, $opts{-service}, - $opts{-authz}, $opts{-user}, $opts{-password}, - $opts{-minssf}, $opts{-maxssf}); + $rc = $self->_authenticate( + $opts{-mechanism}, $opts{-service}, $opts{-authz}, $opts{-user}, + $opts{-password}, $opts{-minssf}, $opts{-maxssf} + ); } if (!$rc && $logindisabled) { @@ -294,28 +308,29 @@ sub authenticate { $opts{-mechanism} ||= 'plain'; if (!$rc && $opts{-mechanism} =~ /(\b|^)(plain|login)($|\b)/i) { - $opts{-user} = getlogin if !defined($opts{-user}); + $opts{-user} = getlogin if !defined($opts{-user}); $opts{-user} = (getpwuid($<))[0] if !defined($opts{-user}); - $opts{-user} = "nobody" if !defined($opts{-user}); + $opts{-user} = "nobody" if !defined($opts{-user}); # claimed to be a SASL bug: "AUTHENTICATE PLAIN" fails. in any case, we # also should provide a way to talk to pre-SASL Cyrus or even (shock # horror) non-Cyrus IMAP servers... # suck... if (!defined($opts{-password})) { - my $tty = (IO::File->new('/dev/tty', O_RDWR) || - *STDERR || *STDIN || *STDOUT); + my $tty + = (IO::File->new('/dev/tty', O_RDWR) || *STDERR || *STDIN || *STDOUT); $tty->autoflush(1); $tty->print("IMAP Password: "); my $ostty; chomp($ostty = `stty -g`); - system "stty -echo -icanon min 1 time 0 2>/dev/null || " . - "stty -echo cbreak"; + system "stty -echo -icanon min 1 time 0 2>/dev/null || " + . "stty -echo cbreak"; chomp($opts{-password} = $tty->getline); $tty->print("\013\010"); system "stty $ostty"; } - my ($kw, $text) = $self->send(undef, undef, 'LOGIN %s %s', - $opts{-user}, $opts{-password}); + my ($kw, $text) + = $self->send(undef, undef, 'LOGIN %s %s', + $opts{-user}, $opts{-password}); $opts{-password} = "\0" x length($opts{-password}); if ($kw eq 'OK') { $rc = 1; diff --git a/perl/imap/IMAP/Admin.pm b/perl/imap/IMAP/Admin.pm index 1758182e46..eb40171111 100644 --- a/perl/imap/IMAP/Admin.pm +++ b/perl/imap/IMAP/Admin.pm @@ -42,9 +42,9 @@ package Cyrus::IMAP::Admin; use strict; use Cyrus::IMAP; use vars qw($VERSION - *create *delete *deleteacl *listacl *list *rename *setacl - *subscribed *quota *quotaroot *info *setinfo *xfer - *subscribe *unsubscribe); + *create *delete *deleteacl *listacl *list *rename *setacl + *subscribed *quota *quotaroot *info *setinfo *xfer + *subscribe *unsubscribe); $VERSION = '1.00'; @@ -57,47 +57,51 @@ sub _cb_ref_eof { my %cb = @_; # indicate that the connection went away print STDERR "\nReferral connection to server lost.\n"; - ${$cb{-rock}} = undef; + ${ $cb{-rock} } = undef; } sub new { my $class = shift; - my $self = bless {}, $class; + my $self = bless {}, $class; $self->{cyrus} = Cyrus::IMAP->new(@_) or $self = undef; # Figure out if the remote supports MAILBOX-REFERRALS # This is sort of annoying that authenticate also issues a CAPABILITY # but the API makes it difficult to get at the results of that command. - if(defined($self)) { - $self->{support_referrals} = 0; - $self->{support_annotatemore} = 0; - $self->{support_list_extended} = 0; - $self->{support_list_special_use} = 0; + if (defined($self)) { + $self->{support_referrals} = 0; + $self->{support_annotatemore} = 0; + $self->{support_list_extended} = 0; + $self->{support_list_special_use} = 0; $self->{support_create_special_use} = 0; - $self->{authopts} = []; - $self->addcallback({-trigger => 'CAPABILITY', - -callback => sub {my %a = @_; - map { - # RFC 2193 IMAP4 Mailbox Referrals - $self->{support_referrals} = 1 - if /^MAILBOX-REFERRALS$/i; - $self->{support_annotatemore} = 1 - if /^ANNOTATEMORE$/i; - $self->{support_metadata} = 1 - if /^METADATA$/i; - # RFC 5258 IMAPv4 - LIST Command Extensions - $self->{support_list_extended} = 1 - if /^LIST-EXTENDED$/i; - # RFC 6154 - IMAP LIST Extension for Special-Use Mailboxes - $self->{support_list_special_use} = 1 - if /^SPECIAL-USE$/i; - # RFC 6154 - IMAP LIST Extension for Special-Use Mailboxes - $self->{support_create_special_use} = 1 - if /^CREATE-SPECIAL-USE$/i; - } - split(/ /, $a{-text})}}); + $self->{authopts} = []; + $self->addcallback({ + -trigger => 'CAPABILITY', + -callback => sub { + my %a = @_; + map { + # RFC 2193 IMAP4 Mailbox Referrals + $self->{support_referrals} = 1 + if /^MAILBOX-REFERRALS$/i; + $self->{support_annotatemore} = 1 + if /^ANNOTATEMORE$/i; + $self->{support_metadata} = 1 + if /^METADATA$/i; + # RFC 5258 IMAPv4 - LIST Command Extensions + $self->{support_list_extended} = 1 + if /^LIST-EXTENDED$/i; + # RFC 6154 - IMAP LIST Extension for Special-Use Mailboxes + $self->{support_list_special_use} = 1 + if /^SPECIAL-USE$/i; + # RFC 6154 - IMAP LIST Extension for Special-Use Mailboxes + $self->{support_create_special_use} = 1 + if /^CREATE-SPECIAL-USE$/i; + } + split(/ /, $a{-text}); + } + }); $self->send(undef, undef, 'CAPABILITY'); - $self->addcallback({-trigger => 'CAPABILITY'}); + $self->addcallback({ -trigger => 'CAPABILITY' }); } $self; @@ -115,99 +119,101 @@ sub AUTOLOAD { no strict 'refs'; $AUTOLOAD =~ s/^.*:://; my $sub = $Cyrus::IMAP::{$AUTOLOAD}; - *$AUTOLOAD = sub { &$sub($_[0]->{cyrus}, @_[1..$#_]); }; + *$AUTOLOAD = sub { &$sub($_[0]->{cyrus}, @_[ 1 .. $#_ ]); }; goto &$AUTOLOAD; } # Wrap around Cyrus::IMAP's authenticate, so that we are sure to # send an rlist command if they support referrals sub authenticate { - my $self = shift; - if(@_) { - $self->{authopts} = \@_; - } - my $rc = $self->{cyrus}->authenticate(@_); - if($rc && $self->{support_referrals}) { - # Advertise our desire for referrals - my $msg; - ($rc, $msg) = $self->send('', '', 'RLIST "" ""'); - if($rc eq "OK") { - $rc = 1; - } else { - $rc = 0; - } + my $self = shift; + if (@_) { + $self->{authopts} = \@_; + } + my $rc = $self->{cyrus}->authenticate(@_); + if ($rc && $self->{support_referrals}) { + # Advertise our desire for referrals + my $msg; + ($rc, $msg) = $self->send('', '', 'RLIST "" ""'); + if ($rc eq "OK") { + $rc = 1; + } else { + $rc = 0; } - return $rc; + } + return $rc; } # Spit out a reference to the previous authentication options: sub _getauthopts { - my $self = shift; - return $self->{authopts}; + my $self = shift; + return $self->{authopts}; } sub reconstruct { - my ($self, $mailbox, $recurse) = @_; - my $rc; - my $msg; - if($recurse == 1) { - ($rc, $msg) = $self->send('', '', 'RECONSTRUCT %s RECURSIVE', - $mailbox); - } else { - ($rc, $msg) = $self->send('', '', 'RECONSTRUCT %s', $mailbox); - } - $self->{error} = $msg; - if($rc eq "OK") { - $rc = 1; - } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { - my ($refserver, $box) = $self->fromURL($1); - my $port = 143; + my ($self, $mailbox, $recurse) = @_; + my $rc; + my $msg; + if ($recurse == 1) { + ($rc, $msg) = $self->send('', '', 'RECONSTRUCT %s RECURSIVE', $mailbox); + } else { + ($rc, $msg) = $self->send('', '', 'RECONSTRUCT %s', $mailbox); + } + $self->{error} = $msg; + if ($rc eq "OK") { + $rc = 1; + } else { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + my ($refserver, $box) = $self->fromURL($1); + my $port = 143; - if($refserver =~ /:/) { - $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; - } + if ($refserver =~ /:/) { + $refserver =~ /([^:]+):(\d+)/; + $refserver = $1; + $port = $2; + } - my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) - or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) - or die "cyradm: cannot authenticate to $refserver\n"; + my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) + or die "cyradm: cannot connect to $refserver\n"; + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) + or die "cyradm: cannot authenticate to $refserver\n"; - my $ret = $cyradm->reconstruct($mailbox,$recurse); - $self->{error} = $cyradm->{error}; - $cyradm = undef; - return $ret; - } else { - $rc = 0; - } + my $ret = $cyradm->reconstruct($mailbox, $recurse); + $self->{error} = $cyradm->{error}; + $cyradm = undef; + return $ret; + } else { + $rc = 0; } - return $rc; + } + return $rc; } sub createmailbox { my ($self, $mbx, $partition, $opts) = @_; - my $cmd = "CREATE %s"; + my $cmd = "CREATE %s"; my @args = (); - # RFC 3501 + cyrus: CREATE mailbox [partition] - # RFC 4466 + RFC 6154: CREATE mailbox ([PARTITION partition ]USE (special-use)) - if (defined ($$opts{'-specialuse'})) { - if($self->{support_create_special_use}) { - if (defined ($partition)) { - $cmd .= " (PARTITION %a USE (%a))" ; + # RFC 3501 + cyrus: CREATE mailbox [partition] + # RFC 4466 + RFC 6154: CREATE mailbox ([PARTITION partition ]USE (special-use)) + if (defined($$opts{'-specialuse'})) { + if ($self->{support_create_special_use}) { + if (defined($partition)) { + $cmd .= " (PARTITION %a USE (%a))"; push @args, ($partition, $$opts{'-specialuse'}); } else { - $cmd .= " (USE (%a))" ; + $cmd .= " (USE (%a))"; push @args, $$opts{'-specialuse'}; } } else { $self->{error} = "Remote does not support CREATE-SPECIAL-USE."; return undef; } - } elsif (defined ($partition)) { + } elsif (defined($partition)) { $cmd .= " %a"; push @args, $partition; } @@ -216,21 +222,24 @@ sub createmailbox { $self->{error} = undef; 1; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->createmailbox($box); @@ -245,26 +254,29 @@ sub createmailbox { sub deletemailbox { my ($self, $mbx) = @_; - my ($rc, $msg) = $self->send('', '', 'DELETE %s', $mbx); + my ($rc, $msg) = $self->send('', '', 'DELETE %s', $mbx); if ($rc eq 'OK') { $self->{error} = undef; 1; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->deletemailbox($box); @@ -288,24 +300,27 @@ sub deleteaclmailbox { if ($rc eq 'OK') { $cnt++; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; - $cnt += $cyradm->deleteaclmailbox($mbx,$acl); + $cnt += $cyradm->deleteaclmailbox($mbx, $acl); $res .= "\n" if $res ne ''; $res .= $acl . ': ' . $cyradm->{error}; @@ -330,17 +345,19 @@ sub deleteaclmailbox { sub listaclmailbox { my ($self, $mbx) = @_; my %info = (); - $self->addcallback({-trigger => 'ACL', - -callback => sub { - my %d = @_; - return unless $d{-text} =~ s/^\"*\Q$mbx\E\"*\s+//; - while ($d{-text} =~ s/(\S+)\s+(\S+)\s*//) { - $d{-rock}{$1} = $2; - } - }, - -rock => \%info}); + $self->addcallback({ + -trigger => 'ACL', + -callback => sub { + my %d = @_; + return unless $d{-text} =~ s/^\"*\Q$mbx\E\"*\s+//; + while ($d{-text} =~ s/(\S+)\s+(\S+)\s*//) { + $d{-rock}{$1} = $2; + } + }, + -rock => \%info + }); my ($rc, $msg) = $self->send('', '', 'GETACL %s', $mbx); - $self->addcallback({-trigger => 'ACL'}); + $self->addcallback({ -trigger => 'ACL' }); if ($rc eq 'OK') { $self->{error} = undef; %info; @@ -358,7 +375,7 @@ sub listmailbox { my $list_cmd; my @list_sel; my @list_ret; - if($self->{support_referrals}) { + if ($self->{support_referrals}) { if ($self->{support_list_extended}) { $list_cmd = 'LIST'; push @list_sel, "REMOTE"; @@ -369,15 +386,21 @@ sub listmailbox { $list_cmd = 'LIST'; } - if(defined ($$opts{'-sel-special-use'}) && !$self->{support_list_special_use}) { + if (defined($$opts{'-sel-special-use'}) && !$self->{support_list_special_use}) + { $self->{error} = "Remote does not support SPECIAL-USE."; return undef; } - if((defined ($$opts{'-sel-special-use'}) || - defined ($$opts{'-sel-recursivematch'}) || - defined ($$opts{'-sel-subscribed'})) - && !$self->{support_list_extended}) { + if ( + ( + defined($$opts{'-sel-special-use'}) + || defined($$opts{'-sel-recursivematch'}) + || defined($$opts{'-sel-subscribed'}) + ) + && !$self->{support_list_extended} + ) + { $self->{error} = "Remote does not support LIST-EXTENDED."; return undef; } @@ -388,16 +411,16 @@ sub listmailbox { # option (or only with REMOTE), as it only makes sense when other # selection options are also used." push @list_sel, "RECURSIVEMATCH" - if defined ($$opts{'-sel-recursivematch'}); + if defined($$opts{'-sel-recursivematch'}); push @list_sel, "SUBSCRIBED" - if defined ($$opts{'-sel-subscribed'}); + if defined($$opts{'-sel-subscribed'}); - if($self->{support_list_special_use}) { + if ($self->{support_list_special_use}) { # always return special-use flags push @list_ret, "SPECIAL-USE"; push @list_sel, "SPECIAL-USE" - if defined ($$opts{'-sel-special-use'}); + if defined($$opts{'-sel-special-use'}); } } @@ -408,61 +431,64 @@ sub listmailbox { # # This should never trigger: MAILBOX-REFERRALS and SPECIAL-USE but no # LIST-EXTENDED. - if ($list_cmd eq "RLIST" && (scalar (@list_ret) > 0 || scalar (@list_sel) > 0)) { - $self->{error} = "Invalid capabilities: MAILBOX-REFERRALS and SPECIAL-USE but no LIST-EXTENDED."; + if ($list_cmd eq "RLIST" && (scalar(@list_ret) > 0 || scalar(@list_sel) > 0)) + { + $self->{error} + = "Invalid capabilities: MAILBOX-REFERRALS and SPECIAL-USE but no LIST-EXTENDED."; return undef; } - $self->addcallback({-trigger => 'LIST', - -callback => sub { - my %d = @_; - next unless $d{-text} =~ s/^\(([^\)]*)\) //; - my $attrs = $1; - my $sep = ''; - my $mbox; - my $extended; - # NIL or (attrs) "sep" "str" - if ($d{-text} =~ /^N/) { - return if $d{-text} !~ s/^NIL//; - } - elsif ($d{-text} =~ s/\"\\?(.)\"//) { - $sep = $1; - } - return unless $d{-text} =~ s/^ //; - if ($d{-text} =~ /{\d+}(.*)/) { - # cope with literals (?) - (undef, $mbox) = split(/\n/, $d{-text}); - } elsif ($d{-text} =~ /^\"(([^\\\"]*\\)*[^\\\"]*)\"/) { - ($mbox = $1) =~ s/\\(.)/$1/g; - } else { - $d{-text} =~ s/^([]!\#-[^-~]+)//; - $mbox = $1; - } - if ($d{-text} =~ s/^ \(("{0,1}[^" ]+"{0,1} \("[^"]*"\))\)//) { - # RFC 5258: mbox-list-extended = "(" [mbox-list-extended-item - # *(SP mbox-list-extended-item)] ")" - $extended = $1; - } - push @{$d{-rock}}, [$mbox, $attrs, $sep, $extended]; - }, - -rock => \@info}); + $self->addcallback({ + -trigger => 'LIST', + -callback => sub { + my %d = @_; + next unless $d{-text} =~ s/^\(([^\)]*)\) //; + my $attrs = $1; + my $sep = ''; + my $mbox; + my $extended; + # NIL or (attrs) "sep" "str" + if ($d{-text} =~ /^N/) { + return if $d{-text} !~ s/^NIL//; + } elsif ($d{-text} =~ s/\"\\?(.)\"//) { + $sep = $1; + } + return unless $d{-text} =~ s/^ //; + if ($d{-text} =~ /{\d+}(.*)/) { + # cope with literals (?) + (undef, $mbox) = split(/\n/, $d{-text}); + } elsif ($d{-text} =~ /^\"(([^\\\"]*\\)*[^\\\"]*)\"/) { + ($mbox = $1) =~ s/\\(.)/$1/g; + } else { + $d{-text} =~ s/^([]!\#-[^-~]+)//; + $mbox = $1; + } + if ($d{-text} =~ s/^ \(("{0,1}[^" ]+"{0,1} \("[^"]*"\))\)//) { + # RFC 5258: mbox-list-extended = "(" [mbox-list-extended-item + # *(SP mbox-list-extended-item)] ")" + $extended = $1; + } + push @{ $d{-rock} }, [ $mbox, $attrs, $sep, $extended ]; + }, + -rock => \@info + }); # list = "LIST" [SP list-select-opts] SP mailbox SP mbox-or-pat # [SP list-return-opts] my @args = (); - my $cmd = $list_cmd; - if (scalar (@list_sel) > 0) { + my $cmd = $list_cmd; + if (scalar(@list_sel) > 0) { $cmd .= " (%a)"; - push @args, join (" ", @list_sel); + push @args, join(" ", @list_sel); } $cmd .= " %s %s"; push @args, ($ref, $pat); - if (scalar (@list_ret) > 0) { + if (scalar(@list_ret) > 0) { $cmd .= " RETURN (%a)"; - push @args, join (" ", @list_ret); + push @args, join(" ", @list_ret); } my ($rc, $msg) = $self->send('', '', $cmd, @args); - $self->addcallback({-trigger => $list_cmd}); + $self->addcallback({ -trigger => $list_cmd }); if ($rc eq 'OK') { $self->{error} = undef; @info; @@ -478,37 +504,38 @@ sub listsubscribed { $ref ||= $pat; my @info = (); my $list_cmd; - if($self->{support_referrals}) { + if ($self->{support_referrals}) { $list_cmd = 'RLSUB'; } else { $list_cmd = 'LSUB'; } - $self->addcallback({-trigger => 'LSUB', - -callback => sub { - my %d = @_; - next unless $d{-text} =~ s/^\(([^\)]*)\) //; - my $attrs = $1; - my $sep = ''; - # NIL or (attrs) "sep" "str" - if ($d{-text} =~ /^N/) { - return if $d{-text} !~ s/^NIL//; - } - elsif ($d{-text} =~ s/\"\\?(.)\"//) { - $sep = $1; - } - return unless $d{-text} =~ s/^ //; - my $mbox; - if ($d{-text} =~ /\"(([^\\\"]*\\.)*[^\\\"]*)\"/) { - ($mbox = $1) =~ s/\\(.)/$1/g; - } else { - $d{-text} =~ /^([]!\#-[^-~]+)/; - $mbox = $1; - } - push @{$d{-rock}}, [$mbox, $attrs, $sep]; - }, - -rock => \@info}); + $self->addcallback({ + -trigger => 'LSUB', + -callback => sub { + my %d = @_; + next unless $d{-text} =~ s/^\(([^\)]*)\) //; + my $attrs = $1; + my $sep = ''; + # NIL or (attrs) "sep" "str" + if ($d{-text} =~ /^N/) { + return if $d{-text} !~ s/^NIL//; + } elsif ($d{-text} =~ s/\"\\?(.)\"//) { + $sep = $1; + } + return unless $d{-text} =~ s/^ //; + my $mbox; + if ($d{-text} =~ /\"(([^\\\"]*\\.)*[^\\\"]*)\"/) { + ($mbox = $1) =~ s/\\(.)/$1/g; + } else { + $d{-text} =~ /^([]!\#-[^-~]+)/; + $mbox = $1; + } + push @{ $d{-rock} }, [ $mbox, $attrs, $sep ]; + }, + -rock => \@info + }); my ($rc, $msg) = $self->send('', '', "$list_cmd %s %s", $pat, $ref); - $self->addcallback({-trigger => $list_cmd}); + $self->addcallback({ -trigger => $list_cmd }); if ($rc eq 'OK') { $self->{error} = undef; @info; @@ -522,35 +549,40 @@ sub listsubscribed { sub listquota { my ($self, $root) = @_; my @info = (); - $self->addcallback({-trigger => 'QUOTA', - -callback => sub { - my %d = @_; - next unless - $d{-text} =~ s/^\S+.* \((\S*) *?(\S*) *?(\S*)\)//; - push @{$d{-rock}}, $1, [$2, $3]; - }, - -rock => \@info}); + $self->addcallback({ + -trigger => 'QUOTA', + -callback => sub { + my %d = @_; + next + unless $d{-text} =~ s/^\S+.* \((\S*) *?(\S*) *?(\S*)\)//; + push @{ $d{-rock} }, $1, [ $2, $3 ]; + }, + -rock => \@info + }); my ($rc, $msg) = $self->send('', '', 'GETQUOTA %s', $root); - $self->addcallback({-trigger => 'QUOTA'}); + $self->addcallback({ -trigger => 'QUOTA' }); if ($rc eq 'OK') { $self->{error} = undef; @info; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my @ret = $cyradm->listquota($root); @@ -567,48 +599,57 @@ sub listquota { sub listquotaroot { my ($self, $root) = @_; - my $qr = ''; + my $qr = ''; my @info = (); - $self->addcallback({-trigger => 'QUOTAROOT', - -callback => sub { - my %d = @_; - return unless ( $d{-text} =~ /^\"[^\"]+\" \"([^\"]+)\"/ or - $d{-text} =~ /^\"[^\"]+\" (\S+)/ or - $d{-text} =~ /[^\"]\S+ \"([^\"]+)\"/ or - $d{-text} =~ /^[^\"]\S+ (\S+)/ - ); - ${$d{-rock}} = $1; - }, - -rock => \$qr}, - {-trigger => 'QUOTA', - -callback => sub { - my %d = @_; - return unless - $d{-text} =~ s/\((\S+) (\S+) (\S+)\)$//; - push @{$d{-rock}}, $1, [$2, $3]; - }, - -rock => \@info}); + $self->addcallback( + { + -trigger => 'QUOTAROOT', + -callback => sub { + my %d = @_; + return + unless ($d{-text} =~ /^\"[^\"]+\" \"([^\"]+)\"/ + or $d{-text} =~ /^\"[^\"]+\" (\S+)/ + or $d{-text} =~ /[^\"]\S+ \"([^\"]+)\"/ + or $d{-text} =~ /^[^\"]\S+ (\S+)/); + ${ $d{-rock} } = $1; + }, + -rock => \$qr + }, + { + -trigger => 'QUOTA', + -callback => sub { + my %d = @_; + return + unless $d{-text} =~ s/\((\S+) (\S+) (\S+)\)$//; + push @{ $d{-rock} }, $1, [ $2, $3 ]; + }, + -rock => \@info + } + ); my ($rc, $msg) = $self->send('', '', 'GETQUOTAROOT %s', $root); - $self->addcallback({-trigger => 'QUOTA'}, {-trigger => 'QUOTAROOT'}); + $self->addcallback({ -trigger => 'QUOTA' }, { -trigger => 'QUOTAROOT' }); if ($rc eq 'OK') { $self->{error} = undef; ($qr, @info); } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my @ret = $cyradm->listquotaroot($root); @@ -626,48 +667,53 @@ sub listquotaroot { sub renamemailbox { my ($self, $src, $dest, $ptn) = @_; - $self->addcallback({-trigger => 'NO', - -callback => sub { - print $_ . "\n"; - }}); + $self->addcallback({ + -trigger => 'NO', + -callback => sub { + print $_ . "\n"; + } + }); my ($rc, $msg); if ($ptn) { ($rc, $msg) = $self->send('', '', 'RENAME %s %s %a', $src, $dest, $ptn); - } - else { + } else { ($rc, $msg) = $self->send('', '', 'RENAME %s %s', $src, $dest); } - $self->addcallback({-trigger => 'NO'}); + $self->addcallback({ -trigger => 'NO' }); if ($rc eq 'OK') { $self->{error} = undef; 1; } else { - if($self->{support_referrals} && - $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\s+([^\]\s]+)\]|) { + if ( $self->{support_referrals} + && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\s+([^\]\s]+)\]|) + { # We need two referrals for this to be valid - my ($refserver, $box) = $self->fromURL($1); + my ($refserver, $box) = $self->fromURL($1); my ($refserver2, $nbox) = $self->fromURL($2); my $port = 143; - if(!($refserver eq $refserver2)) { + if (!($refserver eq $refserver2)) { $self->{error} = "Inter-server referral. Not implemented."; return 1; } - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->renamemailbox($box, $box, $nbox); @@ -683,15 +729,17 @@ sub renamemailbox { sub xfermailbox { my ($self, $mbox, $server, $ptn) = @_; - $self->addcallback({-trigger => 'NO', - -callback => sub { - print $_ . "\n"; - }}); + $self->addcallback({ + -trigger => 'NO', + -callback => sub { + print $_ . "\n"; + } + }); - my ($rc, $msg) = $self->send('', '', 'XFER %s %s%a%a', $mbox, $server, - $ptn ? ' ' : $ptn, $ptn); + my ($rc, $msg) = $self->send('', '', 'XFER %s %s%a%a', + $mbox, $server, $ptn ? ' ' : $ptn, $ptn); - $self->addcallback({-trigger => 'NO'}); + $self->addcallback({ -trigger => 'NO' }); if ($rc eq 'OK') { $self->{error} = undef; @@ -705,13 +753,15 @@ sub xfermailbox { # hm. this list can't be confused with valid ACL values as of 1.6.19, except # for "all". sigh. -my %aclalias = (none => '', - read => 'lrs', - post => 'lrsp', - append => 'lrsip', - write => 'lrswipkxte', - delete => 'lrxte', - all => 'lrswipkxtea'); +my %aclalias = ( + none => '', + read => 'lrs', + post => 'lrsp', + append => 'lrsip', + write => 'lrswipkxte', + delete => 'lrxte', + all => 'lrswipkxtea' +); sub setaclmailbox { my ($self, $mbx, %acl) = @_; @@ -719,30 +769,33 @@ sub setaclmailbox { my $res = ''; my ($rc, $msg); foreach my $id (keys %acl) { - $acl{$id} = $aclalias{$acl{$id}} if defined $aclalias{$acl{$id}}; + $acl{$id} = $aclalias{ $acl{$id} } if defined $aclalias{ $acl{$id} }; ($rc, $msg) = $self->send('', '', 'SETACL %s %s %s', $mbx, $id, $acl{$id}); if ($rc eq 'OK') { $cnt++; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->setaclmailbox($mbx, %acl); - if(defined($ret)) { + if (defined($ret)) { $cnt++; $rc = 'OK'; } else { @@ -769,12 +822,12 @@ sub setquota { my ($self, $mbx, %quota) = @_; foreach my $id (keys %quota) { if ($id !~ /^[]!\#-[^-~]+$/) { - $self->{error} = $id . ': not an atom'; - return undef; + $self->{error} = $id . ': not an atom'; + return undef; } if ($quota{$id} !~ /^\d+$/) { - $self->{error} = $id . ': ' . $quota{$id} . ': not a number'; - return undef; + $self->{error} = $id . ': ' . $quota{$id} . ': not a number'; + return undef; } } my ($rc, $msg) = $self->send('', '', 'SETQUOTA %s (%v)', $mbx, \%quota); @@ -782,21 +835,24 @@ sub setquota { $self->{error} = undef; 1; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->setquota($mbx, %quota); @@ -809,15 +865,14 @@ sub setquota { } } - # map protocol name to user visible name sub _attribname2access { my $k = shift; - if ($k eq 'value.priv' ) { - return 'private'; + if ($k eq 'value.priv') { + return 'private'; } elsif ($k eq 'value.shared') { - return 'shared'; + return 'shared'; } else { return $k; } @@ -826,63 +881,67 @@ sub _attribname2access { sub mboxconfig { my ($self, $mailbox, $entry, $value, $private) = @_; - my %values = ( "comment" => "/comment", - "expire" => "/vendor/cmu/cyrus-imapd/expire", - "news2mail" => "/vendor/cmu/cyrus-imapd/news2mail", - "sharedseen" => "/vendor/cmu/cyrus-imapd/sharedseen", - "sieve" => "/vendor/cmu/cyrus-imapd/sieve", - "squat" => "/vendor/cmu/cyrus-imapd/squat", - "pop3showafter" => "/vendor/cmu/cyrus-imapd/pop3showafter" ); - - if(!$self->{support_metadata}) { + my %values = ( + "comment" => "/comment", + "expire" => "/vendor/cmu/cyrus-imapd/expire", + "news2mail" => "/vendor/cmu/cyrus-imapd/news2mail", + "sharedseen" => "/vendor/cmu/cyrus-imapd/sharedseen", + "sieve" => "/vendor/cmu/cyrus-imapd/sieve", + "squat" => "/vendor/cmu/cyrus-imapd/squat", + "pop3showafter" => "/vendor/cmu/cyrus-imapd/pop3showafter" + ); + + if (!$self->{support_metadata}) { $self->{error} = "Remote does not support METADATA."; return undef; } - if(exists($values{$entry})) { + if (exists($values{$entry})) { $entry = $values{$entry}; } else { - $self->{error} = "Unknown parameter $entry" unless substr($entry,0,1) eq "/"; + $self->{error} = "Unknown parameter $entry" + unless substr($entry, 0, 1) eq "/"; } my ($rc, $msg); - $value = undef if($value eq "none"); - if (defined ($private)) { + $value = undef if ($value eq "none"); + if (defined($private)) { $entry = "/private" . $entry; } else { $entry = "/shared" . $entry; } - if(defined($value)) { - ($rc, $msg) = $self->send('', '', - 'SETMETADATA %q (%q %q)', - $mailbox, $entry, $value); + if (defined($value)) { + ($rc, $msg) + = $self->send('', '', 'SETMETADATA %q (%q %q)', $mailbox, $entry, $value); } else { - ($rc, $msg) = $self->send('', '', - 'SETMETADATA %q (%q NIL)', - $mailbox, $entry); + ($rc, $msg) + = $self->send('', '', 'SETMETADATA %q (%q NIL)', $mailbox, $entry); } if ($rc eq 'OK') { $self->{error} = undef; 1; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->mboxconfig($mailbox, $entry, $value); @@ -897,34 +956,33 @@ sub mboxconfig { sub setinfoserver { my ($self, $entry, $value) = @_; - if(!$self->{support_metadata}) { + if (!$self->{support_metadata}) { $self->{error} = "Remote does not support METADATA."; return undef; } - my %values = ( "comment" => "/comment", - "motd" => "/motd", - "admin" => "/admin", - "shutdown" => "/vendor/cmu/cyrus-imapd/shutdown", - "expire" => "/vendor/cmu/cyrus-imapd/expire", - "squat" => "/vendor/cmu/cyrus-imapd/squat"); + my %values = ( + "comment" => "/comment", + "motd" => "/motd", + "admin" => "/admin", + "shutdown" => "/vendor/cmu/cyrus-imapd/shutdown", + "expire" => "/vendor/cmu/cyrus-imapd/expire", + "squat" => "/vendor/cmu/cyrus-imapd/squat" + ); $entry = $values{$entry} if (exists($values{$entry})); - $value = undef if($value eq "none"); + $value = undef if ($value eq "none"); my ($rc, $msg); $entry = "/shared" . $entry; - if(defined($value)) { - ($rc, $msg) = $self->send('', '', - "SETMETADATA \"\" (%q %q)", - $entry, $value); + if (defined($value)) { + ($rc, $msg) + = $self->send('', '', "SETMETADATA \"\" (%q %q)", $entry, $value); } else { - ($rc, $msg) = $self->send('', '', - "SETMETADATA \"\" (%q NIL)", - $entry); + ($rc, $msg) = $self->send('', '', "SETMETADATA \"\" (%q NIL)", $entry); } if ($rc eq 'OK') { @@ -938,124 +996,125 @@ sub setinfoserver { *setinfo = *setinfoserver; sub getmetadata { - my $self = shift; - my $box = shift; + my $self = shift; + my $box = shift; my @entries = @_; - if(!defined($box)) { + if (!defined($box)) { $box = ""; } - if(!$self->{support_metadata}) { + if (!$self->{support_metadata}) { $self->{error} = "Remote does not support METADATA."; return undef; } my %info = (); - $self->addcallback({-trigger => 'METADATA', - -callback => sub { - my %d = @_; - my $text = $d{-text}; - - # There were several draft iterations of this, - # but we only support the latest form command. - # Still, we might encounter entry-value pairs - # with nstring or literal8 values, possibly - # mixed in the same response. - # XXX Known bug: - # We support single nstring and literal8 entry - # value pairs, but only multiple nstring pairs. - if ($text =~ - /^\s*\"?([^\(]*?)\"?\s+\(((\"?[^"\{]+\"?\s+\"?[^"\)\{]+\"?\s*)+)\)/) { - my $mdbox = $1; - my $pairs = $2; - while ($pairs =~ /\"?([^"\{]+)\"?\s+\"?([^"\)\{]+)\"?/g) { - my $mdkey = $1; - my $mdvalue = $2; - if($mdbox ne "") { - $mdkey = "/mailbox/$mdkey"; - if ($mdkey =~ /private/) { - $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; - } elsif ($mdkey =~ /shared/) { - $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; - } - } else { - $mdkey = "/server/$mdkey"; - if ($mdkey =~ /private/) { - $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; - } elsif ($mdkey =~ /shared/) { - $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; - } - } - } - } - elsif ($text =~ - /^\s*\"?([^\(]*?)\"?\s+\(\"?([^"\{]*)\"?\s+\"?([^"\)\{]*)\"?\)/) { - my $mdbox = $1; - my $mdkey = $2; - my $mdvalue = $3; - if($mdbox ne "") { - $mdkey = "/mailbox/$mdkey"; - if ($mdkey =~ /private/) { - $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; - } elsif ($mdkey =~ /shared/) { - $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; - } - } else { - $mdkey = "/server/$mdkey"; - if ($mdkey =~ /private/) { - $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; - } elsif ($mdkey =~ /shared/) { - $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; - } - } - } elsif ($text =~ - /^\s*\"?([^\(]*?)\"?\s+\(\"?([^"\{]*?)\"?\s+\{(.*)\}\r\n/) { - my $mdbox = $1; - my $mdkey = $2; - my $len = $3; - $text =~ s/^\s*\"?([^\(]*?)\"?\s+\(\"?([^"]*)\"?\s+\{(.*)\}\r\n//s; - my $mdvalue = substr($text, 0, $len); - # Single annotation (literal style), - # possibly multiple values -- multiple - # values not tested. - if($mdbox ne "") { - $mdkey = "/mailbox/$mdkey"; - if ($mdkey =~ /private/) { - $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; - } elsif ($mdkey =~ /shared/) { - $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; - } - } else { - $mdkey = "/server/$mdkey"; - if ($mdkey =~ /private/) { - $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; - } elsif ($mdkey =~ /shared/) { - $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; - } - } - } else { - ; # XXX: unrecognized line, how to notify caller? - 1; - } - }, - -rock => \%info}); + $self->addcallback({ + -trigger => 'METADATA', + -callback => sub { + my %d = @_; + my $text = $d{-text}; + + # There were several draft iterations of this, + # but we only support the latest form command. + # Still, we might encounter entry-value pairs + # with nstring or literal8 values, possibly + # mixed in the same response. + # XXX Known bug: + # We support single nstring and literal8 entry + # value pairs, but only multiple nstring pairs. + if ($text =~ + /^\s*\"?([^\(]*?)\"?\s+\(((\"?[^"\{]+\"?\s+\"?[^"\)\{]+\"?\s*)+)\)/) + { + my $mdbox = $1; + my $pairs = $2; + while ($pairs =~ /\"?([^"\{]+)\"?\s+\"?([^"\)\{]+)\"?/g) { + my $mdkey = $1; + my $mdvalue = $2; + if ($mdbox ne "") { + $mdkey = "/mailbox/$mdkey"; + if ($mdkey =~ /private/) { + $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; + } elsif ($mdkey =~ /shared/) { + $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; + } + } else { + $mdkey = "/server/$mdkey"; + if ($mdkey =~ /private/) { + $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; + } elsif ($mdkey =~ /shared/) { + $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; + } + } + } + } elsif ($text =~ + /^\s*\"?([^\(]*?)\"?\s+\(\"?([^"\{]*)\"?\s+\"?([^"\)\{]*)\"?\)/) + { + my $mdbox = $1; + my $mdkey = $2; + my $mdvalue = $3; + if ($mdbox ne "") { + $mdkey = "/mailbox/$mdkey"; + if ($mdkey =~ /private/) { + $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; + } elsif ($mdkey =~ /shared/) { + $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; + } + } else { + $mdkey = "/server/$mdkey"; + if ($mdkey =~ /private/) { + $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; + } elsif ($mdkey =~ /shared/) { + $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; + } + } + } elsif ( + $text =~ /^\s*\"?([^\(]*?)\"?\s+\(\"?([^"\{]*?)\"?\s+\{(.*)\}\r\n/) + { + my $mdbox = $1; + my $mdkey = $2; + my $len = $3; + $text =~ s/^\s*\"?([^\(]*?)\"?\s+\(\"?([^"]*)\"?\s+\{(.*)\}\r\n//s; + my $mdvalue = substr($text, 0, $len); + # Single annotation (literal style), + # possibly multiple values -- multiple + # values not tested. + if ($mdbox ne "") { + $mdkey = "/mailbox/$mdkey"; + if ($mdkey =~ /private/) { + $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; + } elsif ($mdkey =~ /shared/) { + $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; + } + } else { + $mdkey = "/server/$mdkey"; + if ($mdkey =~ /private/) { + $d{-rock}->{"$mdbox"}->{'private'}->{$mdkey} = $mdvalue; + } elsif ($mdkey =~ /shared/) { + $d{-rock}->{"$mdbox"}->{'shared'}->{$mdkey} = $mdvalue; + } + } + } else { + ; # XXX: unrecognized line, how to notify caller? + 1; + } + }, + -rock => \%info + }); # send getmetadata "/mailbox/name/* or /private/* and /shared/*" - my($rc, $msg); - if(scalar(@entries)) { + my ($rc, $msg); + if (scalar(@entries)) { foreach my $annot (@entries) { - ($rc, $msg) = $self->send('', '', "GETMETADATA %q (%q)", - $box, $annot); - last if($rc ne 'OK'); + ($rc, $msg) = $self->send('', '', "GETMETADATA %q (%q)", $box, $annot); + last if ($rc ne 'OK'); } } else { - ($rc, $msg) = $self->send('', '', "GETMETADATA %q (\"/private/*\")", - $box); - ($rc, $msg) = $self->send('', '', "GETMETADATA %q (\"/shared/*\")", - $box); + ($rc, $msg) = $self->send('', '', "GETMETADATA %q (\"/private/*\")", $box); + ($rc, $msg) = $self->send('', '', "GETMETADATA %q (\"/shared/*\")", $box); } - $self->addcallback({-trigger => 'METADATA'}); + $self->addcallback({ -trigger => 'METADATA' }); if ($rc eq 'OK') { $self->{error} = undef; %info; @@ -1067,81 +1126,84 @@ sub getmetadata { *info = *getmetadata; sub getinfo { - my $self = shift; - my $box = shift; + my $self = shift; + my $box = shift; my @entries = @_; my @myentries; - if(scalar(@entries)) { - foreach my $annot (@entries) { - push @myentries, '/private' . $annot; - push @myentries, '/shared' . $annot; - } + if (scalar(@entries)) { + foreach my $annot (@entries) { + push @myentries, '/private' . $annot; + push @myentries, '/shared' . $annot; + } } return $self->getmetadata($box, @myentries); } *info = *getinfo; - sub setmetadata { my ($self, $mailbox, $entry, $value, $private) = @_; - my %values = ( "comment" => "/private/comment", - "expire" => "/shared/vendor/cmu/cyrus-imapd/expire", - "news2mail" => "/shared/vendor/cmu/cyrus-imapd/news2mail", - "sharedseen" => "/shared/vendor/cmu/cyrus-imapd/sharedseen", - "sieve" => "/shared/vendor/cmu/cyrus-imapd/sieve", - "specialuse" => "/private/specialuse", - "squat" => "/shared/vendor/cmu/cyrus-imapd/squat", - "pop3showafter" => "/shared/vendor/cmu/cyrus-imapd/pop3showafter" ); - - if(!$self->{support_metadata}) { + my %values = ( + "comment" => "/private/comment", + "expire" => "/shared/vendor/cmu/cyrus-imapd/expire", + "news2mail" => "/shared/vendor/cmu/cyrus-imapd/news2mail", + "sharedseen" => "/shared/vendor/cmu/cyrus-imapd/sharedseen", + "sieve" => "/shared/vendor/cmu/cyrus-imapd/sieve", + "specialuse" => "/private/specialuse", + "squat" => "/shared/vendor/cmu/cyrus-imapd/squat", + "pop3showafter" => "/shared/vendor/cmu/cyrus-imapd/pop3showafter" + ); + + if (!$self->{support_metadata}) { $self->{error} = "Remote does not support METADATA."; return undef; } - if(exists($values{$entry})) { + if (exists($values{$entry})) { $entry = $values{$entry}; } else { - $self->{error} = "Unknown parameter $entry" unless substr($entry,0,1) eq "/"; + $self->{error} = "Unknown parameter $entry" + unless substr($entry, 0, 1) eq "/"; } my ($rc, $msg); - $value = undef if($value eq "none"); - if (defined ($private)) { + $value = undef if ($value eq "none"); + if (defined($private)) { $entry =~ s/^\/shared\//\/private\//i; } - if(defined($value)) { - ($rc, $msg) = $self->send('', '', - "SETMETADATA %q (%q %q)", - $mailbox, $entry, $value); + if (defined($value)) { + ($rc, $msg) + = $self->send('', '', "SETMETADATA %q (%q %q)", $mailbox, $entry, $value); } else { - ($rc, $msg) = $self->send('', '', - "SETMETADATA %q (%q NIL)", - $mailbox, $entry); + ($rc, $msg) + = $self->send('', '', "SETMETADATA %q (%q NIL)", $mailbox, $entry); } if ($rc eq 'OK') { $self->{error} = undef; 1; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->mboxconfig($mailbox, $entry, $value); @@ -1156,26 +1218,29 @@ sub setmetadata { sub subscribemailbox { my ($self, $mbx) = @_; - my ($rc, $msg) = $self->send('', '', 'SUBSCRIBE %s', $mbx); + my ($rc, $msg) = $self->send('', '', 'SUBSCRIBE %s', $mbx); if ($rc eq 'OK') { $self->{error} = undef; 1; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->subscribemailbox($box); @@ -1191,26 +1256,29 @@ sub subscribemailbox { sub unsubscribemailbox { my ($self, $mbx) = @_; - my ($rc, $msg) = $self->send('', '', 'UNSUBSCRIBE %s', $mbx); + my ($rc, $msg) = $self->send('', '', 'UNSUBSCRIBE %s', $mbx); if ($rc eq 'OK') { $self->{error} = undef; 1; } else { - if($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { + if ($self->{support_referrals} && $msg =~ m|^\[REFERRAL\s+([^\]\s]+)\]|) { my ($refserver, $box) = $self->fromURL($1); my $port = 143; - if($refserver =~ /:/) { + if ($refserver =~ /:/) { $refserver =~ /([^:]+):(\d+)/; - $refserver = $1; $port = $2; + $refserver = $1; + $port = $2; } my $cyradm = Cyrus::IMAP::Admin->new($refserver, $port) or die "cyradm: cannot connect to $refserver\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_ref_eof, - -rock => \$cyradm}); - $cyradm->authenticate(@{$self->_getauthopts()}) + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_ref_eof, + -rock => \$cyradm + }); + $cyradm->authenticate(@{ $self->_getauthopts() }) or die "cyradm: cannot authenticate to $refserver\n"; my $ret = $cyradm->unsubscribemailbox($box); diff --git a/perl/imap/IMAP/Shell.pm b/perl/imap/IMAP/Shell.pm index f65ca8983e..eeaab9208b 100644 --- a/perl/imap/IMAP/Shell.pm +++ b/perl/imap/IMAP/Shell.pm @@ -60,125 +60,125 @@ use Cyrus::IMAP::Admin; use Getopt::Long qw(:config no_ignore_case); use Exporter; use POSIX (); -use Carp qw(confess); +use Carp qw(confess); use vars qw(@ISA @EXPORT $VERSION *cyradm); $VERSION = "1.00"; -@ISA = qw(Exporter); -@EXPORT = qw(cyradm shell run); +@ISA = qw(Exporter); +@EXPORT = qw(cyradm shell run); # note aliases -my %builtins = (exit => - [\&_sc_exit, '[number]', 'exit cyradm'], - quit => 'exit', - help => - [\&_sc_help, '[command]', 'show commands'], - '?' => 'help', - lam => 'listacl', - listacl => - [\&_sc_listacl, 'mailbox', 'list ACLs on mailbox'], - listaclmailbox => 'listacl', - lm => 'listmailbox', - listmailbox => - [\&_sc_list, '[-subscribed] [-specialuse] [pattern [base]]', - 'list mailboxes'], - server => - [\&_sc_server, '[-noauthenticate] [server]', - 'show current server or connect to server'], - servername => 'server', - connect => 'server', - authenticate => - [\&_sc_auth, - '[-minssf N] [-maxssf N] [-mechanisms list] [-service name] [-tlskey keyfile] [-notls] [-cafile cacertfile] [-capath cacertdir] [user]', - 'authenticate to server'], - auth => 'authenticate', - login => 'authenticate', - listquota => - [\&_sc_quota, 'root', 'list quotas on specified root'], - lq => 'listquota', - listquotaroot => - [\&_sc_quotaroot, 'mailbox', - 'show quota roots and quotas for mailbox'], - lqr => 'listquotaroot', - lqm => 'listquotaroot', - disconnect => - [\&_sc_disconn, '', 'disconnect from current server'], - disc => 'disconnect', - chdir => - [\&_sc_chdir, 'directory', 'change current directory'], - cd => 'chdir', - createmailbox => - [\&_sc_create, '[--partition partition] [--specialuse specialuse] mailbox [partition]', - 'create mailbox'], - create => 'createmailbox', - cm => 'createmailbox', - deleteaclmailbox => - [\&_sc_deleteacl, 'mailbox id [id ...]', - 'remove ACLs from mailbox'], - deleteacl => 'deleteaclmailbox', - dam => 'deleteaclmailbox', - deletemailbox => - [\&_sc_delete, 'mailbox [host]', 'delete mailbox'], - delete => 'deletemailbox', - dm => 'deletemailbox', - getmetadata => - [\&_sc_getmetadata, '[mailbox]', - 'display mailbox/server metadata'], - getmd => 'getmetadata', - info => - [\&_sc_info, '[mailbox]', - 'display mailbox/server annotations'], - mboxcfg => - [\&_sc_mboxcfg, '[--private] mailbox [comment|expire|news2mail|sieve|squat|/] value', - 'configure mailbox'], - mboxconfig => 'mboxcfg', - reconstruct => - [\&_sc_reconstruct, 'mailbox', 'reconstruct mailbox (if supported)'], - renamemailbox => - [\&_sc_rename, - '[--partition partition] oldname newname [partition]', - 'rename (and optionally relocate) mailbox'], - rename => 'renamemailbox', - renm => 'renamemailbox', - setaclmailbox => - [\&_sc_setacl, 'mailbox id rights [id rights ...]', - 'set ACLs on mailbox'], - setacl => 'setaclmailbox', - sam => 'setaclmailbox', - setinfo => - [\&_sc_setinfo, '[motd|comment|admin|shutdown|expire|squat] text', - 'set server metadata'], - setmetadata => - [\&_sc_setmetadata, '[--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|specialuse|squat|/] value', - 'set metadata to mailbox'], - setmd => 'setmetadata', - setquota => - [\&_sc_setquota, - 'mailbox resource value [resource value ...]', - 'set quota on mailbox or resource'], - sq => 'setquota', - version => - [\&_sc_version, '', - 'display version info of current server'], - ver => 'version', - xfermailbox => - [\&_sc_xfer, - '[--partition partition] mailbox server [partition]', - 'transfer (relocate) a mailbox to a different server'], - xfer => 'xfermailbox', - subscribe => - [\&_sc_subscribe, '[mailbox]', - 'subscribe to a mailbox'], - sub => 'subscribe', - unsubscribe => - [\&_sc_unsubscribe, '[mailbox]', - 'unsubscribe from a mailbox'], - unsub => 'unsubscribe', - #? alias - #? unalias - #? load - #? unload - ); +my %builtins = ( + exit => [ \&_sc_exit, '[number]', 'exit cyradm' ], + quit => 'exit', + help => [ \&_sc_help, '[command]', 'show commands' ], + '?' => 'help', + lam => 'listacl', + listacl => [ \&_sc_listacl, 'mailbox', 'list ACLs on mailbox' ], + listaclmailbox => 'listacl', + lm => 'listmailbox', + listmailbox => [ + \&_sc_list, + '[-subscribed] [-specialuse] [pattern [base]]', + 'list mailboxes' + ], + server => [ + \&_sc_server, + '[-noauthenticate] [server]', + 'show current server or connect to server' + ], + servername => 'server', + connect => 'server', + authenticate => [ + \&_sc_auth, + '[-minssf N] [-maxssf N] [-mechanisms list] [-service name] [-tlskey keyfile] [-notls] [-cafile cacertfile] [-capath cacertdir] [user]', + 'authenticate to server' + ], + auth => 'authenticate', + login => 'authenticate', + listquota => [ \&_sc_quota, 'root', 'list quotas on specified root' ], + lq => 'listquota', + listquotaroot => + [ \&_sc_quotaroot, 'mailbox', 'show quota roots and quotas for mailbox' ], + lqr => 'listquotaroot', + lqm => 'listquotaroot', + disconnect => [ \&_sc_disconn, '', 'disconnect from current server' ], + disc => 'disconnect', + chdir => [ \&_sc_chdir, 'directory', 'change current directory' ], + cd => 'chdir', + createmailbox => [ + \&_sc_create, + '[--partition partition] [--specialuse specialuse] mailbox [partition]', + 'create mailbox' + ], + create => 'createmailbox', + cm => 'createmailbox', + deleteaclmailbox => + [ \&_sc_deleteacl, 'mailbox id [id ...]', 'remove ACLs from mailbox' ], + deleteacl => 'deleteaclmailbox', + dam => 'deleteaclmailbox', + deletemailbox => [ \&_sc_delete, 'mailbox [host]', 'delete mailbox' ], + delete => 'deletemailbox', + dm => 'deletemailbox', + getmetadata => + [ \&_sc_getmetadata, '[mailbox]', 'display mailbox/server metadata' ], + getmd => 'getmetadata', + info => [ \&_sc_info, '[mailbox]', 'display mailbox/server annotations' ], + mboxcfg => [ + \&_sc_mboxcfg, + '[--private] mailbox [comment|expire|news2mail|sieve|squat|/] value', + 'configure mailbox' + ], + mboxconfig => 'mboxcfg', + reconstruct => + [ \&_sc_reconstruct, 'mailbox', 'reconstruct mailbox (if supported)' ], + renamemailbox => [ + \&_sc_rename, + '[--partition partition] oldname newname [partition]', + 'rename (and optionally relocate) mailbox' + ], + rename => 'renamemailbox', + renm => 'renamemailbox', + setaclmailbox => [ + \&_sc_setacl, 'mailbox id rights [id rights ...]', 'set ACLs on mailbox' + ], + setacl => 'setaclmailbox', + sam => 'setaclmailbox', + setinfo => [ + \&_sc_setinfo, + '[motd|comment|admin|shutdown|expire|squat] text', + 'set server metadata' + ], + setmetadata => [ + \&_sc_setmetadata, + '[--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|specialuse|squat|/] value', + 'set metadata to mailbox' + ], + setmd => 'setmetadata', + setquota => [ + \&_sc_setquota, + 'mailbox resource value [resource value ...]', + 'set quota on mailbox or resource' + ], + sq => 'setquota', + version => [ \&_sc_version, '', 'display version info of current server' ], + ver => 'version', + xfermailbox => [ + \&_sc_xfer, + '[--partition partition] mailbox server [partition]', + 'transfer (relocate) a mailbox to a different server' + ], + xfer => 'xfermailbox', + subscribe => [ \&_sc_subscribe, '[mailbox]', 'subscribe to a mailbox' ], + sub => 'subscribe', + unsubscribe => + [ \&_sc_unsubscribe, '[mailbox]', 'unsubscribe from a mailbox' ], + unsub => 'unsubscribe', + #? alias + #? unalias + #? load + #? unload +); # ugh. ugh. suck. aieee. my $use_rl = 'Cyrus::IMAP::DummyReadline'; @@ -193,7 +193,7 @@ sub _cb_eof { my %cb = @_; # indicate that the connection went away print STDERR "\nConnection to server lost.\n"; - ${$cb{-rock}} = undef; + ${ $cb{-rock} } = undef; } # okay, this sucks. the alternatives are worse. @@ -206,7 +206,7 @@ sub _nexttoken { my $lr = shift; $$lr =~ s/^(\s+)// and $coll_command .= $1; my $quoted = 0; - my $q = ''; + my $q = ''; my @tok = ('', undef); # this is cute. (shells are funny that way) @@ -233,8 +233,8 @@ sub _nexttoken { if ($$lr =~ /^\\(.)/) { # gack. "consistency? wazzat?" $coll_command .= "\\" . $1; - $tok[0] .= "'" if $q eq "'" && $1 ne "'"; - $tok[0] .= $1; + $tok[0] .= "'" if $q eq "'" && $1 ne "'"; + $tok[0] .= $1; $$lr =~ s///; next; } @@ -273,22 +273,19 @@ sub _execvv { $av0 = $builtins{$av0}; } if (defined($builtins{$av0})) { - &{$builtins{$av0}[0]}($cyrref, $av0, $fa, $lfa, @argv); - } - else { + &{ $builtins{$av0}[0] }($cyrref, $av0, $fa, $lfa, @argv); + } else { my $pid = fork; if (!defined($pid)) { die "fork: $!\n"; - } - elsif ($pid) { + } elsif ($pid) { waitpid($pid, 0); - } - else { + } else { $argv[0] =~ s!^.*/!!; my $fd = 0; # process redirections in $fa # sorted so lower $lfa->[$fh]->fileno consumed before $fh! - foreach my $fh (sort {$a->fileno <=> $b->fileno} @$lfa) { + foreach my $fh (sort { $a->fileno <=> $b->fileno } @$lfa) { if (!defined($fh)) { POSIX::close($fd); } else { @@ -311,7 +308,7 @@ sub _execvv { sub _execv { my ($cyrref, $cmd, $av0, $fa, $lfa, @argv) = @_; my $rc; - local($@); + local ($@); if (!defined(eval { $rc = &_execvv; })) { $lfa->[2]->print($@); $lfa->[2]->print("\n") unless substr($@, -1, 1) eq "\n"; @@ -329,29 +326,24 @@ sub _redir { $amp = ''; } if ($op eq '>') { - $rop = O_WRONLY|O_CREAT|O_TRUNC; + $rop = O_WRONLY | O_CREAT | O_TRUNC; $src = 1 if !defined($src) || $src eq ''; - } - elsif ($op eq '>>') { - $rop = O_WRONLY|O_CREAT|O_APPEND; + } elsif ($op eq '>>') { + $rop = O_WRONLY | O_CREAT | O_APPEND; $src = 1 if !defined($src) || $src eq ''; - } - elsif ($op eq '<') { + } elsif ($op eq '<') { $rop = O_RDONLY; $src = 0 if !defined($src) || $src eq ''; - } - else { + } else { die "can't handle \`$op' redirection\n"; } if ($amp) { die "invalid file descriptor \`$dst'\n" if $dst ne '-' && $dst != /^\d+$/; if ($dst eq '-') { $fha->[$src] = undef; - } - elsif (!defined($fha->[$dst])) { + } elsif (!defined($fha->[$dst])) { die "file descriptor \`$dst' not open\n"; - } - else { + } else { $fha->[$src] = IO::File->new("$op&" . $fha->[$dst]->fileno); } } else { @@ -362,10 +354,10 @@ sub _redir { # this was once trivial, then I added parsing for redirection... sub _exec { my ($cyrref, $fa, $cmd) = @_; - $fa ||= [*STDIN, *STDOUT, *STDERR]; + $fa ||= [ *STDIN, *STDOUT, *STDERR ]; # clone it: only "exec" has permanent effects on the fh stack - my $lfa = [@$fa]; - my @argv = (); + my $lfa = [@$fa]; + my @argv = (); my $state = ''; my ($tok, $type); while (($tok, $type) = _nexttoken(\$cmd) and defined($type)) { @@ -373,8 +365,7 @@ sub _exec { if ($state eq '') { # @@ here is where we should do aliasing, if we do it at all push(@argv, $tok); - } - else { + } else { # at this point, $state is the redirection (/^([<>])\1?\&?$/) and # $arg->[0] is the destination. if $argv[$#argv] matches /^\d+$/, # it is the affected file handle. @@ -383,25 +374,21 @@ sub _exec { _redir($lfa, $state, $tok, $target); $state = ''; } - } - elsif ($tok eq ';') { + } elsif ($tok eq ';') { _execv($cyrref, $coll_command, $argv[0], $fa, $lfa, @argv); $coll_command = ''; - @argv = (); - } - elsif ($tok eq '&') { + @argv = (); + } elsif ($tok eq '&') { if ($state ne '<' && $state ne '>') { die "syntax error: cannot deal with \`&' here\n"; } $state .= '&'; - } - elsif ($tok eq '<' || $tok eq '>') { + } elsif ($tok eq '<' || $tok eq '>') { if ($state ne '' && ($state ne $tok || $state eq '<')) { die "syntax error: cannot deal with \`$tok' here\n"; } $state .= $tok; - } - else { + } else { die "syntax error: don't understand \`$tok'\n"; } } @@ -414,16 +401,24 @@ sub _exec { # not too horrible sub _run { my $cyradm = shift; - my $fstk = shift || [*STDIN, *STDOUT, *STDERR]; - my $fin = shift || $fstk->[0] || *STDIN; + my $fstk = shift || [ *STDIN, *STDOUT, *STDERR ]; + my $fin = shift || $fstk->[0] || *STDIN; my ($hfh, $line); $hfh = $use_rl->new('cyradm shell', $fin, $fstk->[1]); $hfh->ornaments(0); my $rc; - while (defined ($line = $hfh->readline((defined $$cyradm ? - $$cyradm->servername : - 'cyradm') . '> '))) { - local($@); + while (defined( + $line = $hfh->readline( + ( + defined $$cyradm + ? $$cyradm->servername + : 'cyradm' + ) + . '> ' + ) + )) + { + local ($@); if (!defined(eval { $rc = _exec($cyradm, $fstk, $line); })) { $fstk->[2]->print($@); $fstk->[2]->print("\n") unless substr($@, -1, 1) eq "\n"; @@ -436,56 +431,72 @@ sub _run { # trivial; wrapper for _run with correct setup sub run { my $cyradm; - _run(\$cyradm, [*STDIN, *STDOUT, *STDERR], *__DATA__); + _run(\$cyradm, [ *STDIN, *STDOUT, *STDERR ], *__DATA__); } # All the real work is done by _run(); this is a convenience wrapper. # (It's not as trivial as run() because it does things expected of standalone # programs, as opposed to things expected from within a program.) sub shell { - my ($server, $port, $authz, $auth, $systemrc, $userrc, $dorc, $mech, $pw, - $tlskey, $notls, $cacert, $capath) = - ('', 143, undef, $ENV{USER} || $ENV{LOGNAME}, '/usr/local/etc/cyradmrc.pl', - "$ENV{HOME}/.cyradmrc.pl", 1, undef, undef, undef, undef, undef, undef); - GetOptions('user|u=s' => \$auth, - 'authz|z=s' => \$authz, - 'rc|r!' => \$dorc, - 'systemrc|S=s' => \$systemrc, - 'userrc=s' => \$userrc, - 'server|s=s' => \$server, - 'port|p=i' => \$port, - 'auth|a=s' => \$mech, - 'password|w=s' => \$pw, - 'tlskey|t:s' => \$tlskey, - 'notls' => \$notls, - 'cafile=s' => \$cacert, - 'cadir=s' => \$capath, - 'capath=s' => \$capath, - 'help|h' => sub { cyradm_usage(); exit(0); }, - 'version|v' => sub { cyradm_version(); exit(0); } - ); + my ( + $server, $port, $authz, $auth, $systemrc, $userrc, $dorc, + $mech, $pw, $tlskey, $notls, $cacert, $capath + ) + = ( + '', 143, undef, $ENV{USER} || $ENV{LOGNAME}, + '/usr/local/etc/cyradmrc.pl', + "$ENV{HOME}/.cyradmrc.pl", 1, undef, undef, undef, undef, undef, undef + ); + GetOptions( + 'user|u=s' => \$auth, + 'authz|z=s' => \$authz, + 'rc|r!' => \$dorc, + 'systemrc|S=s' => \$systemrc, + 'userrc=s' => \$userrc, + 'server|s=s' => \$server, + 'port|p=i' => \$port, + 'auth|a=s' => \$mech, + 'password|w=s' => \$pw, + 'tlskey|t:s' => \$tlskey, + 'notls' => \$notls, + 'cafile=s' => \$cacert, + 'cadir=s' => \$capath, + 'capath=s' => \$capath, + 'help|h' => sub { cyradm_usage(); exit(0); }, + 'version|v' => sub { cyradm_version(); exit(0); } + ); if ($server ne '' && @ARGV) { die "cyradm: may not specify server both with --server and bare arg\n"; } if (@ARGV) { $server = shift(@ARGV); - $port = shift(@ARGV) if @ARGV; + $port = shift(@ARGV) if @ARGV; cyradm_usage() if @ARGV; } my $cyradm; if ($server ne '') { $cyradm = Cyrus::IMAP::Admin->new($server, $port) or die "cyradm: cannot connect to server\n"; - $cyradm->addcallback({-trigger => 'EOF', - -callback => \&_cb_eof, - -rock => \$cyradm}); - $cyradm->authenticate(-authz => $authz, -user => $auth, - -mechanism => $mech, -password => $pw, - -tlskey => $tlskey, -notls => $notls, - -cafile => $cacert, -capath => $capath) - or die "cyradm: cannot authenticate to server" . (defined($mech)?" with $mech":"") . " as $auth\n"; - } - my $fstk = [*STDIN, *STDOUT, *STDERR]; + $cyradm->addcallback({ + -trigger => 'EOF', + -callback => \&_cb_eof, + -rock => \$cyradm + }); + $cyradm->authenticate( + -authz => $authz, + -user => $auth, + -mechanism => $mech, + -password => $pw, + -tlskey => $tlskey, + -notls => $notls, + -cafile => $cacert, + -capath => $capath + ) + or die "cyradm: cannot authenticate to server" + . (defined($mech) ? " with $mech" : "") + . " as $auth\n"; + } + my $fstk = [ *STDIN, *STDOUT, *STDERR ]; if ($dorc && $systemrc ne '' && -f $systemrc) { my $fh = IO::File->new($systemrc, O_RDONLY); _run(\$cyradm, $fstk, *$fh) if $fh; @@ -551,12 +562,11 @@ sub _sc_help { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt, $rc); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: help [command]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -564,7 +574,7 @@ sub _sc_help { push(@nargv, @argv); $rc = 0; if (@nargv) { - foreach my $cmd (sort {$a cmp $b} @nargv) { + foreach my $cmd (sort { $a cmp $b } @nargv) { $rc = 1 if !do_help($lfh->[1], $cmd, @nargv); } } else { @@ -573,19 +583,19 @@ sub _sc_help { my $cmd; foreach $cmd (keys %builtins) { if (ref($builtins{$cmd})) { - $cmds{$cmd} ||= [[], '']; + $cmds{$cmd} ||= [ [], '' ]; $cmds{$cmd}[1] = $builtins{$cmd}[2]; } else { - $cmds{$builtins{$cmd}} ||= [[], '']; - push(@{$cmds{$builtins{$cmd}}[0]}, $cmd); + $cmds{ $builtins{$cmd} } ||= [ [], '' ]; + push(@{ $cmds{ $builtins{$cmd} }[0] }, $cmd); } } my $nwid = 0; foreach $cmd (keys %cmds) { - $cmds{$cmd}[0] = join(', ', $cmd, @{$cmds{$cmd}[0]}); + $cmds{$cmd}[0] = join(', ', $cmd, @{ $cmds{$cmd}[0] }); $nwid = length($cmds{$cmd}[0]) if $nwid < length($cmds{$cmd}[0]); } - foreach $cmd (sort {$a cmp $b} keys %cmds) { + foreach $cmd (sort { $a cmp $b } keys %cmds) { $lfh->[1]->printf("%-*s %s\n", $nwid, $cmds{$cmd}[0], $cmds{$cmd}[1]); } } @@ -596,12 +606,11 @@ sub _sc_exit { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: exit [number]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -619,27 +628,28 @@ sub _sc_list { my $cmd = 'listmailbox'; my (@nargv, $opt, %opts, $subscribed); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt ne '' && '-subscribed' =~ /^\Q$opt/ || $opt eq '--subscribed') { $subscribed = 1; - } elsif ($opt ne '' && '-specialuse' =~ /^\Q$opt/ || $opt eq '--specialuse') { + } elsif ($opt ne '' && '-specialuse' =~ /^\Q$opt/ || $opt eq '--specialuse') + { $opts{'-sel-special-use'} = 1; - } elsif ($opt ne '' && '-recursivematch' =~ /^\Q$opt/ || $opt eq '--recursivematch') { + } elsif ($opt ne '' && '-recursivematch' =~ /^\Q$opt/ + || $opt eq '--recursivematch') + { $opts{'-sel-recursivematch'} = 1; - } - elsif ($opt =~ /^-/) { + } elsif ($opt =~ /^-/) { die "usage: listmailbox [-subscribed] [-specialuse] [pattern [base]]\n"; - } - else { + } else { push(@nargv, $opt); last; } } if ($subscribed) { - if (scalar (keys %opts) > 0 ) { + if (scalar(keys %opts) > 0) { # LIST + LIST-EXTENDED $opts{'-sel-subscribed'} = 1; } else { @@ -653,8 +663,8 @@ sub _sc_list { if (@nargv > 2) { die "usage: listmailbox [-subscribed] [-specialuse] [pattern [base]]\n"; } - push(@nargv, '*') if !@nargv; - push(@nargv, undef) if scalar (@nargv) < 2; # no ref + push(@nargv, '*') if !@nargv; + push(@nargv, undef) if scalar(@nargv) < 2; # no ref push(@nargv, \%opts); if (!$cyrref || !$$cyrref) { die "listmailbox: no connection to server\n"; @@ -671,7 +681,7 @@ sub _sc_list { if ($mbx->[1] ne '') { $l .= ' (' . $mbx->[1] . ')'; } - if (defined ($mbx->[3])) { + if (defined($mbx->[3])) { $l .= ' (' . $mbx->[3] . ')'; } if (length($l) + 1 > $w) { @@ -680,7 +690,7 @@ sub _sc_list { push(@l, $l); } return 1 if !@l; - @l = sort {$a cmp $b} @l; + @l = sort { $a cmp $b } @l; my $ll = $ENV{COLUMNS} || 79; $w = $ll if $w > $ll; my $n = int($ll / $w); @@ -688,7 +698,7 @@ sub _sc_list { for ($l = 0; $l < int((@l + $n - 1) / $n); $l++) { for ($c = 0; $c < @l; $c += int((@l + $n - 1) / $n)) { if ($l + $c < @l) { - $lfh->[1]->print($l[$l + $c], ' ' x ($w + 1 - length($l[$l + $c]))); + $lfh->[1]->print($l[ $l + $c ], ' ' x ($w + 1 - length($l[ $l + $c ]))); } } $lfh->[1]->print("\n"); @@ -700,13 +710,12 @@ sub _sc_listacl { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: listaclmailbox mailbox\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -719,18 +728,18 @@ sub _sc_listacl { die "listaclmailbox: no connection to server\n"; } - if($nargv[0] =~ /(\*|%)/) { + if ($nargv[0] =~ /(\*|%)/) { # list operation my @res = $$cyrref->listmailbox(($nargv[0])); foreach my $mbx (@res) { - my $name = $mbx->[0]; + my $name = $mbx->[0]; my $flags = $mbx->[1]; - next if($flags =~ /(\\noselect|\\nonexistent|\\placeholder)/i); - $lfh->[1]->print($name,":\n"); + next if ($flags =~ /(\\noselect|\\nonexistent|\\placeholder)/i); + $lfh->[1]->print($name, ":\n"); my %acl = $$cyrref->listaclmailbox($name); if (defined $$cyrref->error) { - $lfh->[2]->print($$cyrref->error, "\n"); - next; + $lfh->[2]->print($$cyrref->error, "\n"); + next; } foreach my $acl (keys %acl) { $lfh->[1]->print(" ", $acl, " ", $acl{$acl}, "\n"); @@ -739,8 +748,8 @@ sub _sc_listacl { } else { my %acl = $$cyrref->listaclmailbox(@nargv); if (defined $$cyrref->error) { - $lfh->[2]->print($$cyrref->error, "\n"); - return 1; + $lfh->[2]->print($$cyrref->error, "\n"); + return 1; } foreach my $acl (keys %acl) { @@ -755,17 +764,17 @@ sub _sc_server { my (@nargv, $opt, $auth); shift(@argv); $auth = 1; - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; - if ($opt ne '' && '-noauthenticate' =~ /^\Q$opt/ || - $opt eq '--noauthenticate') { + if ( $opt ne '' && '-noauthenticate' =~ /^\Q$opt/ + || $opt eq '--noauthenticate') + { $auth = 0; next; } if ($opt =~ /^-/) { die "usage: server [-noauthenticate] [server]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -777,16 +786,14 @@ sub _sc_server { } $lfh->[1]->print($$cyrref->servername, "\n"); 0; - } - elsif (@nargv == 1) { + } elsif (@nargv == 1) { $$cyrref = Cyrus::IMAP::Admin->new($nargv[0]) or die "server: $nargv[0]: cannot connect to server\n"; if ($auth) { $$cyrref->authenticate or die "server: $nargv[0]: cannot authenticate\n"; } 0; - } - else { + } else { die "usage: server [-noauthenticate] [server]\n"; } } @@ -795,7 +802,7 @@ sub _sc_auth { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt, %opts, $want); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { if (defined $want) { $opts{$want} = $opt; $want = undef; @@ -840,17 +847,16 @@ sub _sc_auth { next; } if ($opt =~ /^-/) { - die "usage: authenticate [-minssf N] [-maxssf N] [-mechanisms STR]\n". - " [-service name] [-tlskey keyfile] [-notls]\n". - " [-cafile cacertfile] [-capath cacertdir]\n". - " [user]\n"; + die "usage: authenticate [-minssf N] [-maxssf N] [-mechanisms STR]\n" + . " [-service name] [-tlskey keyfile] [-notls]\n" + . " [-cafile cacertfile] [-capath cacertdir]\n" + . " [user]\n"; } } if ($opt =~ /^-/) { - die "usage: authenticate [-minssf N] [-maxssf N] [-mechanisms STR]\n". - " [-service name] [user]\n"; - } - else { + die "usage: authenticate [-minssf N] [-maxssf N] [-mechanisms STR]\n" + . " [-service name] [user]\n"; + } else { push(@nargv, $opt); last; } @@ -858,13 +864,13 @@ sub _sc_auth { push(@nargv, @argv); if (@nargv > 1) { if (Cyrus::IMAP::havetls()) { - die "usage: authenticate [-minssf N] [-maxssf N] [-mechanisms STR]\n". - " [-service name] [-tlskey keyfile] [-notls]\n". - " [-cafile cacertfile] [-capath cacertdir]\n". - " [user]\n"; + die "usage: authenticate [-minssf N] [-maxssf N] [-mechanisms STR]\n" + . " [-service name] [-tlskey keyfile] [-notls]\n" + . " [-cafile cacertfile] [-capath cacertdir]\n" + . " [user]\n"; } else { - die "usage: authenticate [-minssf N] [-maxssf N] [-mechanisms STR]\n". - " [-service name] [user]\n"; + die "usage: authenticate [-minssf N] [-maxssf N] [-mechanisms STR]\n" + . " [-service name] [user]\n"; } } if (@nargv) { @@ -884,13 +890,12 @@ sub _sc_quota { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: listquota root\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -904,8 +909,8 @@ sub _sc_quota { } my %quota = $$cyrref->listquota(@nargv); foreach my $quota (keys %quota) { - $lfh->[1]->print(" ", $quota, " ", $quota{$quota}[0], "/", - $quota{$quota}[1]); + $lfh->[1] + ->print(" ", $quota, " ", $quota{$quota}[0], "/", $quota{$quota}[1]); if ($quota{$quota}[1]) { $lfh->[1]->print(" (", $quota{$quota}[0] * 100 / $quota{$quota}[1], "%)"); } @@ -918,13 +923,12 @@ sub _sc_quotaroot { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: listquotaroot mailbox\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -941,8 +945,8 @@ sub _sc_quotaroot { my ($used, $tot); foreach my $quota (keys %quota) { ($used, $tot) = split(/ /, $quota{$quota}); - $lfh->[1]->print(" ", $quota, " ", $quota{$quota}[0], "/", - $quota{$quota}[1]); + $lfh->[1] + ->print(" ", $quota, " ", $quota{$quota}[0], "/", $quota{$quota}[1]); if ($quota{$quota}[1]) { $lfh->[1]->print(" (", $quota{$quota}[0] * 100 / $quota{$quota}[1], "%)"); } @@ -955,13 +959,12 @@ sub _sc_disconn { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: disconnect\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -981,13 +984,12 @@ sub _sc_chdir { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: chdir directory\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1004,7 +1006,7 @@ sub _sc_create { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt, $part, $want, %opts); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { if ($want) { if ($want eq '-partition') { $part = $opt; @@ -1024,9 +1026,9 @@ sub _sc_create { } last if $opt eq '--'; if ($opt =~ /^-/) { - die "usage: createmailbox [--partition partition] [--specialuse specialuse] mailbox [partition]\n"; - } - else { + die + "usage: createmailbox [--partition partition] [--specialuse specialuse] mailbox [partition]\n"; + } else { push(@nargv, $opt); last; } @@ -1035,7 +1037,7 @@ sub _sc_create { if (!@nargv || @nargv > 2) { die "usage: createmailbox [--partition partition] mailbox [partition]\n"; } - push(@nargv, $part) if (defined ($part)); + push(@nargv, $part) if (defined($part)); push(@nargv, undef) if (@nargv < 2); push(@nargv, \%opts); if (!$cyrref || !$$cyrref) { @@ -1049,12 +1051,11 @@ sub _sc_delete { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: deletemailbox mailbox [host]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1067,17 +1068,17 @@ sub _sc_delete { die "deletemailbox: no connection to server\n"; } - if($nargv[0] =~ /(\*|%)/) { + if ($nargv[0] =~ /(\*|%)/) { # list operation my @res = $$cyrref->listmailbox(($nargv[0])); foreach my $mbx (@res) { - my $name = $mbx->[0]; + my $name = $mbx->[0]; my $flags = $mbx->[1]; - next if($flags =~ /(\\noselect|\\nonexistent|\\placeholder)/i); + next if ($flags =~ /(\\noselect|\\nonexistent|\\placeholder)/i); print "Deleting mailbox $name..."; $nargv[0] = $name; my $rc = $$cyrref->delete(@nargv); - if(!defined($rc)) { + if (!defined($rc)) { print $$cyrref->error . "\n"; last; } else { @@ -1095,16 +1096,15 @@ sub _sc_reconstruct { my (@nargv, $opt); my $recurse = 0; shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt =~ /^-/) { - if($opt eq "-r") { + if ($opt eq "-r") { $recurse = 1; } else { die "usage: reconstruct [-r] mailbox\n"; } - } - else { + } else { push(@nargv, $opt); last; } @@ -1116,7 +1116,7 @@ sub _sc_reconstruct { if (!$cyrref || !$$cyrref) { die "reconstruct: no connection to server\n"; } - $$cyrref->reconstruct(@nargv) || die "reconstruct: " .$$cyrref->error. "\n"; + $$cyrref->reconstruct(@nargv) || die "reconstruct: " . $$cyrref->error . "\n"; 0; } @@ -1124,7 +1124,7 @@ sub _sc_rename { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt, $want, $part); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { if ($want) { $part = $opt; $want = undef; @@ -1136,10 +1136,9 @@ sub _sc_rename { } last if $opt eq '--'; if ($opt =~ /^-/) { - die "usage: renamemailbox [--partition name] oldname " . - "newname [partition]\n"; - } - else { + die "usage: renamemailbox [--partition name] oldname " + . "newname [partition]\n"; + } else { push(@nargv, $opt); last; } @@ -1147,14 +1146,14 @@ sub _sc_rename { push(@nargv, @argv); $part = pop(@nargv) if @nargv > 2 && !defined($part); if (@nargv != 2) { - die "usage: renamemailbox [--partition name] oldname " . - "newname [partition]\n"; + die "usage: renamemailbox [--partition name] oldname " + . "newname [partition]\n"; } if (!$cyrref || !$$cyrref) { die "renamemailbox: no connection to server\n"; } - $$cyrref->rename($nargv[0], $nargv[1], $part) || - die "renamemailbox: " . $$cyrref->error . "\n"; + $$cyrref->rename($nargv[0], $nargv[1], $part) + || die "renamemailbox: " . $$cyrref->error . "\n"; 0; } @@ -1162,7 +1161,7 @@ sub _sc_xfer { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt, $want, $part); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { if ($want) { $part = $opt; $want = undef; @@ -1174,10 +1173,9 @@ sub _sc_xfer { } last if $opt eq '--'; if ($opt =~ /^-/) { - die "usage: xfermailbox [--partition name] mailbox " . - "server [partition]\n"; - } - else { + die "usage: xfermailbox [--partition name] mailbox " + . "server [partition]\n"; + } else { push(@nargv, $opt); last; } @@ -1185,14 +1183,14 @@ sub _sc_xfer { push(@nargv, @argv); $part = pop(@nargv) if @nargv > 2 && !defined($part); if (@nargv != 2) { - die "usage: xfermailbox [--partition name] mailbox " . - "server [partition]\n"; + die "usage: xfermailbox [--partition name] mailbox " + . "server [partition]\n"; } if (!$cyrref || !$$cyrref) { die "xfermailbox: no connection to server\n"; } - $$cyrref->xfer($nargv[0], $nargv[1], $part) || - die "xfermailbox: " . $$cyrref->error . "\n"; + $$cyrref->xfer($nargv[0], $nargv[1], $part) + || die "xfermailbox: " . $$cyrref->error . "\n"; 0; } @@ -1200,12 +1198,11 @@ sub _sc_deleteacl { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: deleteaclmailbox mailbox id [id ...]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1218,13 +1215,13 @@ sub _sc_deleteacl { die "deleteaclmailbox: no connection to server\n"; } - if($nargv[0] =~ /(\*|%)/) { + if ($nargv[0] =~ /(\*|%)/) { # list operation my @res = $$cyrref->listmailbox(($nargv[0])); foreach my $mbx (@res) { - my $name = $mbx->[0]; + my $name = $mbx->[0]; my $flags = $mbx->[1]; - next if($flags =~ /(\\noselect|\\nonexistent|\\placeholder)/i); + next if ($flags =~ /(\\noselect|\\nonexistent|\\placeholder)/i); # If id of '*' is passed then delete all existing acls if ($nargv[1] eq '*') { my %acl = $$cyrref->listaclmailbox($name); @@ -1240,7 +1237,7 @@ sub _sc_deleteacl { print "Deleting acl on $name..."; $nargv[0] = $name; my $rc = $$cyrref->deleteacl(@nargv); - if(!defined($rc)) { + if (!defined($rc)) { print $$cyrref->error . "\n"; last; } else { @@ -1260,8 +1257,8 @@ sub _sc_deleteacl { push(@nargv, $acl, $acl{$acl}); } } - $$cyrref->deleteacl(@nargv) || - die "deleteaclmailbox: " . $$cyrref->error . "\n"; + $$cyrref->deleteacl(@nargv) + || die "deleteaclmailbox: " . $$cyrref->error . "\n"; } 0; @@ -1271,12 +1268,11 @@ sub _sc_setacl { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: setaclmailbox mailbox id rights [id rights ...]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1289,17 +1285,17 @@ sub _sc_setacl { die "setaclmailbox: no connection to server\n"; } - if($nargv[0] =~ /(\*|%)/) { + if ($nargv[0] =~ /(\*|%)/) { # list operation my @res = $$cyrref->listmailbox(($nargv[0])); foreach my $mbx (@res) { - my $name = $mbx->[0]; + my $name = $mbx->[0]; my $flags = $mbx->[1]; - next if($flags =~ /(\\noselect|\\nonexistent|\\placeholder)/i); + next if ($flags =~ /(\\noselect|\\nonexistent|\\placeholder)/i); print "Setting ACL on $name..."; $nargv[0] = $name; my $rc = $$cyrref->setacl(@nargv); - if(!defined($rc)) { + if (!defined($rc)) { print $$cyrref->error . "\n"; last; } else { @@ -1316,31 +1312,30 @@ sub _sc_setquota { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt =~ /^-/) { - die ("usage: setquota mailbox limit num [limit num ...]\n" . - " setquota mailbox num\n"); - } - else { + die( "usage: setquota mailbox limit num [limit num ...]\n" + . " setquota mailbox num\n"); + } else { push(@nargv, $opt); last; } } push(@nargv, @argv); if (@nargv == 2) { - my ($mbox, $limit) = @nargv; - if ($limit eq 'none') { - @nargv = ($mbox); - print "remove quota\n"; - } else { - @nargv = ($mbox, "STORAGE", $limit); - print "quota:", $limit, "\n"; - } + my ($mbox, $limit) = @nargv; + if ($limit eq 'none') { + @nargv = ($mbox); + print "remove quota\n"; + } else { + @nargv = ($mbox, "STORAGE", $limit); + print "quota:", $limit, "\n"; + } } if ((@nargv - 1) % 2) { - die ("usage: setquota mailbox limit num [limit num ...]\n" . - " setquota mailbox num\n"); + die( "usage: setquota mailbox limit num [limit num ...]\n" + . " setquota mailbox num\n"); } if (!$cyrref || !$$cyrref) { die "setquota: no connection to server\n"; @@ -1353,13 +1348,12 @@ sub _sc_version { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: version\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1373,13 +1367,15 @@ sub _sc_version { } my $info; - $$cyrref->addcallback({-trigger => 'ID', - -callback => sub { - my %d = @_; - $info = $d{-text}; - }}); + $$cyrref->addcallback({ + -trigger => 'ID', + -callback => sub { + my %d = @_; + $info = $d{-text}; + } + }); my ($rc, $msg) = $$cyrref->send('', '', 'ID NIL'); - $$cyrref->addcallback({-trigger => 'ID'}); + $$cyrref->addcallback({ -trigger => 'ID' }); if ($rc ne 'OK') { $lfh->[2]->print($msg, "\n"); return 1; @@ -1392,10 +1388,10 @@ sub _sc_version { while ($info =~ s/\"([^\"]+)\"\s+(\"[^\"]+\"|NIL)\s*//) { my $field = $1; my $value = $2; - $value =~ s/\"//g; # strip quotes - # split environment into multiple lines + $value =~ s/\"//g; # strip quotes + # split environment into multiple lines $value =~ s/;/\n /g if $field eq 'environment'; - $value = '' if $value eq 'NIL'; # convert NIL to empty string + $value = '' if $value eq 'NIL'; # convert NIL to empty string $lfh->[1]->printf("%-11s: %s\n", $field, $value); } 0; @@ -1405,13 +1401,12 @@ sub _sc_info { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: info [mailbox]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1427,23 +1422,24 @@ sub _sc_info { } foreach my $mailbox (sort keys %info) { - if($mailbox eq "") { - print "{Server Wide}\n"; - } else { - print "{$mailbox}:\n"; - } + if ($mailbox eq "") { + print "{Server Wide}\n"; + } else { + print "{$mailbox}:\n"; + } my %attribname = (); - foreach my $attribname (sort keys %{$info{$mailbox}}) { - foreach my $attrib (sort keys %{$info{$mailbox}->{$attribname}}) { - if(!exists $attribname{$attribname}) { + foreach my $attribname (sort keys %{ $info{$mailbox} }) { + foreach my $attrib (sort keys %{ $info{$mailbox}->{$attribname} }) { + if (!exists $attribname{$attribname}) { $attribname{$attribname} = 'x'; print " $attribname:\n"; } $attrib =~ /([^\/]*)$/; my $attrname = $1; - $lfh->[1]->print(" ", $attrname, ": ", $info{$mailbox}->{$attribname}->{$attrib}, "\n"); + $lfh->[1]->print(" ", $attrname, ": ", + $info{$mailbox}->{$attribname}->{$attrib}, "\n"); } } } @@ -1454,34 +1450,33 @@ sub _sc_getmetadata { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: getmetadata [mailbox]\n"; - } - else { + } else { push(@nargv, $opt); last; } } - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { if ($opt eq 'comment') { - push(@nargv, '/private/comment'); + push(@nargv, '/private/comment'); } elsif ($opt eq 'expire') { - push(@nargv, '/shared/vendor/cmu/cyrus-imapd/expire'); + push(@nargv, '/shared/vendor/cmu/cyrus-imapd/expire'); } elsif ($opt eq 'news2mail') { - push(@nargv, '/shared/vendor/cmu/cyrus-imapd/news2mail'); + push(@nargv, '/shared/vendor/cmu/cyrus-imapd/news2mail'); } elsif ($opt eq 'pop3showafter') { - push(@nargv, '/shared/vendor/cmu/cyrus-imapd/pop3showafter'); + push(@nargv, '/shared/vendor/cmu/cyrus-imapd/pop3showafter'); } elsif ($opt eq 'sharedseen') { - push(@nargv, '/shared/vendor/cmu/cyrus-imapd/sharedseen'); + push(@nargv, '/shared/vendor/cmu/cyrus-imapd/sharedseen'); } elsif ($opt eq 'sieve') { - push(@nargv, '/shared/vendor/cmu/cyrus-imapd/sieve'); + push(@nargv, '/shared/vendor/cmu/cyrus-imapd/sieve'); } elsif ($opt eq 'specialuse') { - push(@nargv, '/private/specialuse'); + push(@nargv, '/private/specialuse'); } elsif ($opt eq 'squat') { - push(@nargv, '/shared/vendor/cmu/cyrus-imapd/squat'); + push(@nargv, '/shared/vendor/cmu/cyrus-imapd/squat'); } else { push(@nargv, $opt); } @@ -1496,23 +1491,24 @@ sub _sc_getmetadata { } foreach my $mailbox (sort keys %info) { - if($mailbox eq "") { - print "{Server Wide}\n"; - } else { - print "{$mailbox}:\n"; - } + if ($mailbox eq "") { + print "{Server Wide}\n"; + } else { + print "{$mailbox}:\n"; + } my %attribname = (); - foreach my $attribname (sort keys %{$info{$mailbox}}) { - foreach my $attrib (sort keys %{$info{$mailbox}->{$attribname}}) { - if(!exists $attribname{$attribname}) { + foreach my $attribname (sort keys %{ $info{$mailbox} }) { + foreach my $attrib (sort keys %{ $info{$mailbox}->{$attribname} }) { + if (!exists $attribname{$attribname}) { $attribname{$attribname} = 'x'; print " $attribname:\n"; } $attrib =~ /([^\/]*)$/; my $attrname = $1; - $lfh->[1]->print(" ", $attrname, ": ", $info{$mailbox}->{$attribname}->{$attrib}, "\n"); + $lfh->[1]->print(" ", $attrname, ": ", + $info{$mailbox}->{$attribname}->{$attrib}, "\n"); } } } @@ -1523,23 +1519,24 @@ sub _sc_setmetadata { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt, $private); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt ne '' && '-private' =~ /^\Q$opt/ || $opt eq '--private') { $private = 1; } elsif ($opt =~ /^-/) { - die "usage: setmetadata [--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|specialuse|squat|/] value\n"; - } - else { + die + "usage: setmetadata [--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|specialuse|squat|/] value\n"; + } else { push(@nargv, $opt); last; } } push(@nargv, @argv); if (@nargv < 2) { - die "usage: setmetadata [--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|specialuse|squat|/] value\n"; + die + "usage: setmetadata [--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|specialuse|squat|/] value\n"; } - if (defined ($private)) { + if (defined($private)) { push(@nargv, $private); } if (!$cyrref || !$$cyrref) { @@ -1553,13 +1550,12 @@ sub _sc_subscribe { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: subscribe [mailbox]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1580,13 +1576,12 @@ sub _sc_unsubscribe { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { # gack. bloody tcl. last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: unsubscribe [mailbox]\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1607,23 +1602,24 @@ sub _sc_mboxcfg { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt, $private); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt ne '' && '-private' =~ /^\Q$opt/ || $opt eq '--private') { $private = 1; } elsif ($opt =~ /^-/) { - die "usage: mboxconfig [--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|squat|/] value\n"; - } - else { + die + "usage: mboxconfig [--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|squat|/] value\n"; + } else { push(@nargv, $opt); last; } } push(@nargv, @argv); if (@nargv < 2) { - die "usage: mboxconfig [--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|squat|/] value\n"; + die + "usage: mboxconfig [--private] mailbox [comment|expire|news2mail|pop3showafter|sharedseen|sieve|squat|/] value\n"; } - if (defined ($private)) { + if (defined($private)) { push(@nargv, $private); } if (!$cyrref || !$$cyrref) { @@ -1637,12 +1633,11 @@ sub _sc_setinfo { my ($cyrref, $name, $fh, $lfh, @argv) = @_; my (@nargv, $opt); shift(@argv); - while (defined ($opt = shift(@argv))) { + while (defined($opt = shift(@argv))) { last if $opt eq '--'; if ($opt =~ /^-/) { die "usage: setinfo [motd|comment|admin|shutdown|expire|squat] text\n"; - } - else { + } else { push(@nargv, $opt); last; } @@ -1670,7 +1665,7 @@ use IO::File; sub new { my ($class, $dummy, $in, $out) = @_; autoflush $out 1; - bless {in => $in, out => $out}, $class; + bless { in => $in, out => $out }, $class; } sub ornaments { @@ -1680,7 +1675,7 @@ sub ornaments { sub readline { my ($self, $prompt) = @_; my $l; - my $fh = $self->{in}; + my $fh = $self->{in}; my $ofh = $self->{out}; print $ofh $prompt; return undef unless defined($l = <$fh>); diff --git a/perl/sieve/managesieve/managesieve.pm b/perl/sieve/managesieve/managesieve.pm index c732258482..011d38d024 100644 --- a/perl/sieve/managesieve/managesieve.pm +++ b/perl/sieve/managesieve/managesieve.pm @@ -67,24 +67,24 @@ require DynaLoader; $VERSION = '0.01'; sub AUTOLOAD { - # This AUTOLOAD is used to 'autoload' constants from the constant() - # XS function. If a constant is not found then control is passed - # to the AUTOLOAD in AutoLoader. - - my $constname; - ($constname = $AUTOLOAD) =~ s/.*:://; - my $val = constant($constname, @_ ? $_[0] : 0); - if ($! != 0) { - if ($! =~ /Invalid/) { - $AutoLoader::AUTOLOAD = $AUTOLOAD; - goto &AutoLoader::AUTOLOAD; - } - else { - croak "Your vendor has not defined Cyrus::SIEVE::managesieve macro $constname"; - } + # This AUTOLOAD is used to 'autoload' constants from the constant() + # XS function. If a constant is not found then control is passed + # to the AUTOLOAD in AutoLoader. + + my $constname; + ($constname = $AUTOLOAD) =~ s/.*:://; + my $val = constant($constname, @_ ? $_[0] : 0); + if ($! != 0) { + if ($! =~ /Invalid/) { + $AutoLoader::AUTOLOAD = $AUTOLOAD; + goto &AutoLoader::AUTOLOAD; + } else { + croak + "Your vendor has not defined Cyrus::SIEVE::managesieve macro $constname"; } - eval "sub $AUTOLOAD { $val }"; - goto &$AUTOLOAD; + } + eval "sub $AUTOLOAD { $val }"; + goto &$AUTOLOAD; } bootstrap Cyrus::SIEVE::managesieve $VERSION;