import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartfeed from '../ts/index.js'; let testSmartFeed: smartfeed.Smartfeed; tap.test('setup', async () => { testSmartFeed = new smartfeed.Smartfeed(); }); tap.test('should create podcast with podcast:guid', async () => { const podcast = testSmartFeed.createPodcastFeed({ domain: 'podcast2.example.com', title: 'Podcast 2.0 Test', description: 'Testing Podcast 2.0 features', category: 'Technology', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Tech Author', itunesOwner: { name: 'Tech Author', email: 'author@example.com' }, itunesImage: 'https://example.com/artwork.jpg', itunesExplicit: false, podcastGuid: '92f49cf0-db3e-5c17-8f11-9c5bd9e1f7ec', }); expect(podcast).toBeInstanceOf(smartfeed.PodcastFeed); expect(podcast.podcastOptions.podcastGuid).toEqual('92f49cf0-db3e-5c17-8f11-9c5bd9e1f7ec'); }); tap.test('should require podcast:guid', async () => { let errorThrown = false; try { testSmartFeed.createPodcastFeed({ domain: 'test.com', title: 'Test', description: 'Test', category: 'Test', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Owner', email: 'owner@example.com' }, itunesImage: 'https://example.com/image.jpg', itunesExplicit: false, // Missing podcastGuid } as any); } catch (error) { errorThrown = true; expect(error.message).toInclude('podcastGuid'); } expect(errorThrown).toEqual(true); }); tap.test('should create podcast with medium type', async () => { const podcast = testSmartFeed.createPodcastFeed({ domain: 'music.example.com', title: 'Music Podcast', description: 'A music podcast', category: 'Music', company: 'Music Inc', companyEmail: 'music@example.com', companyDomain: 'https://example.com', itunesCategory: 'Music', itunesAuthor: 'DJ', itunesOwner: { name: 'DJ', email: 'dj@example.com' }, itunesImage: 'https://example.com/music.jpg', itunesExplicit: false, podcastGuid: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', podcastMedium: 'music', }); expect(podcast.podcastOptions.podcastMedium).toEqual('music'); }); tap.test('should validate podcast medium type', async () => { let errorThrown = false; try { testSmartFeed.createPodcastFeed({ domain: 'test.com', title: 'Test', description: 'Test', category: 'Test', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Owner', email: 'owner@example.com' }, itunesImage: 'https://example.com/image.jpg', itunesExplicit: false, podcastGuid: 'test-guid-123', podcastMedium: 'invalid' as any, }); } catch (error) { errorThrown = true; expect(error.message).toInclude('medium must be one of'); } expect(errorThrown).toEqual(true); }); tap.test('should create locked podcast', async () => { const podcast = testSmartFeed.createPodcastFeed({ domain: 'locked.example.com', title: 'Locked Podcast', description: 'A locked podcast', category: 'News', company: 'News Inc', companyEmail: 'news@example.com', companyDomain: 'https://example.com', itunesCategory: 'News', itunesAuthor: 'Reporter', itunesOwner: { name: 'Reporter', email: 'reporter@example.com' }, itunesImage: 'https://example.com/news.jpg', itunesExplicit: false, podcastGuid: 'locked-guid-456', podcastLocked: true, podcastLockOwner: 'owner@example.com', }); expect(podcast.podcastOptions.podcastLocked).toEqual(true); expect(podcast.podcastOptions.podcastLockOwner).toEqual('owner@example.com'); }); tap.test('should require lock owner when podcast is locked', async () => { let errorThrown = false; try { testSmartFeed.createPodcastFeed({ domain: 'test.com', title: 'Test', description: 'Test', category: 'Test', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Owner', email: 'owner@example.com' }, itunesImage: 'https://example.com/image.jpg', itunesExplicit: false, podcastGuid: 'test-guid-789', podcastLocked: true, // Missing podcastLockOwner }); } catch (error) { errorThrown = true; expect(error.message).toInclude('lock owner'); expect(error.message).toInclude('required when podcast is locked'); } expect(errorThrown).toEqual(true); }); tap.test('should validate lock owner email format', async () => { let errorThrown = false; try { testSmartFeed.createPodcastFeed({ domain: 'test.com', title: 'Test', description: 'Test', category: 'Test', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Owner', email: 'owner@example.com' }, itunesImage: 'https://example.com/image.jpg', itunesExplicit: false, podcastGuid: 'test-guid-abc', podcastLocked: true, podcastLockOwner: 'not-an-email', }); } catch (error) { errorThrown = true; expect(error.message).toInclude('email'); } expect(errorThrown).toEqual(true); }); tap.test('should include podcast:guid in RSS export', async () => { const podcast = testSmartFeed.createPodcastFeed({ domain: 'export.example.com', title: 'Export Test', description: 'Testing RSS export', category: 'Technology', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Author', email: 'author@example.com' }, itunesImage: 'https://example.com/artwork.jpg', itunesExplicit: false, podcastGuid: 'export-test-guid-123', }); podcast.addEpisode({ title: 'Episode 1', authorName: 'Author', imageUrl: 'https://example.com/episode.jpg', timestamp: Date.now(), url: 'https://example.com/episode/1', content: 'Test episode', audioUrl: 'https://example.com/audio.mp3', audioType: 'audio/mpeg', audioLength: 1000000, itunesDuration: 600, }); const rss = podcast.exportPodcastRss(); expect(rss).toInclude('export-test-guid-123'); expect(rss).toInclude('xmlns:podcast="https://podcastindex.org/namespace/1.0"'); }); tap.test('should include podcast:medium in RSS export', async () => { const podcast = testSmartFeed.createPodcastFeed({ domain: 'video.example.com', title: 'Video Podcast', description: 'A video podcast', category: 'Video', company: 'Video Inc', companyEmail: 'video@example.com', companyDomain: 'https://example.com', itunesCategory: 'Video', itunesAuthor: 'Videographer', itunesOwner: { name: 'Videographer', email: 'video@example.com' }, itunesImage: 'https://example.com/video.jpg', itunesExplicit: false, podcastGuid: 'video-guid-def', podcastMedium: 'video', }); const rss = podcast.exportPodcastRss(); expect(rss).toInclude('video'); }); tap.test('should default to podcast medium when not specified', async () => { const podcast = testSmartFeed.createPodcastFeed({ domain: 'default.example.com', title: 'Default Podcast', description: 'Testing default medium', category: 'Technology', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Author', email: 'author@example.com' }, itunesImage: 'https://example.com/artwork.jpg', itunesExplicit: false, podcastGuid: 'default-guid-ghi', }); const rss = podcast.exportPodcastRss(); expect(rss).toInclude('podcast'); }); tap.test('should include podcast:locked in RSS export', async () => { const podcast = testSmartFeed.createPodcastFeed({ domain: 'locked-export.example.com', title: 'Locked Export Test', description: 'Testing locked RSS export', category: 'Technology', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Author', email: 'author@example.com' }, itunesImage: 'https://example.com/artwork.jpg', itunesExplicit: false, podcastGuid: 'locked-export-guid-jkl', podcastLocked: true, podcastLockOwner: 'lock@example.com', }); const rss = podcast.exportPodcastRss(); expect(rss).toInclude('yes'); }); tap.test('should include podcast:locked as "no" when not locked', async () => { const podcast = testSmartFeed.createPodcastFeed({ domain: 'unlocked.example.com', title: 'Unlocked Podcast', description: 'Testing unlocked RSS export', category: 'Technology', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Author', email: 'author@example.com' }, itunesImage: 'https://example.com/artwork.jpg', itunesExplicit: false, podcastGuid: 'unlocked-guid-mno', podcastLocked: false, }); const rss = podcast.exportPodcastRss(); expect(rss).toInclude('no'); }); tap.test('should support all medium types', async () => { const mediums: Array<'podcast' | 'music' | 'video' | 'film' | 'audiobook' | 'newsletter' | 'blog'> = [ 'podcast', 'music', 'video', 'film', 'audiobook', 'newsletter', 'blog', ]; for (const medium of mediums) { const podcast = testSmartFeed.createPodcastFeed({ domain: `${medium}.example.com`, title: `${medium} Test`, description: `Testing ${medium} medium`, category: 'Test', company: 'Test Inc', companyEmail: 'test@example.com', companyDomain: 'https://example.com', itunesCategory: 'Technology', itunesAuthor: 'Author', itunesOwner: { name: 'Author', email: 'author@example.com' }, itunesImage: 'https://example.com/artwork.jpg', itunesExplicit: false, podcastGuid: `${medium}-guid`, podcastMedium: medium, }); expect(podcast.podcastOptions.podcastMedium).toEqual(medium); } }); export default tap.start();