Something went wrong. Try again.
A social knowledge tool for research trails, built on ATProto semble.so
research-tool social-network tools-for-thought atproto
Something went wrong. Try again.
TypeScript
at development
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305import { CreateCollectionUseCase } from '../../application/useCases/commands/CreateCollectionUseCase';import { InMemoryCollectionRepository } from '../utils/InMemoryCollectionRepository';import { FakeCollectionPublisher } from '../utils/FakeCollectionPublisher';import { CuratorId } from '../../domain/value-objects/CuratorId';import { CollectionAccessType } from '../../domain/Collection';import { FakeEventPublisher } from '../utils/FakeEventPublisher';import { CollectionCreatedEvent } from '../../domain/events/CollectionCreatedEvent';import { EventNames } from 'src/shared/infrastructure/events/EventConfig';
describe('CreateCollectionUseCase', () => { let useCase: CreateCollectionUseCase; let collectionRepository: InMemoryCollectionRepository; let collectionPublisher: FakeCollectionPublisher; let eventPublisher: FakeEventPublisher; let curatorId: CuratorId;
beforeEach(() => { collectionRepository = new InMemoryCollectionRepository(); collectionPublisher = new FakeCollectionPublisher(); eventPublisher = new FakeEventPublisher(); useCase = new CreateCollectionUseCase( collectionRepository, collectionPublisher, ); curatorId = CuratorId.create('did:plc:testcurator').unwrap(); });
afterEach(() => { collectionRepository.clear(); collectionPublisher.clear(); eventPublisher.clear(); });
describe('Basic collection creation', () => { it('should successfully create a collection', async () => { const request = { name: 'My Test Collection', description: 'A collection for testing purposes', curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isOk()).toBe(true); const response = result.unwrap(); expect(response.collectionId).toBeDefined();
// Verify collection was saved const savedCollections = collectionRepository.getAllCollections(); expect(savedCollections).toHaveLength(1);
const savedCollection = savedCollections[0]!; expect(savedCollection.name.value).toBe('My Test Collection'); expect(savedCollection.description?.value).toBe( 'A collection for testing purposes', ); expect(savedCollection.authorId.equals(curatorId)).toBe(true); expect(savedCollection.accessType).toBe(CollectionAccessType.CLOSED); expect(savedCollection.isPublished).toBe(true); expect(savedCollection.publishedRecordId).toBeDefined(); });
it('should create collection without description', async () => { const request = { name: 'Collection Without Description', curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isOk()).toBe(true); const response = result.unwrap(); expect(response.collectionId).toBeDefined();
// Verify collection was saved const savedCollections = collectionRepository.getAllCollections(); expect(savedCollections).toHaveLength(1);
const savedCollection = savedCollections[0]!; expect(savedCollection.name.value).toBe('Collection Without Description'); expect(savedCollection.description).toBeUndefined(); });
it('should publish collection after creation', async () => { const request = { name: 'Published Collection', curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isOk()).toBe(true);
// Verify collection was published const publishedCollections = collectionPublisher.getPublishedCollections(); expect(publishedCollections).toHaveLength(1);
const publishedCollection = publishedCollections[0]!; expect(publishedCollection.name.value).toBe('Published Collection'); }); });
describe('Validation', () => { it('should fail with invalid curator ID', async () => { const request = { name: 'Test Collection', curatorId: 'invalid-curator-id', };
const result = await useCase.execute(request);
expect(result.isErr()).toBe(true); if (result.isErr()) { expect(result.error.message).toContain('Invalid curator ID'); } });
it('should fail with empty collection name', async () => { const request = { name: '', curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isErr()).toBe(true); if (result.isErr()) { expect(result.error.message).toContain( 'Collection name cannot be empty', ); } });
it('should fail with collection name that is too long', async () => { const request = { name: 'a'.repeat(101), // Exceeds MAX_LENGTH curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isErr()).toBe(true); if (result.isErr()) { expect(result.error.message).toContain('Collection name cannot exceed'); } });
it('should fail with description that is too long', async () => { const request = { name: 'Valid Collection Name', description: 'a'.repeat(501), // Exceeds MAX_LENGTH curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isErr()).toBe(true); if (result.isErr()) { expect(result.error.message).toContain( 'Collection description cannot exceed', ); } });
it('should trim whitespace from collection name', async () => { const request = { name: ' Collection With Whitespace ', curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isOk()).toBe(true);
// Verify name was trimmed const savedCollections = collectionRepository.getAllCollections(); const savedCollection = savedCollections[0]!; expect(savedCollection.name.value).toBe('Collection With Whitespace'); });
it('should trim whitespace from description', async () => { const request = { name: 'Test Collection', description: ' Description with whitespace ', curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isOk()).toBe(true);
// Verify description was trimmed const savedCollections = collectionRepository.getAllCollections(); const savedCollection = savedCollections[0]!; expect(savedCollection.description?.value).toBe( 'Description with whitespace', ); }); });
describe('Publishing integration', () => { it('should handle publishing failure gracefully', async () => { // Configure publisher to fail collectionPublisher.setShouldFail(true);
const request = { name: 'Collection That Fails to Publish', curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isErr()).toBe(true); if (result.isErr()) { expect(result.error.message).toContain('Failed to publish collection'); }
// Verify collection was not saved if publishing failed const savedCollections = collectionRepository.getAllCollections(); expect(savedCollections).toHaveLength(1); // Collection is saved before publishing });
it('should save collection with published record ID after successful publish', async () => { const request = { name: 'Successfully Published Collection', curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isOk()).toBe(true);
// Verify collection has published record ID const savedCollections = collectionRepository.getAllCollections(); const savedCollection = savedCollections[0]!; expect(savedCollection.isPublished).toBe(true); expect(savedCollection.publishedRecordId).toBeDefined(); expect(savedCollection.publishedRecordId?.uri).toBeDefined(); expect(savedCollection.publishedRecordId?.cid).toBeDefined(); }); });
describe('Edge cases', () => { it('should handle maximum length collection name', async () => { const request = { name: 'a'.repeat(100), // Exactly MAX_LENGTH curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isOk()).toBe(true);
// Verify name was saved correctly const savedCollections = collectionRepository.getAllCollections(); const savedCollection = savedCollections[0]!; expect(savedCollection.name.value.length).toBe(100); });
it('should handle maximum length description', async () => { const request = { name: 'Test Collection', description: 'a'.repeat(500), // Exactly MAX_LENGTH curatorId: curatorId.value, };
const result = await useCase.execute(request);
expect(result.isOk()).toBe(true);
// Verify description was saved correctly const savedCollections = collectionRepository.getAllCollections(); const savedCollection = savedCollections[0]!; expect(savedCollection.description?.value.length).toBe(500); });
it('should create multiple collections for same curator', async () => { const firstRequest = { name: 'First Collection', curatorId: curatorId.value, };
const secondRequest = { name: 'Second Collection', curatorId: curatorId.value, };
const firstResult = await useCase.execute(firstRequest); const secondResult = await useCase.execute(secondRequest);
expect(firstResult.isOk()).toBe(true); expect(secondResult.isOk()).toBe(true);
// Verify both collections were saved const savedCollections = collectionRepository.getAllCollections(); expect(savedCollections).toHaveLength(2);
const collectionNames = savedCollections.map((c) => c.name.value); expect(collectionNames).toContain('First Collection'); expect(collectionNames).toContain('Second Collection'); }); });});