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