1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees; 21 22use Aura\Router\Route; 23use Aura\Router\RouterContainer; 24use Fig\Http\Message\RequestMethodInterface; 25use Fisharebest\Webtrees\Http\RequestHandlers\GedcomLoad; 26use Fisharebest\Webtrees\Http\Routes\WebRoutes; 27use Fisharebest\Webtrees\Module\ModuleThemeInterface; 28use Fisharebest\Webtrees\Module\WebtreesTheme; 29use Fisharebest\Webtrees\Services\MigrationService; 30use Fisharebest\Webtrees\Services\ModuleService; 31use Fisharebest\Webtrees\Services\TimeoutService; 32use Fisharebest\Webtrees\Services\TreeService; 33use Illuminate\Database\Capsule\Manager as DB; 34use Psr\Http\Message\ServerRequestInterface; 35use Psr\Http\Message\StreamFactoryInterface; 36use Psr\Http\Message\UploadedFileFactoryInterface; 37use Psr\Http\Message\UploadedFileInterface; 38 39use function app; 40use function basename; 41use function filesize; 42use function http_build_query; 43use function microtime; 44 45use const UPLOAD_ERR_OK; 46 47/** 48 * Base class for unit tests 49 */ 50class TestCase extends \PHPUnit\Framework\TestCase 51{ 52 /** @var object */ 53 public static $mock_functions; 54 /** @var bool */ 55 protected static $uses_database = false; 56 57 /** 58 * Things to run once, before all the tests. 59 */ 60 public static function setUpBeforeClass(): void 61 { 62 parent::setUpBeforeClass(); 63 64 $webtrees = new Webtrees(); 65 $webtrees->registerFactories(); 66 67 app()->bind(ModuleThemeInterface::class, WebtreesTheme::class); 68 69 // Need the routing table, to generate URLs. 70 $router_container = new RouterContainer('/'); 71 (new WebRoutes())->load($router_container->getMap()); 72 app()->instance(RouterContainer::class, $router_container); 73 74 I18N::init('en-US', true); 75 76 if (static::$uses_database) { 77 static::createTestDatabase(); 78 79 // Boot modules 80 (new ModuleService())->bootModules(new WebtreesTheme()); 81 } 82 } 83 84 /** 85 * Things to run once, AFTER all the tests. 86 */ 87 public static function tearDownAfterClass(): void 88 { 89 if (static::$uses_database) { 90 $pdo = DB::connection()->getPdo(); 91 unset($pdo); 92 } 93 94 parent::tearDownAfterClass(); 95 } 96 97 /** 98 * Create an SQLite in-memory database for testing 99 */ 100 protected static function createTestDatabase(): void 101 { 102 $capsule = new DB(); 103 $capsule->addConnection([ 104 'driver' => 'sqlite', 105 'database' => ':memory:', 106 ]); 107 $capsule->setAsGlobal(); 108 109 // Migrations create logs, which requires an IP address, which requires a request 110 self::createRequest(); 111 112 // Create tables 113 $migration_service = new MigrationService(); 114 $migration_service->updateSchema('\Fisharebest\Webtrees\Schema', 'WT_SCHEMA_VERSION', Webtrees::SCHEMA_VERSION); 115 116 // Create config data 117 $migration_service->seedDatabase(); 118 } 119 120 /** 121 * Create a request and bind it into the container. 122 * 123 * @param string $method 124 * @param string[] $query 125 * @param string[] $params 126 * @param UploadedFileInterface[] $files 127 * @param string[] $attributes 128 * 129 * @return ServerRequestInterface 130 */ 131 protected static function createRequest( 132 string $method = RequestMethodInterface::METHOD_GET, 133 array $query = [], 134 array $params = [], 135 array $files = [], 136 array $attributes = [] 137 ): ServerRequestInterface { 138 $uri = 'https://webtrees.test/index.php?' . http_build_query($query); 139 140 $request = Registry::serverRequestFactory() 141 ->createServerRequest($method, $uri) 142 ->withQueryParams($query) 143 ->withParsedBody($params) 144 ->withUploadedFiles($files) 145 ->withAttribute('base_url', 'https://webtrees.test') 146 ->withAttribute('client-ip', '127.0.0.1') 147 ->withAttribute('user', new GuestUser()) 148 ->withAttribute('route', new Route()); 149 150 foreach ($attributes as $key => $value) { 151 $request = $request->withAttribute($key, $value); 152 153 if ($key === 'tree') { 154 app()->instance(Tree::class, $value); 155 } 156 } 157 158 app()->instance(ServerRequestInterface::class, $request); 159 160 return $request; 161 } 162 163 /** 164 * Things to run before every test. 165 */ 166 protected function setUp(): void 167 { 168 parent::setUp(); 169 170 if (static::$uses_database) { 171 DB::connection()->beginTransaction(); 172 } 173 } 174 175 /** 176 * Things to run after every test 177 */ 178 protected function tearDown(): void 179 { 180 if (static::$uses_database) { 181 DB::connection()->rollBack(); 182 } 183 184 Site::$preferences = []; 185 186 Auth::logout(); 187 } 188 189 /** 190 * Import a GEDCOM file into the test database. 191 * 192 * @param string $gedcom_file 193 * 194 * @return Tree 195 */ 196 protected function importTree(string $gedcom_file): Tree 197 { 198 $tree_service = new TreeService(); 199 $tree = $tree_service->create(basename($gedcom_file), basename($gedcom_file)); 200 $stream = Registry::streamFactory()->createStreamFromFile(__DIR__ . '/data/' . $gedcom_file); 201 202 $tree_service->importGedcomFile($tree, $stream, $gedcom_file); 203 204 $timeout_service = new TimeoutService(microtime(true)); 205 $controller = new GedcomLoad($timeout_service, $tree_service); 206 $request = self::createRequest()->withAttribute('tree', $tree); 207 208 do { 209 $controller->handle($request); 210 211 $imported = $tree->getPreference('imported'); 212 } while (!$imported); 213 214 return $tree; 215 } 216 217 /** 218 * Create an uploaded file for a request. 219 * 220 * @param string $filename 221 * @param string $mime_type 222 * 223 * @return UploadedFileInterface 224 */ 225 protected function createUploadedFile(string $filename, string $mime_type): UploadedFileInterface 226 { 227 $stream = Registry::streamFactory()->createStreamFromFile($filename); 228 $size = filesize($filename); 229 $status = UPLOAD_ERR_OK; 230 $client_name = basename($filename); 231 232 return Registry::uploadedFileFactory() 233 ->createUploadedFile($stream, $size, $status, $client_name, $mime_type); 234 } 235} 236