From be4f621d42c8e6bf38704579c30c84bd444b3e5d Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 2 Aug 2026 14:02:47 -0400 Subject: [PATCH] Move podcast types to own file; more WIP on role (#17) --- src/MyWebLog.Domain/MyWebLog.Domain.fsproj | 1 + src/MyWebLog.Domain/PodcastTypes.fs | 531 ++++++++++++++ src/MyWebLog.Domain/SupportTypes.fs | 524 -------------- .../Domain/PodcastTypesTests.fs | 654 ++++++++++++++++++ .../Domain/SupportTypesTests.fs | 196 ------ src/MyWebLog.Tests/MyWebLog.Tests.fsproj | 1 + src/MyWebLog.Tests/Program.fs | 2 +- 7 files changed, 1188 insertions(+), 721 deletions(-) create mode 100644 src/MyWebLog.Domain/PodcastTypes.fs create mode 100644 src/MyWebLog.Tests/Domain/PodcastTypesTests.fs diff --git a/src/MyWebLog.Domain/MyWebLog.Domain.fsproj b/src/MyWebLog.Domain/MyWebLog.Domain.fsproj index dad413d..45505af 100644 --- a/src/MyWebLog.Domain/MyWebLog.Domain.fsproj +++ b/src/MyWebLog.Domain/MyWebLog.Domain.fsproj @@ -1,6 +1,7 @@  + diff --git a/src/MyWebLog.Domain/PodcastTypes.fs b/src/MyWebLog.Domain/PodcastTypes.fs new file mode 100644 index 0000000..252be03 --- /dev/null +++ b/src/MyWebLog.Domain/PodcastTypes.fs @@ -0,0 +1,531 @@ +namespace MyWebLog + +/// Valid values for the iTunes explicit rating +[] +type ExplicitRating = + | Yes + | No + | Clean + + /// Parse a string into an explicit rating + /// The string representation of the rating + /// The ExplicitRating parsed from the string + /// If the string is not valid + static member Parse rating = + match rating with + | "yes" -> Yes + | "no" -> No + | "clean" -> Clean + | _ -> invalidArg (nameof rating) $"{rating} is not a valid explicit rating" + + /// + override this.ToString() = + match this with Yes -> "yes" | No -> "no" | Clean -> "clean" + + +/// A location (specified by Podcast Index) +type Location = { + /// The name of the location (free-form text) + Name: string + + /// A geographic coordinate string (RFC 5870) + Geo: string + + /// An OpenStreetMap query + Osm: string option +} + +open NodaTime + +/// A chapter in a podcast episode +type Chapter = { + /// The start time for the chapter + StartTime: Duration + + /// The title for this chapter + Title: string option + + /// A URL for an image for this chapter + ImageUrl: string option + + /// A URL with information pertaining to this chapter + Url: string option + + /// Whether this chapter is hidden + IsHidden: bool option + + /// The episode end time for the chapter + EndTime: Duration option + + /// A location that applies to a chapter + Location: Location option +} with + + /// An empty chapter + static member Empty = + { StartTime = Duration.Zero + Title = None + ImageUrl = None + Url = None + IsHidden = None + EndTime = None + Location = None } + +/// A group to which a podcast person can belong +[] +type PersonGroup = + | Administration + | AudioPostProduction + | AudioProduction + | Cast + | Community + | CreativeDirection + | Misc + | VideoPostProduction + | VideoProduction + | Visuals + | Writing + + /// Parse a string into a group + /// The string representation of the group + /// The PersonGroup instance parsed from the string + /// If the string is not valid + static member Parse group = + match group with + | "Administration" -> Administration + | "Audio Post-Production" -> AudioPostProduction + | "Audio Production" -> AudioProduction + | "Cast" -> Cast + | "Community" -> Community + | "Creative Direction" -> CreativeDirection + | "Misc." -> Misc + | "Video Post-Production" -> VideoPostProduction + | "Video Production" -> VideoProduction + | "Visuals" -> Visuals + | "Writing" -> Writing + | _ -> invalidArg (nameof group) $"{group} is not a valid group" + + /// + override this.ToString() = + match this with + | Administration -> "Administration" + | AudioPostProduction -> "Audio Post-Production" + | AudioProduction -> "Audio Production" + | Cast -> "Cast" + | Community -> "Community" + | CreativeDirection -> "Creative Direction" + | Misc -> "Misc." + | VideoPostProduction -> "Video Post-Production" + | VideoProduction -> "Video Production" + | Visuals -> "Visuals" + | Writing -> "Writing" + + +/// The role a person plays for a podcast or a podcast episode +type PersonRole = + | Announcer + | AssistantCamera + | AssistantDirector + | AssistantEditor + | AssociateProducer + | AudioEditor + | AudioEngineer + | Author + | BookingCoordinator + | CameraGrip + | CameraOperator + | CoHost + | Composer + | Consultant + | ContentManager + | CoverArtDesigner + | CoWriter + | CreativeDirector + | DevelopmentProducer + | Director + | EditorialDirector + | ExecutiveProducer + | FactChecker + | FoleyArtist + | GraphicDesigner + | Guest + | GuestHost + | GuestWriter + | Host + | Intern + | LightingDesigner + | Logger + | ManagingEditor + | MarketingManager + | MusicContributor + | MusicProduction + | Narrator + | PostProductionEngineer + | Producer + | ProductionAssistant + | ProductionCoordinator + | RemoteRecordingEngineer + | Reporter + | Researcher + | SalesManager + | SalesRepresentative + | ScriptCoordinator + | ScriptEditor + | SeniorProducer + | SocialMediaManager + | Songwriter + | SoundDesigner + | StoryEditor + | StudioCoordinator + | TechnicalDirector + | TechnicalManager + | ThemeMusic + | Transcriber + | Translator + | VideoEditor + | VoiceActor + | Writer + | WritingEditor + + /// Parse a string into a role + /// The string representation of the role + /// The PersonRole instance parsed from the string + /// If the string is not valid + static member Parse role = + match role with + | "Announcer" -> Announcer + | "Assistant Camera" -> AssistantCamera + | "Assistant Director" -> AssistantDirector + | "Assistant Editor" -> AssistantEditor + | "Associate Producer" -> AssociateProducer + | "Audio Editor" -> AudioEditor + | "Audio Engineer" -> AudioEngineer + | "Author" -> Author + | "Booking Coordinator" -> BookingCoordinator + | "Camera Grip" -> CameraGrip + | "Camera Operator" -> CameraOperator + | "Co-Host" -> CoHost + | "Composer" -> Composer + | "Consultant" -> Consultant + | "Content Manager" -> ContentManager + | "Cover Art Designer" -> CoverArtDesigner + | "Co-Writer" -> CoWriter + | "Creative Director" -> CreativeDirector + | "Development Producer" -> DevelopmentProducer + | "Director" -> Director + | "Editorial Director" -> EditorialDirector + | "Executive Producer" -> ExecutiveProducer + | "Fact Checker" -> FactChecker + | "Foley Artist" -> FoleyArtist + | "Graphic Designer" -> GraphicDesigner + | "Guest" -> Guest + | "Guest Host" -> GuestHost + | "Guest Writer" -> GuestWriter + | "Host" -> Host + | "Intern" -> Intern + | "Lighting Designer" -> LightingDesigner + | "Logger" -> Logger + | "Managing Editor" -> ManagingEditor + | "Marketing Manager" -> MarketingManager + | "Music Contributor" -> MusicContributor + | "Music Production" -> MusicProduction + | "Narrator" -> Narrator + | "Post Production Engineer" -> PostProductionEngineer + | "Producer" -> Producer + | "Production Assistant" -> ProductionAssistant + | "Production Coordinator" -> ProductionCoordinator + | "Remote Recording Engineer" -> RemoteRecordingEngineer + | "Reporter" -> Reporter + | "Researcher" -> Researcher + | "Sales Manager" -> SalesManager + | "Sales Representative" -> SalesRepresentative + | "Script Coordinator" -> ScriptCoordinator + | "Script Editor" -> ScriptEditor + | "Senior Producer" -> SeniorProducer + | "Social Media Manager" -> SocialMediaManager + | "Songwriter" -> Songwriter + | "Sound Designer" -> SoundDesigner + | "Story Editor" -> StoryEditor + | "Studio Coordinator" -> StudioCoordinator + | "Technical Director" -> TechnicalDirector + | "Technical Manager" -> TechnicalManager + | "Theme Music" -> ThemeMusic + | "Transcriber" -> Transcriber + | "Translator" -> Translator + | "Editor (Video)" -> VideoEditor + | "Voice Actor" -> VoiceActor + | "Writer" -> Writer + | "Editor (Writing)" -> WritingEditor + | _ -> invalidArg (nameof role) $"{role} is not a valid role" + + /// Role / group cross-reference + static member GroupXref = + [ Announcer, Cast + AssistantCamera, VideoProduction + AssistantDirector, CreativeDirection + AssistantEditor, VideoPostProduction + AssociateProducer, CreativeDirection + AudioEditor, AudioPostProduction + AudioEngineer, AudioProduction + Author, Writing + BookingCoordinator, Administration + CameraGrip, VideoProduction + CameraOperator, VideoProduction + CoHost, Cast + Composer, AudioPostProduction + ContentManager, Administration + Consultant, Misc + CoverArtDesigner, Visuals + CoWriter, Writing + CreativeDirector, CreativeDirection + DevelopmentProducer, CreativeDirection + Director, CreativeDirection + EditorialDirector, Writing + ExecutiveProducer, CreativeDirection + FactChecker, Writing + FoleyArtist, AudioPostProduction + GraphicDesigner, Visuals + Guest, Cast + GuestHost, Cast + GuestWriter, Writing + Host, Cast + Intern, Misc + LightingDesigner, VideoProduction + Logger, Writing + ManagingEditor, Writing + MarketingManager, Administration + MusicContributor, AudioPostProduction + MusicProduction, AudioPostProduction + Narrator, Cast + PostProductionEngineer, AudioProduction + Producer, CreativeDirection + ProductionAssistant, Administration + ProductionCoordinator, Administration + RemoteRecordingEngineer, AudioProduction + Reporter, Cast + Researcher, Writing + SalesManager, Administration + SalesRepresentative, Administration + ScriptCoordinator, Writing + ScriptEditor, Writing + SeniorProducer, CreativeDirection + SocialMediaManager, Community + Songwriter, Writing + SoundDesigner, AudioPostProduction + StoryEditor, Writing + StudioCoordinator, AudioProduction + TechnicalDirector, AudioProduction + TechnicalManager, AudioProduction + ThemeMusic, AudioPostProduction + Transcriber, Writing + Translator, Writing + VideoEditor, VideoPostProduction + VoiceActor, Cast + Writer, Writing + WritingEditor, Writing ] + + /// + override this.ToString() = + match this with + | Announcer -> "Announcer" + | AssistantCamera -> "Assistant Camera" + | AssistantDirector -> "Assistant Director" + | AssistantEditor -> "Assistant Editor" + | AssociateProducer -> "Associate Producer" + | AudioEditor -> "Audio Editor" + | AudioEngineer -> "Audio Engineer" + | Author -> "Author" + | BookingCoordinator -> "Booking Coordinator" + | CameraGrip -> "Camera Grip" + | CameraOperator -> "Camera Operator" + | CoHost -> "Co-Host" + | Composer -> "Composer" + | Consultant -> "Consultant" + | ContentManager -> "Content Manager" + | CoverArtDesigner -> "Cover Art Designer" + | CoWriter -> "Co-Writer" + | CreativeDirector -> "Creative Director" + | DevelopmentProducer -> "Development Producer" + | Director -> "Director" + | EditorialDirector -> "Editorial Director" + | ExecutiveProducer -> "Executive Producer" + | FactChecker -> "Fact Checker" + | FoleyArtist -> "Foley Artist" + | GraphicDesigner -> "Graphic Designer" + | Guest -> "Guest" + | GuestHost -> "Guest Host" + | GuestWriter -> "Guest Writer" + | Host -> "Host" + | Intern -> "Intern" + | LightingDesigner -> "Lighting Designer" + | Logger -> "Logger" + | ManagingEditor -> "Managing Editor" + | MarketingManager -> "Marketing Manager" + | MusicContributor -> "Music Contributor" + | MusicProduction -> "Music Production" + | Narrator -> "Narrator" + | PostProductionEngineer -> "Post Production Engineer" + | Producer -> "Producer" + | ProductionAssistant -> "Production Assistant" + | ProductionCoordinator -> "Production Coordinator" + | RemoteRecordingEngineer -> "Remote Recording Engineer" + | Reporter -> "Reporter" + | Researcher -> "Researcher" + | SalesManager -> "Sales Manager" + | SalesRepresentative -> "Sales Representative" + | ScriptCoordinator -> "Script Coordinator" + | ScriptEditor -> "Script Editor" + | SeniorProducer -> "Senior Producer" + | SocialMediaManager -> "Social Media Manager" + | Songwriter -> "Songwriter" + | SoundDesigner -> "Sound Designer" + | StoryEditor -> "Story Editor" + | StudioCoordinator -> "Studio Coordinator" + | TechnicalDirector -> "Technical Director" + | TechnicalManager -> "Technical Manager" + | ThemeMusic -> "Theme Music" + | Transcriber -> "Transcriber" + | Translator -> "Translator" + | VideoEditor -> "Editor (Video)" + | VoiceActor -> "Voice Actor" + | Writer -> "Writer" + | WritingEditor -> "Editor (Writing)" + + /// Get the display name of the role + /// Both Video Production and Writing have an "Editor"; this is how it should be rendered in RSS feeds and + /// in the UI + member this.ToDisplay() = + match this with VideoEditor | WritingEditor -> "Editor" | _ -> string this + + /// Get the PersonGroup to which this role belongs + member this.Group = + PersonRole.GroupXref |> List.find (fun it -> fst it = this) |> snd + + +open NodaTime.Text + +/// A podcast episode +type Episode = { + /// The URL to the media file for the episode (may be permalink) + Media: string + + /// The length of the media file, in bytes + Length: int64 + + /// The duration of the episode + Duration: Duration option + + /// The media type of the file (overrides podcast default if present) + MediaType: string option + + /// The URL to the image file for this episode (overrides podcast image if present, may be permalink) + ImageUrl: string option + + /// A subtitle for this episode + Subtitle: string option + + /// This episode's explicit rating (overrides podcast rating if present) + Explicit: ExplicitRating option + + /// Chapters for this episode + Chapters: Chapter list option + + /// A link to a chapter file + ChapterFile: string option + + /// The MIME type for the chapter file + ChapterType: string option + + /// Whether the chapters have locations that should be displayed as waypoints + ChapterWaypoints: bool option + + /// The URL for the transcript of the episode (may be permalink) + TranscriptUrl: string option + + /// The MIME type of the transcript + TranscriptType: string option + + /// The language in which the transcript is written + TranscriptLang: string option + + /// If true, the transcript will be declared (in the feed) to be a captions file + TranscriptCaptions: bool option + + /// The season number (for serialized podcasts) + SeasonNumber: int option + + /// A description of the season + SeasonDescription: string option + + /// The episode number + EpisodeNumber: double option + + /// A description of the episode + EpisodeDescription: string option +} with + + /// An empty episode + static member Empty = + { Media = "" + Length = 0L + Duration = None + MediaType = None + ImageUrl = None + Subtitle = None + Explicit = None + Chapters = None + ChapterFile = None + ChapterType = None + ChapterWaypoints = None + TranscriptUrl = None + TranscriptType = None + TranscriptLang = None + TranscriptCaptions = None + SeasonNumber = None + SeasonDescription = None + EpisodeNumber = None + EpisodeDescription = None } + + /// Format a duration for an episode + /// A duration formatted in hours, minutes, and seconds + member this.FormatDuration() = + this.Duration |> Option.map (DurationPattern.CreateWithInvariantCulture("H:mm:ss").Format) + + +/// PodcastIndex.org podcast:medium allowed values +[] +type PodcastMedium = + | Podcast + | Music + | Video + | Film + | Audiobook + | Newsletter + | Blog + + /// Parse a string into a podcast medium + /// The string to be parsed + /// The PodcastMedium parsed from the string + /// If the string is not valid + static member Parse medium = + match medium with + | "podcast" -> Podcast + | "music" -> Music + | "video" -> Video + | "film" -> Film + | "audiobook" -> Audiobook + | "newsletter" -> Newsletter + | "blog" -> Blog + | _ -> invalidArg (nameof medium) $"{medium} is not a valid podcast medium" + + /// + override this.ToString() = + match this with + | Podcast -> "podcast" + | Music -> "music" + | Video -> "video" + | Film -> "film" + | Audiobook -> "audiobook" + | Newsletter -> "newsletter" + | Blog -> "blog" + diff --git a/src/MyWebLog.Domain/SupportTypes.fs b/src/MyWebLog.Domain/SupportTypes.fs index cd9a68d..39f7a4a 100644 --- a/src/MyWebLog.Domain/SupportTypes.fs +++ b/src/MyWebLog.Domain/SupportTypes.fs @@ -203,492 +203,6 @@ type CommentStatus = match this with Approved -> "Approved" | Pending -> "Pending" | Spam -> "Spam" -/// Valid values for the iTunes explicit rating -[] -type ExplicitRating = - | Yes - | No - | Clean - - /// Parse a string into an explicit rating - /// The string representation of the rating - /// The ExplicitRating parsed from the string - /// If the string is not valid - static member Parse rating = - match rating with - | "yes" -> Yes - | "no" -> No - | "clean" -> Clean - | _ -> invalidArg (nameof rating) $"{rating} is not a valid explicit rating" - - /// - override this.ToString() = - match this with Yes -> "yes" | No -> "no" | Clean -> "clean" - - -/// A location (specified by Podcast Index) -type Location = { - /// The name of the location (free-form text) - Name: string - - /// A geographic coordinate string (RFC 5870) - Geo: string - - /// An OpenStreetMap query - Osm: string option -} - - -/// A chapter in a podcast episode -type Chapter = { - /// The start time for the chapter - StartTime: Duration - - /// The title for this chapter - Title: string option - - /// A URL for an image for this chapter - ImageUrl: string option - - /// A URL with information pertaining to this chapter - Url: string option - - /// Whether this chapter is hidden - IsHidden: bool option - - /// The episode end time for the chapter - EndTime: Duration option - - /// A location that applies to a chapter - Location: Location option -} with - - /// An empty chapter - static member Empty = - { StartTime = Duration.Zero - Title = None - ImageUrl = None - Url = None - IsHidden = None - EndTime = None - Location = None } - -/// A group to which a podcast person can belong -type PersonGroup = - | Administration - | AudioPostProduction - | AudioProduction - | Cast - | Community - | CreativeDirection - | Misc - | VideoPostProduction - | VideoProduction - | Visuals - | Writing - - /// Parse a string into a group - /// The string representation of the group - /// The PersonGroup instance parsed from the string - /// If the string is not valid - static member Parse group = - match group with - | "Administration" -> Administration - | "Audio Post-Production" -> AudioPostProduction - | "Audio Production" -> AudioProduction - | "Cast" -> Cast - | "Community" -> Community - | "Creative Direction" -> CreativeDirection - | "Misc." -> Misc - | "Video Post-Production" -> VideoPostProduction - | "Video Production" -> VideoProduction - | "Visuals" -> Visuals - | "Writing" -> Writing - | _ -> invalidArg (nameof group) $"{group} is not a valid group" - - /// - override this.ToString() = - match this with - | Administration -> "Administration" - | AudioPostProduction -> "Audio Post-Production" - | AudioProduction -> "Audio Production" - | Cast -> "Cast" - | Community -> "Community" - | CreativeDirection -> "Creative Direction" - | Misc -> "Misc." - | VideoPostProduction -> "Video Post-Production" - | VideoProduction -> "Video Production" - | Visuals -> "Visuals" - | Writing -> "Writing" - - -/// The role a person plays for a podcast or a podcast episode -type PersonRole = - | Announcer - | AssistantCamera - | AssistantDirector - | AssistantEditor - | AssociateProducer - | AudioEditor - | AudioEngineer - | Author - | BookingCoordinator - | CameraGrip - | CameraOperator - | CoHost - | Composer - | Consultant - | ContentManager - | CoverArtDesigner - | CoWriter - | CreativeDirector - | DevelopmentProducer - | Director - | EditorialDirector - | ExecutiveProducer - | FactChecker - | FoleyArtist - | GraphicDesigner - | Guest - | GuestHost - | GuestWriter - | Host - | Intern - | LightingDesigner - | Logger - | ManagingEditor - | MarketingManager - | MusicContributor - | MusicProduction - | Narrator - | PostProductionEngineer - | Producer - | ProductionAssistant - | ProductionCoordinator - | RemoteRecordingEngineer - | Reporter - | Researcher - | SalesManager - | SalesRepresentative - | ScriptCoordinator - | ScriptEditor - | SeniorProducer - | SocialMediaManager - | Songwriter - | SoundDesigner - | StoryEditor - | StudioCoordinator - | TechnicalDirector - | TechnicalManager - | ThemeMusic - | Transcriber - | Translator - | VideoEditor - | VoiceActor - | Writer - | WritingEditor - - /// Parse a string into a role - /// The string representation of the role - /// The PersonRole instance parsed from the string - /// If the string is not valid - static member Parse role = - match role with - | "Announcer" -> Announcer - | "Assistant Camera" -> AssistantCamera - | "Assistant Director" -> AssistantDirector - | "Assistant Editor" -> AssistantEditor - | "Associate Producer" -> AssociateProducer - | "Audio Editor" -> AudioEditor - | "Audio Engineer" -> AudioEngineer - | "Author" -> Author - | "Booking Coordinator" -> BookingCoordinator - | "Camera Grip" -> CameraGrip - | "Camera Operator" -> CameraOperator - | "Co-Host" -> CoHost - | "Composer" -> Composer - | "Consultant" -> Consultant - | "Content Manager" -> ContentManager - | "Cover Art Designer" -> CoverArtDesigner - | "Co-Writer" -> CoWriter - | "Creative Director" -> CreativeDirector - | "Development Producer" -> DevelopmentProducer - | "Director" -> Director - | "Editorial Director" -> EditorialDirector - | "Executive Producer" -> ExecutiveProducer - | "Fact Checker" -> FactChecker - | "Foley Artist" -> FoleyArtist - | "Graphic Designer" -> GraphicDesigner - | "Guest" -> Guest - | "Guest Host" -> GuestHost - | "Guest Writer" -> GuestWriter - | "Host" -> Host - | "Intern" -> Intern - | "Lighting Designer" -> LightingDesigner - | "Logger" -> Logger - | "Managing Editor" -> ManagingEditor - | "Marketing Manager" -> MarketingManager - | "Music Contributor" -> MusicContributor - | "Music Production" -> MusicProduction - | "Narrator" -> Narrator - | "Post Production Engineer" -> PostProductionEngineer - | "Producer" -> Producer - | "Production Assistant" -> ProductionAssistant - | "Production Coordinator" -> ProductionCoordinator - | "Remote Recording Engineer" -> RemoteRecordingEngineer - | "Reporter" -> Reporter - | "Researcher" -> Researcher - | "Sales Manager" -> SalesManager - | "Sales Representative" -> SalesRepresentative - | "Script Coordinator" -> ScriptCoordinator - | "Script Editor" -> ScriptEditor - | "Senior Producer" -> SeniorProducer - | "Social Media Manager" -> SocialMediaManager - | "Songwriter" -> Songwriter - | "Sound Designer" -> SoundDesigner - | "Story Editor" -> StoryEditor - | "Studio Coordinator" -> StudioCoordinator - | "Technical Director" -> TechnicalDirector - | "Technical Manager" -> TechnicalManager - | "Theme Music" -> ThemeMusic - | "Transcriber" -> Transcriber - | "Translator" -> Translator - | "Editor (Video)" -> VideoEditor - | "Voice Actor" -> VoiceActor - | "Writer" -> Writer - | "Editor (Writing)" -> WritingEditor - | _ -> invalidArg (nameof role) $"{role} is not a valid role" - - /// - override this.ToString() = - match this with - | Announcer -> "Announcer" - | AssistantCamera -> "Assistant Camera" - | AssistantDirector -> "Assistant Director" - | AssistantEditor -> "Assistant Editor" - | AssociateProducer -> "Associate Producer" - | AudioEditor -> "Audio Editor" - | AudioEngineer -> "Audio Engineer" - | Author -> "Author" - | BookingCoordinator -> "Booking Coordinator" - | CameraGrip -> "Camera Grip" - | CameraOperator -> "Camera Operator" - | CoHost -> "Co-Host" - | Composer -> "Composer" - | Consultant -> "Consultant" - | ContentManager -> "Content Manager" - | CoverArtDesigner -> "Cover Art Designer" - | CoWriter -> "Co-Writer" - | CreativeDirector -> "Creative Director" - | DevelopmentProducer -> "Development Producer" - | Director -> "Director" - | EditorialDirector -> "Editorial Director" - | ExecutiveProducer -> "Executive Producer" - | FactChecker -> "Fact Checker" - | FoleyArtist -> "Foley Artist" - | GraphicDesigner -> "Graphic Designer" - | Guest -> "Guest" - | GuestHost -> "Guest Host" - | GuestWriter -> "Guest Writer" - | Host -> "Host" - | Intern -> "Intern" - | LightingDesigner -> "Lighting Designer" - | Logger -> "Logger" - | ManagingEditor -> "Managing Editor" - | MarketingManager -> "Marketing Manager" - | MusicContributor -> "Music Contributor" - | MusicProduction -> "Music Production" - | Narrator -> "Narrator" - | PostProductionEngineer -> "Post Production Engineer" - | Producer -> "Producer" - | ProductionAssistant -> "Production Assistant" - | ProductionCoordinator -> "Production Coordinator" - | RemoteRecordingEngineer -> "Remote Recording Engineer" - | Reporter -> "Reporter" - | Researcher -> "Researcher" - | SalesManager -> "Sales Manager" - | SalesRepresentative -> "Sales Representative" - | ScriptCoordinator -> "Script Coordinator" - | ScriptEditor -> "Script Editor" - | SeniorProducer -> "Senior Producer" - | SocialMediaManager -> "Social Media Manager" - | Songwriter -> "Songwriter" - | SoundDesigner -> "Sound Designer" - | StoryEditor -> "Story Editor" - | StudioCoordinator -> "Studio Coordinator" - | TechnicalDirector -> "Technical Director" - | TechnicalManager -> "Technical Manager" - | ThemeMusic -> "Theme Music" - | Transcriber -> "Transcriber" - | Translator -> "Translator" - | VideoEditor -> "Editor (Video)" - | VoiceActor -> "Voice Actor" - | Writer -> "Writer" - | WritingEditor -> "Editor (Writing)" - - /// Get the display name of the role - /// Both Video Production and Writing have an "Editor"; this is how it should be rendered in RSS feeds and - /// in the UI - member this.ToDisplay() = - match this with VideoEditor | WritingEditor -> "Editor" | _ -> string this - - /// Role / group cross-reference - static member GroupXref = - [ Announcer, Cast - AssistantCamera, VideoProduction - AssistantDirector, CreativeDirection - AssistantEditor, VideoPostProduction - AssociateProducer, CreativeDirection - AudioEditor, AudioPostProduction - AudioEngineer, AudioProduction - Author, Writing - BookingCoordinator, Administration - CameraGrip, VideoProduction - CameraOperator, VideoProduction - CoHost, Cast - Composer, AudioPostProduction - ContentManager, Administration - Consultant, Misc - CoverArtDesigner, Visuals - CoWriter, Writing - CreativeDirector, CreativeDirection - DevelopmentProducer, CreativeDirection - Director, CreativeDirection - EditorialDirector, Writing - ExecutiveProducer, CreativeDirection - FactChecker, Writing - FoleyArtist, AudioPostProduction - GraphicDesigner, Visuals - Guest, Cast - GuestHost, Cast - GuestWriter, Writing - Host, Cast - Intern, Misc - LightingDesigner, VideoProduction - Logger, Writing - ManagingEditor, Writing - MarketingManager, Administration - MusicContributor, AudioPostProduction - MusicProduction, AudioPostProduction - Narrator, Cast - PostProductionEngineer, AudioProduction - Producer, CreativeDirection - ProductionAssistant, Administration - ProductionCoordinator, Administration - RemoteRecordingEngineer, AudioProduction - Reporter, Cast - Researcher, Writing - SalesManager, Administration - SalesRepresentative, Administration - ScriptCoordinator, Writing - ScriptEditor, Writing - SeniorProducer, CreativeDirection - SocialMediaManager, Community - Songwriter, Writing - SoundDesigner, AudioPostProduction - StoryEditor, Writing - StudioCoordinator, AudioProduction - TechnicalDirector, AudioProduction - TechnicalManager, AudioProduction - ThemeMusic, AudioPostProduction - Transcriber, Writing - Translator, Writing - VideoEditor, VideoPostProduction - VoiceActor, Cast - Writer, Writing - WritingEditor, Writing ] - - -open NodaTime.Text - -/// A podcast episode -type Episode = { - /// The URL to the media file for the episode (may be permalink) - Media: string - - /// The length of the media file, in bytes - Length: int64 - - /// The duration of the episode - Duration: Duration option - - /// The media type of the file (overrides podcast default if present) - MediaType: string option - - /// The URL to the image file for this episode (overrides podcast image if present, may be permalink) - ImageUrl: string option - - /// A subtitle for this episode - Subtitle: string option - - /// This episode's explicit rating (overrides podcast rating if present) - Explicit: ExplicitRating option - - /// Chapters for this episode - Chapters: Chapter list option - - /// A link to a chapter file - ChapterFile: string option - - /// The MIME type for the chapter file - ChapterType: string option - - /// Whether the chapters have locations that should be displayed as waypoints - ChapterWaypoints: bool option - - /// The URL for the transcript of the episode (may be permalink) - TranscriptUrl: string option - - /// The MIME type of the transcript - TranscriptType: string option - - /// The language in which the transcript is written - TranscriptLang: string option - - /// If true, the transcript will be declared (in the feed) to be a captions file - TranscriptCaptions: bool option - - /// The season number (for serialized podcasts) - SeasonNumber: int option - - /// A description of the season - SeasonDescription: string option - - /// The episode number - EpisodeNumber: double option - - /// A description of the episode - EpisodeDescription: string option -} with - - /// An empty episode - static member Empty = - { Media = "" - Length = 0L - Duration = None - MediaType = None - ImageUrl = None - Subtitle = None - Explicit = None - Chapters = None - ChapterFile = None - ChapterType = None - ChapterWaypoints = None - TranscriptUrl = None - TranscriptType = None - TranscriptLang = None - TranscriptCaptions = None - SeasonNumber = None - SeasonDescription = None - EpisodeNumber = None - EpisodeDescription = None } - - /// Format a duration for an episode - /// A duration formatted in hours, minutes, and seconds - member this.FormatDuration() = - this.Duration |> Option.map (DurationPattern.CreateWithInvariantCulture("H:mm:ss").Format) - - /// Types of markup text type MarkupText = /// Markdown text @@ -1069,44 +583,6 @@ type PageId = match this with PageId it -> it -/// PodcastIndex.org podcast:medium allowed values -[] -type PodcastMedium = - | Podcast - | Music - | Video - | Film - | Audiobook - | Newsletter - | Blog - - /// Parse a string into a podcast medium - /// The string to be parsed - /// The PodcastMedium parsed from the string - /// If the string is not valid - static member Parse medium = - match medium with - | "podcast" -> Podcast - | "music" -> Music - | "video" -> Video - | "film" -> Film - | "audiobook" -> Audiobook - | "newsletter" -> Newsletter - | "blog" -> Blog - | _ -> invalidArg (nameof medium) $"{medium} is not a valid podcast medium" - - /// - override this.ToString() = - match this with - | Podcast -> "podcast" - | Music -> "music" - | Video -> "video" - | Film -> "film" - | Audiobook -> "audiobook" - | Newsletter -> "newsletter" - | Blog -> "blog" - - /// Statuses for posts [] type PostStatus = diff --git a/src/MyWebLog.Tests/Domain/PodcastTypesTests.fs b/src/MyWebLog.Tests/Domain/PodcastTypesTests.fs new file mode 100644 index 0000000..01c2149 --- /dev/null +++ b/src/MyWebLog.Tests/Domain/PodcastTypesTests.fs @@ -0,0 +1,654 @@ +module PodcastTypesTests + +open System +open Expecto +open MyWebLog +open NodaTime + +/// Tests for the ExplicitRating type +let explicitRatingTests = testList "ExplicitRating" [ + testList "Parse" [ + test "succeeds for \"yes\"" { + Expect.equal (ExplicitRating.Parse "yes") Yes "\"yes\" not parsed correctly" + } + test "succeeds for \"no\"" { + Expect.equal (ExplicitRating.Parse "no") No "\"no\" not parsed correctly" + } + test "succeeds for \"clean\"" { + Expect.equal (ExplicitRating.Parse "clean") Clean "\"clean\" not parsed correctly" + } + test "fails for unrecognized value" { + Expect.throwsT + (fun () -> ignore (ExplicitRating.Parse "maybe")) "Invalid value should have raised an exception" + } + ] + testList "ToString" [ + test "Yes succeeds" { + Expect.equal (string Yes) "yes" "Yes string incorrect" + } + test "No succeeds" { + Expect.equal (string No) "no" "No string incorrect" + } + test "Clean succeeds" { + Expect.equal (string Clean) "clean" "Clean string incorrect" + } + ] +] + +/// Tests for the Episode type +let episodeTests = testList "Episode" [ + testList "FormatDuration" [ + test "succeeds when no duration is specified" { + Expect.isNone (Episode.Empty.FormatDuration()) "A missing duration should have returned None" + } + test "succeeds when duration is specified" { + Expect.equal + ({ Episode.Empty with + Duration = Some (Duration.FromMinutes 3L + Duration.FromSeconds 13L) }.FormatDuration()) + (Some "0:03:13") + "Duration not formatted correctly" + } + test "succeeds when duration is > 10 hours" { + Expect.equal + ({ Episode.Empty with Duration = Some (Duration.FromHours 11) }.FormatDuration()) + (Some "11:00:00") + "Duration not formatted correctly" + } + ] +] + +/// Tests for the PersonGroup type +let personGroupTests = testList "PersonGroup" [ + testList "Parse" [ + test "succeeds for \"Administration\"" { + Expect.equal (PersonGroup.Parse "Administration") Administration "Administration not parsed correctly" + } + test "succeeds for \"Audio Post-Production\"" { + Expect.equal + (PersonGroup.Parse "Audio Post-Production") + AudioPostProduction + "Audio Post-Production not parsed correctly" + } + test "succeeds for \"Audio Production\"" { + Expect.equal + (PersonGroup.Parse "Audio Production") AudioProduction "Audio Production not parsed correctly" + } + test "succeeds for \"Cast\"" { + Expect.equal (PersonGroup.Parse "Cast") Cast "Cast not parsed correctly" + } + test "succeeds for \"Community\"" { + Expect.equal (PersonGroup.Parse "Community") Community "Community not parsed correctly" + } + test "succeeds for \"Creative Direction\"" { + Expect.equal + (PersonGroup.Parse "Creative Direction") CreativeDirection "Creative Direction not parsed correctly" + } + test "succeeds for \"Misc.\"" { + Expect.equal (PersonGroup.Parse "Misc.") Misc "Misc. not parsed correctly" + } + test "succeeds for \"Video Post-Production\"" { + Expect.equal + (PersonGroup.Parse "Video Post-Production") + VideoPostProduction + "Video Post-Production not parsed correctly" + } + test "succeeds for \"Video Production\"" { + Expect.equal + (PersonGroup.Parse "Video Production") VideoProduction "Video Production not parsed correctly" + } + test "succeeds for \"Visuals\"" { + Expect.equal (PersonGroup.Parse "Visuals") Visuals "Visuals not parsed correctly" + } + test "succeeds for \"Writing\"" { + Expect.equal (PersonGroup.Parse "Writing") Writing "Writing not parsed correctly" + } + test "fails for unrecognized value" { + Expect.throwsT + (fun () -> ignore (PersonGroup.Parse "Fan")) "Invalid value should have raised an exception" + } + ] + testList "ToString" [ + test "Administration succeeds" { + Expect.equal (string Administration) "Administration" "Administration string incorrect" + } + test "Audio Post-Production succeeds" { + Expect.equal (string AudioPostProduction) "Audio Post-Production" "Audio Post-Production string incorrect" + } + test "Audio Production succeeds" { + Expect.equal (string AudioProduction) "Audio Production" "Audio Production string incorrect" + } + test "Cast succeeds" { + Expect.equal (string Cast) "Cast" "Cast string incorrect" + } + test "Community succeeds" { + Expect.equal (string Community) "Community" "Community string incorrect" + } + test "Creative Direction succeeds" { + Expect.equal (string CreativeDirection) "Creative Direction" "Creative Direction string incorrect" + } + test "Misc succeeds" { + Expect.equal (string Misc) "Misc." "Misc string incorrect" + } + test "Video Post-Production succeeds" { + Expect.equal (string VideoPostProduction) "Video Post-Production" "Video Post-Production string incorrect" + } + test "Video Production succeeds" { + Expect.equal (string VideoProduction) "Video Production" "Video Production string incorrect" + } + test "Visuals succeeds" { + Expect.equal (string Visuals) "Visuals" "Visuals string incorrect" + } + test "Writing succeeds" { + Expect.equal (string Writing) "Writing" "Writing string incorrect" + } + ] +] + +/// Unit tests for the PersonRole type +let personRoleTests = testList "PersonRole" [ + testList "Parse" [ + test "succeeds for \"Announcer\"" { + Expect.equal (PersonRole.Parse "Announcer") Announcer "Announcer not parsed correctly" + } + test "succeeds for \"Assistant Camera\"" { + Expect.equal (PersonRole.Parse "Assistant Camera") AssistantCamera "Assistant Camera not parsed correctly" + } + test "succeeds for \"Assistant Director\"" { + Expect.equal + (PersonRole.Parse "Assistant Director") AssistantDirector "Assistant Director not parsed correctly" + } + test "succeeds for \"Assistant Editor\"" { + Expect.equal (PersonRole.Parse "Assistant Editor") AssistantEditor "Assistant Editor not parsed correctly" + } + test "succeeds for \"Associate Producer\"" { + Expect.equal + (PersonRole.Parse "Associate Producer") AssociateProducer "Associate Producer not parsed correctly" + } + test "succeeds for \"Audio Editor\"" { + Expect.equal (PersonRole.Parse "Audio Editor") AudioEditor "Audio Editor not parsed correctly" + } + test "succeeds for \"Audio Engineer\"" { + Expect.equal (PersonRole.Parse "Audio Engineer") AudioEngineer "Audio Engineer not parsed correctly" + } + test "succeeds for \"Author\"" { + Expect.equal (PersonRole.Parse "Author") Author "Author not parsed correctly" + } + test "succeeds for \"Booking Coordinator\"" { + Expect.equal + (PersonRole.Parse "Booking Coordinator") BookingCoordinator "Booking Coordinator not parsed correctly" + } + test "succeeds for \"Camera Grip\"" { + Expect.equal (PersonRole.Parse "Camera Grip") CameraGrip "Camera Grip not parsed correctly" + } + test "succeeds for \"Camera Operator\"" { + Expect.equal (PersonRole.Parse "Camera Operator") CameraOperator "Camera Operator not parsed correctly" + } + test "succeeds for \"Co-Host\"" { + Expect.equal (PersonRole.Parse "Co-Host") CoHost "Co-Host not parsed correctly" + } + test "succeeds for \"Composer\"" { + Expect.equal (PersonRole.Parse "Composer") Composer "Composer not parsed correctly" + } + test "succeeds for \"Consultant\"" { + Expect.equal (PersonRole.Parse "Consultant") Consultant "Consultant not parsed correctly" + } + test "succeeds for \"Content Manager\"" { + Expect.equal (PersonRole.Parse "Content Manager") ContentManager "Content Manager not parsed correctly" + } + test "succeeds for \"Cover Art Designer\"" { + Expect.equal + (PersonRole.Parse "Cover Art Designer") CoverArtDesigner "Cover Art Designer not parsed correctly" + } + test "succeeds for \"Co-Writer\"" { + Expect.equal (PersonRole.Parse "Co-Writer") CoWriter "Co-Writer not parsed correctly" + } + test "succeeds for \"Creative Director\"" { + Expect.equal + (PersonRole.Parse "Creative Director") CreativeDirector "Creative Director not parsed correctly" + } + test "succeeds for \"Development Producer\"" { + Expect.equal + (PersonRole.Parse "Development Producer") + DevelopmentProducer + "Development Producer not parsed correctly" + } + test "succeeds for \"Director\"" { + Expect.equal (PersonRole.Parse "Director") Director "Director not parsed correctly" + } + test "succeeds for \"Editorial Director\"" { + Expect.equal + (PersonRole.Parse "Editorial Director") EditorialDirector "Editorial Director not parsed correctly" + } + test "succeeds for \"Executive Producer\"" { + Expect.equal + (PersonRole.Parse "Executive Producer") ExecutiveProducer "Executive Producer not parsed correctly" + } + test "succeeds for \"Fact Checker\"" { + Expect.equal (PersonRole.Parse "Fact Checker") FactChecker "Fact Checker not parsed correctly" + } + test "succeeds for \"Foley Artist\"" { + Expect.equal (PersonRole.Parse "Foley Artist") FoleyArtist "Foley Artist not parsed correctly" + } + test "succeeds for \"Graphic Designer\"" { + Expect.equal (PersonRole.Parse "Graphic Designer") GraphicDesigner "Graphic Designer not parsed correctly" + } + test "succeeds for \"Guest\"" { + Expect.equal (PersonRole.Parse "Guest") Guest "Guest not parsed correctly" + } + test "succeeds for \"Guest Host\"" { + Expect.equal (PersonRole.Parse "Guest Host") GuestHost "Guest Host not parsed correctly" + } + test "succeeds for \"Guest Writer\"" { + Expect.equal (PersonRole.Parse "Guest Writer") GuestWriter "Guest Writer not parsed correctly" + } + test "succeeds for \"Host\"" { + Expect.equal (PersonRole.Parse "Host") Host "Host not parsed correctly" + } + test "succeeds for \"Intern\"" { + Expect.equal (PersonRole.Parse "Intern") Intern "Intern not parsed correctly" + } + test "succeeds for \"Lighting Designer\"" { + Expect.equal + (PersonRole.Parse "Lighting Designer") LightingDesigner "Lighting Designer not parsed correctly" + } + test "succeeds for \"Logger\"" { + Expect.equal (PersonRole.Parse "Logger") Logger "Logger not parsed correctly" + } + test "succeeds for \"Managing Editor\"" { + Expect.equal (PersonRole.Parse "Managing Editor") ManagingEditor "Managing Editor not parsed correctly" + } + test "succeeds for \"Marketing Manager\"" { + Expect.equal + (PersonRole.Parse "Marketing Manager") MarketingManager "Marketing Manager not parsed correctly" + } + test "succeeds for \"Music Contributor\"" { + Expect.equal + (PersonRole.Parse "Music Contributor") MusicContributor "Music Contributor not parsed correctly" + } + test "succeeds for \"Music Production\"" { + Expect.equal (PersonRole.Parse "Music Production") MusicProduction "Music Production not parsed correctly" + } + test "succeeds for \"Narrator\"" { + Expect.equal (PersonRole.Parse "Narrator") Narrator "Narrator not parsed correctly" + } + test "succeeds for \"Post Production Engineer\"" { + Expect.equal + (PersonRole.Parse "Post Production Engineer") + PostProductionEngineer + "Post Production Engineer not parsed correctly" + } + test "succeeds for \"Producer\"" { + Expect.equal (PersonRole.Parse "Producer") Producer "Producer not parsed correctly" + } + test "succeeds for \"Production Assistant\"" { + Expect.equal + (PersonRole.Parse "Production Assistant") + ProductionAssistant + "Production Assistant not parsed correctly" + } + test "succeeds for \"Production Coordinator\"" { + Expect.equal + (PersonRole.Parse "Production Coordinator") + ProductionCoordinator + "Production Coordinator not parsed correctly" + } + test "succeeds for \"Remote Recording Engineer\"" { + Expect.equal + (PersonRole.Parse "Remote Recording Engineer") + RemoteRecordingEngineer + "Remote Recording Engineer not parsed correctly" + } + test "succeeds for \"Reporter\"" { + Expect.equal (PersonRole.Parse "Reporter") Reporter "Reporter not parsed correctly" + } + test "succeeds for \"Researcher\"" { + Expect.equal (PersonRole.Parse "Researcher") Researcher "Researcher not parsed correctly" + } + test "succeeds for \"Sales Manager\"" { + Expect.equal (PersonRole.Parse "Sales Manager") SalesManager "Sales Manager not parsed correctly" + } + test "succeeds for \"Sales Representative\"" { + Expect.equal + (PersonRole.Parse "Sales Representative") + SalesRepresentative + "Sales Representative not parsed correctly" + } + test "succeeds for \"Script Coordinator\"" { + Expect.equal + (PersonRole.Parse "Script Coordinator") ScriptCoordinator "Script Coordinator not parsed correctly" + } + test "succeeds for \"Script Editor\"" { + Expect.equal (PersonRole.Parse "Script Editor") ScriptEditor "Script Editor not parsed correctly" + } + test "succeeds for \"Senior Producer\"" { + Expect.equal (PersonRole.Parse "Senior Producer") SeniorProducer "Senior Producer not parsed correctly" + } + test "succeeds for \"Social Media Manager\"" { + Expect.equal + (PersonRole.Parse "Social Media Manager") SocialMediaManager "Social Media Manager not parsed correctly" + } + test "succeeds for \"Songwriter\"" { + Expect.equal (PersonRole.Parse "Songwriter") Songwriter "Songwriter not parsed correctly" + } + test "succeeds for \"Sound Designer\"" { + Expect.equal (PersonRole.Parse "Sound Designer") SoundDesigner "Sound Designer not parsed correctly" + } + test "succeeds for \"Story Editor\"" { + Expect.equal (PersonRole.Parse "Story Editor") StoryEditor "Story Editor not parsed correctly" + } + test "succeeds for \"Studio Coordinator\"" { + Expect.equal + (PersonRole.Parse "Studio Coordinator") StudioCoordinator "Studio Coordinator not parsed correctly" + } + test "succeeds for \"Technical Director\"" { + Expect.equal + (PersonRole.Parse "Technical Director") TechnicalDirector "Technical Director not parsed correctly" + } + test "succeeds for \"Technical Manager\"" { + Expect.equal + (PersonRole.Parse "Technical Manager") TechnicalManager "Technical Manager not parsed correctly" + } + test "succeeds for \"Theme Music\"" { + Expect.equal (PersonRole.Parse "Theme Music") ThemeMusic "Theme Music not parsed correctly" + } + test "succeeds for \"Transcriber\"" { + Expect.equal (PersonRole.Parse "Transcriber") Transcriber "Transcriber not parsed correctly" + } + test "succeeds for \"Translator\"" { + Expect.equal (PersonRole.Parse "Translator") Translator "Translator not parsed correctly" + } + test "succeeds for \"Editor (Video)\"" { + Expect.equal (PersonRole.Parse "Editor (Video)") VideoEditor "Editor (Video) not parsed correctly" + } + test "succeeds for \"Voice Actor\"" { + Expect.equal (PersonRole.Parse "Voice Actor") VoiceActor "Voice Actor not parsed correctly" + } + test "succeeds for \"Writer\"" { + Expect.equal (PersonRole.Parse "Writer") Writer "Writer not parsed correctly" + } + test "succeeds for \"Editor (Writing)\"" { + Expect.equal (PersonRole.Parse "Editor (Writing)") WritingEditor "Editor (Writing) not parsed correctly" + } + test "fails for unrecognized value" { + Expect.throwsT + (fun () -> ignore (PersonRole.Parse "Gaffer")) "Invalid value should have raised an exception" + } + ] + testList "ToString" [ + test "succeeds for Announcer" { + Expect.equal (string Announcer) "Announcer" "Announcer string incorrect" + } + test "succeeds for Assistant Camera" { + Expect.equal (string AssistantCamera) "Assistant Camera" "Assistant Camera string incorrect" + } + test "succeeds for Assistant Director" { + Expect.equal (string AssistantDirector) "Assistant Director" "Assistant Director string incorrect" + } + test "succeeds for Assistant Editor" { + Expect.equal (string AssistantEditor) "Assistant Editor" "Assistant Editor string incorrect" + } + test "succeeds for Associate Producer" { + Expect.equal (string AssociateProducer) "Associate Producer" "Associate Producer string incorrect" + } + test "succeeds for Audio Editor" { + Expect.equal (string AudioEditor) "Audio Editor" "Audio Editor string incorrect" + } + test "succeeds for Audio Engineer" { + Expect.equal (string AudioEngineer) "Audio Engineer" "Audio Engineer string incorrect" + } + test "succeeds for Author" { + Expect.equal (string Author) "Author" "Author string incorrect" + } + test "succeeds for Booking Coordinator" { + Expect.equal (string BookingCoordinator) "Booking Coordinator" "Booking Coordinator string incorrect" + } + test "succeeds for Camera Grip" { + Expect.equal (string CameraGrip) "Camera Grip" "Camera Grip string incorrect" + } + test "succeeds for Camera Operator" { + Expect.equal (string CameraOperator) "Camera Operator" "Camera Operator string incorrect" + } + test "succeeds for Co-Host" { + Expect.equal (string CoHost) "Co-Host" "Co-Host string incorrect" + } + test "succeeds for Composer" { + Expect.equal (string Composer) "Composer" "Composer string incorrect" + } + test "succeeds for Consultant" { + Expect.equal (string Consultant) "Consultant" "Consultant string incorrect" + } + test "succeeds for Content Manager" { + Expect.equal (string ContentManager) "Content Manager" "Content Manager string incorrect" + } + test "succeeds for Cover Art Designer" { + Expect.equal (string CoverArtDesigner) "Cover Art Designer" "Cover Art Designer string incorrect" + } + test "succeeds for Co-Writer" { + Expect.equal (string CoWriter) "Co-Writer" "Co-Writer string incorrect" + } + test "succeeds for Creative Director" { + Expect.equal (string CreativeDirector) "Creative Director" "Creative Director string incorrect" + } + test "succeeds for Development Producer" { + Expect.equal (string DevelopmentProducer) "Development Producer" "Development Producer string incorrect" + } + test "succeeds for Director" { + Expect.equal (string Director) "Director" "Director string incorrect" + } + test "succeeds for Editorial Director" { + Expect.equal (string EditorialDirector) "Editorial Director" "Editorial Director string incorrect" + } + test "succeeds for Executive Producer" { + Expect.equal (string ExecutiveProducer) "Executive Producer" "Executive Producer string incorrect" + } + test "succeeds for Fact Checker" { + Expect.equal (string FactChecker) "Fact Checker" "Fact Checker string incorrect" + } + test "succeeds for Foley Artist" { + Expect.equal (string FoleyArtist) "Foley Artist" "Foley Artist string incorrect" + } + test "succeeds for Graphic Designer" { + Expect.equal (string GraphicDesigner) "Graphic Designer" "Graphic Designer string incorrect" + } + test "succeeds for Guest" { + Expect.equal (string Guest) "Guest" "Guest string incorrect" + } + test "succeeds for Guest Host" { + Expect.equal (string GuestHost) "Guest Host" "Guest Host string incorrect" + } + test "succeeds for Guest Writer" { + Expect.equal (string GuestWriter) "Guest Writer" "Guest Writer string incorrect" + } + test "succeeds for Host" { + Expect.equal (string Host) "Host" "Host string incorrect" + } + test "succeeds for Intern" { + Expect.equal (string Intern) "Intern" "Intern string incorrect" + } + test "succeeds for Lighting Designer" { + Expect.equal (string LightingDesigner) "Lighting Designer" "Lighting Designer string incorrect" + } + test "succeeds for Logger" { + Expect.equal (string Logger) "Logger" "Logger string incorrect" + } + test "succeeds for Managing Editor" { + Expect.equal (string ManagingEditor) "Managing Editor" "Managing Editor string incorrect" + } + test "succeeds for Marketing Manager" { + Expect.equal (string MarketingManager) "Marketing Manager" "Marketing Manager string incorrect" + } + test "succeeds for Music Contributor" { + Expect.equal (string MusicContributor) "Music Contributor" "Music Contributor string incorrect" + } + test "succeeds for Music Production" { + Expect.equal (string MusicProduction) "Music Production" "Music Production string incorrect" + } + test "succeeds for Narrator" { + Expect.equal (string Narrator) "Narrator" "Narrator string incorrect" + } + test "succeeds for Post Production Engineer" { + Expect.equal + (string PostProductionEngineer) "Post Production Engineer" "Post Production Engineer string incorrect" + } + test "succeeds for Producer" { + Expect.equal (string Producer) "Producer" "Producer string incorrect" + } + test "succeeds for Production Assistant" { + Expect.equal (string ProductionAssistant) "Production Assistant" "Production Assistant string incorrect" + } + test "succeeds for Production Coordinator" { + Expect.equal + (string ProductionCoordinator) "Production Coordinator" "Production Coordinator string incorrect" + } + test "succeeds for Remote Recording Engineer" { + Expect.equal + (string RemoteRecordingEngineer) + "Remote Recording Engineer" + "Remote Recording Engineer string incorrect" + } + test "succeeds for Reporter" { + Expect.equal (string Reporter) "Reporter" "Reporter string incorrect" + } + test "succeeds for Researcher" { + Expect.equal (string Researcher) "Researcher" "Researcher string incorrect" + } + test "succeeds for Sales Manager" { + Expect.equal (string SalesManager) "Sales Manager" "Sales Manager string incorrect" + } + test "succeeds for Sales Representative" { + Expect.equal (string SalesRepresentative) "Sales Representative" "Sales Representative string incorrect" + } + test "succeeds for Script Coordinator" { + Expect.equal (string ScriptCoordinator) "Script Coordinator" "Script Coordinator string incorrect" + } + test "succeeds for Script Editor" { + Expect.equal (string ScriptEditor) "Script Editor" "Script Editor string incorrect" + } + test "succeeds for Senior Producer" { + Expect.equal (string SeniorProducer) "Senior Producer" "Senior Producer string incorrect" + } + test "succeeds for Social Media Manager" { + Expect.equal (string SocialMediaManager) "Social Media Manager" "Social Media Manager string incorrect" + } + test "succeeds for Songwriter" { + Expect.equal (string Songwriter) "Songwriter" "Songwriter string incorrect" + } + test "succeeds for Sound Designer" { + Expect.equal (string SoundDesigner) "Sound Designer" "Sound Designer string incorrect" + } + test "succeeds for Story Editor" { + Expect.equal (string StoryEditor) "Story Editor" "Story Editor string incorrect" + } + test "succeeds for Studio Coordinator" { + Expect.equal (string StudioCoordinator) "Studio Coordinator" "Studio Coordinator string incorrect" + } + test "succeeds for Technical Director" { + Expect.equal (string TechnicalDirector) "Technical Director" "Technical Director string incorrect" + } + test "succeeds for Technical Manager" { + Expect.equal (string TechnicalManager) "Technical Manager" "Technical Manager string incorrect" + } + test "succeeds for Theme Music" { + Expect.equal (string ThemeMusic) "Theme Music" "Theme Music string incorrect" + } + test "succeeds for Transcriber" { + Expect.equal (string Transcriber) "Transcriber" "Transcriber string incorrect" + } + test "succeeds for Translator" { + Expect.equal (string Translator) "Translator" "Translator string incorrect" + } + test "succeeds for Video Editor" { + Expect.equal (string VideoEditor) "Editor (Video)" "Video Editor string incorrect" + } + test "succeeds for Voice Actor" { + Expect.equal (string VoiceActor) "Voice Actor" "Voice Editor string incorrect" + } + test "succeeds for Writer" { + Expect.equal (string Writer) "Writer" "Writer string incorrect" + } + test "succeeds for Writing Editor" { + Expect.equal (string WritingEditor) "Editor (Writing)" "Writing Editor string incorrect" + } + ] + test "Role / Group Xref has all entries" { + Expect.hasLength PersonRole.GroupXref 63 "There should be 63 roles in the role / group xref list" + } + test "all values round-trip between ToString and Parse" { + PersonRole.GroupXref + |> List.map fst + |> List.iter (fun it -> + Expect.equal (it |> string |> PersonRole.Parse) it "The value did not round-trip correctly") + } + test "ToDisplay succeeds for all values" { + PersonRole.GroupXref + |> List.map fst + |> List.iter (fun it -> + match it with + | VideoEditor | WritingEditor -> + Expect.equal (it.ToDisplay()) "Editor" "The display string should be \"Editor\"" + | _ -> Expect.equal (it.ToDisplay()) (string it) "The display string should be its string value") + } +] + +/// Unit tests for the PodcastMedium type +let podcastMediumTests = testList "PodcastMedium" [ + testList "Parse" [ + test "succeeds for \"podcast\"" { + Expect.equal (PodcastMedium.Parse "podcast") Podcast "\"podcast\" not parsed correctly" + } + test "succeeds for \"music\"" { + Expect.equal (PodcastMedium.Parse "music") Music "\"music\" not parsed correctly" + } + test "succeeds for \"video\"" { + Expect.equal (PodcastMedium.Parse "video") Video "\"video\" not parsed correctly" + } + test "succeeds for \"film\"" { + Expect.equal (PodcastMedium.Parse "film") Film "\"film\" not parsed correctly" + } + test "succeeds for \"audiobook\"" { + Expect.equal (PodcastMedium.Parse "audiobook") Audiobook "\"audiobook\" not parsed correctly" + } + test "succeeds for \"newsletter\"" { + Expect.equal (PodcastMedium.Parse "newsletter") Newsletter "\"newsletter\" not parsed correctly" + } + test "succeeds for \"blog\"" { + Expect.equal (PodcastMedium.Parse "blog") Blog "\"blog\" not parsed correctly" + } + test "fails for invalid type" { + Expect.throwsT + (fun () -> ignore (PodcastMedium.Parse "laser")) "Invalid value should have raised an exception" + } + ] + testList "ToString" [ + test "succeeds for Podcast" { + Expect.equal (string Podcast) "podcast" "Podcast string incorrect" + } + test "succeeds for Music" { + Expect.equal (string Music) "music" "Music string incorrect" + } + test "succeeds for Video" { + Expect.equal (string Video) "video" "Video string incorrect" + } + test "succeeds for Film" { + Expect.equal (string Film) "film" "Film string incorrect" + } + test "succeeds for Audiobook" { + Expect.equal (string Audiobook) "audiobook" "Audiobook string incorrect" + } + test "succeeds for Newsletter" { + Expect.equal (string Newsletter) "newsletter" "Newsletter string incorrect" + } + test "succeeds for Blog" { + Expect.equal (string Blog) "blog" "Blog string incorrect" + } + ] +] + +/// All tests for the Domain.PodcastTypes file +let all = testList "SupportTypes" [ + explicitRatingTests + episodeTests + personGroupTests + personRoleTests + podcastMediumTests +] diff --git a/src/MyWebLog.Tests/Domain/SupportTypesTests.fs b/src/MyWebLog.Tests/Domain/SupportTypesTests.fs index 87701ac..217dccd 100644 --- a/src/MyWebLog.Tests/Domain/SupportTypesTests.fs +++ b/src/MyWebLog.Tests/Domain/SupportTypesTests.fs @@ -173,57 +173,6 @@ let commentStatusTests = testList "CommentStatus" [ ] ] -/// Tests for the ExplicitRating type -let explicitRatingTests = testList "ExplicitRating" [ - testList "Parse" [ - test "succeeds for \"yes\"" { - Expect.equal (ExplicitRating.Parse "yes") Yes "\"yes\" not parsed correctly" - } - test "succeeds for \"no\"" { - Expect.equal (ExplicitRating.Parse "no") No "\"no\" not parsed correctly" - } - test "succeeds for \"clean\"" { - Expect.equal (ExplicitRating.Parse "clean") Clean "\"clean\" not parsed correctly" - } - test "fails for unrecognized value" { - Expect.throwsT - (fun () -> ignore (ExplicitRating.Parse "maybe")) "Invalid value should have raised an exception" - } - ] - testList "ToString" [ - test "Yes succeeds" { - Expect.equal (string Yes) "yes" "Yes string incorrect" - } - test "No succeeds" { - Expect.equal (string No) "no" "No string incorrect" - } - test "Clean succeeds" { - Expect.equal (string Clean) "clean" "Clean string incorrect" - } - ] -] - -/// Tests for the Episode type -let episodeTests = testList "Episode" [ - testList "FormatDuration" [ - test "succeeds when no duration is specified" { - Expect.isNone (Episode.Empty.FormatDuration()) "A missing duration should have returned None" - } - test "succeeds when duration is specified" { - Expect.equal - ({ Episode.Empty with - Duration = Some (Duration.FromMinutes 3L + Duration.FromSeconds 13L) }.FormatDuration()) - (Some "0:03:13") - "Duration not formatted correctly" - } - test "succeeds when duration is > 10 hours" { - Expect.equal - ({ Episode.Empty with Duration = Some (Duration.FromHours 11) }.FormatDuration()) - (Some "11:00:00") - "Duration not formatted correctly" - } - ] -] /// Unit tests for the MarkupText type let markupTextTests = testList "MarkupText" [ @@ -684,147 +633,6 @@ let openGraphPropertiesTests = testList "OpenGraphProperties" [ ] ] -/// Tests for the PersonGroup type -let personGroupTests = testList "PersonGroup" [ - testList "Parse" [ - test "succeeds for \"Administration\"" { - Expect.equal (PersonGroup.Parse "Administration") Administration "Administration not parsed correctly" - } - test "succeeds for \"Audio Post-Production\"" { - Expect.equal - (PersonGroup.Parse "Audio Post-Production") - AudioPostProduction - "Audio Post-Production not parsed correctly" - } - test "succeeds for \"Audio Production\"" { - Expect.equal - (PersonGroup.Parse "Audio Production") AudioProduction "Audio Production not parsed correctly" - } - test "succeeds for \"Cast\"" { - Expect.equal (PersonGroup.Parse "Cast") Cast "Cast not parsed correctly" - } - test "succeeds for \"Community\"" { - Expect.equal (PersonGroup.Parse "Community") Community "Community not parsed correctly" - } - test "succeeds for \"Creative Direction\"" { - Expect.equal - (PersonGroup.Parse "Creative Direction") CreativeDirection "Creative Direction not parsed correctly" - } - test "succeeds for \"Misc.\"" { - Expect.equal (PersonGroup.Parse "Misc.") Misc "Misc. not parsed correctly" - } - test "succeeds for \"Video Post-Production\"" { - Expect.equal - (PersonGroup.Parse "Video Post-Production") - VideoPostProduction - "Video Post-Production not parsed correctly" - } - test "succeeds for \"Video Production\"" { - Expect.equal - (PersonGroup.Parse "Video Production") VideoProduction "Video Production not parsed correctly" - } - test "succeeds for \"Visuals\"" { - Expect.equal (PersonGroup.Parse "Visuals") Visuals "Visuals not parsed correctly" - } - test "succeeds for \"Writing\"" { - Expect.equal (PersonGroup.Parse "Writing") Writing "Writing not parsed correctly" - } - test "fails for unrecognized value" { - Expect.throwsT - (fun () -> ignore (PersonGroup.Parse "Fan")) "Invalid value should have raised an exception" - } - ] - testList "ToString" [ - test "Administration succeeds" { - Expect.equal (string Administration) "Administration" "Administration string incorrect" - } - test "Audio Post-Production succeeds" { - Expect.equal (string AudioPostProduction) "Audio Post-Production" "Audio Post-Production string incorrect" - } - test "Audio Production succeeds" { - Expect.equal (string AudioProduction) "Audio Production" "Audio Production string incorrect" - } - test "Cast succeeds" { - Expect.equal (string Cast) "Cast" "Cast string incorrect" - } - test "Community succeeds" { - Expect.equal (string Community) "Community" "Community string incorrect" - } - test "Creative Direction succeeds" { - Expect.equal (string CreativeDirection) "Creative Direction" "Creative Direction string incorrect" - } - test "Misc succeeds" { - Expect.equal (string Misc) "Misc." "Misc string incorrect" - } - test "Video Post-Production succeeds" { - Expect.equal (string VideoPostProduction) "Video Post-Production" "Video Post-Production string incorrect" - } - test "Video Production succeeds" { - Expect.equal (string VideoProduction) "Video Production" "Video Production string incorrect" - } - test "Visuals succeeds" { - Expect.equal (string Visuals) "Visuals" "Visuals string incorrect" - } - test "Writing succeeds" { - Expect.equal (string Writing) "Writing" "Writing string incorrect" - } - ] -] - -/// Unit tests for the PodcastMedium type -let podcastMediumTests = testList "PodcastMedium" [ - testList "Parse" [ - test "succeeds for \"podcast\"" { - Expect.equal (PodcastMedium.Parse "podcast") Podcast "\"podcast\" not parsed correctly" - } - test "succeeds for \"music\"" { - Expect.equal (PodcastMedium.Parse "music") Music "\"music\" not parsed correctly" - } - test "succeeds for \"video\"" { - Expect.equal (PodcastMedium.Parse "video") Video "\"video\" not parsed correctly" - } - test "succeeds for \"film\"" { - Expect.equal (PodcastMedium.Parse "film") Film "\"film\" not parsed correctly" - } - test "succeeds for \"audiobook\"" { - Expect.equal (PodcastMedium.Parse "audiobook") Audiobook "\"audiobook\" not parsed correctly" - } - test "succeeds for \"newsletter\"" { - Expect.equal (PodcastMedium.Parse "newsletter") Newsletter "\"newsletter\" not parsed correctly" - } - test "succeeds for \"blog\"" { - Expect.equal (PodcastMedium.Parse "blog") Blog "\"blog\" not parsed correctly" - } - test "fails for invalid type" { - Expect.throwsT - (fun () -> ignore (PodcastMedium.Parse "laser")) "Invalid value should have raised an exception" - } - ] - testList "ToString" [ - test "succeeds for Podcast" { - Expect.equal (string Podcast) "podcast" "Podcast string incorrect" - } - test "succeeds for Music" { - Expect.equal (string Music) "music" "Music string incorrect" - } - test "succeeds for Video" { - Expect.equal (string Video) "video" "Video string incorrect" - } - test "succeeds for Film" { - Expect.equal (string Film) "film" "Film string incorrect" - } - test "succeeds for Audiobook" { - Expect.equal (string Audiobook) "audiobook" "Audiobook string incorrect" - } - test "succeeds for Newsletter" { - Expect.equal (string Newsletter) "newsletter" "Newsletter string incorrect" - } - test "succeeds for Blog" { - Expect.equal (string Blog) "blog" "Blog string incorrect" - } - ] -] - /// Unit tests for the PostStatus type let postStatusTests = testList "PostStatus" [ testList "Parse" [ @@ -919,16 +727,12 @@ let all = testList "SupportTypes" [ accessLevelTests autoHtmxTypeTests commentStatusTests - explicitRatingTests - episodeTests markupTextTests openGraphAudioTests openGraphImageTests openGraphVideoTests openGraphTypeTests openGraphPropertiesTests - personGroupTests - podcastMediumTests postStatusTests customFeedSourceTests themeAssetIdTests diff --git a/src/MyWebLog.Tests/MyWebLog.Tests.fsproj b/src/MyWebLog.Tests/MyWebLog.Tests.fsproj index 5910123..f5bdaf5 100644 --- a/src/MyWebLog.Tests/MyWebLog.Tests.fsproj +++ b/src/MyWebLog.Tests/MyWebLog.Tests.fsproj @@ -5,6 +5,7 @@ + diff --git a/src/MyWebLog.Tests/Program.fs b/src/MyWebLog.Tests/Program.fs index 2804126..6e7bed0 100644 --- a/src/MyWebLog.Tests/Program.fs +++ b/src/MyWebLog.Tests/Program.fs @@ -15,7 +15,7 @@ let allDatabases = not (rethinkOnly || sqliteOnly || postgresOnly) let allTests = testList "MyWebLog" [ // Skip unit tests if running an isolated database test if allDatabases then - testList "Domain" [ SupportTypesTests.all; DataTypesTests.all; ViewModelsTests.all ] + testList "Domain" [ PodcastTypesTests.all; SupportTypesTests.all; DataTypesTests.all; ViewModelsTests.all ] testList "Data (Unit)" [ ConvertersTests.all; UtilsTests.all ] // Whether to skip integration tests if RethinkDbDataTests.env "UNIT_ONLY" "0" <> "1" then