<?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\DiscussionImport;
use seekquarry\yioop\library\UnitTest;
/**
* Unit tests for DiscussionImport, the pure helper logic behind importing a
* MantisBT bug tracker into a Yioop issue tracker. Each test checks one
* piece on its own, with no database: reading a dump's INSERT rows, judging
* a username, building a byline and an issue body, decoding a stored
* attachment, and writing the wiki references for a post's attachments.
*
* @author Chris Pollett
*/
class DiscussionImportTest extends UnitTest
{
/**
* These tests need nothing set up beforehand.
*/
public function setUp()
{
}
/**
* These tests leave nothing behind to clean up.
*/
public function tearDown()
{
}
/**
* The INSERT reader keeps a quoted comma inside its field, turns a
* doubled quote into a single one, reads an unquoted NULL as a real
* null, and returns nothing for a table the dump does not mention.
*/
public function parseInsertsTestCase()
{
$sql = "INSERT INTO `t` VALUES (1,'it''s','a,b'),(2,NULL,'x');";
$rows = DiscussionImport::parseInserts($sql, "t");
$this->assertEqual(2, count($rows), "two rows are read");
$this->assertEqual("it's", $rows[0][1],
"a doubled quote becomes a single one");
$this->assertEqual("a,b", $rows[0][2],
"a comma inside quotes stays in the field");
$this->assertTrue($rows[1][1] === null,
"an unquoted NULL is read as null");
$this->assertEqual(0, count(DiscussionImport::parseInserts($sql, "u")),
"a table not in the dump yields no rows");
}
/**
* A backslash-n in a quoted field is read back as a real newline, so
* multi-line descriptions survive the dump.
*/
public function parseInsertsEscapeTestCase()
{
$sql = "INSERT INTO `t` VALUES ('line1" . "\\n" . "line2');";
$rows = DiscussionImport::parseInserts($sql, "t");
$this->assertEqual("line1\nline2", $rows[0][0],
"an escaped newline is decoded");
}
/**
* A username can be reused when it is non-empty, short enough, and made
* only of letters, digits, dots, dashes, and underscores; a blank name,
* one with a space, or an over-long one cannot.
*/
public function usernameCompatibleTestCase()
{
$this->assertTrue(DiscussionImport::usernameCompatible("alice"),
"a plain name can be reused");
$this->assertTrue(DiscussionImport::usernameCompatible("a.b-c_d"),
"dots, dashes, and underscores are allowed");
$this->assertFalse(DiscussionImport::usernameCompatible(""),
"a blank name cannot be reused");
$this->assertFalse(DiscussionImport::usernameCompatible("has space"),
"a name with a space cannot be reused");
$this->assertFalse(
DiscussionImport::usernameCompatible(str_repeat("a", 100)),
"an over-long name cannot be reused");
}
/**
* A fallback byline prefers the real name with the email in angle
* brackets, falls back to the username when there is no real name, and
* is empty when nothing at all is known.
*/
public function fallbackBylineTestCase()
{
$this->assertEqual("Alice A <a@x.com>",
DiscussionImport::fallbackByline(["realname" => "Alice A",
"username" => "alice", "email" => "a@x.com"]),
"real name and email are shown together");
$this->assertEqual("alice",
DiscussionImport::fallbackByline(["realname" => "",
"username" => "alice", "email" => ""]),
"the username stands in when nothing else is known");
$this->assertEqual("",
DiscussionImport::fallbackByline(["realname" => "",
"username" => "", "email" => ""]),
"nothing known gives an empty byline");
}
/**
* The issue body keeps the description, steps, and extra information,
* adds the original-reporter byline only for a bot-folded user, and
* never adds the old source-metadata footer.
*/
public function issueBodyTestCase()
{
$body = DiscussionImport::issueBody(["the bug", "do x", "a note"], "");
$this->assertTrue(strpos($body, "the bug") !== false,
"the description is kept");
$this->assertTrue(strpos($body, "Steps to reproduce") !== false,
"the steps are kept");
$this->assertTrue(strpos($body, "Additional information") !== false,
"the extra information is kept");
$this->assertTrue(strpos($body, "Imported from MantisBT") === false,
"the source-metadata footer is not added");
$with_note = DiscussionImport::issueBody(["b", "", ""], "Grace <g@x>");
$this->assertTrue(
strpos($with_note, "Originally reported by Grace <g@x>") !==
false, "a fallback reporter is credited");
}
/**
* Attachment bytes come through unchanged when stored plainly, are
* decoded when stored as a hex literal, and are empty when the dump
* holds none.
*/
public function fileContentTestCase()
{
$this->assertEqual("hi", DiscussionImport::fileContent("hi"),
"plain bytes are kept as is");
$this->assertEqual("Hi", DiscussionImport::fileContent("0x4869"),
"a hex literal is decoded");
$this->assertEqual("", DiscussionImport::fileContent(""),
"an empty value stays empty");
$this->assertEqual("", DiscussionImport::fileContent(null),
"a null value becomes empty");
}
/**
* The wiki references name one resource per attachment that has bytes,
* and skip an attachment the dump had no bytes for.
*/
public function fileReferencesTestCase()
{
$refs = DiscussionImport::fileReferences([
["filename" => "a.png", "content" => "x"],
["filename" => "b.txt", "content" => ""],
["filename" => "c.log", "content" => "y"]]);
$this->assertTrue(strpos($refs, "((resource:a.png|a.png))") !== false,
"a file with bytes is referenced");
$this->assertTrue(strpos($refs, "b.txt") === false,
"a file with no bytes is not referenced");
$this->assertTrue(strpos($refs, "((resource:c.log|c.log))") !== false,
"another file with bytes is referenced");
}
}