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 Fisharebest\Webtrees\Contracts\CacheFactoryInterface; 23use Fisharebest\Webtrees\Contracts\UserInterface; 24use Fisharebest\Webtrees\Functions\FunctionsImport; 25use Fisharebest\Webtrees\Services\GedcomExportService; 26use Fisharebest\Webtrees\Services\TreeService; 27use Fisharebest\Webtrees\Services\UserService; 28use InvalidArgumentException; 29use Symfony\Component\Cache\Adapter\NullAdapter; 30 31use function fclose; 32use function file_get_contents; 33use function preg_replace; 34use function stream_get_contents; 35 36/** 37 * Test harness for the class Tree 38 */ 39class TreeTest extends TestCase 40{ 41 protected static bool $uses_database = true; 42 43 public function setUp(): void 44 { 45 parent::setUp(); 46 47 $cache_factory = $this->createMock(CacheFactoryInterface::class); 48 $cache_factory->method('array')->willReturn(new Cache(new NullAdapter())); 49 Registry::cache($cache_factory); 50 } 51 52 /** 53 * @covers \Fisharebest\Webtrees\Tree::__construct 54 * @covers \Fisharebest\Webtrees\Tree::id 55 * @covers \Fisharebest\Webtrees\Tree::name 56 * @covers \Fisharebest\Webtrees\Tree::title 57 * @return void 58 */ 59 public function testConstructor(): void 60 { 61 $tree_service = new TreeService(); 62 $tree = $tree_service->create('name', 'title'); 63 64 self::assertSame('name', $tree->name()); 65 self::assertSame('title', $tree->title()); 66 } 67 68 /** 69 * @covers \Fisharebest\Webtrees\Tree::getPreference 70 * @covers \Fisharebest\Webtrees\Tree::setPreference 71 * @return void 72 */ 73 public function testTreePreferences(): void 74 { 75 $tree_service = new TreeService(); 76 $tree = $tree_service->create('name', 'title'); 77 78 $tree->setPreference('foo', 'bar'); 79 $pref = $tree->getPreference('foo'); 80 self::assertSame('bar', $pref); 81 } 82 83 /** 84 * @covers \Fisharebest\Webtrees\Tree::getUserPreference 85 * @covers \Fisharebest\Webtrees\Tree::setUserPreference 86 * @return void 87 */ 88 public function testUserTreePreferences(): void 89 { 90 $user_service = new UserService(); 91 $tree_service = new TreeService(); 92 $tree = $tree_service->create('name', 'title'); 93 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 94 95 $pref = $tree->getUserPreference($user, 'foo', 'default'); 96 self::assertSame('default', $pref); 97 98 $tree->setUserPreference($user, 'foo', 'bar'); 99 $pref = $tree->getUserPreference($user, 'foo', 'default'); 100 self::assertSame('bar', $pref); 101 } 102 103 /** 104 * @covers \Fisharebest\Webtrees\Tree::createIndividual 105 * @return void 106 */ 107 public function testCreateInvalidIndividual(): void 108 { 109 $this->expectException(InvalidArgumentException::class); 110 111 $user_service = new UserService(); 112 $tree_service = new TreeService(); 113 $tree = $tree_service->create('name', 'title'); 114 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 115 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 116 Auth::login($user); 117 118 $tree->createIndividual("0 @@ FOO\n1 SEX U"); 119 } 120 121 /** 122 * @covers \Fisharebest\Webtrees\Tree::createIndividual 123 * @return void 124 */ 125 public function testCreateIndividual(): void 126 { 127 $user_service = new UserService(); 128 $tree_service = new TreeService(); 129 $tree = $tree_service->create('name', 'title'); 130 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 131 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 132 Auth::login($user); 133 134 $record = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 135 self::assertTrue($record->isPendingAddition()); 136 137 $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 138 $record = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 139 self::assertFalse($record->isPendingAddition()); 140 } 141 142 /** 143 * @covers \Fisharebest\Webtrees\Tree::createFamily 144 * @return void 145 */ 146 public function testCreateInvalidFamily(): void 147 { 148 $this->expectException(InvalidArgumentException::class); 149 150 $user_service = new UserService(); 151 $tree_service = new TreeService(); 152 $tree = $tree_service->create('name', 'title'); 153 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 154 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 155 Auth::login($user); 156 157 $tree->createFamily("0 @@ FOO\n1 MARR Y"); 158 } 159 160 /** 161 * @covers \Fisharebest\Webtrees\Tree::createFamily 162 * @return void 163 */ 164 public function testCreateFamily(): void 165 { 166 $user_service = new UserService(); 167 $tree_service = new TreeService(); 168 $tree = $tree_service->create('name', 'title'); 169 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 170 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 171 Auth::login($user); 172 173 $record = $tree->createFamily("0 @@ FAM\n1 MARR Y"); 174 self::assertTrue($record->isPendingAddition()); 175 176 $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 177 $record = $tree->createFamily("0 @@ FAM\n1 MARR Y"); 178 self::assertFalse($record->isPendingAddition()); 179 } 180 181 /** 182 * @covers \Fisharebest\Webtrees\Tree::createMediaObject 183 * @return void 184 */ 185 public function testCreateInvalidMediaObject(): void 186 { 187 $this->expectException(InvalidArgumentException::class); 188 189 $user_service = new UserService(); 190 $tree_service = new TreeService(); 191 $tree = $tree_service->create('name', 'title'); 192 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 193 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 194 Auth::login($user); 195 196 $tree->createMediaObject("0 @@ FOO\n1 MARR Y"); 197 } 198 199 /** 200 * @covers \Fisharebest\Webtrees\Tree::createMediaObject 201 * @return void 202 */ 203 public function testCreateMediaObject(): void 204 { 205 $user_service = new UserService(); 206 $tree_service = new TreeService(); 207 $tree = $tree_service->create('name', 'title'); 208 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 209 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 210 Auth::login($user); 211 212 $record = $tree->createMediaObject("0 @@ OBJE\n1 FILE foo.jpeg"); 213 self::assertTrue($record->isPendingAddition()); 214 215 $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 216 $record = $tree->createMediaObject("0 @@ OBJE\n1 FILE foo.jpeg"); 217 self::assertFalse($record->isPendingAddition()); 218 } 219 220 /** 221 * @covers \Fisharebest\Webtrees\Tree::createRecord 222 * @return void 223 */ 224 public function testCreateInvalidRecord(): void 225 { 226 $this->expectException(InvalidArgumentException::class); 227 228 $user_service = new UserService(); 229 $tree_service = new TreeService(); 230 $tree = $tree_service->create('name', 'title'); 231 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 232 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 233 Auth::login($user); 234 235 $tree->createRecord("0 @@FOO\n1 NOTE noted"); 236 } 237 238 /** 239 * @covers \Fisharebest\Webtrees\Tree::createRecord 240 * @return void 241 */ 242 public function testCreateRecord(): void 243 { 244 $user_service = new UserService(); 245 $tree_service = new TreeService(); 246 $tree = $tree_service->create('name', 'title'); 247 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 248 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 249 Auth::login($user); 250 251 $record = $tree->createRecord("0 @@ FOO\n1 NOTE noted"); 252 self::assertTrue($record->isPendingAddition()); 253 254 $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 255 $record = $tree->createRecord("0 @@ FOO\n1 NOTE noted"); 256 self::assertFalse($record->isPendingAddition()); 257 } 258 259 /** 260 * @covers \Fisharebest\Webtrees\Tree::significantIndividual 261 * @return void 262 */ 263 public function testSignificantIndividual(): void 264 { 265 $user_service = new UserService(); 266 $tree_service = new TreeService(); 267 $tree = $tree_service->create('name', 'title'); 268 $user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 269 $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 270 Auth::login($user); 271 272 // Delete the tree's default individual. 273 FunctionsImport::updateRecord('0 @X1@ INDI', $tree, true); 274 275 // No individuals in tree? Fake individual 276 self::assertSame('I', $tree->significantIndividual($user)->xref()); 277 278 $record1 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 279 $record2 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 280 $record3 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 281 $record4 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 282 283 // Individuals exist? First one (lowest XREF). 284 self::assertSame($record1->xref(), $tree->significantIndividual($user)->xref()); 285 286 // Preference for tree? 287 $tree->setPreference('PEDIGREE_ROOT_ID', $record2->xref()); 288 self::assertSame($record2->xref(), $tree->significantIndividual($user)->xref()); 289 290 // User preference 291 $tree->setUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF, $record3->xref()); 292 self::assertSame($record3->xref(), $tree->significantIndividual($user)->xref()); 293 294 // User record 295 $tree->setUserPreference($user, UserInterface::PREF_TREE_DEFAULT_XREF, $record4->xref()); 296 self::assertSame($record4->xref(), $tree->significantIndividual($user)->xref()); 297 } 298 299 /** 300 * @covers \Fisharebest\Webtrees\Services\TreeService::importGedcomFile 301 * @covers \Fisharebest\Webtrees\Services\TreeService::deleteGenealogyData 302 * @return void 303 */ 304 public function testImportAndDeleteGedcomFile(): void 305 { 306 $tree_service = new TreeService(); 307 $tree = $this->importTree('demo.ged'); 308 self::assertNotNull($tree_service->all()->get('demo.ged')); 309 Site::setPreference('DEFAULT_GEDCOM', $tree->name()); 310 311 $tree_service->delete($tree); 312 313 self::assertNull($tree_service->all()->get('demo.ged')); 314 self::assertSame('', Site::getPreference('DEFAULT_GEDCOM')); 315 } 316 317 /** 318 * @covers \Fisharebest\Webtrees\Tree::hasPendingEdit 319 * @return void 320 */ 321 public function testHasPendingEdits(): void 322 { 323 $user_service = new UserService(); 324 $tree = $this->importTree('demo.ged'); 325 $user = $user_service->create('admin', 'Administrator', 'admin@example.com', 'secret'); 326 $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 327 Auth::login($user); 328 329 $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 330 $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 331 self::assertFalse($tree->hasPendingEdit()); 332 333 $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, ''); 334 $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/"); 335 self::assertTrue($tree->hasPendingEdit()); 336 } 337 338 /** 339 * @covers \Fisharebest\Webtrees\Services\GedcomExportService::export 340 * @return void 341 */ 342 public function testExportGedcom(): void 343 { 344 $tree = $this->importTree('demo.ged'); 345 346 $gedcom_export_service = new GedcomExportService(); 347 348 $resource = $gedcom_export_service->export($tree, true); 349 $original = file_get_contents(__DIR__ . '/../data/demo.ged'); 350 $export = stream_get_contents($resource); 351 fclose($resource); 352 353 // The version, date and time in the HEAD record will be different. 354 $original = preg_replace('/\n2 VERS .*/', '', $original, 1); 355 $export = preg_replace('/\n2 VERS .*/', '', $export, 1); 356 $original = preg_replace('/\n1 DATE .. ... ..../', '', $original, 1); 357 $export = preg_replace('/\n1 DATE .. ... ..../', '', $export, 1); 358 $original = preg_replace('/\n2 TIME ..:..:../', '', $original, 1); 359 $export = preg_replace('/\n2 TIME ..:..:../', '', $export, 1); 360 361 self::assertSame($original, $export); 362 } 363} 364