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