xref: /webtrees/tests/app/TreeTest.php (revision a26ec5ede3c2ecdb45b783755ede967935aaecba)
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