Hello world
', ...overrides, }; } function createFetchedAuthorData( options: {iconUrl?: string; authorName?: string; preferredUsername?: string; authorUrl?: string} = {}, ): ActivityPubPost & ActivityPubAuthor { const authorUrl = options.authorUrl ?? 'https://remote.example/@alice'; return { id: 'https://remote.example/users/alice', type: 'Person', url: authorUrl, published: '2026-02-16T09:00:00.000Z', attributedTo: authorUrl, content: '', name: options.authorName ?? 'Alice', preferredUsername: options.preferredUsername ?? 'alice', ...(options.iconUrl !== undefined ? { icon: { type: 'Image', mediaType: 'image/png', url: options.iconUrl, }, } : {}), }; } describe('ActivityPubFormatter', () => { it('omits empty icon URLs when author and server icons are missing', async () => { const formatter = new ActivityPubFormatter(new MockMediaService()); const post = createPost(); const context = createContext({serverIcon: ''}); const embed = await formatter.buildActivityPubEmbed(post, new URL(post.url), context, async () => null); expect(embed.author).toBeDefined(); expect(embed.author?.url).toBe('https://remote.example/users/alice'); expect(embed.author).not.toHaveProperty('icon_url'); expect(embed.footer).toBeDefined(); expect(embed.footer).not.toHaveProperty('icon_url'); expect(() => MessageEmbedResponseSchema.parse(embed)).not.toThrow(); }); it('omits invalid icon URLs from author and footer', async () => { const formatter = new ActivityPubFormatter(new MockMediaService()); const post = createPost(); const context = createContext({serverIcon: 'not-a-url'}); const fetchedAuthorData = createFetchedAuthorData({iconUrl: 'not-a-url', authorUrl: 'not-a-url'}); const embed = await formatter.buildActivityPubEmbed( post, new URL(post.url), context, async () => fetchedAuthorData, ); expect(embed.author).toBeDefined(); expect(embed.author).not.toHaveProperty('url'); expect(embed.author).not.toHaveProperty('icon_url'); expect(embed.footer).toBeDefined(); expect(embed.footer).not.toHaveProperty('icon_url'); expect(() => MessageEmbedResponseSchema.parse(embed)).not.toThrow(); }); it('omits an empty author icon URL from object attributedTo payloads', async () => { const formatter = new ActivityPubFormatter(new MockMediaService()); const post = createPost({ attributedTo: { id: 'https://remote.example/users/alice', type: 'Person', name: 'Alice', preferredUsername: 'alice', url: 'https://remote.example/@alice', icon: { type: 'Image', mediaType: 'image/png', url: '', }, }, }); const context = createContext(); const embed = await formatter.buildActivityPubEmbed(post, new URL(post.url), context, async () => null); expect(embed.author).toBeDefined(); expect(embed.author).not.toHaveProperty('icon_url'); expect(() => MessageEmbedResponseSchema.parse(embed)).not.toThrow(); }); it('omits an invalid author url from object attributedTo payloads', async () => { const formatter = new ActivityPubFormatter(new MockMediaService()); const post = createPost({ attributedTo: { id: 'https://remote.example/users/alice', type: 'Person', name: 'Alice', preferredUsername: 'alice', url: 'not-a-url', }, }); const context = createContext(); const embed = await formatter.buildActivityPubEmbed(post, new URL(post.url), context, async () => null); expect(embed.author).toBeDefined(); expect(embed.author).not.toHaveProperty('url'); expect(() => MessageEmbedResponseSchema.parse(embed)).not.toThrow(); }); it('keeps valid icon URLs for author and footer', async () => { const formatter = new ActivityPubFormatter(new MockMediaService()); const post = createPost(); const context = createContext({serverIcon: 'https://remote.example/server-icon.png'}); const fetchedAuthorData = createFetchedAuthorData({iconUrl: 'https://remote.example/avatar.png'}); const embed = await formatter.buildActivityPubEmbed( post, new URL(post.url), context, async () => fetchedAuthorData, ); expect(embed.author?.url).toBe('https://remote.example/@alice'); expect(embed.author?.icon_url).toBe('https://remote.example/avatar.png'); expect(embed.footer?.icon_url).toBe('https://remote.example/server-icon.png'); expect(() => MessageEmbedResponseSchema.parse(embed)).not.toThrow(); }); });