<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\tests;
use seekquarry\yioop\library\mail\ImapListing;
use seekquarry\yioop\library\UnitTest;
use seekquarry\yioop\models\MailModel;
/**
* Unit tests for MailModel, which keeps the Mail activity's per-user
* view state (folder sort, the sorted-view sequence list, and learned
* sort support) in the browsing session. Each test starts from an
* empty session and checks that what is stored comes back unchanged.
*
* @author Chris Pollett
*/
class MailModelTest extends UnitTest
{
/**
* The model under test
* @var object
*/
public $mail_model;
/**
* Starts each test from an empty session and a fresh model.
*/
public function setUp()
{
$_SESSION = [];
$this->mail_model = new MailModel();
}
/**
* Clears the session so one test does not bleed into the next.
*/
public function tearDown()
{
$_SESSION = [];
}
/**
* A stored sort comes back unchanged, and an unset folder reads
* back as null.
*/
public function sortRoundTripTestCase()
{
$this->assertTrue($this->mail_model->storedSort(7, 'INBOX')
=== null,
'no sort is remembered before one is stored');
$sort = ['key' => 'subject', 'reverse' => true];
$this->mail_model->storeSort(7, 'INBOX', $sort);
$this->assertEqual($sort, $this->mail_model->storedSort(7, 'INBOX'),
'the stored sort reads back unchanged');
$this->assertTrue($this->mail_model->storedSort(7, 'Sent')
=== null,
'a different folder keeps its own (empty) sort');
}
/**
* A cached sorted-view sequence reads back under the matching
* account, folder, filter, and sort signature.
*/
public function cacheSequenceRoundTripTestCase()
{
$sort = ['key' => 'from', 'reverse' => false];
$this->mail_model->cacheSequence(7, 'INBOX', '', $sort, false,
false, ['sequence' => [5, 2, 9]]);
$signature = ImapListing::sortSignature($sort, false, false);
$this->assertEqual([5, 2, 9],
$this->mail_model->cachedSequence(7, 'INBOX', '', $signature),
'the cached sequence reads back for its own view');
$this->assertEqual([],
$this->mail_model->cachedSequence(7, 'INBOX', 'q', $signature),
'a different filter has no cached sequence');
}
/**
* A result that carries no ordered list (the plain newest-first
* view) caches nothing.
*/
public function cacheSequenceEmptyTestCase()
{
$sort = ['key' => 'date', 'reverse' => false];
$this->mail_model->cacheSequence(7, 'INBOX', '', $sort, false,
false, ['before' => 4]);
$signature = ImapListing::sortSignature($sort, false, false);
$this->assertEqual([],
$this->mail_model->cachedSequence(7, 'INBOX', '', $signature),
'nothing is cached when the result has no sequence');
}
/**
* Sort support is unknown until learned, then remembered.
*/
public function sortSupportRoundTripTestCase()
{
$this->assertTrue($this->mail_model->sortSupportHint(7) === null,
'sort support is unknown before it is learned');
$this->mail_model->cacheSortSupport(7, true);
$this->assertTrue($this->mail_model->sortSupportHint(7),
'a learned sort support reads back true');
}
/**
* A cached folder list reads back while fresh, and is also
* visible through the raw and whole-map readers.
*/
public function folderCacheRoundTripTestCase()
{
$folders = [['NAME' => 'INBOX'], ['NAME' => 'Sent']];
$this->mail_model->cacheFolders(5, $folders);
$this->assertEqual($folders,
$this->mail_model->cachedFolders(5),
'a fresh cached list reads back');
$this->assertEqual($folders,
$this->mail_model->foldersFor(5),
'the raw reader returns the cached list');
$whole = $this->mail_model->allCachedFolders();
$this->assertEqual($folders, $whole[5],
'the whole-map reader is keyed by account id');
}
/**
* Once a cached list ages past its freshness stamp, the
* freshness-checked reader reports nothing while the raw reader
* still returns whatever is cached.
*/
public function folderCacheStaleTestCase()
{
$folders = [['NAME' => 'INBOX']];
$this->mail_model->cacheFolders(5, $folders);
$_SESSION["MAIL_FOLDERS_FRESH_UNTIL"][5] = time() - 1;
$this->assertTrue($this->mail_model->cachedFolders(5) === null,
'a stale list is not returned by the fresh reader');
$this->assertEqual($folders,
$this->mail_model->foldersFor(5),
'the raw reader ignores freshness');
}
/**
* Invalidating an account drops both its list and its freshness
* stamp, so every reader comes back empty for it.
*/
public function invalidateFoldersTestCase()
{
$this->mail_model->cacheFolders(5, [['NAME' => 'INBOX']]);
$this->mail_model->invalidateFolders(5);
$this->assertTrue($this->mail_model->cachedFolders(5) === null,
'the fresh reader is empty after invalidation');
$this->assertEqual([], $this->mail_model->foldersFor(5),
'the raw reader is empty after invalidation');
$this->assertTrue(
empty($this->mail_model->allCachedFolders()[5]),
'the whole map no longer carries the account');
}
}