diff --git a/src/utils/feed-generator.test.ts b/src/utils/feed-generator.test.ts new file mode 100644 index 0000000..50deca1 --- /dev/null +++ b/src/utils/feed-generator.test.ts @@ -0,0 +1,107 @@ +import { describe, it, expect } from "vitest"; +import { generateRssFeed, generateAtomFeed } from "./feed-generator"; +import { FeedConfig, EmailData } from "../types"; + +const mockFeedConfig: FeedConfig = { + title: "Test Newsletter", + description: "A test feed", + site_url: "https://test.getmynews.app/rss/abc123", + feed_url: "https://test.getmynews.app/rss/abc123", + language: "en", + created_at: 1700000000000, +}; + +const mockEmails: EmailData[] = [ + { + subject: "Hello World", + from: "Alice ", + content: "

Hello from Alice

", + receivedAt: 1700000001000, + headers: {}, + }, +]; + +const BASE_URL = "https://test.getmynews.app"; +const FEED_ID = "abc123"; + +describe("generateRssFeed", () => { + it("returns RSS 2.0 with channel element", () => { + const result = generateRssFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain(""); + expect(result).toContain("Test Newsletter"); + }); + + it("includes rss self-link in RSS output", () => { + const result = generateRssFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain(`${BASE_URL}/rss/${FEED_ID}`); + }); + + it("includes email entries as elements", () => { + const result = generateRssFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain(""); + expect(result).toContain("Hello World"); + }); + + it("works with empty emails array", () => { + const result = generateRssFeed(mockFeedConfig, [], BASE_URL, FEED_ID); + expect(result).toContain(""); + expect(result).not.toContain(""); + }); +}); + +describe("generateAtomFeed", () => { + it("returns Atom 1.0 namespace", () => { + const result = generateAtomFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain('xmlns="http://www.w3.org/2005/Atom"'); + }); + + it("contains root element", () => { + const result = generateAtomFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain(""); + }); + + it("includes feed title", () => { + const result = generateAtomFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain("Test Newsletter"); + }); + + it("includes elements for each email", () => { + const result = generateAtomFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain(""); + expect(result).toContain("Hello World"); + }); + + it("includes author information", () => { + const result = generateAtomFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain("Alice"); + }); + + it("self-link points to atom URL", () => { + const result = generateAtomFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain(`${BASE_URL}/atom/${FEED_ID}`); + }); + + it("includes rss alternate link", () => { + const result = generateAtomFeed(mockFeedConfig, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain(`${BASE_URL}/rss/${FEED_ID}`); + }); + + it("works with empty emails array", () => { + const result = generateAtomFeed(mockFeedConfig, [], BASE_URL, FEED_ID); + expect(result).toContain(""); + }); + + it("handles config without description", () => { + const configNoDesc: FeedConfig = { ...mockFeedConfig, description: undefined }; + const result = generateAtomFeed(configNoDesc, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain('xmlns="http://www.w3.org/2005/Atom"'); + }); + + it("handles config with author field", () => { + const configWithAuthor: FeedConfig = { ...mockFeedConfig, author: "Bob" }; + const result = generateAtomFeed(configWithAuthor, mockEmails, BASE_URL, FEED_ID); + expect(result).toContain("Bob"); + }); +}); diff --git a/src/utils/feed-generator.ts b/src/utils/feed-generator.ts index 50bda90..363ff5f 100644 --- a/src/utils/feed-generator.ts +++ b/src/utils/feed-generator.ts @@ -13,12 +13,12 @@ function parseFromAddress(from: string): { name: string; email?: string } { return { name: from.trim() }; } -export function generateRssFeed( +function buildFeed( feedConfig: FeedConfig, emails: EmailData[], baseUrl: string, feedId: string, -): string { +): Feed { const feed = new Feed({ title: feedConfig.title, description: feedConfig.description || "", @@ -29,7 +29,8 @@ export function generateRssFeed( generator: "Email-to-RSS", copyright: `Copyright © ${new Date().getFullYear()} ${feedConfig.title}`, feedLinks: { - rss: feedConfig.feed_url, + rss: `${baseUrl}/rss/${feedId}`, + atom: `${baseUrl}/atom/${feedId}`, }, author: feedConfig.author ? { @@ -52,5 +53,23 @@ export function generateRssFeed( }); } - return feed.rss2(); + return feed; +} + +export function generateRssFeed( + feedConfig: FeedConfig, + emails: EmailData[], + baseUrl: string, + feedId: string, +): string { + return buildFeed(feedConfig, emails, baseUrl, feedId).rss2(); +} + +export function generateAtomFeed( + feedConfig: FeedConfig, + emails: EmailData[], + baseUrl: string, + feedId: string, +): string { + return buildFeed(feedConfig, emails, baseUrl, feedId).atom1(); }