diff --git a/src/parser/phase/__test__/phase-8.test.ts b/src/parser/phase/__test__/phase-8.test.ts new file mode 100644 index 0000000..28b17f4 --- /dev/null +++ b/src/parser/phase/__test__/phase-8.test.ts @@ -0,0 +1,95 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import * as helpers from "../../__test__/helpers"; + +const phase = 8; + +describe("phase 8", () => { + let feed; + beforeAll(async () => { + feed = await helpers.loadSimple(); + }); + + describe("podcast:follow", () => { + const supportedName = "follow"; + + it("correctly identifies a basic feed", () => { + const result = helpers.parseValidFeed(feed); + + expect(result).not.toHaveProperty("podcastFollow"); + expect(helpers.getPhaseSupport(result, phase)).not.toContain(supportedName); + }); + + it("ignores missing url", () => { + const xml = helpers.spliceFeed( + feed, + // missing url, not valid + `` + ); + const result = helpers.parseValidFeed(xml); + + expect(result).not.toHaveProperty("podcastFollow"); + expect(helpers.getPhaseSupport(result, phase)).not.toContain(supportedName); + }); + + it("extracts a single follow url", () => { + const xml = helpers.spliceFeed( + feed, + `` + ); + const result = helpers.parseValidFeed(xml); + + expect(result).toHaveProperty("podcastFollow"); + expect(result.podcastFollow).toHaveProperty("url", "https://examplehost.com/feed/12345678/followlinks.json"); + expect(helpers.getPhaseSupport(result, phase)).toContain(supportedName); + }); + + it("extracts follow url with different domain", () => { + const xml = helpers.spliceFeed( + feed, + `` + ); + const result = helpers.parseValidFeed(xml); + + expect(result).toHaveProperty("podcastFollow"); + expect(result.podcastFollow).toHaveProperty("url", "https://f.prxu.org/72/subscribelinks.json"); + expect(helpers.getPhaseSupport(result, phase)).toContain(supportedName); + }); + + it("handles multiple follow tags by taking the first one", () => { + const xml = helpers.spliceFeed( + feed, + ` + ` + ); + const result = helpers.parseValidFeed(xml); + + expect(result).toHaveProperty("podcastFollow"); + expect(result.podcastFollow).toHaveProperty("url", "https://examplehost.com/feed/12345678/followlinks.json"); + expect(helpers.getPhaseSupport(result, phase)).toContain(supportedName); + }); + + it("handles empty url attribute", () => { + const xml = helpers.spliceFeed( + feed, + `` + ); + const result = helpers.parseValidFeed(xml); + + expect(result).not.toHaveProperty("podcastFollow"); + expect(helpers.getPhaseSupport(result, phase)).not.toContain(supportedName); + }); + + it("handles whitespace-only url attribute", () => { + const xml = helpers.spliceFeed( + feed, + `` + ); + const result = helpers.parseValidFeed(xml); + + expect(result).not.toHaveProperty("podcastFollow"); + expect(helpers.getPhaseSupport(result, phase)).not.toContain(supportedName); + }); + }); +}); diff --git a/src/parser/phase/index.ts b/src/parser/phase/index.ts index bd05e09..6b9cce7 100644 --- a/src/parser/phase/index.ts +++ b/src/parser/phase/index.ts @@ -15,6 +15,7 @@ import * as phase4 from "./phase-4"; import * as phase5 from "./phase-5"; import * as phase6 from "./phase-6"; import * as phase7 from "./phase-7"; +import * as phase8 from "./phase-8"; import * as pending from "./phase-pending"; import { XmlNodeSource } from "./types"; @@ -92,6 +93,8 @@ const feeds: FeedUpdate[] = [ phase7.podcastChat, phase7.podcastPublisher, + phase8.podcastFollow, + pending.id, pending.social, pending.podcastRecommendations, diff --git a/src/parser/phase/phase-8.ts b/src/parser/phase/phase-8.ts new file mode 100644 index 0000000..0e017d3 --- /dev/null +++ b/src/parser/phase/phase-8.ts @@ -0,0 +1,22 @@ +import { firstIfArray, getAttribute, getKnownAttribute } from "../shared"; +import type { XmlNode } from "../types"; + +export type Phase8Follow = { + url: string; +}; + +export const podcastFollow = { + phase: 8, + name: "follow", + tag: "podcast:follow", + nodeTransform: firstIfArray, + supportCheck: (node: XmlNode): boolean => + Boolean(getAttribute(node, "url")), + fn(node: XmlNode): { podcastFollow: Phase8Follow } { + return { + podcastFollow: { + url: getKnownAttribute(node, "url"), + }, + }; + }, +}; diff --git a/src/parser/types.ts b/src/parser/types.ts index c5119f6..38e6bee 100644 --- a/src/parser/types.ts +++ b/src/parser/types.ts @@ -22,6 +22,7 @@ import type { import type { Phase5Blocked, Phase5BlockedPlatforms, Phase5SocialInteract } from "./phase/phase-5"; import type { Phase6RemoteItem, Phase6TxtEntry } from "./phase/phase-6"; import type { Phase7Chat, Phase7Publisher } from "./phase/phase-7"; +import type { Phase8Follow } from "./phase/phase-8"; import { PhasePendingPodcastId, PhasePendingSocial, @@ -199,6 +200,10 @@ export interface FeedObject extends BasicFeed { podcastImages?: Phase4PodcastImage[]; podcastRecommendations?: PhasePendingPodcastRecommendation[]; // #endregion + // #region Phase 8 + /** URL pointing to a JSON file containing follow links for the podcast */ + podcastFollow?: Phase8Follow; + // #endregion } export type Enclosure = {