diff --git a/src/modules/atproto/tests/domain/DID.test.ts b/src/modules/atproto/tests/domain/DID.test.ts new file mode 100644 index 00000000..339ac227 --- /dev/null +++ b/src/modules/atproto/tests/domain/DID.test.ts @@ -0,0 +1,154 @@ +import { DID, InvalidDIDError } from '../../domain/DID'; + +describe('DID', () => { + describe('create', () => { + it('should create a valid DID with did:plc format', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const result = DID.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(didString); + } + }); + + it('should create a valid DID with did:web format', () => { + const didString = 'did:web:example.com'; + const result = DID.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(didString); + } + }); + + it('should create a valid DID with did:web format with path', () => { + const didString = 'did:web:example.com%2Fpath'; + const result = DID.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(didString); + } + }); + + it('should trim whitespace from input', () => { + const didString = ' did:plc:lehcqqkwzcwvjvw66uthu5oq '; + const result = DID.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe('did:plc:lehcqqkwzcwvjvw66uthu5oq'); + } + }); + + it('should reject empty string', () => { + const result = DID.create(''); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDError); + expect(result.error.message).toBe('DID cannot be empty'); + } + }); + + it('should reject whitespace-only string', () => { + const result = DID.create(' '); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDError); + expect(result.error.message).toBe('DID cannot be empty'); + } + }); + + it('should reject invalid DID format without did: prefix', () => { + const result = DID.create('plc:lehcqqkwzcwvjvw66uthu5oq'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDError); + expect(result.error.message).toContain('Invalid DID format'); + } + }); + + it('should reject unsupported DID method', () => { + const result = DID.create('did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDError); + expect(result.error.message).toContain('Invalid DID format'); + } + }); + + it('should reject malformed did:plc', () => { + const result = DID.create('did:plc:'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDError); + expect(result.error.message).toContain('Invalid DID format'); + } + }); + + it('should reject malformed did:web', () => { + const result = DID.create('did:web:'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDError); + expect(result.error.message).toContain('Invalid DID format'); + } + }); + }); + + describe('value getter', () => { + it('should return the DID string value', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const result = DID.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(didString); + } + }); + }); + + describe('toString', () => { + it('should return the DID string value', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const result = DID.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.toString()).toBe(didString); + } + }); + }); + + describe('equals', () => { + it('should return true for identical DIDs', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const did1Result = DID.create(didString); + const did2Result = DID.create(didString); + + expect(did1Result.isOk()).toBe(true); + expect(did2Result.isOk()).toBe(true); + if (did1Result.isOk() && did2Result.isOk()) { + expect(did1Result.value.equals(did2Result.value)).toBe(true); + } + }); + + it('should return false for different DIDs', () => { + const did1Result = DID.create('did:plc:lehcqqkwzcwvjvw66uthu5oq'); + const did2Result = DID.create('did:plc:anotherdidvalue123456789'); + + expect(did1Result.isOk()).toBe(true); + expect(did2Result.isOk()).toBe(true); + if (did1Result.isOk() && did2Result.isOk()) { + expect(did1Result.value.equals(did2Result.value)).toBe(false); + } + }); + }); +}); diff --git a/src/modules/atproto/tests/domain/DIDOrHandle.test.ts b/src/modules/atproto/tests/domain/DIDOrHandle.test.ts new file mode 100644 index 00000000..c77d2403 --- /dev/null +++ b/src/modules/atproto/tests/domain/DIDOrHandle.test.ts @@ -0,0 +1,242 @@ +import { DIDOrHandle, InvalidDIDOrHandleError } from '../../domain/DIDOrHandle'; + +describe('DIDOrHandle', () => { + describe('create with DID', () => { + it('should create a DIDOrHandle with valid DID', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const result = DIDOrHandle.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(didString); + expect(result.value.isDID).toBe(true); + expect(result.value.isHandle).toBe(false); + } + }); + + it('should create a DIDOrHandle with did:web format', () => { + const didString = 'did:web:example.com'; + const result = DIDOrHandle.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(didString); + expect(result.value.isDID).toBe(true); + expect(result.value.isHandle).toBe(false); + } + }); + + it('should reject invalid DID format', () => { + const result = DIDOrHandle.create('did:invalid:format'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDOrHandleError); + expect(result.error.message).toContain('Invalid DID'); + } + }); + }); + + describe('create with Handle', () => { + it('should create a DIDOrHandle with valid handle', () => { + const handleString = 'user.example.com'; + const result = DIDOrHandle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(handleString); + expect(result.value.isDID).toBe(false); + expect(result.value.isHandle).toBe(true); + } + }); + + it('should create a DIDOrHandle with basic domain handle', () => { + const handleString = 'example.com'; + const result = DIDOrHandle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(handleString); + expect(result.value.isDID).toBe(false); + expect(result.value.isHandle).toBe(true); + } + }); + + it('should reject invalid handle format', () => { + const result = DIDOrHandle.create('invalid-handle'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDOrHandleError); + expect(result.error.message).toContain('Invalid handle'); + } + }); + }); + + describe('input validation', () => { + it('should trim whitespace from input', () => { + const didString = ' did:plc:lehcqqkwzcwvjvw66uthu5oq '; + const result = DIDOrHandle.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe('did:plc:lehcqqkwzcwvjvw66uthu5oq'); + } + }); + + it('should reject empty string', () => { + const result = DIDOrHandle.create(''); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDOrHandleError); + expect(result.error.message).toBe('Value cannot be empty'); + } + }); + + it('should reject whitespace-only string', () => { + const result = DIDOrHandle.create(' '); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidDIDOrHandleError); + expect(result.error.message).toBe('Value cannot be empty'); + } + }); + }); + + describe('getDID', () => { + it('should return DID object when created with DID', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const result = DIDOrHandle.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + const did = result.value.getDID(); + expect(did).toBeDefined(); + expect(did?.value).toBe(didString); + } + }); + + it('should return undefined when created with handle', () => { + const handleString = 'example.com'; + const result = DIDOrHandle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + const did = result.value.getDID(); + expect(did).toBeUndefined(); + } + }); + }); + + describe('getHandle', () => { + it('should return Handle object when created with handle', () => { + const handleString = 'example.com'; + const result = DIDOrHandle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + const handle = result.value.getHandle(); + expect(handle).toBeDefined(); + expect(handle?.value).toBe(handleString); + } + }); + + it('should return undefined when created with DID', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const result = DIDOrHandle.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + const handle = result.value.getHandle(); + expect(handle).toBeUndefined(); + } + }); + }); + + describe('toString', () => { + it('should return the original string value for DID', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const result = DIDOrHandle.create(didString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.toString()).toBe(didString); + } + }); + + it('should return the original string value for handle', () => { + const handleString = 'example.com'; + const result = DIDOrHandle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.toString()).toBe(handleString); + } + }); + }); + + describe('equals', () => { + it('should return true for identical DID values', () => { + const didString = 'did:plc:lehcqqkwzcwvjvw66uthu5oq'; + const didOrHandle1Result = DIDOrHandle.create(didString); + const didOrHandle2Result = DIDOrHandle.create(didString); + + expect(didOrHandle1Result.isOk()).toBe(true); + expect(didOrHandle2Result.isOk()).toBe(true); + if (didOrHandle1Result.isOk() && didOrHandle2Result.isOk()) { + expect(didOrHandle1Result.value.equals(didOrHandle2Result.value)).toBe( + true, + ); + } + }); + + it('should return true for identical handle values', () => { + const handleString = 'example.com'; + const didOrHandle1Result = DIDOrHandle.create(handleString); + const didOrHandle2Result = DIDOrHandle.create(handleString); + + expect(didOrHandle1Result.isOk()).toBe(true); + expect(didOrHandle2Result.isOk()).toBe(true); + if (didOrHandle1Result.isOk() && didOrHandle2Result.isOk()) { + expect(didOrHandle1Result.value.equals(didOrHandle2Result.value)).toBe( + true, + ); + } + }); + + it('should return false for different values', () => { + const didResult = DIDOrHandle.create('did:plc:lehcqqkwzcwvjvw66uthu5oq'); + const handleResult = DIDOrHandle.create('example.com'); + + expect(didResult.isOk()).toBe(true); + expect(handleResult.isOk()).toBe(true); + if (didResult.isOk() && handleResult.isOk()) { + expect(didResult.value.equals(handleResult.value)).toBe(false); + } + }); + + it('should return false for different DIDs', () => { + const did1Result = DIDOrHandle.create('did:plc:lehcqqkwzcwvjvw66uthu5oq'); + const did2Result = DIDOrHandle.create('did:plc:anotherdidvalue123456789'); + + expect(did1Result.isOk()).toBe(true); + expect(did2Result.isOk()).toBe(true); + if (did1Result.isOk() && did2Result.isOk()) { + expect(did1Result.value.equals(did2Result.value)).toBe(false); + } + }); + + it('should return false for different handles', () => { + const handle1Result = DIDOrHandle.create('example.com'); + const handle2Result = DIDOrHandle.create('different.com'); + + expect(handle1Result.isOk()).toBe(true); + expect(handle2Result.isOk()).toBe(true); + if (handle1Result.isOk() && handle2Result.isOk()) { + expect(handle1Result.value.equals(handle2Result.value)).toBe(false); + } + }); + }); +}); diff --git a/src/modules/atproto/tests/domain/Handle.test.ts b/src/modules/atproto/tests/domain/Handle.test.ts new file mode 100644 index 00000000..1c40fb7e --- /dev/null +++ b/src/modules/atproto/tests/domain/Handle.test.ts @@ -0,0 +1,235 @@ +import { Handle, InvalidHandleError } from '../../domain/Handle'; + +describe('Handle', () => { + describe('create', () => { + it('should create a valid handle with basic domain', () => { + const handleString = 'example.com'; + const result = Handle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(handleString); + } + }); + + it('should create a valid handle with subdomain', () => { + const handleString = 'user.example.com'; + const result = Handle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(handleString); + } + }); + + it('should create a valid handle with multiple subdomains', () => { + const handleString = 'subsub.subdomain.example.com'; + const result = Handle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(handleString); + } + }); + + it('should create a valid handle with hyphens', () => { + const handleString = 'my-user.example-site.com'; + const result = Handle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(handleString); + } + }); + + it('should create a valid handle with numbers', () => { + const handleString = 'user123.example2.com'; + const result = Handle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(handleString); + } + }); + + it('should trim whitespace from input', () => { + const handleString = ' example.com '; + const result = Handle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe('example.com'); + } + }); + + it('should reject empty string', () => { + const result = Handle.create(''); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle cannot be empty'); + } + }); + + it('should reject whitespace-only string', () => { + const result = Handle.create(' '); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle cannot be empty'); + } + }); + + it('should reject handle without dot', () => { + const result = Handle.create('example'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject handle starting with dot', () => { + const result = Handle.create('.example.com'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject handle ending with dot', () => { + const result = Handle.create('example.com.'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject handle starting with hyphen', () => { + const result = Handle.create('-example.com'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject handle ending with hyphen', () => { + const result = Handle.create('example.com-'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject domain part starting with hyphen', () => { + const result = Handle.create('-user.example.com'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject domain part ending with hyphen', () => { + const result = Handle.create('user-.example.com'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject handle with invalid characters', () => { + const result = Handle.create('user@example.com'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject handle with empty domain part', () => { + const result = Handle.create('user..com'); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + + it('should reject handle with domain part too long', () => { + const longPart = 'a'.repeat(64); // 64 characters, exceeds 63 limit + const result = Handle.create(`${longPart}.com`); + + expect(result.isErr()).toBe(true); + if (result.isErr()) { + expect(result.error).toBeInstanceOf(InvalidHandleError); + expect(result.error.message).toBe('Handle must be a valid domain'); + } + }); + }); + + describe('value getter', () => { + it('should return the handle string value', () => { + const handleString = 'example.com'; + const result = Handle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.value).toBe(handleString); + } + }); + }); + + describe('toString', () => { + it('should return the handle string value', () => { + const handleString = 'example.com'; + const result = Handle.create(handleString); + + expect(result.isOk()).toBe(true); + if (result.isOk()) { + expect(result.value.toString()).toBe(handleString); + } + }); + }); + + describe('equals', () => { + it('should return true for identical handles', () => { + const handleString = 'example.com'; + const handle1Result = Handle.create(handleString); + const handle2Result = Handle.create(handleString); + + expect(handle1Result.isOk()).toBe(true); + expect(handle2Result.isOk()).toBe(true); + if (handle1Result.isOk() && handle2Result.isOk()) { + expect(handle1Result.value.equals(handle2Result.value)).toBe(true); + } + }); + + it('should return false for different handles', () => { + const handle1Result = Handle.create('example.com'); + const handle2Result = Handle.create('different.com'); + + expect(handle1Result.isOk()).toBe(true); + expect(handle2Result.isOk()).toBe(true); + if (handle1Result.isOk() && handle2Result.isOk()) { + expect(handle1Result.value.equals(handle2Result.value)).toBe(false); + } + }); + }); +});