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