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