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