This repository has no description
25 kB
810 lines
1import { GetCollectionsForUrlUseCase } from '../../application/useCases/queries/GetCollectionsForUrlUseCase';
2import { InMemoryCardRepository } from '../utils/InMemoryCardRepository';
3import { InMemoryCollectionRepository } from '../utils/InMemoryCollectionRepository';
4import { InMemoryCollectionQueryRepository } from '../utils/InMemoryCollectionQueryRepository';
5import { CuratorId } from '../../domain/value-objects/CuratorId';
6import { CardBuilder } from '../utils/builders/CardBuilder';
7import { CollectionBuilder } from '../utils/builders/CollectionBuilder';
8import { CardTypeEnum } from '../../domain/value-objects/CardType';
9import { URL } from '../../domain/value-objects/URL';
10import { PublishedRecordId } from '../../domain/value-objects/PublishedRecordId';
11import {
12 CollectionSortField,
13 SortOrder,
14} from '../../domain/ICollectionQueryRepository';
15import { FakeProfileService } from '../utils/FakeProfileService';
16import { InMemoryFollowsRepository } from '../../../user/tests/infrastructure/InMemoryFollowsRepository';
17
18describe('GetCollectionsForUrlUseCase', () => {
19 let useCase: GetCollectionsForUrlUseCase;
20 let cardRepository: InMemoryCardRepository;
21 let collectionRepository: InMemoryCollectionRepository;
22 let collectionQueryRepository: InMemoryCollectionQueryRepository;
23 let profileService: FakeProfileService;
24 let followsRepository: InMemoryFollowsRepository;
25 let curator1: CuratorId;
26 let curator2: CuratorId;
27 let curator3: CuratorId;
28
29 beforeEach(() => {
30 cardRepository = InMemoryCardRepository.getInstance();
31 collectionRepository = InMemoryCollectionRepository.getInstance();
32 collectionQueryRepository = new InMemoryCollectionQueryRepository(
33 collectionRepository,
34 cardRepository,
35 );
36 profileService = new FakeProfileService();
37 followsRepository = InMemoryFollowsRepository.getInstance();
38
39 useCase = new GetCollectionsForUrlUseCase(
40 collectionQueryRepository,
41 profileService,
42 collectionRepository,
43 followsRepository,
44 );
45
46 curator1 = CuratorId.create('did:plc:curator1').unwrap();
47 curator2 = CuratorId.create('did:plc:curator2').unwrap();
48 curator3 = CuratorId.create('did:plc:curator3').unwrap();
49
50 // Add profiles for test curators
51 profileService.addProfile({
52 id: curator1.value,
53 name: 'Curator One',
54 handle: 'curator1',
55 avatarUrl: 'https://example.com/avatar1.jpg',
56 });
57
58 profileService.addProfile({
59 id: curator2.value,
60 name: 'Curator Two',
61 handle: 'curator2',
62 avatarUrl: 'https://example.com/avatar2.jpg',
63 });
64
65 profileService.addProfile({
66 id: curator3.value,
67 name: 'Curator Three',
68 handle: 'curator3',
69 avatarUrl: 'https://example.com/avatar3.jpg',
70 });
71 });
72
73 afterEach(() => {
74 cardRepository.clear();
75 collectionRepository.clear();
76 collectionQueryRepository.clear();
77 profileService.clear();
78 });
79
80 describe('Collections with URL cards', () => {
81 it('should return all collections containing cards with the specified URL', async () => {
82 const testUrl = 'https://example.com/shared-article';
83 const url = URL.create(testUrl).unwrap();
84
85 // Create URL cards for different users with the same URL
86 const card1 = new CardBuilder()
87 .withCuratorId(curator1.value)
88 .withType(CardTypeEnum.URL)
89 .withUrl(url)
90 .build();
91
92 const card2 = new CardBuilder()
93 .withCuratorId(curator2.value)
94 .withType(CardTypeEnum.URL)
95 .withUrl(url)
96 .build();
97
98 const card3 = new CardBuilder()
99 .withCuratorId(curator3.value)
100 .withType(CardTypeEnum.URL)
101 .withUrl(url)
102 .build();
103
104 if (
105 card1 instanceof Error ||
106 card2 instanceof Error ||
107 card3 instanceof Error
108 ) {
109 throw new Error('Failed to create cards');
110 }
111
112 // Add cards to their respective libraries
113 card1.addToLibrary(curator1);
114 card2.addToLibrary(curator2);
115 card3.addToLibrary(curator3);
116
117 await cardRepository.save(card1);
118 await cardRepository.save(card2);
119 await cardRepository.save(card3);
120
121 // Create collections for each user and add their cards
122 const collection1 = new CollectionBuilder()
123 .withAuthorId(curator1.value)
124 .withName('Tech Articles')
125 .withDescription('My tech articles')
126 .build();
127
128 const collection2 = new CollectionBuilder()
129 .withAuthorId(curator2.value)
130 .withName('Reading List')
131 .withDescription('Articles to read')
132 .build();
133
134 const collection3 = new CollectionBuilder()
135 .withAuthorId(curator3.value)
136 .withName('Favorites')
137 .build();
138
139 if (
140 collection1 instanceof Error ||
141 collection2 instanceof Error ||
142 collection3 instanceof Error
143 ) {
144 throw new Error('Failed to create collections');
145 }
146
147 // Add cards to collections
148 collection1.addCard(card1.cardId, curator1);
149 collection2.addCard(card2.cardId, curator2);
150 collection3.addCard(card3.cardId, curator3);
151
152 // Mark collections as published
153 const publishedRecordId1 = PublishedRecordId.create({
154 uri: 'at://did:plc:curator1/network.cosmik.collection/collection1',
155 cid: 'bafyreicollection1',
156 });
157 const publishedRecordId2 = PublishedRecordId.create({
158 uri: 'at://did:plc:curator2/network.cosmik.collection/collection2',
159 cid: 'bafyreicollection2',
160 });
161 const publishedRecordId3 = PublishedRecordId.create({
162 uri: 'at://did:plc:curator3/network.cosmik.collection/collection3',
163 cid: 'bafyreicollection3',
164 });
165
166 collection1.markAsPublished(publishedRecordId1);
167 collection2.markAsPublished(publishedRecordId2);
168 collection3.markAsPublished(publishedRecordId3);
169
170 await collectionRepository.save(collection1);
171 await collectionRepository.save(collection2);
172 await collectionRepository.save(collection3);
173
174 // Execute the use case
175 const query = {
176 url: testUrl,
177 };
178
179 const result = await useCase.execute(query);
180
181 // Verify the result
182 expect(result.isOk()).toBe(true);
183 const response = result.unwrap();
184
185 expect(response.collections).toHaveLength(3);
186 expect(response.pagination.totalCount).toBe(3);
187
188 // Check that all three collections are included
189 const collectionIds = response.collections.map((c) => c.id);
190 expect(collectionIds).toContain(
191 collection1.collectionId.getStringValue(),
192 );
193 expect(collectionIds).toContain(
194 collection2.collectionId.getStringValue(),
195 );
196 expect(collectionIds).toContain(
197 collection3.collectionId.getStringValue(),
198 );
199
200 // Verify collection details
201 const techArticles = response.collections.find(
202 (c) => c.name === 'Tech Articles',
203 );
204 expect(techArticles).toBeDefined();
205 expect(techArticles?.description).toBe('My tech articles');
206 expect(techArticles?.author.id).toBe(curator1.value);
207 expect(techArticles?.uri).toBe(
208 'at://did:plc:curator1/network.cosmik.collection/collection1',
209 );
210
211 const readingList = response.collections.find(
212 (c) => c.name === 'Reading List',
213 );
214 expect(readingList).toBeDefined();
215 expect(readingList?.description).toBe('Articles to read');
216 expect(readingList?.author.id).toBe(curator2.value);
217
218 const favorites = response.collections.find(
219 (c) => c.name === 'Favorites',
220 );
221 expect(favorites).toBeDefined();
222 expect(favorites?.description).toBeUndefined();
223 expect(favorites?.author.id).toBe(curator3.value);
224 });
225
226 it('should return empty array when no collections contain cards with the specified URL', async () => {
227 const testUrl = 'https://example.com/nonexistent-article';
228
229 const query = {
230 url: testUrl,
231 };
232
233 const result = await useCase.execute(query);
234
235 expect(result.isOk()).toBe(true);
236 const response = result.unwrap();
237
238 expect(response.collections).toHaveLength(0);
239 expect(response.pagination.totalCount).toBe(0);
240 });
241
242 it('should not return collections that contain cards with different URLs', async () => {
243 const testUrl1 = 'https://example.com/article1';
244 const testUrl2 = 'https://example.com/article2';
245 const url1 = URL.create(testUrl1).unwrap();
246 const url2 = URL.create(testUrl2).unwrap();
247
248 // Create cards with different URLs
249 const card1 = new CardBuilder()
250 .withCuratorId(curator1.value)
251 .withType(CardTypeEnum.URL)
252 .withUrl(url1)
253 .build();
254
255 const card2 = new CardBuilder()
256 .withCuratorId(curator2.value)
257 .withType(CardTypeEnum.URL)
258 .withUrl(url2)
259 .build();
260
261 if (card1 instanceof Error || card2 instanceof Error) {
262 throw new Error('Failed to create cards');
263 }
264
265 card1.addToLibrary(curator1);
266 card2.addToLibrary(curator2);
267
268 await cardRepository.save(card1);
269 await cardRepository.save(card2);
270
271 // Create collections
272 const collection1 = new CollectionBuilder()
273 .withAuthorId(curator1.value)
274 .withName('Collection 1')
275 .build();
276
277 const collection2 = new CollectionBuilder()
278 .withAuthorId(curator2.value)
279 .withName('Collection 2')
280 .build();
281
282 if (collection1 instanceof Error || collection2 instanceof Error) {
283 throw new Error('Failed to create collections');
284 }
285
286 collection1.addCard(card1.cardId, curator1);
287 collection2.addCard(card2.cardId, curator2);
288
289 await collectionRepository.save(collection1);
290 await collectionRepository.save(collection2);
291
292 // Query for testUrl1
293 const query = {
294 url: testUrl1,
295 };
296
297 const result = await useCase.execute(query);
298
299 expect(result.isOk()).toBe(true);
300 const response = result.unwrap();
301
302 expect(response.collections).toHaveLength(1);
303 expect(response.collections[0]!.name).toBe('Collection 1');
304 expect(response.collections[0]!.author.id).toBe(curator1.value);
305 });
306
307 it('should return multiple collections from the same user if they contain the URL', async () => {
308 const testUrl = 'https://example.com/popular-article';
309 const url = URL.create(testUrl).unwrap();
310
311 // Create URL card
312 const card = new CardBuilder()
313 .withCuratorId(curator1.value)
314 .withType(CardTypeEnum.URL)
315 .withUrl(url)
316 .build();
317
318 if (card instanceof Error) {
319 throw new Error('Failed to create card');
320 }
321
322 card.addToLibrary(curator1);
323 await cardRepository.save(card);
324
325 // Create multiple collections for the same user
326 const collection1 = new CollectionBuilder()
327 .withAuthorId(curator1.value)
328 .withName('Tech')
329 .build();
330
331 const collection2 = new CollectionBuilder()
332 .withAuthorId(curator1.value)
333 .withName('Favorites')
334 .build();
335
336 const collection3 = new CollectionBuilder()
337 .withAuthorId(curator1.value)
338 .withName('To Read')
339 .build();
340
341 if (
342 collection1 instanceof Error ||
343 collection2 instanceof Error ||
344 collection3 instanceof Error
345 ) {
346 throw new Error('Failed to create collections');
347 }
348
349 // Add the same card to all collections
350 collection1.addCard(card.cardId, curator1);
351 collection2.addCard(card.cardId, curator1);
352 collection3.addCard(card.cardId, curator1);
353
354 await collectionRepository.save(collection1);
355 await collectionRepository.save(collection2);
356 await collectionRepository.save(collection3);
357
358 // Execute the use case
359 const query = {
360 url: testUrl,
361 };
362
363 const result = await useCase.execute(query);
364
365 expect(result.isOk()).toBe(true);
366 const response = result.unwrap();
367
368 expect(response.collections).toHaveLength(3);
369
370 const collectionNames = response.collections.map((c) => c.name);
371 expect(collectionNames).toContain('Tech');
372 expect(collectionNames).toContain('Favorites');
373 expect(collectionNames).toContain('To Read');
374
375 // All should have the same author
376 response.collections.forEach((collection) => {
377 expect(collection.author.id).toBe(curator1.value);
378 });
379 });
380
381 it('should handle collections without published record IDs', async () => {
382 const testUrl = 'https://example.com/unpublished-article';
383 const url = URL.create(testUrl).unwrap();
384
385 // Create URL card
386 const card = new CardBuilder()
387 .withCuratorId(curator1.value)
388 .withType(CardTypeEnum.URL)
389 .withUrl(url)
390 .build();
391
392 if (card instanceof Error) {
393 throw new Error('Failed to create card');
394 }
395
396 card.addToLibrary(curator1);
397 await cardRepository.save(card);
398
399 // Create collection without publishing it
400 const collection = new CollectionBuilder()
401 .withAuthorId(curator1.value)
402 .withName('Unpublished Collection')
403 .build();
404
405 if (collection instanceof Error) {
406 throw new Error('Failed to create collection');
407 }
408
409 collection.addCard(card.cardId, curator1);
410 await collectionRepository.save(collection);
411
412 // Execute the use case
413 const query = {
414 url: testUrl,
415 };
416
417 const result = await useCase.execute(query);
418
419 expect(result.isOk()).toBe(true);
420 const response = result.unwrap();
421
422 expect(response.collections).toHaveLength(1);
423 expect(response.collections[0]!.name).toBe('Unpublished Collection');
424 expect(response.collections[0]!.uri).toBeUndefined();
425 });
426 });
427
428 describe('Pagination', () => {
429 it('should paginate results correctly', async () => {
430 const testUrl = 'https://example.com/popular-article';
431 const url = URL.create(testUrl).unwrap();
432
433 // Create 5 cards with the same URL from different users
434 const cards = [];
435 const curators = [];
436 const collections = [];
437
438 for (let i = 1; i <= 5; i++) {
439 const curator = CuratorId.create(`did:plc:curator${i}`).unwrap();
440 curators.push(curator);
441
442 // Add profile for this curator
443 profileService.addProfile({
444 id: curator.value,
445 name: `Curator ${i}`,
446 handle: `curator${i}`,
447 avatarUrl: `https://example.com/avatar${i}.jpg`,
448 });
449
450 const card = new CardBuilder()
451 .withCuratorId(curator.value)
452 .withType(CardTypeEnum.URL)
453 .withUrl(url)
454 .build();
455
456 if (card instanceof Error) {
457 throw new Error(`Failed to create card ${i}`);
458 }
459
460 card.addToLibrary(curator);
461 cards.push(card);
462 await cardRepository.save(card);
463
464 // Create collection for each user
465 const collection = new CollectionBuilder()
466 .withAuthorId(curator.value)
467 .withName(`Collection ${i}`)
468 .build();
469
470 if (collection instanceof Error) {
471 throw new Error(`Failed to create collection ${i}`);
472 }
473
474 collection.addCard(card.cardId, curator);
475 collections.push(collection);
476 await collectionRepository.save(collection);
477 }
478
479 // Test first page with limit 2
480 const query1 = {
481 url: testUrl,
482 page: 1,
483 limit: 2,
484 };
485
486 const result1 = await useCase.execute(query1);
487 expect(result1.isOk()).toBe(true);
488 const response1 = result1.unwrap();
489
490 expect(response1.collections).toHaveLength(2);
491 expect(response1.pagination.currentPage).toBe(1);
492 expect(response1.pagination.totalCount).toBe(5);
493 expect(response1.pagination.totalPages).toBe(3);
494 expect(response1.pagination.hasMore).toBe(true);
495
496 // Test second page
497 const query2 = {
498 url: testUrl,
499 page: 2,
500 limit: 2,
501 };
502
503 const result2 = await useCase.execute(query2);
504 expect(result2.isOk()).toBe(true);
505 const response2 = result2.unwrap();
506
507 expect(response2.collections).toHaveLength(2);
508 expect(response2.pagination.currentPage).toBe(2);
509 expect(response2.pagination.hasMore).toBe(true);
510
511 // Test last page
512 const query3 = {
513 url: testUrl,
514 page: 3,
515 limit: 2,
516 };
517
518 const result3 = await useCase.execute(query3);
519 expect(result3.isOk()).toBe(true);
520 const response3 = result3.unwrap();
521
522 expect(response3.collections).toHaveLength(1);
523 expect(response3.pagination.currentPage).toBe(3);
524 expect(response3.pagination.hasMore).toBe(false);
525 });
526
527 it('should respect limit cap of 100', async () => {
528 const query = {
529 url: 'https://example.com/test',
530 limit: 200, // Should be capped at 100
531 };
532
533 const result = await useCase.execute(query);
534 expect(result.isOk()).toBe(true);
535 const response = result.unwrap();
536
537 expect(response.pagination.limit).toBe(100);
538 });
539
540 it('should use default pagination values', async () => {
541 const testUrl = 'https://example.com/test-article';
542
543 const query = {
544 url: testUrl,
545 };
546
547 const result = await useCase.execute(query);
548 expect(result.isOk()).toBe(true);
549 const response = result.unwrap();
550
551 expect(response.pagination.currentPage).toBe(1);
552 expect(response.pagination.limit).toBe(20);
553 });
554 });
555
556 describe('Sorting', () => {
557 it('should use default sorting parameters (NAME, ASC)', async () => {
558 const testUrl = 'https://example.com/test-article';
559
560 const query = {
561 url: testUrl,
562 };
563
564 const result = await useCase.execute(query);
565 expect(result.isOk()).toBe(true);
566 const response = result.unwrap();
567
568 expect(response.sorting.sortBy).toBe(CollectionSortField.NAME);
569 expect(response.sorting.sortOrder).toBe(SortOrder.ASC);
570 });
571
572 it('should use provided sorting parameters', async () => {
573 const testUrl = 'https://example.com/test-article';
574
575 const query = {
576 url: testUrl,
577 sortBy: CollectionSortField.CREATED_AT,
578 sortOrder: SortOrder.DESC,
579 };
580
581 const result = await useCase.execute(query);
582 expect(result.isOk()).toBe(true);
583 const response = result.unwrap();
584
585 expect(response.sorting.sortBy).toBe(CollectionSortField.CREATED_AT);
586 expect(response.sorting.sortOrder).toBe(SortOrder.DESC);
587 });
588
589 it('should sort collections by name in ascending order by default', async () => {
590 const testUrl = 'https://example.com/article';
591 const url = URL.create(testUrl).unwrap();
592
593 // Create URL cards
594 const card1 = new CardBuilder()
595 .withCuratorId(curator1.value)
596 .withType(CardTypeEnum.URL)
597 .withUrl(url)
598 .build();
599
600 const card2 = new CardBuilder()
601 .withCuratorId(curator2.value)
602 .withType(CardTypeEnum.URL)
603 .withUrl(url)
604 .build();
605
606 const card3 = new CardBuilder()
607 .withCuratorId(curator3.value)
608 .withType(CardTypeEnum.URL)
609 .withUrl(url)
610 .build();
611
612 if (
613 card1 instanceof Error ||
614 card2 instanceof Error ||
615 card3 instanceof Error
616 ) {
617 throw new Error('Failed to create cards');
618 }
619
620 card1.addToLibrary(curator1);
621 card2.addToLibrary(curator2);
622 card3.addToLibrary(curator3);
623
624 await cardRepository.save(card1);
625 await cardRepository.save(card2);
626 await cardRepository.save(card3);
627
628 // Create collections with names that should be sorted
629 const collectionZ = new CollectionBuilder()
630 .withAuthorId(curator1.value)
631 .withName('Zebra Collection')
632 .build();
633
634 const collectionA = new CollectionBuilder()
635 .withAuthorId(curator2.value)
636 .withName('Apple Collection')
637 .build();
638
639 const collectionM = new CollectionBuilder()
640 .withAuthorId(curator3.value)
641 .withName('Mango Collection')
642 .build();
643
644 if (
645 collectionZ instanceof Error ||
646 collectionA instanceof Error ||
647 collectionM instanceof Error
648 ) {
649 throw new Error('Failed to create collections');
650 }
651
652 collectionZ.addCard(card1.cardId, curator1);
653 collectionA.addCard(card2.cardId, curator2);
654 collectionM.addCard(card3.cardId, curator3);
655
656 await collectionRepository.save(collectionZ);
657 await collectionRepository.save(collectionA);
658 await collectionRepository.save(collectionM);
659
660 const query = {
661 url: testUrl,
662 };
663
664 const result = await useCase.execute(query);
665 expect(result.isOk()).toBe(true);
666 const response = result.unwrap();
667
668 expect(response.collections).toHaveLength(3);
669 expect(response.collections[0]!.name).toBe('Apple Collection');
670 expect(response.collections[1]!.name).toBe('Mango Collection');
671 expect(response.collections[2]!.name).toBe('Zebra Collection');
672 });
673
674 it('should sort collections by name in descending order', async () => {
675 const testUrl = 'https://example.com/article';
676 const url = URL.create(testUrl).unwrap();
677
678 // Create URL cards
679 const card1 = new CardBuilder()
680 .withCuratorId(curator1.value)
681 .withType(CardTypeEnum.URL)
682 .withUrl(url)
683 .build();
684
685 const card2 = new CardBuilder()
686 .withCuratorId(curator2.value)
687 .withType(CardTypeEnum.URL)
688 .withUrl(url)
689 .build();
690
691 const card3 = new CardBuilder()
692 .withCuratorId(curator3.value)
693 .withType(CardTypeEnum.URL)
694 .withUrl(url)
695 .build();
696
697 if (
698 card1 instanceof Error ||
699 card2 instanceof Error ||
700 card3 instanceof Error
701 ) {
702 throw new Error('Failed to create cards');
703 }
704
705 card1.addToLibrary(curator1);
706 card2.addToLibrary(curator2);
707 card3.addToLibrary(curator3);
708
709 await cardRepository.save(card1);
710 await cardRepository.save(card2);
711 await cardRepository.save(card3);
712
713 // Create collections with names that should be sorted
714 const collectionZ = new CollectionBuilder()
715 .withAuthorId(curator1.value)
716 .withName('Zebra Collection')
717 .build();
718
719 const collectionA = new CollectionBuilder()
720 .withAuthorId(curator2.value)
721 .withName('Apple Collection')
722 .build();
723
724 const collectionM = new CollectionBuilder()
725 .withAuthorId(curator3.value)
726 .withName('Mango Collection')
727 .build();
728
729 if (
730 collectionZ instanceof Error ||
731 collectionA instanceof Error ||
732 collectionM instanceof Error
733 ) {
734 throw new Error('Failed to create collections');
735 }
736
737 collectionZ.addCard(card1.cardId, curator1);
738 collectionA.addCard(card2.cardId, curator2);
739 collectionM.addCard(card3.cardId, curator3);
740
741 await collectionRepository.save(collectionZ);
742 await collectionRepository.save(collectionA);
743 await collectionRepository.save(collectionM);
744
745 const query = {
746 url: testUrl,
747 sortBy: CollectionSortField.NAME,
748 sortOrder: SortOrder.DESC,
749 };
750
751 const result = await useCase.execute(query);
752 expect(result.isOk()).toBe(true);
753 const response = result.unwrap();
754
755 expect(response.collections).toHaveLength(3);
756 expect(response.collections[0]!.name).toBe('Zebra Collection');
757 expect(response.collections[1]!.name).toBe('Mango Collection');
758 expect(response.collections[2]!.name).toBe('Apple Collection');
759 });
760 });
761
762 describe('Validation', () => {
763 it('should fail with invalid URL', async () => {
764 const query = {
765 url: 'not-a-valid-url',
766 };
767
768 const result = await useCase.execute(query);
769
770 expect(result.isErr()).toBe(true);
771 if (result.isErr()) {
772 expect(result.error.message).toContain('Invalid URL');
773 }
774 });
775 });
776
777 describe('Error handling', () => {
778 it('should handle repository errors gracefully', async () => {
779 // Create a mock repository that throws an error
780 const errorCollectionQueryRepository = {
781 findByCreator: jest.fn(),
782 getCollectionsContainingCardForUser: jest.fn(),
783 getCollectionsWithUrl: jest
784 .fn()
785 .mockRejectedValue(new Error('Database error')),
786 searchCollections: jest.fn(),
787 getOpenCollectionsWithContributor: jest.fn(),
788 getCollectionContributors: jest.fn(),
789 };
790
791 const errorUseCase = new GetCollectionsForUrlUseCase(
792 errorCollectionQueryRepository,
793 profileService,
794 collectionRepository,
795 followsRepository,
796 );
797
798 const query = {
799 url: 'https://example.com/test-url',
800 };
801
802 const result = await errorUseCase.execute(query);
803
804 expect(result.isErr()).toBe(true);
805 if (result.isErr()) {
806 expect(result.error.message).toContain('Database error');
807 }
808 });
809 });
810});