96 lines
3.9 KiB
PHP
96 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit;
|
|
|
|
use App\Services\AzerothCoreAccountDumpReader;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\TestCase;
|
|
use RuntimeException;
|
|
|
|
class AzerothCoreAccountDumpReaderTest extends TestCase
|
|
{
|
|
private array $files = [];
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
foreach ($this->files as $path) {
|
|
unlink($path);
|
|
}
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_reads_mysqldump_column_order_and_escaped_binary_without_hex_blob(): void
|
|
{
|
|
$bytes = "\0\n\r\t\x08\x1a'\"\\".substr(hash('sha256', 'fixture', true), 0, 23);
|
|
$literal = strtr($bytes, [
|
|
"\0" => '\\0', "\n" => '\\n', "\r" => '\\r', "\t" => '\\t',
|
|
"\x08" => '\\b', "\x1a" => '\\Z', "'" => "\\'", '"' => '\\"', '\\' => '\\\\',
|
|
]);
|
|
$sql = <<<SQL
|
|
-- mysqldump without --hex-blob
|
|
USE `acore_auth`;
|
|
CREATE TABLE `account` (
|
|
`id` int unsigned NOT NULL,
|
|
`username` varchar(32) NOT NULL,
|
|
`salt` binary(32) NOT NULL,
|
|
`verifier` binary(32) NOT NULL,
|
|
`email` varchar(255) DEFAULT NULL,
|
|
PRIMARY KEY (`id`),
|
|
KEY `email_index` (`email`)
|
|
) ENGINE=InnoDB;
|
|
INSERT INTO `account` VALUES (42,'SINDORING',_binary '$literal','$literal',NULL),(43,'SECOND',0x00,X'ff','quoted''mail');
|
|
CREATE TABLE `account_access` (`id` int NOT NULL, `gmlevel` tinyint NOT NULL, `RealmID` int NOT NULL);
|
|
INSERT INTO `account_access` VALUES (42,3,-1);
|
|
SQL;
|
|
$rows = iterator_to_array((new AzerothCoreAccountDumpReader)->rows($this->file($sql)));
|
|
$this->assertCount(3, $rows);
|
|
$this->assertSame('account', $rows[0]['table']);
|
|
$this->assertSame(['id' => '42', 'username' => 'SINDORING', 'salt' => $bytes, 'verifier' => $bytes, 'email' => null], $rows[0]['row']);
|
|
$this->assertSame("\0", $rows[1]['row']['salt']);
|
|
$this->assertSame("\xff", $rows[1]['row']['verifier']);
|
|
$this->assertSame("quoted'mail", $rows[1]['row']['email']);
|
|
$this->assertSame(['table' => 'account_access', 'row' => ['id' => '42', 'gmlevel' => '3', 'RealmID' => '-1']], $rows[2]);
|
|
}
|
|
|
|
public function test_gzip_filters_source_database_and_ignores_unrelated_sql(): void
|
|
{
|
|
$sql = <<<'SQL'
|
|
USE `other_auth`;
|
|
INSERT INTO `account` (`id`,`username`) VALUES (1,'OTHER');
|
|
DROP DATABASE `moonwell-web`;
|
|
USE `acore_auth`;
|
|
INSERT IGNORE INTO `account` (`id`,`username`,`email`) VALUES (42,_utf8mb4 'SINDORING','semicolon;value');
|
|
INSERT INTO `other_auth`.`account_access` (`id`,`gmlevel`) VALUES (1,3);
|
|
SQL;
|
|
$rows = iterator_to_array((new AzerothCoreAccountDumpReader)->rows($this->file(gzencode($sql))));
|
|
$this->assertSame([['table' => 'account', 'row' => ['id' => '42', 'username' => 'SINDORING', 'email' => 'semicolon;value']]], $rows);
|
|
}
|
|
|
|
#[DataProvider('invalidStatements')]
|
|
public function test_rejects_unsupported_or_incomplete_account_sql(string $sql): void
|
|
{
|
|
$this->expectException(RuntimeException::class);
|
|
iterator_to_array((new AzerothCoreAccountDumpReader)->rows($this->file($sql)));
|
|
}
|
|
|
|
public static function invalidStatements(): array
|
|
{
|
|
return [
|
|
'missing column definitions' => ["INSERT INTO `account` VALUES (1,'PLAYER');"],
|
|
'column count mismatch' => ["INSERT INTO `account` (`id`) VALUES (1,'PLAYER');"],
|
|
'truncated' => ['INSERT INTO `account` (`id`) VALUES (1'],
|
|
'expressions' => ["INSERT INTO `account` (`salt`) VALUES (UNHEX('abcd'));"],
|
|
'appended SQL' => ['INSERT INTO `account` (`id`) VALUES (1); DROP DATABASE `web`;'],
|
|
];
|
|
}
|
|
|
|
private function file(string $contents): string
|
|
{
|
|
$path = tempnam(sys_get_temp_dir(), 'auth-parser-');
|
|
file_put_contents($path, $contents);
|
|
$this->files[] = $path;
|
|
|
|
return $path;
|
|
}
|
|
}
|