initOutput(\rcmail_action::MODE_AJAX, 'mail', 'search'); $this->assertTrue($action->checks()); $_GET = [ '_q' => 'test', '_mbox' => 'INBOX', ]; // Set expected storage function calls/results self::mockStorage() ->registerFunction('set_page') ->registerFunction('set_search_set') ->registerFunction('search', new \rcube_result_index()) ->registerFunction('get_search_set', []) ->registerFunction('get_search_set', []) ->registerFunction('get_pagesize', 10) ->registerFunction('get_pagesize', 10) ->registerFunction('get_folder', 'INBOX') ->registerFunction('list_messages', []) ->registerFunction('get_error_code', null) ->registerFunction('count', 0) ->registerFunction('get_quota', false); $this->runAndAssert($action, OutputJsonMock::E_EXIT); $result = $output->getOutput(); $this->assertContains('Content-Type: application/json; charset=UTF-8', $output->headers); $this->assertSame('search', $result['action']); $this->assertSame(0, $result['env']['messagecount']); $this->assertSame(0, $result['env']['pagecount']); $this->assertSame(0, $result['env']['exists']); $this->assertTrue(strpos($result['exec'], 'this.display_message("Search returned no matches.","notice",0);') !== false); $this->assertTrue(strpos($result['exec'], 'this.set_rowcount("Mailbox is empty","INBOX");') !== false); $this->assertTrue(strpos($result['exec'], 'this.set_pagetitle("Roundcube Webmail :: Search result");') !== false); $this->assertTrue(strpos($result['exec'], 'this.set_quota') !== false); } /** * Test searching mail (non-empty result) */ public function test_search_non_empty_result() { $action = new \rcmail_action_mail_search(); $output = $this->initOutput(\rcmail_action::MODE_AJAX, 'mail', 'search'); $_GET = [ '_q' => 'test', '_mbox' => 'INBOX', ]; $index = new \rcube_result_index('INBOX', 'SEARCH 10'); // Set expected storage function calls/results self::mockStorage() ->registerFunction('set_page') ->registerFunction('set_search_set') ->registerFunction('search') ->registerFunction('get_search_set', ['SEARCH HEADER SUBJECT test', $index, 'UTF-8', '', false]) ->registerFunction('get_search_set', ['SEARCH HEADER SUBJECT test', $index, 'UTF-8', '', false]) ->registerFunction('get_pagesize', 10) ->registerFunction('get_pagesize', 10) ->registerFunction('get_folder', 'INBOX') ->registerFunction('list_messages', [ 10 => \rcube_message_header::from_array([ 'id' => 42, 'uid' => 10, 'subject' => 'test message', 'from' => 'test1@test.com', 'to' => 'Test ', 'date' => 'Sun, 13 Mar 2022 17:08:18 +0100', 'size' => 889, 'content-type' => 'text/plain', ]), ]) ->registerFunction('get_threading', false) ->registerFunction('get_threading', false) ->registerFunction('get_threading', false) ->registerFunction('count', 1) ->registerFunction('count', 1) ->registerFunction('count', 1) ->registerFunction('folder_data', []) ->registerFunction('get_quota', false); $this->runAndAssert($action, OutputJsonMock::E_EXIT); $result = $output->getOutput(); $this->assertContains('Content-Type: application/json; charset=UTF-8', $output->headers); $this->assertSame('search', $result['action']); $this->assertSame(1, $result['env']['messagecount']); $this->assertSame(1, $result['env']['pagecount']); $this->assertSame(1, $result['env']['exists']); $this->assertTrue(strpos($result['exec'], 'this.display_message("1 messages found.","confirmation",0);') !== false); $this->assertTrue(strpos($result['exec'], 'this.set_rowcount("Messages 1 to 1 of 1","INBOX");') !== false); $this->assertTrue(strpos($result['exec'], 'this.set_pagetitle("Roundcube Webmail :: Search result");') !== false); } /** * Test search_input() method * * @dataProvider provide_search_input_cases */ #[DataProvider('provide_search_input_cases')] public function test_search_input($input, $output) { if (is_array($input)) { $result = call_user_func_array('rcmail_action_mail_search::search_input', $input); } else { $result = \rcmail_action_mail_search::search_input($input); } $this->assertSame($output, $result); } /** * Test data for test_search_input() */ public static function provide_search_input_cases(): iterable { $week = new \DateInterval('P1W'); $weekDate = (new \DateTime('now', new \DateTimeZone('UTC')))->sub($week)->format('j-M-Y'); return [ [ '', '', ], [ 'from:test', 'HEADER FROM test', ], [ 'body:test', 'BODY test', ], [ 'text:"test1 test2"', 'TEXT "test1 test2"', ], [ 'test1 subject:test2', 'HEADER SUBJECT test1 HEADER SUBJECT test2', ], [ 'cc:test1 bcc:test2', 'HEADER CC test1 HEADER BCC test2', ], [ 'replyto:test1', 'OR HEADER REPLY-TO test1 HEADER MAIL-REPLY-TO test1', ], [ 'followupto:test1', 'OR HEADER FOLLOWUP-TO test1 HEADER MAIL-FOLLOWUP-TO test1', ], [ 'is:read IS:unread is:flaGGed is:Unflagged', 'SEEN UNSEEN FLAGGED UNFLAGGED', ], [ 'is:unseen IS:seen is:Deleted is:Undeleted is:answered is:unanswered', 'UNSEEN SEEN DELETED UNDELETED ANSWERED UNANSWERED', ], [ 'since:1w before:1w', "SINCE {$weekDate} BEFORE {$weekDate}", ], [ 'since:2022-12-31 before:2022-11-30', 'SINCE 31-Dec-2022 BEFORE 30-Nov-2022', ], [ 'since:2022/1/1 before:2022/11/30', 'SINCE 1-Jan-2022 BEFORE 30-Nov-2022', ], [ 'smaller:1KB larger:1M', 'SMALLER 1024 LARGER 1048576', ], [ '"from:test1"', 'HEADER SUBJECT from:test1', ], [ 'text body from', 'HEADER SUBJECT text HEADER SUBJECT body HEADER SUBJECT from', ], [ '"text body from"', 'HEADER SUBJECT "text body from"', ], [ '"text body" from', 'HEADER SUBJECT "text body" HEADER SUBJECT from', ], [ ' to:"test1\" test2" body:"test3 test4" "test5"', 'HEADER TO "test1\" test2" BODY "test3 test4" HEADER SUBJECT test5', ], [ ['test', 'from,to', 'UNSEEN', '-1W'], "UNSEEN BEFORE {$weekDate} OR HEADER FROM test HEADER TO test", ], // test OR-operator and AND-operator [ '"OR"', 'HEADER SUBJECT OR', ], [ '"or" "OR"', 'HEADER SUBJECT or HEADER SUBJECT OR', ], [ 'test1 "OR" test2', 'HEADER SUBJECT test1 HEADER SUBJECT OR HEADER SUBJECT test2', ], [ 'test1 OR test2', 'OR HEADER SUBJECT test1 HEADER SUBJECT test2', ], [ 'test1 OR test2 OR test3', 'OR HEADER SUBJECT test1 OR HEADER SUBJECT test2 HEADER SUBJECT test3', ], [ 'from:test1 OR to:test2', 'OR HEADER FROM test1 HEADER TO test2', ], [ 'replyto:test1 or from:test2', 'OR OR HEADER REPLY-TO test1 HEADER MAIL-REPLY-TO test1 HEADER FROM test2', ], [ 'from:test1 body:test2 OR to:test3', 'HEADER FROM test1 OR BODY test2 HEADER TO test3', ], [ 'or or or', '', ], [ 'or from:test1 body:test2 OR to:test3 or', 'HEADER FROM test1 OR BODY test2 HEADER TO test3', ], [ 'from:test1 or or body:test2 OR to:test3', 'OR HEADER FROM test1 OR BODY test2 HEADER TO test3', ], [ 'from:test1 and body:test2', 'HEADER FROM test1 BODY test2', ], [ 'from:test1 and body:test2 or to:test3', 'HEADER FROM test1 OR BODY test2 HEADER TO test3', ], // test negation [ '-from:test1 and -body:test2', 'NOT HEADER FROM test1 NOT BODY test2', ], [ 'from:-test1 and body:test2 or -to:test3', 'HEADER FROM -test1 OR BODY test2 NOT HEADER TO test3', ], [ '-since:1w -before:1w', "NOT SINCE {$weekDate} NOT BEFORE {$weekDate}", ], [ '-smaller:1KB -larger:1M', 'NOT SMALLER 1024 NOT LARGER 1048576', ], [ '-"from:test1"', 'NOT HEADER SUBJECT from:test1', ], [ '"-from:test1"', 'HEADER SUBJECT -from:test1', ], ]; } /** * Test search_interval_criteria() method * * @dataProvider provide_search_interval_criteria_cases */ #[DataProvider('provide_search_interval_criteria_cases')] public function test_search_interval_criteria($input, $output) { $result = \rcmail_action_mail_search::search_interval_criteria($input); $this->assertSame($output, $result); } /** * Test data for test_search_interval_criteria() */ public static function provide_search_interval_criteria_cases(): iterable { $week = new \DateInterval('P1W'); $month = new \DateInterval('P1M'); $year = new \DateInterval('P1Y'); $utcTz = new \DateTimeZone('UTC'); return [ ['', null], ['1W', 'SINCE ' . (new \DateTime('now', $utcTz))->sub($week)->format('j-M-Y')], ['1M', 'SINCE ' . (new \DateTime('now', $utcTz))->sub($month)->format('j-M-Y')], ['1Y', 'SINCE ' . (new \DateTime('now', $utcTz))->sub($year)->format('j-M-Y')], ['-1W', 'BEFORE ' . (new \DateTime('now', $utcTz))->sub($week)->format('j-M-Y')], ['-1M', 'BEFORE ' . (new \DateTime('now', $utcTz))->sub($month)->format('j-M-Y')], ['-1Y', 'BEFORE ' . (new \DateTime('now', $utcTz))->sub($year)->format('j-M-Y')], ]; } }