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