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