Support relative URLs in OpenGraph properties (#52)

This commit is contained in:
Daniel J. Summers 2025-08-01 22:16:19 -04:00
parent 8b190a6c23
commit cba1bbfa28
9 changed files with 172 additions and 140 deletions

View File

@ -388,6 +388,12 @@ type WebLog = {
member this.AbsoluteUrl(permalink: Permalink) =
$"{this.UrlBase}/{permalink}"
/// <summary>Convert a string URL to an absolute URL for this web log if required</summary>
/// <param name="url">The URL which may be translated to an absolute one</param>
/// <returns>The given URL if it was already absolute, or a corresponding absolute URL if not</returns>
member this.UrlToAbsolute(url: string) =
if url.StartsWith "http" then url else this.AbsoluteUrl(Permalink url)
/// <summary>Generate a relative URL for the given link</summary>
/// <param name="permalink">The permalink for which a relative URL should be generated</param>
/// <returns>A relative URL for the given link</returns>

View File

@ -418,9 +418,12 @@ type OpenGraphAudio = {
|> dict
/// <summary>The <c>meta</c> properties for this image</summary>
member this.Properties = seq {
yield "og:audio", this.Url
if this.Url.StartsWith "https:" then yield "og:audio:secure_url", this.Url
/// <param name="urlTransform">A function to convert relative URLs to absolute URLs</param>
/// <returns>A sequence of key/value pairs for this OpenGraph audio file</returns>
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
/// <summary>The <c>meta</c> properties for this image</summary>
member this.Properties = seq {
yield "og:image", this.Url
if this.Url.StartsWith "https:" then yield "og:image:secure_url", this.Url
/// <param name="urlTransform">A function to convert relative URLs to absolute URLs</param>
/// <returns>A sequence of key/value pairs for this OpenGraph image file</returns>
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
/// <summary>The <c>meta</c> properties for this video</summary>
member this.Properties = seq {
yield "og:video", this.Url
if this.Url.StartsWith "https:" then yield "og:video:secure_url", this.Url
/// <param name="urlTransform">A function to convert relative URLs to absolute URLs</param>
/// <returns>A sequence of key/value pairs for this OpenGraph video file</returns>
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 }
/// <summary>The <c>meta</c> properties for this page or post</summary>
member this.Properties = seq {
/// <param name="urlTransform">A function to convert relative URLs to absolute URLs</param>
/// <returns>A sequence of key/value pairs for this set of OpenGraph properties</returns>
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 -> ()
}

View File

@ -504,21 +504,18 @@ type EditCommonModel() =
post.OpenGraph |> Option.iter this.PopulateOpenGraph
/// <summary>Convert the properties of the model into a set of OpenGraph properties</summary>
/// <param name="webLog">The current web log</param>
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() =
/// <summary>Update a page with values from this model</summary>
/// <param name="page">The page to be updated</param>
/// <param name="webLog">The web log to which this page belongs</param>
/// <param name="now">The <c>Instant</c> to use for this particular update</param>
/// <returns>The page, updated with the values from this model</returns>
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() =
/// <summary>Update a post with values from the submitted form</summary>
/// <param name="post">The post which should be updated</param>
/// <param name="webLog">The web log to which this post belongs</param>
/// <param name="now">The <c>Instant</c> to use for this particular update</param>
/// <returns>The post, updated with the values from this model</returns>
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 })

View File

@ -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

View File

@ -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"

View File

@ -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"
}

View File

@ -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" }

View File

@ -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

View File

@ -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}<meta name=generator content="{app.Generator}">"""