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