diff --git a/src/MyWebLog.Domain/DataTypes.fs b/src/MyWebLog.Domain/DataTypes.fs index ddd805b..88f6134 100644 --- a/src/MyWebLog.Domain/DataTypes.fs +++ b/src/MyWebLog.Domain/DataTypes.fs @@ -388,6 +388,12 @@ type WebLog = { member this.AbsoluteUrl(permalink: Permalink) = $"{this.UrlBase}/{permalink}" + /// Convert a string URL to an absolute URL for this web log if required + /// The URL which may be translated to an absolute one + /// The given URL if it was already absolute, or a corresponding absolute URL if not + member this.UrlToAbsolute(url: string) = + if url.StartsWith "http" then url else this.AbsoluteUrl(Permalink url) + /// Generate a relative URL for the given link /// The permalink for which a relative URL should be generated /// A relative URL for the given link diff --git a/src/MyWebLog.Domain/SupportTypes.fs b/src/MyWebLog.Domain/SupportTypes.fs index f271343..73864a3 100644 --- a/src/MyWebLog.Domain/SupportTypes.fs +++ b/src/MyWebLog.Domain/SupportTypes.fs @@ -418,9 +418,12 @@ type OpenGraphAudio = { |> dict /// The meta properties for this image - member this.Properties = seq { - yield "og:audio", this.Url - if this.Url.StartsWith "https:" then yield "og:audio:secure_url", this.Url + /// A function to convert relative URLs to absolute URLs + /// A sequence of key/value pairs for this OpenGraph audio file + member this.ToProperties(urlTransform: string -> string) = seq { + let url = urlTransform this.Url + yield "og:audio", url + if url.StartsWith "https:" then yield "og:audio:secure_url", url match this.Type with | Some typ -> yield "og:audio:type", typ | None -> @@ -472,9 +475,12 @@ type OpenGraphImage = { |> dict /// The meta properties for this image - member this.Properties = seq { - yield "og:image", this.Url - if this.Url.StartsWith "https:" then yield "og:image:secure_url", this.Url + /// A function to convert relative URLs to absolute URLs + /// A sequence of key/value pairs for this OpenGraph image file + member this.ToProperties(urlTransform: string -> string) = seq { + let url = urlTransform this.Url + yield "og:image", url + if url.StartsWith "https:" then yield "og:image:secure_url", url match this.Type with | Some typ -> yield "og:image:type", typ | None -> @@ -520,9 +526,12 @@ type OpenGraphVideo = { |> dict /// The meta properties for this video - member this.Properties = seq { - yield "og:video", this.Url - if this.Url.StartsWith "https:" then yield "og:video:secure_url", this.Url + /// A function to convert relative URLs to absolute URLs + /// A sequence of key/value pairs for this OpenGraph video file + member this.ToProperties(urlTransform: string -> string) = seq { + let url = urlTransform this.Url + yield "og:video", url + if url.StartsWith "https:" then yield "og:video:secure_url", url match this.Type with | Some typ -> yield "og:video:type", typ | None -> @@ -633,17 +642,19 @@ type OpenGraphProperties = { Other = None } /// The meta properties for this page or post - member this.Properties = seq { + /// A function to convert relative URLs to absolute URLs + /// A sequence of key/value pairs for this set of OpenGraph properties + member this.ToProperties urlTransform = seq { yield "og:type", string this.Type - yield! this.Image.Properties + yield! this.Image.ToProperties urlTransform match this.Description with Some desc -> yield "og:description", desc | None -> () match this.Determiner with Some det -> yield "og:determiner", det | None -> () match this.Locale with Some loc -> yield "og:locale", loc | None -> () match this.LocaleAlternate with | Some alt -> yield! alt |> List.map (fun it -> "og:locale:alternate", it) | None -> () - match this.Audio with Some audio -> yield! audio.Properties | None -> () - match this.Video with Some video -> yield! video.Properties | None -> () + match this.Audio with Some audio -> yield! audio.ToProperties urlTransform | None -> () + match this.Video with Some video -> yield! video.ToProperties urlTransform | None -> () match this.Other with Some oth -> yield! oth |> List.map (fun it -> it.Name, it.Value) | None -> () } diff --git a/src/MyWebLog.Domain/ViewModels.fs b/src/MyWebLog.Domain/ViewModels.fs index 423f190..2ec79f6 100644 --- a/src/MyWebLog.Domain/ViewModels.fs +++ b/src/MyWebLog.Domain/ViewModels.fs @@ -504,21 +504,18 @@ type EditCommonModel() = post.OpenGraph |> Option.iter this.PopulateOpenGraph /// Convert the properties of the model into a set of OpenGraph properties - /// The current web log - member this.ToOpenGraph(webLog: WebLog) = + member this.ToOpenGraph() = if this.AssignOpenGraph then - let toAbsolute (url: string) = - if url.StartsWith "http" then url else webLog.AbsoluteUrl (Permalink url) let audio = match this.OpenGraphAudioUrl.Trim() with | "" -> None - | url -> Some { OpenGraphAudio.Url = toAbsolute url; Type = noneIfBlank this.OpenGraphAudioType } + | url -> Some { OpenGraphAudio.Url = url; Type = noneIfBlank this.OpenGraphAudioType } let video = match this.OpenGraphVideoUrl.Trim() with | "" -> None | url -> Some { - OpenGraphVideo.Url = toAbsolute url + OpenGraphVideo.Url = url Type = noneIfBlank this.OpenGraphVideoType Width = noneIfBlank this.OpenGraphVideoWidth |> Option.map int Height = noneIfBlank this.OpenGraphVideoHeight |> Option.map int @@ -526,7 +523,7 @@ type EditCommonModel() = Some { Type = if this.OpenGraphType = "" then Article else OpenGraphType.Parse this.OpenGraphType Image = { - Url = toAbsolute this.OpenGraphImageUrl + Url = this.OpenGraphImageUrl Type = noneIfBlank this.OpenGraphImageType Width = noneIfBlank this.OpenGraphImageWidth |> Option.map int Height = noneIfBlank this.OpenGraphImageHeight |> Option.map int @@ -749,10 +746,9 @@ type EditPageModel() = /// Update a page with values from this model /// The page to be updated - /// The web log to which this page belongs /// The Instant to use for this particular update /// The page, updated with the values from this model - member this.UpdatePage (page: Page) webLog now = + member this.UpdatePage (page: Page) now = let revision = { AsOf = now; Text = MarkupText.Parse $"{this.Source}: {this.Text}" } // Detect a permalink change, and add the prior one to the prior list match string page.Permalink with @@ -768,7 +764,7 @@ type EditPageModel() = IsInPageList = this.IsShownInPageList Template = match this.Template with "" -> None | tmpl -> Some tmpl Text = revision.Text.AsHtml() - OpenGraph = this.ToOpenGraph webLog + OpenGraph = this.ToOpenGraph() Metadata = Seq.zip this.MetaNames this.MetaValues |> Seq.filter (fun it -> fst it > "") |> Seq.map (fun it -> { Name = fst it; Value = snd it }) @@ -908,10 +904,9 @@ type EditPostModel() = /// Update a post with values from the submitted form /// The post which should be updated - /// The web log to which this post belongs /// The Instant to use for this particular update /// The post, updated with the values from this model - member this.UpdatePost (post: Post) (webLog: WebLog) now = + member this.UpdatePost (post: Post) now = let revision = { AsOf = now; Text = MarkupText.Parse $"{this.Source}: {this.Text}" } // Detect a permalink change, and add the prior one to the prior list match string post.Permalink with @@ -935,7 +930,7 @@ type EditPostModel() = Template = match this.Template.Trim() with "" -> None | tmpl -> Some tmpl CategoryIds = this.CategoryIds |> Array.map CategoryId |> List.ofArray Status = if this.DoPublish then Published else post.Status - OpenGraph = this.ToOpenGraph webLog + OpenGraph = this.ToOpenGraph() Metadata = Seq.zip this.MetaNames this.MetaValues |> Seq.filter (fun it -> fst it > "") |> Seq.map (fun it -> { Name = fst it; Value = snd it }) diff --git a/src/MyWebLog.Tests/Domain/DataTypesTests.fs b/src/MyWebLog.Tests/Domain/DataTypesTests.fs index 8d04321..5e88437 100644 --- a/src/MyWebLog.Tests/Domain/DataTypesTests.fs +++ b/src/MyWebLog.Tests/Domain/DataTypesTests.fs @@ -34,6 +34,18 @@ let webLogTests = testList "WebLog" [ "https://my.site/blog/page.html" "Absolute URL is incorrect" } + testList "UrlToAbsolute" [ + test "succeeds for relative URL" { + Expect.equal + ({ WebLog.Empty with UrlBase = "https://my.site" }.UrlToAbsolute "blog/page.html") + "https://my.site/blog/page.html" + "Absolute URL is incorrect" + } + test "succeeds for absolute URL" { + Expect.equal + (WebLog.Empty.UrlToAbsolute "https://test.units") "https://test.units" "Absolute URL is incorrect" + } + ] testList "RelativeUrl" [ test "succeeds for domain root URL" { Expect.equal diff --git a/src/MyWebLog.Tests/Domain/SupportTypesTests.fs b/src/MyWebLog.Tests/Domain/SupportTypesTests.fs index 884a471..84a3170 100644 --- a/src/MyWebLog.Tests/Domain/SupportTypesTests.fs +++ b/src/MyWebLog.Tests/Domain/SupportTypesTests.fs @@ -259,67 +259,79 @@ let markupTextTests = testList "MarkupText" [ /// Unit tests for the OpenGraphAudio type let openGraphAudioTests = testList "OpenGraphAudio" [ - testList "Properties" [ + let webLog = { WebLog.Empty with UrlBase = "https://unit.test/taco" } + let transform = webLog.UrlToAbsolute + testList "ToProperties" [ test "succeeds with minimum required" { - let props = Array.ofSeq { OpenGraphAudio.Empty with Url = "http://test.this" }.Properties + let props = Array.ofSeq ({ OpenGraphAudio.Empty with Url = "http://test.this" }.ToProperties transform) Expect.hasLength props 1 "There should be one property" Expect.equal props[0] ("og:audio", "http://test.this") "The URL was not written correctly" } test "succeeds with secure URL" { - let props = Array.ofSeq { OpenGraphAudio.Empty with Url = "https://test.this" }.Properties + let props = Array.ofSeq ({ OpenGraphAudio.Empty with Url = "https://test.this" }.ToProperties transform) Expect.hasLength props 2 "There should be two properties" Expect.equal props[0] ("og:audio", "https://test.this") "The URL was not written correctly" Expect.equal props[1] ("og:audio:secure_url", "https://test.this") "The Secure URL was not written correctly" } test "succeeds with all properties filled" { - let props = Array.ofSeq { Url = "http://test.this"; Type = Some "audio/mpeg" }.Properties + let props = Array.ofSeq ({ Url = "http://test.this"; Type = Some "audio/mpeg" }.ToProperties transform) Expect.hasLength props 2 "There should be two properties" Expect.equal props[0] ("og:audio", "http://test.this") "The URL was not written correctly" Expect.equal props[1] ("og:audio:type", "audio/mpeg") "The MIME type was not written correctly" } - test "succeeds when deriving AAC" { - let props = Array.ofSeq { OpenGraphAudio.Empty with Url = "/this/cool.file.aac" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:audio:type", "audio/aac") "The MIME type for AAC was not derived correctly" + test "succeeds when deriving AAC and transforming URL" { + let props = Array.ofSeq ({ OpenGraphAudio.Empty with Url = "this/cool.file.aac" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal + props[0] + ("og:audio", "https://unit.test/taco/this/cool.file.aac") + "The URL was not transformed correctly" + Expect.equal + props[1] + ("og:audio:secure_url", "https://unit.test/taco/this/cool.file.aac") + "The URL was not transformed correctly" + Expect.equal props[2] ("og:audio:type", "audio/aac") "The MIME type for AAC was not derived correctly" } test "succeeds when deriving MP3" { - let props = Array.ofSeq { OpenGraphAudio.Empty with Url = "/an.other/song.mp3" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:audio:type", "audio/mpeg") "The MIME type for MP3 was not derived correctly" + let props = Array.ofSeq ({ OpenGraphAudio.Empty with Url = "an.other/song.mp3" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:audio:type", "audio/mpeg") "The MIME type for MP3 was not derived correctly" } test "succeeds when deriving OGA" { - let props = Array.ofSeq { OpenGraphAudio.Empty with Url = "/talks/speex.oga" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:audio:type", "audio/ogg") "The MIME type for OGA was not derived correctly" + let props = Array.ofSeq ({ OpenGraphAudio.Empty with Url = "talks/speex.oga" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:audio:type", "audio/ogg") "The MIME type for OGA was not derived correctly" } test "succeeds when deriving WAV" { - let props = Array.ofSeq { OpenGraphAudio.Empty with Url = "/some/old.school.wav" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:audio:type", "audio/wav") "The MIME type for WAV was not derived correctly" + let props = Array.ofSeq ({ OpenGraphAudio.Empty with Url = "some/old.school.wav" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:audio:type", "audio/wav") "The MIME type for WAV was not derived correctly" } test "succeeds when deriving WEBA" { - let props = Array.ofSeq { OpenGraphAudio.Empty with Url = "/new/format/file.weba" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:audio:type", "audio/webm") "The MIME type for WEBA was not derived correctly" + let props = Array.ofSeq ({ OpenGraphAudio.Empty with Url = "new/format/file.weba" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:audio:type", "audio/webm") "The MIME type for WEBA was not derived correctly" } test "succeeds when type cannot be derived" { - let props = Array.ofSeq { OpenGraphAudio.Empty with Url = "/profile.jpg" }.Properties - Expect.hasLength props 1 "There should be one property (only URL; no type derived)" + let props = Array.ofSeq ({ OpenGraphAudio.Empty with Url = "profile.jpg" }.ToProperties transform) + Expect.hasLength props 2 "There should be two properties (only URLs; no type derived)" } ] ] /// Tests for the OpenGraphImage type let openGraphImageTests = testList "OpenGraphImage" [ - testList "Properties" [ + let webLog = { WebLog.Empty with UrlBase = "https://unit.test/taco" } + let transform = webLog.UrlToAbsolute + testList "ToProperties" [ test "succeeds with minimum required" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "http://test.url" }.Properties + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "http://test.url" }.ToProperties transform) Expect.hasLength props 1 "There should be one property" Expect.equal props[0] ("og:image", "http://test.url") "The URL was not written correctly" } test "succeeds with secure URL" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "https://secure.url" }.Properties + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "https://secure.url" }.ToProperties transform) Expect.hasLength props 2 "There should be two properties" Expect.equal props[0] ("og:image", "https://secure.url") "The URL was not written correctly" Expect.equal @@ -331,7 +343,7 @@ let openGraphImageTests = testList "OpenGraphImage" [ Type = Some "image/jpeg" Width = Some 400 Height = Some 600 - Alt = Some "This ought to be good" }.Properties + Alt = Some "This ought to be good" }.ToProperties transform |> Array.ofSeq Expect.hasLength props 5 "There should be five properties" Expect.equal props[0] ("og:image", "http://test.this") "The URL was not written correctly" @@ -340,69 +352,77 @@ let openGraphImageTests = testList "OpenGraphImage" [ Expect.equal props[3] ("og:image:height", "600") "The height was not written correctly" Expect.equal props[4] ("og:image:alt", "This ought to be good") "The alt text was not written correctly" } - test "succeeds when deriving BMP" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/old/windows.bmp" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:image:type", "image/bmp") "The MIME type for BMP was not derived correctly" + test "succeeds when deriving BMP and transforming URL" { + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "old/windows.bmp" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal + props[0] ("og:image", "https://unit.test/taco/old/windows.bmp") "The URL was not transformed correctly" + Expect.equal + props[1] + ("og:image:secure_url", "https://unit.test/taco/old/windows.bmp") + "The URL was not transformed correctly" + Expect.equal props[2] ("og:image:type", "image/bmp") "The MIME type for BMP was not derived correctly" } test "succeeds when deriving GIF" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/its.a.soft.g.gif" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:image:type", "image/gif") "The MIME type for GIF was not derived correctly" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "its.a.soft.g.gif" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:image:type", "image/gif") "The MIME type for GIF was not derived correctly" } test "succeeds when deriving ICO" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/favicon.ico" }.Properties - Expect.hasLength props 2 "There should be two properties" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "favicon.ico" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" Expect.equal - props[1] ("og:image:type", "image/vnd.microsoft.icon") "The MIME type for ICO was not derived correctly" + props[2] ("og:image:type", "image/vnd.microsoft.icon") "The MIME type for ICO was not derived correctly" } test "succeeds when deriving JPEG" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/big/name/photo.jpeg" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:image:type", "image/jpeg") "The MIME type for JPEG was not derived correctly" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "big/name/photo.jpeg" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:image:type", "image/jpeg") "The MIME type for JPEG was not derived correctly" } test "succeeds when deriving PNG" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/some/nice/graphic.png" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:image:type", "image/png") "The MIME type for PNG was not derived correctly" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "some/nice/graphic.png" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:image:type", "image/png") "The MIME type for PNG was not derived correctly" } test "succeeds when deriving SVG" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/fancy-new-vector.svg" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:image:type", "image/svg+xml") "The MIME type for SVG was not derived correctly" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "fancy-new-vector.svg" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:image:type", "image/svg+xml") "The MIME type for SVG was not derived correctly" } test "succeeds when deriving TIF" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/tagged/file.tif" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:image:type", "image/tiff") "The MIME type for TIF was not derived correctly" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "tagged/file.tif" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:image:type", "image/tiff") "The MIME type for TIF was not derived correctly" } test "succeeds when deriving TIFF" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/tagged/file.two.tiff" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:image:type", "image/tiff") "The MIME type for TIFF was not derived correctly" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "tagged/file.two.tiff" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:image:type", "image/tiff") "The MIME type for TIFF was not derived correctly" } test "succeeds when deriving WEBP" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/modern/photo.webp" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:image:type", "image/webp") "The MIME type for WEBP was not derived correctly" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "modern/photo.webp" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:image:type", "image/webp") "The MIME type for WEBP was not derived correctly" } test "succeeds when type cannot be derived" { - let props = Array.ofSeq { OpenGraphImage.Empty with Url = "/intro.mp3" }.Properties - Expect.hasLength props 1 "There should be one property (only URL; no type derived)" + let props = Array.ofSeq ({ OpenGraphImage.Empty with Url = "intro.mp3" }.ToProperties transform) + Expect.hasLength props 2 "There should be two properties (only URLs; no type derived)" } ] ] /// Unit tests for the OpenGraphVideo type let openGraphVideoTests = testList "OpenGraphVideo" [ - testList "Properties" [ + let webLog = { WebLog.Empty with UrlBase = "https://unit.test/taco" } + let transform = webLog.UrlToAbsolute + testList "ToProperties" [ test "succeeds with minimum required" { - let props = Array.ofSeq { OpenGraphVideo.Empty with Url = "http://url.test" }.Properties + let props = Array.ofSeq ({ OpenGraphVideo.Empty with Url = "http://url.test" }.ToProperties transform) Expect.hasLength props 1 "There should be one property" Expect.equal props[0] ("og:video", "http://url.test") "The URL was not written correctly" } test "succeeds with secure URL" { - let props = Array.ofSeq { OpenGraphVideo.Empty with Url = "https://url.secure" }.Properties + let props = Array.ofSeq ({ OpenGraphVideo.Empty with Url = "https://url.secure" }.ToProperties transform) Expect.hasLength props 2 "There should be two properties" Expect.equal props[0] ("og:video", "https://url.secure") "The URL was not written correctly" Expect.equal @@ -413,7 +433,7 @@ let openGraphVideoTests = testList "OpenGraphVideo" [ { Url = "http://test.this" Type = Some "video/mpeg" Width = Some 1200 - Height = Some 900 }.Properties + Height = Some 900 }.ToProperties transform |> Array.ofSeq Expect.hasLength props 4 "There should be five properties" Expect.equal props[0] ("og:video", "http://test.this") "The URL was not written correctly" @@ -421,34 +441,41 @@ let openGraphVideoTests = testList "OpenGraphVideo" [ Expect.equal props[2] ("og:video:width", "1200") "The width was not written correctly" Expect.equal props[3] ("og:video:height", "900") "The height was not written correctly" } - test "succeeds when deriving AVI" { - let props = Array.ofSeq { OpenGraphVideo.Empty with Url = "/my.video.avi" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:video:type", "video/x-msvideo") "The MIME type for AVI was not derived correctly" + test "succeeds when deriving AVI and transforming URL" { + let props = Array.ofSeq ({ OpenGraphVideo.Empty with Url = "my.video.avi" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal + props[0] ("og:video", "https://unit.test/taco/my.video.avi") "The URL not transformed correctly" + Expect.equal + props[1] + ("og:video:secure_url", "https://unit.test/taco/my.video.avi") + "The URL not transformed correctly" + Expect.equal props[2] ("og:video:type", "video/x-msvideo") "The MIME type for AVI was not derived correctly" } test "succeeds when deriving MP4" { - let props = Array.ofSeq { OpenGraphVideo.Empty with Url = "/chapters/1/01.mp4" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:video:type", "video/mp4") "The MIME type for MP4 was not derived correctly" + let props = Array.ofSeq ({ OpenGraphVideo.Empty with Url = "chapters/1/01.mp4" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:video:type", "video/mp4") "The MIME type for MP4 was not derived correctly" } test "succeeds when deriving MPEG" { - let props = Array.ofSeq { OpenGraphVideo.Empty with Url = "/viral/video.mpeg" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:video:type", "video/mpeg") "The MIME type for MPEG was not derived correctly" + let props = Array.ofSeq ({ OpenGraphVideo.Empty with Url = "viral/video.mpeg" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:video:type", "video/mpeg") "The MIME type for MPEG was not derived correctly" } test "succeeds when deriving OGV" { - let props = Array.ofSeq { OpenGraphVideo.Empty with Url = "/open/video/example.ogv" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:video:type", "video/ogg") "The MIME type for OGV was not derived correctly" + let props = + Array.ofSeq ({ OpenGraphVideo.Empty with Url = "open/video/example.ogv" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:video:type", "video/ogg") "The MIME type for OGV was not derived correctly" } test "succeeds when deriving WEBM" { - let props = Array.ofSeq { OpenGraphVideo.Empty with Url = "/images/hero.webm" }.Properties - Expect.hasLength props 2 "There should be two properties" - Expect.equal props[1] ("og:video:type", "video/webm") "The MIME type for WEBM was not derived correctly" + let props = Array.ofSeq ({ OpenGraphVideo.Empty with Url = "images/hero.webm" }.ToProperties transform) + Expect.hasLength props 3 "There should be three properties" + Expect.equal props[2] ("og:video:type", "video/webm") "The MIME type for WEBM was not derived correctly" } test "succeeds when type cannot be derived" { - let props = Array.ofSeq { OpenGraphVideo.Empty with Url = "/favicon.ico" }.Properties - Expect.hasLength props 1 "There should be one property (only URL; no type derived)" + let props = Array.ofSeq ({ OpenGraphVideo.Empty with Url = "favicon.ico" }.ToProperties transform) + Expect.hasLength props 2 "There should be two properties (only URLs; no type derived)" } ] ] @@ -552,7 +579,8 @@ let openGraphPropertiesTests = testList "OpenGraphProperties" [ test "succeeds with minimal values" { let props = { OpenGraphProperties.Empty with - Image = { OpenGraphImage.Empty with Url = "http://this.aint.nothing" } }.Properties + Image = { OpenGraphImage.Empty with Url = "http://this.aint.nothing" } } + .ToProperties WebLog.Empty.UrlToAbsolute |> Array.ofSeq Expect.hasLength props 2 "There should have been two properties" Expect.equal props[0] ("og:type", "article") "Type not written correctly" @@ -568,7 +596,8 @@ let openGraphPropertiesTests = testList "OpenGraphProperties" [ Locale = Some "en_US" LocaleAlternate = Some [ "en_UK"; "es_MX" ] Video = Some { OpenGraphVideo.Empty with Url = "http://this.video.file" } - Other = Some [ { Name = "book.publisher"; Value = "Yep" } ] }.Properties + Other = Some [ { Name = "book.publisher"; Value = "Yep" } ] } + .ToProperties WebLog.Empty.UrlToAbsolute |> Array.ofSeq Expect.hasLength props 10 "There should have been ten properties" Expect.equal props[0] ("og:type", "book") "Type not written correctly" diff --git a/src/MyWebLog.Tests/Domain/ViewModelsTests.fs b/src/MyWebLog.Tests/Domain/ViewModelsTests.fs index b2e4a33..0b51333 100644 --- a/src/MyWebLog.Tests/Domain/ViewModelsTests.fs +++ b/src/MyWebLog.Tests/Domain/ViewModelsTests.fs @@ -438,15 +438,14 @@ let editCommonModelTests = testList "EditCommonModel" [ ] testList "ToOpenGraph" [ test "succeeds when OpenGraph properties are not assigned" { - Expect.isNone - (EditCommonModel().ToOpenGraph WebLog.Empty) "No OpenGraph properties should have returned None" + Expect.isNone (EditCommonModel().ToOpenGraph()) "No OpenGraph properties should have returned None" } test "succeeds when minimal OpenGraph properties are assigned" { let model = EditCommonModel() model.AssignOpenGraph <- true model.OpenGraphType <- string Article model.OpenGraphImageUrl <- "https://unit.test/img.tiff" - let tryOg = model.ToOpenGraph WebLog.Empty + let tryOg = model.ToOpenGraph() Expect.isSome tryOg "There should have been a set of OpenGraph properties returned" let og = tryOg.Value Expect.equal og.Type Article "OpenGraph type not filled correctly" @@ -484,7 +483,7 @@ let editCommonModelTests = testList "EditCommonModel" [ model.OpenGraphVideoHeight <- "768" model.OpenGraphExtraNames <- [| "og:duration"; "og:rating" |] model.OpenGraphExtraValues <- [| "1:30:27"; "G" |] - let tryOg = model.ToOpenGraph WebLog.Empty + let tryOg = model.ToOpenGraph() Expect.isSome tryOg "There should have been a set of OpenGraph properties returned" let og = tryOg.Value Expect.equal og.Type VideoMovie "OpenGraph type not filled correctly" @@ -514,24 +513,6 @@ let editCommonModelTests = testList "EditCommonModel" [ [ { Name = "og:duration"; Value = "1:30:27" }; { Name = "og:rating"; Value = "G" } ] "OpenGraph extra properties not filled properly" } - test "succeeds when relative URLs are assigned" { - let model = EditCommonModel() - model.AssignOpenGraph <- true - model.OpenGraphType <- string Article - model.OpenGraphImageUrl <- "image.jpg" - model.OpenGraphAudioUrl <- "tunes/sound.ogg" - model.OpenGraphVideoUrl <- "teaser.mp4" - let tryOg = model.ToOpenGraph { WebLog.Empty with UrlBase = "https://test.units/verify" } - Expect.isSome tryOg "There should have been a set of OpenGraph properties returned" - let og = tryOg.Value - Expect.equal og.Image.Url "https://test.units/verify/image.jpg" "OpenGraph image URL not filled properly" - Expect.isSome og.Audio "OpenGraph audio should have been filled" - Expect.equal - og.Audio.Value.Url "https://test.units/verify/tunes/sound.ogg" "OpenGraph audio URL not filled properly" - Expect.isSome og.Video "OpenGraph video should have been filled" - Expect.equal - og.Video.Value.Url "https://test.units/verify/teaser.mp4" "OpenGraph video URL not filled properly" - } ] ] @@ -743,7 +724,7 @@ let editPageModelTests = testList "EditPageModel" [ let model = EditPageModel.FromPage testFullPage model.Title <- "Updated Page" model.IsShownInPageList <- false - let page = model.UpdatePage testFullPage WebLog.Empty (Noda.epoch + Duration.FromHours 4) + let page = model.UpdatePage testFullPage (Noda.epoch + Duration.FromHours 4) Expect.equal page.Title "Updated Page" "Title not filled properly" Expect.equal page.Permalink (Permalink "blog/page.html") "Permalink not filled properly" Expect.isEmpty page.PriorPermalinks "PriorPermalinks should be empty" @@ -778,7 +759,7 @@ let editPageModelTests = testList "EditPageModel" [ model.MetaNames <- [| "banana"; "apple"; "grape" |] model.MetaValues <- [| "monkey"; "zebra"; "ape" |] let now = Noda.epoch + Duration.FromDays 7 - let page = model.UpdatePage testFullPage WebLog.Empty now + let page = model.UpdatePage testFullPage now Expect.equal page.Title "My Updated Page" "Title not filled properly" Expect.equal page.Permalink (Permalink "blog/updated.html") "Permalink not filled properly" Expect.equal page.PriorPermalinks [ Permalink "blog/page.html" ] "PriorPermalinks not filled properly" @@ -919,7 +900,7 @@ let editPostModelTests = testList "EditPostModel" [ model testList "UpdatePost" [ test "succeeds for a full podcast episode" { - let post = (updatedModel ()).UpdatePost testFullPost WebLog.Empty (Noda.epoch + Duration.FromDays 400) + let post = (updatedModel ()).UpdatePost testFullPost (Noda.epoch + Duration.FromDays 400) Expect.equal post.Title "An Updated Post" "Title not filled properly" Expect.equal post.Permalink (Permalink "1970/01/updated-post.html") "Permalink not filled properly" Expect.equal post.PriorPermalinks [ Permalink "1970/01/a-post.html" ] "PriorPermalinks not filled properly" @@ -980,7 +961,7 @@ let editPostModelTests = testList "EditPostModel" [ minModel.SeasonDescription <- "" minModel.EpisodeNumber <- "" minModel.EpisodeDescription <- "" - let post = minModel.UpdatePost testFullPost WebLog.Empty (Noda.epoch + Duration.FromDays 500) + let post = minModel.UpdatePost testFullPost (Noda.epoch + Duration.FromDays 500) Expect.isSome post.Episode "There should have been a podcast episode" let ep = post.Episode.Value Expect.equal ep.Media "an-updated-ep.mp3" "Media not filled properly" @@ -1007,7 +988,7 @@ let editPostModelTests = testList "EditPostModel" [ minModel.ChapterSource <- "internal" minModel.ChapterFile <- "" minModel.ChapterType <- "" - let post = minModel.UpdatePost testFullPost WebLog.Empty (Noda.epoch + Duration.FromDays 500) + let post = minModel.UpdatePost testFullPost (Noda.epoch + Duration.FromDays 500) Expect.isSome post.Episode "There should have been a podcast episode" let ep = post.Episode.Value Expect.equal ep.Chapters (Some []) "Chapters not filled properly" @@ -1020,7 +1001,6 @@ let editPostModelTests = testList "EditPostModel" [ let post = minModel.UpdatePost { testFullPost with Episode = Some { testFullPost.Episode.Value with Chapters = Some [] } } - WebLog.Empty (Noda.epoch + Duration.FromDays 500) Expect.isSome post.Episode "There should have been a podcast episode" let ep = post.Episode.Value @@ -1033,15 +1013,14 @@ let editPostModelTests = testList "EditPostModel" [ let model = updatedModel () model.IsEpisode <- false model.Template <- "" - let post = model.UpdatePost testFullPost WebLog.Empty Noda.epoch + let post = model.UpdatePost testFullPost Noda.epoch Expect.isNone post.Template "Template not filled properly" Expect.isNone post.Episode "Episode not filled properly" } test "succeeds when publishing a draft" { let model = updatedModel () model.DoPublish <- true - let post = - model.UpdatePost { testFullPost with Status = Draft } WebLog.Empty (Noda.epoch + Duration.FromDays 375) + let post = model.UpdatePost { testFullPost with Status = Draft } (Noda.epoch + Duration.FromDays 375) Expect.equal post.Status Published "Status not set properly" Expect.equal post.PublishedOn (Some (Noda.epoch + Duration.FromDays 375)) "PublishedOn not set properly" } diff --git a/src/MyWebLog/Handlers/Page.fs b/src/MyWebLog/Handlers/Page.fs index 4f47040..0e47a95 100644 --- a/src/MyWebLog/Handlers/Page.fs +++ b/src/MyWebLog/Handlers/Page.fs @@ -164,7 +164,7 @@ let save : HttpHandler = requireAccess Author >=> fun next ctx -> task { match! tryPage with | Some page when canEdit page.AuthorId ctx -> let updateList = page.IsInPageList <> model.IsShownInPageList - let updatedPage = model.UpdatePage page ctx.WebLog now + let updatedPage = model.UpdatePage page now do! (if model.IsNew then data.Page.Add else data.Page.Update) updatedPage if updateList then do! PageListCache.update ctx do! addMessage ctx { UserMessage.Success with Message = "Page saved successfully" } diff --git a/src/MyWebLog/Handlers/Post.fs b/src/MyWebLog/Handlers/Post.fs index fd20dc8..d6c9039 100644 --- a/src/MyWebLog/Handlers/Post.fs +++ b/src/MyWebLog/Handlers/Post.fs @@ -495,7 +495,7 @@ let save : HttpHandler = requireAccess Author >=> fun next ctx -> task { | Some post when canEdit post.AuthorId ctx -> let priorCats = post.CategoryIds let updatedPost = - model.UpdatePost post ctx.WebLog (Noda.now ()) + model.UpdatePost post (Noda.now ()) |> function | post -> if model.SetPublished then diff --git a/src/MyWebLog/Template.fs b/src/MyWebLog/Template.fs index 39c567d..8f5aae9 100644 --- a/src/MyWebLog/Template.fs +++ b/src/MyWebLog/Template.fs @@ -207,7 +207,7 @@ let parser = |> app.WebLog.AbsoluteUrl |> function url -> writeOgProp ("og:url", url) match if app.IsPage then app.Page.OpenGraph else app.Posts.Posts[0].OpenGraph with - | Some props -> props.Properties |> Seq.iter writeOgProp + | Some props -> props.ToProperties app.WebLog.UrlToAbsolute |> Seq.iter writeOgProp | None -> () writer.WriteLine $"""{s}"""