From 5b1337fafc081aed167d87e3e7efdebf9a99331e Mon Sep 17 00:00:00 2001 From: Wesley Finck Date: Tue, 17 Mar 2026 11:28:45 -0700 Subject: [PATCH] update unit and integration tests --- .../commands/UpdateCollectionUseCase.ts | 18 ++--- src/modules/cards/domain/Collection.ts | 12 +++ .../DrizzleCollectionRepository.ts | 41 ++++++++-- ...lectionQueryRepository.integration.test.ts | 39 ++++----- ...leCollectionRepository.integration.test.ts | 81 ++++++++++++------- .../utils/InMemoryCollectionRepository.ts | 6 +- 6 files changed, 131 insertions(+), 66 deletions(-) diff --git a/src/modules/cards/application/useCases/commands/UpdateCollectionUseCase.ts b/src/modules/cards/application/useCases/commands/UpdateCollectionUseCase.ts index 7108a6ab..e8dd22eb 100644 --- a/src/modules/cards/application/useCases/commands/UpdateCollectionUseCase.ts +++ b/src/modules/cards/application/useCases/commands/UpdateCollectionUseCase.ts @@ -125,9 +125,9 @@ export class UpdateCollectionUseCase // Update collection metadata with new published record ID const updateMetadataResult = await this.collectionRepository.updateMetadata(collectionId, { - name: request.name, - description: request.description, - accessType: request.accessType, + name: collection.name.value, + description: collection.description?.value, + accessType: collection.accessType, publishedRecordId: request.publishedRecordId, }); if (updateMetadataResult.isErr()) { @@ -154,9 +154,9 @@ export class UpdateCollectionUseCase // Update collection metadata with new published record ID const updateMetadataResult = await this.collectionRepository.updateMetadata(collectionId, { - name: request.name, - description: request.description, - accessType: request.accessType, + name: collection.name.value, + description: collection.description?.value, + accessType: collection.accessType, publishedRecordId: republishResult.value, }); if (updateMetadataResult.isErr()) { @@ -168,9 +168,9 @@ export class UpdateCollectionUseCase // Not published - just update metadata const updateMetadataResult = await this.collectionRepository.updateMetadata(collectionId, { - name: request.name, - description: request.description, - accessType: request.accessType, + name: collection.name.value, + description: collection.description?.value, + accessType: collection.accessType, }); if (updateMetadataResult.isErr()) { return err( diff --git a/src/modules/cards/domain/Collection.ts b/src/modules/cards/domain/Collection.ts index 6639f6fb..62fa3618 100644 --- a/src/modules/cards/domain/Collection.ts +++ b/src/modules/cards/domain/Collection.ts @@ -406,6 +406,12 @@ export class Collection extends AggregateRoot { this.props.collaboratorIds.push(collaboratorId); this.props.updatedAt = new Date(); + // Track the command for optimized persistence + this.pendingCommands.push({ + type: CollectionCommandType.ADD_COLLABORATOR, + payload: { collaboratorId }, + }); + return ok(undefined); } @@ -424,6 +430,12 @@ export class Collection extends AggregateRoot { ); this.props.updatedAt = new Date(); + // Track the command for optimized persistence + this.pendingCommands.push({ + type: CollectionCommandType.REMOVE_COLLABORATOR, + payload: { collaboratorId }, + }); + return ok(undefined); } diff --git a/src/modules/cards/infrastructure/repositories/DrizzleCollectionRepository.ts b/src/modules/cards/infrastructure/repositories/DrizzleCollectionRepository.ts index 6c362f6a..3975a5af 100644 --- a/src/modules/cards/infrastructure/repositories/DrizzleCollectionRepository.ts +++ b/src/modules/cards/infrastructure/repositories/DrizzleCollectionRepository.ts @@ -789,10 +789,39 @@ export class DrizzleCollectionRepository implements ICollectionRepository { break; } - case CollectionCommandType.ADD_COLLABORATOR: - case CollectionCommandType.REMOVE_COLLABORATOR: - // Handle collaborator changes if needed + case CollectionCommandType.ADD_COLLABORATOR: { + const { collaboratorId } = command.payload; + const collaboratorLinkId = new UniqueEntityID().toString(); + + // Insert the new collaborator + await tx + .insert(collectionCollaborators) + .values({ + id: collaboratorLinkId, + collectionId: collectionId, + collaboratorId: collaboratorId.value, + }) + .onConflictDoNothing(); // Idempotent - ignore if already exists break; + } + + case CollectionCommandType.REMOVE_COLLABORATOR: { + const { collaboratorId } = command.payload; + + // Delete the collaborator + await tx + .delete(collectionCollaborators) + .where( + and( + eq(collectionCollaborators.collectionId, collectionId), + eq( + collectionCollaborators.collaboratorId, + collaboratorId.value, + ), + ), + ); + break; + } } } @@ -895,13 +924,13 @@ export class DrizzleCollectionRepository implements ICollectionRepository { updatedAt: new Date(), }; - if (updates.name !== undefined) { + if ('name' in updates) { updateData.name = updates.name; } - if (updates.description !== undefined) { + if ('description' in updates) { updateData.description = updates.description; } - if (updates.accessType !== undefined) { + if ('accessType' in updates) { updateData.accessType = updates.accessType; } diff --git a/src/modules/cards/tests/infrastructure/DrizzleCollectionQueryRepository.integration.test.ts b/src/modules/cards/tests/infrastructure/DrizzleCollectionQueryRepository.integration.test.ts index d7cdf5ac..0a694baa 100644 --- a/src/modules/cards/tests/infrastructure/DrizzleCollectionQueryRepository.integration.test.ts +++ b/src/modules/cards/tests/infrastructure/DrizzleCollectionQueryRepository.integration.test.ts @@ -125,8 +125,8 @@ describe('DrizzleCollectionQueryRepository', () => { ).unwrap(); // Save collections - await collectionRepository.save(collection1); - await collectionRepository.save(collection2); + await collectionRepository.create(collection1); + await collectionRepository.create(collection2); // Query collections const result = await queryRepository.findByCreator(curatorId.value, { @@ -204,7 +204,8 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - // Add card to collection + // Create collection first, then add card + await collectionRepository.create(collection); collection.addCard(card.cardId, curatorId); await collectionRepository.save(collection); @@ -221,7 +222,7 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - await collectionRepository.save(emptyCollection); + await collectionRepository.create(emptyCollection); // Query collections const result = await queryRepository.findByCreator(curatorId.value, { @@ -289,9 +290,9 @@ describe('DrizzleCollectionQueryRepository', () => { ).unwrap(); // Save collections - await collectionRepository.save(collection1); - await collectionRepository.save(collection2); - await collectionRepository.save(collection3); + await collectionRepository.create(collection1); + await collectionRepository.create(collection2); + await collectionRepository.create(collection3); // Create cards and add different numbers to collections for card count sorting const card1 = CardFactory.create({ @@ -317,6 +318,7 @@ describe('DrizzleCollectionQueryRepository', () => { collection3.addCard(card3.cardId, curatorId); collection2.addCard(card1.cardId, curatorId); + // Save to process the ADD_CARD commands await collectionRepository.save(collection2); await collectionRepository.save(collection3); }); @@ -463,7 +465,7 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); } }); @@ -591,7 +593,7 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); // Add cards to match expected card count for (let i = 0; i < collectionData.cardCount; i++) { @@ -607,6 +609,7 @@ describe('DrizzleCollectionQueryRepository', () => { collection.addCard(card.cardId, curatorId); } + // Save to process the ADD_CARD commands if (collectionData.cardCount > 0) { await collectionRepository.save(collection); } @@ -720,7 +723,7 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); } }); @@ -887,7 +890,7 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); const result = await queryRepository.findByCreator(curatorId.value, { page: 1, @@ -957,7 +960,7 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); const result = await queryRepository.findByCreator(curatorId.value, { page: 1, @@ -992,7 +995,7 @@ describe('DrizzleCollectionQueryRepository', () => { collection.markAsPublished(publishedRecordId); // Save the collection - await collectionRepository.save(collection); + await collectionRepository.create(collection); const result = await queryRepository.findByCreator(curatorId.value, { page: 1, @@ -1034,9 +1037,9 @@ describe('DrizzleCollectionQueryRepository', () => { // Mark the collection as published in the domain model publishedCollection.markAsPublished(publishedRecordId); - // Save both collections - await collectionRepository.save(publishedCollection); - await collectionRepository.save(unpublishedCollection); + // Create both collections + await collectionRepository.create(publishedCollection); + await collectionRepository.create(unpublishedCollection); const result = await queryRepository.findByCreator(curatorId.value, { page: 1, @@ -1091,7 +1094,7 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); const result = await queryRepository.findByCreator(curatorId.value, { page: 1, @@ -1117,7 +1120,7 @@ describe('DrizzleCollectionQueryRepository', () => { new UniqueEntityID(), ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); const result = await queryRepository.findByCreator(curatorId.value, { page: 999999, diff --git a/src/modules/cards/tests/infrastructure/DrizzleCollectionRepository.integration.test.ts b/src/modules/cards/tests/infrastructure/DrizzleCollectionRepository.integration.test.ts index fabc39eb..99184304 100644 --- a/src/modules/cards/tests/infrastructure/DrizzleCollectionRepository.integration.test.ts +++ b/src/modules/cards/tests/infrastructure/DrizzleCollectionRepository.integration.test.ts @@ -89,9 +89,9 @@ describe('DrizzleCollectionRepository', () => { collectionId, ).unwrap(); - // Save the collection - const saveResult = await collectionRepository.save(collection); - expect(saveResult.isOk()).toBe(true); + // Create the collection + const createResult = await collectionRepository.create(collection); + expect(createResult.isOk()).toBe(true); // Retrieve the collection const retrievedResult = await collectionRepository.findById( @@ -126,6 +126,10 @@ describe('DrizzleCollectionRepository', () => { collectionId, ).unwrap(); + // Create the empty collection first + const createResult = await collectionRepository.create(collection); + expect(createResult.isOk()).toBe(true); + // Add a collaborator const addCollaboratorResult = collection.addCollaborator( collaboratorId, @@ -133,7 +137,7 @@ describe('DrizzleCollectionRepository', () => { ); expect(addCollaboratorResult.isOk()).toBe(true); - // Save the collection + // Save to process the ADD_COLLABORATOR command const saveResult = await collectionRepository.save(collection); expect(saveResult.isOk()).toBe(true); @@ -179,11 +183,15 @@ describe('DrizzleCollectionRepository', () => { collectionId, ).unwrap(); + // Create the empty collection first + const createResult = await collectionRepository.create(collection); + expect(createResult.isOk()).toBe(true); + // Add the card to the collection const addCardResult = collection.addCard(card.cardId, curatorId); expect(addCardResult.isOk()).toBe(true); - // Save the collection + // Save to process the ADD_CARD command const saveResult = await collectionRepository.save(collection); expect(saveResult.isOk()).toBe(true); @@ -220,23 +228,16 @@ describe('DrizzleCollectionRepository', () => { collectionId, ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); - // Update the collection - const updatedCollection = Collection.create( + // Update the collection metadata + await collectionRepository.updateMetadata( + CollectionId.create(collectionId).unwrap(), { - authorId: curatorId, name: 'Updated Name', description: 'Updated description', - accessType: CollectionAccessType.CLOSED, - collaboratorIds: [], - createdAt: new Date(), - updatedAt: new Date(), }, - collectionId, - ).unwrap(); - - await collectionRepository.save(updatedCollection); + ); // Retrieve the updated collection const retrievedResult = await collectionRepository.findById( @@ -265,7 +266,7 @@ describe('DrizzleCollectionRepository', () => { collectionId, ).unwrap(); - await collectionRepository.save(collection); + await collectionRepository.create(collection); // Delete the collection const deleteResult = await collectionRepository.delete( @@ -309,8 +310,8 @@ describe('DrizzleCollectionRepository', () => { collection2Id, ).unwrap(); - await collectionRepository.save(collection1); - await collectionRepository.save(collection2); + await collectionRepository.create(collection1); + await collectionRepository.create(collection2); // Find collections by curator ID const foundCollectionsResult = @@ -365,10 +366,15 @@ describe('DrizzleCollectionRepository', () => { collection2Id, ).unwrap(); + // Create the empty collections first + await collectionRepository.create(collection1); + await collectionRepository.create(collection2); + // Add card to both collections collection1.addCard(card.cardId, curatorId); collection2.addCard(card.cardId, curatorId); + // Save to process ADD_CARD commands await collectionRepository.save(collection1); await collectionRepository.save(collection2); @@ -410,9 +416,9 @@ describe('DrizzleCollectionRepository', () => { collection.markAsPublished(publishedRecordId); - // Save the collection - const saveResult = await collectionRepository.save(collection); - expect(saveResult.isOk()).toBe(true); + // Create the collection + const createResult = await collectionRepository.create(collection); + expect(createResult.isOk()).toBe(true); // Retrieve the collection const retrievedResult = await collectionRepository.findById( @@ -457,6 +463,10 @@ describe('DrizzleCollectionRepository', () => { collectionId, ).unwrap(); + // Create the empty collection first + const createResult = await collectionRepository.create(collection); + expect(createResult.isOk()).toBe(true); + // Add card to collection collection.addCard(card.cardId, curatorId); @@ -468,7 +478,7 @@ describe('DrizzleCollectionRepository', () => { collection.markCardLinkAsPublished(card.cardId, linkPublishedRecord); - // Save the collection + // Save to process the ADD_CARD and UPDATE_CARD_LINK commands const saveResult = await collectionRepository.save(collection); expect(saveResult.isOk()).toBe(true); @@ -540,11 +550,17 @@ describe('DrizzleCollectionRepository', () => { collection3Id, ).unwrap(); + // Create the empty collections first + await collectionRepository.create(collection1); + await collectionRepository.create(collection2); + await collectionRepository.create(collection3); + // Add card to all collections collection1.addCard(card.cardId, curatorId); collection2.addCard(card.cardId, curatorId); collection3.addCard(card.cardId, collaboratorId); + // Save to process ADD_CARD commands await collectionRepository.save(collection1); await collectionRepository.save(collection2); await collectionRepository.save(collection3); @@ -598,9 +614,6 @@ describe('DrizzleCollectionRepository', () => { collection1Id, ).unwrap(); - // curatorId adds card to collaborator's collection - collection1.addCard(card.cardId, curatorId); - // Collection 2: Owned by collaboratorId, card added by collaboratorId const collection2Id = new UniqueEntityID(); const collection2 = Collection.create( @@ -615,9 +628,6 @@ describe('DrizzleCollectionRepository', () => { collection2Id, ).unwrap(); - // collaboratorId adds card to their own collection - collection2.addCard(card.cardId, collaboratorId); - // Collection 3: Owned by curatorId, card added by curatorId const collection3Id = new UniqueEntityID(); const collection3 = Collection.create( @@ -632,9 +642,20 @@ describe('DrizzleCollectionRepository', () => { collection3Id, ).unwrap(); + // Create all collections first + await collectionRepository.create(collection1); + await collectionRepository.create(collection2); + await collectionRepository.create(collection3); + + // Then add cards (creates pending commands) + // curatorId adds card to collaborator's collection + collection1.addCard(card.cardId, curatorId); + // collaboratorId adds card to their own collection + collection2.addCard(card.cardId, collaboratorId); // curatorId adds card to their own collection collection3.addCard(card.cardId, curatorId); + // Process the commands await collectionRepository.save(collection1); await collectionRepository.save(collection2); await collectionRepository.save(collection3); diff --git a/src/modules/cards/tests/utils/InMemoryCollectionRepository.ts b/src/modules/cards/tests/utils/InMemoryCollectionRepository.ts index 9334243b..3b421d16 100644 --- a/src/modules/cards/tests/utils/InMemoryCollectionRepository.ts +++ b/src/modules/cards/tests/utils/InMemoryCollectionRepository.ts @@ -186,10 +186,10 @@ export class InMemoryCollectionRepository implements ICollectionRepository { // For in-memory implementation, we need to update the collection directly // This is a simplified implementation that updates the internal state - if (updates.name !== undefined || updates.description !== undefined) { + if ('name' in updates || 'description' in updates) { const updateResult = collection.updateDetails( - updates.name || collection.name.value, - updates.description !== undefined + 'name' in updates ? updates.name! : collection.name.value, + 'description' in updates ? updates.description : collection.description?.value, ); -- 2.51.2