Skip to content

Commit

Permalink
Test message rendering
Browse files Browse the repository at this point in the history
Run ./tests/MessageRendering/run-tests.sh to run all tests in that
directory, which are supposed to check the rendering of concrete
messages.

The messages are served by a dovecot container from the Maildir in
`tests/MessageRendering/src/maildir` and identified by their `Message-Id`.
  • Loading branch information
pabzm committed May 21, 2024
1 parent ec1155a commit 825c1a3
Show file tree
Hide file tree
Showing 13 changed files with 502 additions and 0 deletions.
11 changes: 11 additions & 0 deletions tests/MessageRendering/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
src/maildir/subscriptions
src/maildir/dovecot-uidvalidity
src/maildir/dovecot.*.log
src/maildir/dovecot.*.cache
src/maildir/dovecot.list.index
src/maildir/dovecot-uid*
src/maildir/.Drafts
src/maildir/.Junk
src/maildir/.Sent
src/maildir/.Trash
.phpunit.result.cache
69 changes: 69 additions & 0 deletions tests/MessageRendering/BasicMessagesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Tests\MessageRendering;

/**
* Test class to test simple messages.
*/
class BasicMessagesTest extends MessageRenderingTestCase
{
/**
* Test that two text mime-parts with disposition "attachment" are shown as
* attachments.
*/
public function test_list_00()
{
$domxpath = $this->run_and_get_html_output_domxpath('[email protected]');
$this->assertSame('Lines', $this->getScrubbedSubject($domxpath));

$this->assertStringStartsWith('Plain text message body.', $this->getBody($domxpath));

$attchElems = $domxpath->query('//span[@class="attachment-name"]');
$this->assertCount(2, $attchElems, 'Attachments');
$this->assertStringStartsWith('lines.txt', $attchElems[0]->textContent);
$this->assertStringStartsWith('lines_lf.txt', $attchElems[1]->textContent);
}

/**
* Test that one inline image is not shown as attachment.
*/
public function test_list_01()
{
$domxpath = $this->run_and_get_html_output_domxpath('[email protected]');

$this->assertSame('Test HTML with local and remote image', $this->getScrubbedSubject($domxpath));

$this->assertSame("Attached image: \nRemote image:", $this->getBody($domxpath));

$attchNames = $domxpath->query('//span[@class="attachment-name"]');
$this->assertCount(0, $attchNames, 'Attachments');
}

/**
* Test that text parts are shown and also listed as attachments, and that
* filenames are properly listed.
*/
public function test_filename()
{
$domxpath = $this->run_and_get_html_output_domxpath('[email protected]');

$this->assertSame('Attachment filename encoding', $this->getScrubbedSubject($domxpath));

$msgParts = $domxpath->query('//div[@class="message-part"]');
$this->assertCount(3, $msgParts, 'Message text parts');

$this->assertSame("foo\nbar\ngna", $msgParts[0]->textContent);
$this->assertSame('潦੯慢ੲ湧', $msgParts[1]->textContent);
$this->assertSame("foo\nbar\ngna", $msgParts[2]->textContent);

$attchNames = $domxpath->query('//span[@class="attachment-name"]');
$this->assertCount(6, $attchNames, 'Attachments');

$this->assertSame('A011.txt', $attchNames[0]->textContent);
$this->assertSame('A012.txt', $attchNames[1]->textContent);
$this->assertSame('A014.txt', $attchNames[2]->textContent);
$this->assertSame('żółć.png', $attchNames[3]->textContent);
$this->assertSame('żółć.png', $attchNames[4]->textContent);
$this->assertSame('very very very very long very very very very long ćććććć very very very long name.txt', $attchNames[5]->textContent);
}
}
107 changes: 107 additions & 0 deletions tests/MessageRendering/MessageRenderingTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

namespace Tests\MessageRendering;

use Masterminds\HTML5;

/**
* Class to base actual test classes on, which test specific message rendering.
*/
class MessageRenderingTestCase extends \ActionTestCase
{
/**
* Get the body from the document, trimmed from surrounding whitespace.
*
* @param \DOMXPath $domxpath
*
* @returns string
*/
protected function getBody(\DOMXPath $domxpath): string
{
$bodyElem = $domxpath->query('//div[@id="messagebody"]');
$this->assertCount(1, $bodyElem, 'Message body');
return trim($bodyElem[0]->textContent);
}

/**
* Get the subject from the document, stripped by the prefix "Subject: ",
* the suffix "Open in new window", and trimmed from surrounding whitespace.
*
* @param \DOMXPath $domxpath
*
* @returns string
*/
protected function getScrubbedSubject(\DOMXPath $domxpath): string
{
$subjectElem = $domxpath->query('//h2[@class="subject"][1]');
$subject = preg_replace('/^\s*Subject:\s*(.*)\s*Open in new window$/', '$1', trim($subjectElem[0]->textContent));
return trim($subject);
}

/**
* Execute run() to render the message with the given $msg_id.
*
* This is useful to check how rcmail_action_mail_show() renders messages.
* It requires a running dovecot to fetch the messages from. Messages need
* to be placed as individual files in
* `tests/src/emails/test@example/Mail/cur/`.
*
* @param string $msg_id Message-ID of the wanted message, without the angle brackets
*
* @returns DOMDocument Parsed by Masterminds/html5 from the output of run().
*/
protected function run_and_get_html_output_domxpath($msg_id): \DOMXPath
{
$rcmail = \rcmail::get_instance();
// We need to overwrite the storage object, else storage_init() just
// returns the cached one (which might be a StorageMock instance).
$mock_storage = $rcmail->storage = null;
$rcmail->storage_init();
// Login our test user so we can fetch messages from the imap server.
$rcmail->login('test', 'pass', 'tls://localhost');
$storage = $rcmail->get_storage();
$storage->set_options(['all_headers' => true]);
// We need to set the folder, else no message can be fetched.
$storage->set_folder('INBOX');
$output = $this->initOutput(\rcmail_action::MODE_HTTP, 'mail', 'preview');
// TODO: Why do we need to set the skin manually?
$output->set_skin('elastic');

$action = new \rcmail_action_mail_show();
$this->assertTrue($action->checks());

$messages_list = $storage->list_messages();

// Find the UID of the wanted message.
$message_uid = null;
foreach ($messages_list as $message_headers) {
if ($message_headers->get('message-id') === "<{$msg_id}>") {
$message_uid = $message_headers->uid;
break;
}
}
if ($message_uid === null) {
throw new \Exception("No message found in messages list with Message-Id '{$msg_id}'");
}

// Prepare and trigger the rendering.
$_GET = ['_uid' => $message_uid];
$html = '';
try {
$action->run();
} catch (\ExitException $e) {
throw $e;
} catch (\Exception $e) {
$this->assertSame('simulatedExit', $e->getMessage());
$html = $output->getOutput();
}

// Reset the storage to the mocked one most other tests expect.
$rcmail->storage = $mock_storage;

// disabled_html_ns=true is a workaround for the performance issue
// https://github.com/Masterminds/html5-php/issues/181
$html5 = new HTML5(['disable_html_ns' => true]);
return new \DOMXPath($html5->loadHTML($html));
}
}
26 changes: 26 additions & 0 deletions tests/MessageRendering/SingleImageNoTextTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Tests\MessageRendering;

/**
* Test class to test "interesting" messages.
*/
class SingleImageNoTextTest extends MessageRenderingTestCase
{
/**
* Test that of a multipart/mixed message which contains only one
* image, that image is shown.
*/
public function test_show_multipart_mixed_single_image_too()
{
$this->markTestSkipped('TBD: test for fixing GH issue 9443');

$domxpath = $this->run_and_get_html_output_domxpath('[email protected]');

Check failure on line 18 in tests/MessageRendering/SingleImageNoTextTest.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Unreachable statement - code above always terminates.

$this->assertSame('Not OK', $this->getScrubbedSubject($domxpath));

$attchNames = $domxpath->query('//span[@class="attachment-name"]');
$this->assertCount(1, $attchNames, 'Attachments');
$this->assertStringStartsWith('Resized_20240427_200026(1).jpeg', $attchNames[0]->textContent);
}
}
44 changes: 44 additions & 0 deletions tests/MessageRendering/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Environment initialization script for unit tests |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <[email protected]> |
| Author: Aleksander Machniak <[email protected]> |
+-----------------------------------------------------------------------+
*/

error_reporting(\E_ALL);

if (\PHP_SAPI != 'cli') {
exit('Not in shell mode (php-cli)');
}

if (!defined('INSTALL_PATH')) {
define('INSTALL_PATH', realpath(__DIR__ . '/../../') . '/');
}

define('ROUNDCUBE_TEST_MODE', true);
define('ROUNDCUBE_TEST_SESSION', microtime(true));
define('TESTS_DIR', __DIR__ . '/');

if (@is_dir(TESTS_DIR . 'config')) {
define('RCUBE_CONFIG_DIR', TESTS_DIR . 'config');
}

// Some tests depend on the way phpunit is executed
$_SERVER['SCRIPT_NAME'] = 'vendor/bin/phpunit';

require_once INSTALL_PATH . 'program/include/iniset.php';

rcmail::get_instance(0, 'test')->config->set('devel_mode', false);
1 change: 1 addition & 0 deletions tests/MessageRendering/dovecot-maildir.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mail_location = maildir:~
14 changes: 14 additions & 0 deletions tests/MessageRendering/phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<phpunit
bootstrap="bootstrap.php"
colors="true"
>
<extensions>
<extension class="Ergebnis\PHPUnit\SlowTestDetector\Extension"/>
</extensions>
<testsuites>
<testsuite name="tests">
<directory>.</directory>
</testsuite>
</testsuites>
</phpunit>
14 changes: 14 additions & 0 deletions tests/MessageRendering/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

testsdir=$(realpath $(dirname $0))
cont_name="roundcubetest-dovecot"

docker run -it --rm -d --name "$cont_name" \
-p 143:143 \
-v "$testsdir/src/maildir:/srv/mail/test" \
-v "$testsdir/dovecot-maildir.conf:/etc/dovecot/conf.d/dovecot-maildir.conf" \
docker.io/dovecot/dovecot:latest >/dev/null || exit 1

"$testsdir/../../vendor/bin/phpunit" -c "$testsdir/phpunit.xml" --fail-on-warning --fail-on-risky

docker stop "$cont_name" >/dev/null
34 changes: 34 additions & 0 deletions tests/MessageRendering/src/maildir/cur/attachment-gh9443.eml:2,S
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Message-Id: <[email protected]>
Date:Mon, 06 May 2024 15:24:47 +0200
From: <[email protected]>
To: [email protected]
Subject: Not OK
Mime-Version:1.0
Content-Type:multipart/mixed;boundary="--------------------------------------------=_NextPart_0_24856"

this is a multi-part message in MIME format.

----------------------------------------------=_NextPart_0_24856
Content-Type:image/jpeg; name="Resized_20240427_200026(1).jpeg"
Content-Transfer-Encoding:base64
Content-Location:Resized_20240427_200026(1).jpeg
Content-ID:<Resized_20240427_200026(1)>

iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81R
UkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0C
MK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2An
hf4QtqobAAAAAElFTkSuQmCC

----------------------------------------------=_NextPart_0_24856--
46 changes: 46 additions & 0 deletions tests/MessageRendering/src/maildir/cur/filename.eml:2,S
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
From: "Thomas B." <[email protected]>
To: Tom Tester <[email protected]>
Subject: Attachment filename encoding
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=1234;
Date: Fri, 23 May 2014 19:44:50 +0200
Message-ID: <[email protected]>

--1234
Content-Transfer-Encoding: base64
Content-Type: text/plain; name=A011.txt

Zm9vDQpiYXINCmduYQ==
--1234
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset=UTF-16LE; name=A012.txt

Zm9vCmJhcgpnbmE=
--1234
Content-Transfer-Encoding: base64
Content-Type: text/plain; name=A013.txt
Content-Disposition: attachment; filename=A014.txt

Zm9vDQpiYXINCmduYQ==
--1234
Content-Transfer-Encoding: base64
Content-Type: image/png; charset=UTF-16LE;
name="=?UTF-8?Q?=C5=BC=C3=B3=C5=82=C4=87=2Epng?="

Zm9vCmJhcgpnbmE=
--1234
Content-Transfer-Encoding: base64
Content-Type: image/png; charset=UTF-16LE; name=A016.txt
Content-Disposition: attachment;
filename*=UTF-8''%C5%BC%C3%B3%C5%82%C4%87.png

Zm9vCmJhcgpnbmE=
--1234
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset=iso-8859-1;
name="=?UTF-8?Q?very_very_very_very_long_very_very_very_very_long_=C4=87?=
=?UTF-8?Q?=C4=87=C4=87=C4=87=C4=87=C4=87_very_very_very_long_name=2Etxt?=
=?UTF-8?Q??="

Zm9vCmJhcgpnbmE=
--1234--
Loading

0 comments on commit 825c1a3

Please sign in to comment.