xref: /webtrees/tests/app/TreeTest.php (revision 5b8d89027920f7089af7ec7d3bc84e3669dd8718)
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 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    /**
44     * Things to run before every test.
45     */
46    protected function setUp(): void
47    {
48        parent::setUp();
49
50        $cache_factory = $this->createMock(CacheFactoryInterface::class);
51        $cache_factory->method('array')->willReturn(new Cache(new NullAdapter()));
52        Registry::cache($cache_factory);
53    }
54
55    /**
56     * @covers \Fisharebest\Webtrees\Tree::__construct
57     * @covers \Fisharebest\Webtrees\Tree::id
58     * @covers \Fisharebest\Webtrees\Tree::name
59     * @covers \Fisharebest\Webtrees\Tree::title
60     * @return void
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     * @return void
76     */
77    public function testTreePreferences(): void
78    {
79        $gedcom_import_service = new GedcomImportService();
80        $tree_service          = new TreeService($gedcom_import_service);
81        $tree                  = $tree_service->create('name', 'title');
82
83        $tree->setPreference('foo', 'bar');
84        $pref = $tree->getPreference('foo');
85        self::assertSame('bar', $pref);
86    }
87
88    /**
89     * @covers \Fisharebest\Webtrees\Tree::getUserPreference
90     * @covers \Fisharebest\Webtrees\Tree::setUserPreference
91     * @return void
92     */
93    public function testUserTreePreferences(): void
94    {
95        $user_service          = new UserService();
96        $gedcom_import_service = new GedcomImportService();
97        $tree_service          = new TreeService($gedcom_import_service);
98        $tree                  = $tree_service->create('name', 'title');
99        $user                  = $user_service->create('user', 'User', 'user@example.com', 'secret');
100
101        $pref = $tree->getUserPreference($user, 'foo', 'default');
102        self::assertSame('default', $pref);
103
104        $tree->setUserPreference($user, 'foo', 'bar');
105        $pref = $tree->getUserPreference($user, 'foo', 'default');
106        self::assertSame('bar', $pref);
107    }
108
109    /**
110     * @covers \Fisharebest\Webtrees\Tree::createIndividual
111     * @return void
112     */
113    public function testCreateInvalidIndividual(): void
114    {
115        $this->expectException(InvalidArgumentException::class);
116
117        $user_service          = new UserService();
118        $gedcom_import_service = new GedcomImportService();
119        $tree_service          = new TreeService($gedcom_import_service);
120        $tree                  = $tree_service->create('name', 'title');
121        $user                  = $user_service->create('user', 'User', 'user@example.com', 'secret');
122        $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1');
123        Auth::login($user);
124
125        $tree->createIndividual("0 @@ FOO\n1 SEX U");
126    }
127
128    /**
129     * @covers \Fisharebest\Webtrees\Tree::createIndividual
130     * @return void
131     */
132    public function testCreateIndividual(): void
133    {
134        $user_service          = new UserService();
135        $gedcom_import_service = new GedcomImportService();
136        $tree_service          = new TreeService($gedcom_import_service);
137        $tree                  = $tree_service->create('name', 'title');
138        $user                  = $user_service->create('user', 'User', 'user@example.com', 'secret');
139        $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1');
140        Auth::login($user);
141
142        $record = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/");
143        self::assertTrue($record->isPendingAddition());
144
145        $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1');
146        $record = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/");
147        self::assertFalse($record->isPendingAddition());
148    }
149
150    /**
151     * @covers \Fisharebest\Webtrees\Tree::createFamily
152     * @return void
153     */
154    public function testCreateInvalidFamily(): void
155    {
156        $this->expectException(InvalidArgumentException::class);
157
158        $user_service          = new UserService();
159        $gedcom_import_service = new GedcomImportService();
160        $tree_service          = new TreeService($gedcom_import_service);
161        $tree                  = $tree_service->create('name', 'title');
162        $user                  = $user_service->create('user', 'User', 'user@example.com', 'secret');
163        $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1');
164        Auth::login($user);
165
166        $tree->createFamily("0 @@ FOO\n1 MARR Y");
167    }
168
169    /**
170     * @covers \Fisharebest\Webtrees\Tree::createFamily
171     * @return void
172     */
173    public function testCreateFamily(): void
174    {
175        $user_service          = new UserService();
176        $gedcom_import_service = new GedcomImportService();
177        $tree_service          = new TreeService($gedcom_import_service);
178        $tree                  = $tree_service->create('name', 'title');
179        $user                  = $user_service->create('user', 'User', 'user@example.com', 'secret');
180        $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1');
181        Auth::login($user);
182
183        $record = $tree->createFamily("0 @@ FAM\n1 MARR Y");
184        self::assertTrue($record->isPendingAddition());
185
186        $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1');
187        $record = $tree->createFamily("0 @@ FAM\n1 MARR Y");
188        self::assertFalse($record->isPendingAddition());
189    }
190
191    /**
192     * @covers \Fisharebest\Webtrees\Tree::createMediaObject
193     * @return void
194     */
195    public function testCreateInvalidMediaObject(): void
196    {
197        $this->expectException(InvalidArgumentException::class);
198
199        $user_service          = new UserService();
200        $gedcom_import_service = new GedcomImportService();
201        $tree_service          = new TreeService($gedcom_import_service);
202        $tree                  = $tree_service->create('name', 'title');
203        $user                  = $user_service->create('user', 'User', 'user@example.com', 'secret');
204        $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1');
205        Auth::login($user);
206
207        $tree->createMediaObject("0 @@ FOO\n1 MARR Y");
208    }
209
210    /**
211     * @covers \Fisharebest\Webtrees\Tree::createMediaObject
212     * @return void
213     */
214    public function testCreateMediaObject(): void
215    {
216        $user_service          = new UserService();
217        $gedcom_import_service = new GedcomImportService();
218        $tree_service          = new TreeService($gedcom_import_service);
219        $tree                  = $tree_service->create('name', 'title');
220        $user                  = $user_service->create('user', 'User', 'user@example.com', 'secret');
221        $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1');
222        Auth::login($user);
223
224        $record = $tree->createMediaObject("0 @@ OBJE\n1 FILE foo.jpeg");
225        self::assertTrue($record->isPendingAddition());
226
227        $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1');
228        $record = $tree->createMediaObject("0 @@ OBJE\n1 FILE foo.jpeg");
229        self::assertFalse($record->isPendingAddition());
230    }
231
232    /**
233     * @covers \Fisharebest\Webtrees\Tree::createRecord
234     * @return void
235     */
236    public function testCreateInvalidRecord(): void
237    {
238        $this->expectException(InvalidArgumentException::class);
239
240        $user_service          = new UserService();
241        $gedcom_import_service = new GedcomImportService();
242        $tree_service          = new TreeService($gedcom_import_service);
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        $tree->createRecord("0 @@FOO\n1 NOTE noted");
249    }
250
251    /**
252     * @covers \Fisharebest\Webtrees\Tree::createRecord
253     * @return void
254     */
255    public function testCreateRecord(): void
256    {
257        $user_service          = new UserService();
258        $gedcom_import_service = new GedcomImportService();
259        $tree_service          = new TreeService($gedcom_import_service);
260        $tree                  = $tree_service->create('name', 'title');
261        $user                  = $user_service->create('user', 'User', 'user@example.com', 'secret');
262        $user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1');
263        Auth::login($user);
264
265        $record = $tree->createRecord("0 @@ FOO\n1 NOTE noted");
266        self::assertTrue($record->isPendingAddition());
267
268        $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1');
269        $record = $tree->createRecord("0 @@ FOO\n1 NOTE noted");
270        self::assertFalse($record->isPendingAddition());
271    }
272
273    /**
274     * @covers \Fisharebest\Webtrees\Tree::significantIndividual
275     * @return void
276     */
277    public function testSignificantIndividual(): void
278    {
279        $gedcom_import_service = new GedcomImportService();
280        $user_service = new UserService();
281        $tree_service = new TreeService($gedcom_import_service);
282        $tree         = $tree_service->create('name', 'title');
283        $user         = $user_service->create('user', 'User', 'user@example.com', 'secret');
284        $user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1');
285        Auth::login($user);
286
287        // Delete the tree's default individual.
288        $gedcom_import_service->updateRecord('0 @X1@ INDI', $tree, true);
289
290        // No individuals in tree?  Fake individual
291        self::assertSame('I', $tree->significantIndividual($user)->xref());
292
293        $record1 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/");
294        $record2 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/");
295        $record3 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/");
296        $record4 = $tree->createIndividual("0 @@ INDI\n1 SEX F\n1 NAME Foo /Bar/");
297
298        // Individuals exist?  First one (lowest XREF).
299        self::assertSame($record1->xref(), $tree->significantIndividual($user)->xref());
300
301        // Preference for tree?
302        $tree->setPreference('PEDIGREE_ROOT_ID', $record2->xref());
303        self::assertSame($record2->xref(), $tree->significantIndividual($user)->xref());
304
305        // User preference
306        $tree->setUserPreference($user, UserInterface::PREF_TREE_ACCOUNT_XREF, $record3->xref());
307        self::assertSame($record3->xref(), $tree->significantIndividual($user)->xref());
308
309        // User record
310        $tree->setUserPreference($user, UserInterface::PREF_TREE_DEFAULT_XREF, $record4->xref());
311        self::assertSame($record4->xref(), $tree->significantIndividual($user)->xref());
312    }
313
314    /**
315     * @covers \Fisharebest\Webtrees\Services\TreeService::importGedcomFile
316     * @covers \Fisharebest\Webtrees\Services\TreeService::deleteGenealogyData
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();
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