This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / modules / cards / tests / application / UpdateUrlCardAssociationsUseCase.test.ts
14 kB 442 lines
1import { UpdateUrlCardAssociationsUseCase } from '../../application/useCases/commands/UpdateUrlCardAssociationsUseCase'; 2import { AddUrlToLibraryUseCase } from '../../application/useCases/commands/AddUrlToLibraryUseCase'; 3import { InMemoryCardRepository } from '../utils/InMemoryCardRepository'; 4import { InMemoryCollectionRepository } from '../utils/InMemoryCollectionRepository'; 5import { FakeCardPublisher } from '../utils/FakeCardPublisher'; 6import { FakeCollectionPublisher } from '../utils/FakeCollectionPublisher'; 7import { FakeMetadataService } from '../utils/FakeMetadataService'; 8import { CardLibraryService } from '../../domain/services/CardLibraryService'; 9import { CardCollectionService } from '../../domain/services/CardCollectionService'; 10import { CuratorId } from '../../domain/value-objects/CuratorId'; 11import { CollectionBuilder } from '../utils/builders/CollectionBuilder'; 12import { CardTypeEnum } from '../../domain/value-objects/CardType'; 13import { FakeEventPublisher } from '../utils/FakeEventPublisher'; 14 15describe('UpdateUrlCardAssociationsUseCase', () => { 16 let useCase: UpdateUrlCardAssociationsUseCase; 17 let addUrlToLibraryUseCase: AddUrlToLibraryUseCase; 18 let cardRepository: InMemoryCardRepository; 19 let collectionRepository: InMemoryCollectionRepository; 20 let cardPublisher: FakeCardPublisher; 21 let collectionPublisher: FakeCollectionPublisher; 22 let metadataService: FakeMetadataService; 23 let cardLibraryService: CardLibraryService; 24 let cardCollectionService: CardCollectionService; 25 let eventPublisher: FakeEventPublisher; 26 let curatorId: CuratorId; 27 28 beforeEach(() => { 29 cardRepository = new InMemoryCardRepository(); 30 collectionRepository = new InMemoryCollectionRepository(); 31 cardPublisher = new FakeCardPublisher(); 32 collectionPublisher = new FakeCollectionPublisher(); 33 metadataService = new FakeMetadataService(); 34 eventPublisher = new FakeEventPublisher(); 35 36 cardLibraryService = new CardLibraryService( 37 cardRepository, 38 cardPublisher, 39 collectionRepository, 40 cardCollectionService, 41 ); 42 cardCollectionService = new CardCollectionService( 43 collectionRepository, 44 collectionPublisher, 45 ); 46 47 useCase = new UpdateUrlCardAssociationsUseCase( 48 cardRepository, 49 cardLibraryService, 50 cardCollectionService, 51 eventPublisher, 52 ); 53 54 addUrlToLibraryUseCase = new AddUrlToLibraryUseCase( 55 cardRepository, 56 metadataService, 57 cardLibraryService, 58 cardCollectionService, 59 eventPublisher, 60 ); 61 62 curatorId = CuratorId.create('did:plc:testcurator').unwrap(); 63 }); 64 65 afterEach(() => { 66 cardRepository.clear(); 67 collectionRepository.clear(); 68 cardPublisher.clear(); 69 collectionPublisher.clear(); 70 metadataService.clear(); 71 eventPublisher.clear(); 72 }); 73 74 describe('Note management', () => { 75 it('should create a note for an existing URL card', async () => { 76 const url = 'https://example.com/article'; 77 78 // First, add the URL to the library 79 const addResult = await addUrlToLibraryUseCase.execute({ 80 url, 81 curatorId: curatorId.value, 82 }); 83 expect(addResult.isOk()).toBe(true); 84 const urlCardId = addResult.unwrap().urlCardId; 85 86 // Now create a note for it 87 const updateRequest = { 88 cardId: urlCardId, 89 curatorId: curatorId.value, 90 note: 'This is my note', 91 }; 92 93 const result = await useCase.execute(updateRequest); 94 95 expect(result.isOk()).toBe(true); 96 const response = result.unwrap(); 97 expect(response.noteCardId).toBeDefined(); 98 99 // Verify note card was created 100 const savedCards = cardRepository.getAllCards(); 101 const noteCard = savedCards.find( 102 (card) => card.content.type === CardTypeEnum.NOTE, 103 ); 104 expect(noteCard).toBeDefined(); 105 expect(noteCard?.content.noteContent?.text).toBe('This is my note'); 106 }); 107 108 it('should update an existing note for a URL card', async () => { 109 const url = 'https://example.com/article'; 110 111 // First, add the URL with a note 112 const addResult = await addUrlToLibraryUseCase.execute({ 113 url, 114 note: 'Original note', 115 curatorId: curatorId.value, 116 }); 117 expect(addResult.isOk()).toBe(true); 118 const addResponse = addResult.unwrap(); 119 120 // Now update the note 121 const updateRequest = { 122 cardId: addResponse.urlCardId, 123 curatorId: curatorId.value, 124 note: 'Updated note', 125 }; 126 127 const result = await useCase.execute(updateRequest); 128 129 expect(result.isOk()).toBe(true); 130 const response = result.unwrap(); 131 expect(response.noteCardId).toBe(addResponse.noteCardId); 132 133 // Verify note was updated 134 const savedCards = cardRepository.getAllCards(); 135 const noteCard = savedCards.find( 136 (card) => card.content.type === CardTypeEnum.NOTE, 137 ); 138 expect(noteCard).toBeDefined(); 139 expect(noteCard?.content.noteContent?.text).toBe('Updated note'); 140 }); 141 142 it('should fail if URL card does not exist', async () => { 143 const request = { 144 cardId: 'nonexistent-card-id', 145 curatorId: curatorId.value, 146 note: 'This should fail', 147 }; 148 149 const result = await useCase.execute(request); 150 151 expect(result.isErr()).toBe(true); 152 if (result.isErr()) { 153 expect(result.error.message).toContain( 154 'URL card not found. Please add the URL to your library first.', 155 ); 156 } 157 }); 158 }); 159 160 describe('Collection management', () => { 161 it('should add URL card to collections', async () => { 162 const url = 'https://example.com/article'; 163 164 // Create collections 165 const collection1 = new CollectionBuilder() 166 .withAuthorId(curatorId.value) 167 .withName('Collection 1') 168 .build(); 169 const collection2 = new CollectionBuilder() 170 .withAuthorId(curatorId.value) 171 .withName('Collection 2') 172 .build(); 173 174 if (collection1 instanceof Error || collection2 instanceof Error) { 175 throw new Error('Failed to create collections'); 176 } 177 178 await collectionRepository.save(collection1); 179 await collectionRepository.save(collection2); 180 181 // Add URL to library 182 const addResult = await addUrlToLibraryUseCase.execute({ 183 url, 184 curatorId: curatorId.value, 185 }); 186 expect(addResult.isOk()).toBe(true); 187 const urlCardId = addResult.unwrap().urlCardId; 188 189 // Add to collections 190 const updateRequest = { 191 cardId: urlCardId, 192 curatorId: curatorId.value, 193 addToCollections: [ 194 collection1.collectionId.getStringValue(), 195 collection2.collectionId.getStringValue(), 196 ], 197 }; 198 199 const result = await useCase.execute(updateRequest); 200 201 expect(result.isOk()).toBe(true); 202 const response = result.unwrap(); 203 expect(response.addedToCollections).toHaveLength(2); 204 expect(response.addedToCollections).toContain( 205 collection1.collectionId.getStringValue(), 206 ); 207 expect(response.addedToCollections).toContain( 208 collection2.collectionId.getStringValue(), 209 ); 210 }); 211 212 it('should remove URL card from collections', async () => { 213 const url = 'https://example.com/article'; 214 215 // Create collection 216 const collection = new CollectionBuilder() 217 .withAuthorId(curatorId.value) 218 .withName('Test Collection') 219 .build(); 220 221 if (collection instanceof Error) { 222 throw new Error('Failed to create collection'); 223 } 224 225 await collectionRepository.save(collection); 226 227 // Add URL to library and collection 228 const addResult = await addUrlToLibraryUseCase.execute({ 229 url, 230 collectionIds: [collection.collectionId.getStringValue()], 231 curatorId: curatorId.value, 232 }); 233 expect(addResult.isOk()).toBe(true); 234 const urlCardId = addResult.unwrap().urlCardId; 235 236 // Remove from collection 237 const updateRequest = { 238 cardId: urlCardId, 239 curatorId: curatorId.value, 240 removeFromCollections: [collection.collectionId.getStringValue()], 241 }; 242 243 const result = await useCase.execute(updateRequest); 244 245 expect(result.isOk()).toBe(true); 246 const response = result.unwrap(); 247 expect(response.removedFromCollections).toHaveLength(1); 248 expect(response.removedFromCollections).toContain( 249 collection.collectionId.getStringValue(), 250 ); 251 }); 252 253 it('should add and remove from different collections in same request', async () => { 254 const url = 'https://example.com/article'; 255 256 // Create collections 257 const collection1 = new CollectionBuilder() 258 .withAuthorId(curatorId.value) 259 .withName('Collection 1') 260 .build(); 261 const collection2 = new CollectionBuilder() 262 .withAuthorId(curatorId.value) 263 .withName('Collection 2') 264 .build(); 265 const collection3 = new CollectionBuilder() 266 .withAuthorId(curatorId.value) 267 .withName('Collection 3') 268 .build(); 269 270 if ( 271 collection1 instanceof Error || 272 collection2 instanceof Error || 273 collection3 instanceof Error 274 ) { 275 throw new Error('Failed to create collections'); 276 } 277 278 await collectionRepository.save(collection1); 279 await collectionRepository.save(collection2); 280 await collectionRepository.save(collection3); 281 282 // Add URL to library with collection1 283 const addResult = await addUrlToLibraryUseCase.execute({ 284 url, 285 collectionIds: [collection1.collectionId.getStringValue()], 286 curatorId: curatorId.value, 287 }); 288 expect(addResult.isOk()).toBe(true); 289 const urlCardId = addResult.unwrap().urlCardId; 290 291 // Add to collection2 and collection3, remove from collection1 292 const updateRequest = { 293 cardId: urlCardId, 294 curatorId: curatorId.value, 295 addToCollections: [ 296 collection2.collectionId.getStringValue(), 297 collection3.collectionId.getStringValue(), 298 ], 299 removeFromCollections: [collection1.collectionId.getStringValue()], 300 }; 301 302 const result = await useCase.execute(updateRequest); 303 304 expect(result.isOk()).toBe(true); 305 const response = result.unwrap(); 306 expect(response.addedToCollections).toHaveLength(2); 307 expect(response.removedFromCollections).toHaveLength(1); 308 expect(response.addedToCollections).toContain( 309 collection2.collectionId.getStringValue(), 310 ); 311 expect(response.addedToCollections).toContain( 312 collection3.collectionId.getStringValue(), 313 ); 314 expect(response.removedFromCollections).toContain( 315 collection1.collectionId.getStringValue(), 316 ); 317 }); 318 }); 319 320 describe('Combined operations', () => { 321 it('should update note and manage collections in same request', async () => { 322 const url = 'https://example.com/article'; 323 324 // Create collection 325 const collection = new CollectionBuilder() 326 .withAuthorId(curatorId.value) 327 .withName('Test Collection') 328 .build(); 329 330 if (collection instanceof Error) { 331 throw new Error('Failed to create collection'); 332 } 333 334 await collectionRepository.save(collection); 335 336 // Add URL to library 337 const addResult = await addUrlToLibraryUseCase.execute({ 338 url, 339 curatorId: curatorId.value, 340 }); 341 expect(addResult.isOk()).toBe(true); 342 const urlCardId = addResult.unwrap().urlCardId; 343 344 // Update note and add to collection 345 const updateRequest = { 346 cardId: urlCardId, 347 curatorId: curatorId.value, 348 note: 'My note about this article', 349 addToCollections: [collection.collectionId.getStringValue()], 350 }; 351 352 const result = await useCase.execute(updateRequest); 353 354 expect(result.isOk()).toBe(true); 355 const response = result.unwrap(); 356 expect(response.noteCardId).toBeDefined(); 357 expect(response.addedToCollections).toHaveLength(1); 358 expect(response.addedToCollections).toContain( 359 collection.collectionId.getStringValue(), 360 ); 361 362 // Verify note was created 363 const savedCards = cardRepository.getAllCards(); 364 const noteCard = savedCards.find( 365 (card) => card.content.type === CardTypeEnum.NOTE, 366 ); 367 expect(noteCard).toBeDefined(); 368 expect(noteCard?.content.noteContent?.text).toBe( 369 'My note about this article', 370 ); 371 }); 372 }); 373 374 describe('Validation', () => { 375 it('should fail when card does not exist', async () => { 376 const request = { 377 cardId: 'nonexistent-card-id', 378 curatorId: curatorId.value, 379 note: 'This should fail', 380 }; 381 382 const result = await useCase.execute(request); 383 384 expect(result.isErr()).toBe(true); 385 if (result.isErr()) { 386 expect(result.error.message).toContain( 387 'URL card not found. Please add the URL to your library first.', 388 ); 389 } 390 }); 391 392 it('should fail with invalid curator ID', async () => { 393 const url = 'https://example.com/article'; 394 395 // Add URL to library first 396 const addResult = await addUrlToLibraryUseCase.execute({ 397 url, 398 curatorId: curatorId.value, 399 }); 400 expect(addResult.isOk()).toBe(true); 401 const urlCardId = addResult.unwrap().urlCardId; 402 403 const request = { 404 cardId: urlCardId, 405 curatorId: 'invalid-curator-id', 406 note: 'This should fail', 407 }; 408 409 const result = await useCase.execute(request); 410 411 expect(result.isErr()).toBe(true); 412 if (result.isErr()) { 413 expect(result.error.message).toContain('Invalid curator ID'); 414 } 415 }); 416 417 it('should fail with invalid collection ID', async () => { 418 const url = 'https://example.com/article'; 419 420 // Add URL to library 421 const addResult = await addUrlToLibraryUseCase.execute({ 422 url, 423 curatorId: curatorId.value, 424 }); 425 expect(addResult.isOk()).toBe(true); 426 const urlCardId = addResult.unwrap().urlCardId; 427 428 const request = { 429 cardId: urlCardId, 430 curatorId: curatorId.value, 431 addToCollections: ['invalid-collection-id'], 432 }; 433 434 const result = await useCase.execute(request); 435 436 expect(result.isErr()).toBe(true); 437 if (result.isErr()) { 438 expect(result.error.message).toContain('Collection not found'); 439 } 440 }); 441 }); 442});