/ tests / MailModelTest.php
<?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');
    }
}
X