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