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