Add person data access, integ tests, backup/restore (#17)

This commit is contained in:
2026-08-02 22:09:03 -04:00
parent d9e7c771ba
commit 5c7d34ffc7
16 changed files with 345 additions and 22 deletions
+10 -1
View File
@@ -9,6 +9,14 @@ module Json =
open Newtonsoft.Json open Newtonsoft.Json
/// <summary>Converter for the <see cref="AccessLevel" /> type</summary>
type AccessLevelConverter() =
inherit JsonConverter<AccessLevel>()
override _.WriteJson(writer: JsonWriter, value: AccessLevel, _: JsonSerializer) =
writer.WriteValue(string value)
override _.ReadJson(reader: JsonReader, _: Type, _: AccessLevel, _: bool, _: JsonSerializer) =
(string >> AccessLevel.Parse) reader.Value
/// <summary>Converter for the <see cref="AutoHtmxType" /> type</summary> /// <summary>Converter for the <see cref="AutoHtmxType" /> type</summary>
type AutoHtmxTypeConverter() = type AutoHtmxTypeConverter() =
inherit JsonConverter<AutoHtmxType>() inherit JsonConverter<AutoHtmxType>()
@@ -195,7 +203,8 @@ module Json =
/// <summary>Configure a serializer to use these converters (and other settings)</summary> /// <summary>Configure a serializer to use these converters (and other settings)</summary>
let configure (ser: JsonSerializer) = let configure (ser: JsonSerializer) =
// Our converters // Our converters
[ AutoHtmxTypeConverter() :> JsonConverter [ AccessLevelConverter() :> JsonConverter
AutoHtmxTypeConverter()
CategoryIdConverter() CategoryIdConverter()
CommentIdConverter() CommentIdConverter()
CommentStatusConverter() CommentStatusConverter()
@@ -22,7 +22,7 @@ type PostgresPersonData(log: ILogger) =
Find.byFieldsOrdered<Person> Find.byFieldsOrdered<Person>
Table.Person Table.Person
All All
[ Field.In (nameof Person.Empty.Id) (assignments |> List.map _.PersonId) ] [ Field.In (nameof Person.Empty.Id) (assignments |> List.map (fun it -> string it.PersonId)) ]
[ Field.Named (nameof Person.Empty.Name) ] [ Field.Named (nameof Person.Empty.Name) ]
/// Find a person by their ID for the given web log /// Find a person by their ID for the given web log
@@ -46,7 +46,7 @@ type PostgresPersonData(log: ILogger) =
/// Find all persons for the given web log /// Find all persons for the given web log
let findByWebLog webLogId = let findByWebLog webLogId =
log.LogTrace "Person.findByWebLog" log.LogTrace "Person.findByWebLog"
Find.byContainsOrdered<Person> Table.Person (webLogContains webLogId) [ Field.Named (nameof Person.Empty.Name) ] Find.byContainsOrdered<Person> Table.Person (webLogDoc webLogId) [ Field.Named (nameof Person.Empty.Name) ]
/// Restore persons from a backup /// Restore persons from a backup
+1 -1
View File
@@ -20,7 +20,7 @@ type SQLitePersonData(conn: SqliteConnection, log: ILogger) =
log.LogTrace "Person.findByAssignment" log.LogTrace "Person.findByAssignment"
conn.findByFieldsOrdered<Person> conn.findByFieldsOrdered<Person>
Table.Person All Table.Person All
[ Field.In "PersonId" (assignments |> List.map _.PersonId) ] [ Field.In (nameof Person.Empty.Id) (assignments |> List.map (fun it -> string it.PersonId)) ]
[ Field.Named (nameof Person.Empty.Name) ] [ Field.Named (nameof Person.Empty.Name) ]
/// Find a person by their ID /// Find a person by their ID
+1 -1
View File
@@ -414,7 +414,7 @@ type PersonId =
/// <summary>Create a new person ID</summary> /// <summary>Create a new person ID</summary>
/// <returns>A new person ID</returns> /// <returns>A new person ID</returns>
static member Create = static member Create() =
Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace('/', '_').Replace('+', '-')[..21] |> PersonId Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace('/', '_').Replace('+', '-')[..21] |> PersonId
/// <inheritdoc /> /// <inheritdoc />
+3 -1
View File
@@ -88,7 +88,9 @@ type AccessLevel =
/// <exception cref="InvalidArgumentException">If the string is not valid</exception> /// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse level = static member Parse level =
match level with match level with
// Legacy name; superseded by Author on PersonRole
| "Author" -> ContentAuthor | "Author" -> ContentAuthor
| "ContentAuthor" -> ContentAuthor
| "Editor" -> Editor | "Editor" -> Editor
| "WebLogAdmin" -> WebLogAdmin | "WebLogAdmin" -> WebLogAdmin
| "Administrator" -> Administrator | "Administrator" -> Administrator
@@ -97,7 +99,7 @@ type AccessLevel =
/// <inheritdoc /> /// <inheritdoc />
override this.ToString() = override this.ToString() =
match this with match this with
| ContentAuthor -> "Author" | ContentAuthor -> "ContentAuthor"
| Editor -> "Editor" | Editor -> "Editor"
| WebLogAdmin -> "WebLogAdmin" | WebLogAdmin -> "WebLogAdmin"
| Administrator -> "Administrator" | Administrator -> "Administrator"
@@ -6,6 +6,20 @@ open MyWebLog
open MyWebLog.Converters.Json open MyWebLog.Converters.Json
open Newtonsoft.Json open Newtonsoft.Json
/// Unit tests for the AutoHtmxTypeConverter type
let accessLevelConverterTests = testList "AccessLevelConverter" [
let opts = JsonSerializerSettings()
opts.Converters.Add(AccessLevelConverter())
test "succeeds when serializing" {
let after = JsonConvert.SerializeObject(Editor, opts)
Expect.equal after "\"Editor\"" "Access Level serialized incorrectly"
}
test "succeeds when deserializing" {
let after = JsonConvert.DeserializeObject<AccessLevel>("\"Author\"", opts)
Expect.equal after ContentAuthor "Access Level deserialized incorrectly"
}
]
/// Unit tests for the AutoHtmxTypeConverter type /// Unit tests for the AutoHtmxTypeConverter type
let autoHtmxTypeConverterTests = testList "AutoHtmxTypeConverter" [ let autoHtmxTypeConverterTests = testList "AutoHtmxTypeConverter" [
let opts = JsonSerializerSettings() let opts = JsonSerializerSettings()
@@ -324,6 +338,7 @@ open NodaTime.Serialization.JsonNet
let configureTests = test "Json.configure succeeds" { let configureTests = test "Json.configure succeeds" {
let has typ (converter: JsonConverter) = converter.GetType() = typ let has typ (converter: JsonConverter) = converter.GetType() = typ
let ser = configure (JsonSerializer.Create()) let ser = configure (JsonSerializer.Create())
Expect.hasCountOf ser.Converters 1u (has typeof<AccessLevelConverter>) "Access Level converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<AutoHtmxTypeConverter>) "Auto htmx type converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<AutoHtmxTypeConverter>) "Auto htmx type converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<CategoryIdConverter>) "Category ID converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<CategoryIdConverter>) "Category ID converter not found"
Expect.hasCountOf ser.Converters 1u (has typeof<CommentIdConverter>) "Comment ID converter not found" Expect.hasCountOf ser.Converters 1u (has typeof<CommentIdConverter>) "Comment ID converter not found"
@@ -354,6 +369,7 @@ let configureTests = test "Json.configure succeeds" {
/// All tests for the Data.Converters file /// All tests for the Data.Converters file
let all = testList "Converters" [ let all = testList "Converters" [
accessLevelConverterTests
autoHtmxTypeConverterTests autoHtmxTypeConverterTests
categoryIdConverterTests categoryIdConverterTests
commentIdConverterTests commentIdConverterTests
+104
View File
@@ -0,0 +1,104 @@
/// <summary>Integration tests for <see cref="IPersonData" /> implementations</summary>
module PersonDataTests
open System
open Expecto
open MyWebLog
open MyWebLog.Data
/// The ID of the root web log
let private rootId = CategoryDataTests.rootId
/// The person ID for "Crackpot"
let private crackpotPersonId = PersonId "Q-6ElUj4cEyg5_wQeMpH_A"
/// The person ID for "Buzzkill" (RIP, JCD)
let private buzzkillPersonId = PersonId "a8M4gN58R0evXnJEt9j95w"
let ``Add succeeds`` (data: IData) = task {
let person =
{ Id = PersonId "person-x"
WebLogId = WebLogId "web-log-x"
Name = "Anonny Moose"
Url = None
ImageUrl = Some "sunglasses.jpg" }
do! data.Person.Add person
let! stored = data.Person.FindById person.Id person.WebLogId
Expect.isSome stored "The added person should have been retrieved"
let it = stored.Value
Expect.equal it.Id person.Id "ID not saved properly"
Expect.equal it.WebLogId person.WebLogId "Web Log ID not saved properly"
Expect.equal it.Name person.Name "Name not saved properly"
Expect.equal it.Url person.Url "URL not saved properly"
Expect.equal it.ImageUrl person.ImageUrl "Image URL not saved properly"
}
let ``FindByAssignment succeeds when assignments are found`` (data: IData) = task {
let assignments =
[ { PersonId = crackpotPersonId; Role = Producer }; { PersonId = buzzkillPersonId; Role = Producer } ]
let! people = data.Person.FindByAssignment assignments
Expect.hasLength people 2 "There should have been two people returned"
Expect.equal (people |> List.head).Name "Buzzkill" "The list was not sorted correctly"
}
let ``FindByAssignment succeeds when no assignments are found`` (data: IData) = task {
let assignments =
[ { PersonId = PersonId "missing"; Role = Guest }
{ PersonId = PersonId "still-missing"; Role = GuestHost } ]
let! people = data.Person.FindByAssignment assignments
Expect.hasLength people 0 "There should not have been any people returned"
}
let ``FindById succeeds when a person is found`` (data: IData) = task {
let! person = data.Person.FindById buzzkillPersonId rootId
Expect.isSome person "There should have been a person returned"
let it = person.Value
Expect.equal it.Id buzzkillPersonId "ID retrieved incorrectly"
Expect.equal it.WebLogId rootId "Web Log ID retrieved incorrectly"
Expect.equal it.Name "Buzzkill" "Name retrieved incorrectly"
Expect.isNone it.Url "URL should have been blank"
Expect.isNone it.ImageUrl "Image URL should have been blank"
}
let ``FindById succeeds when a person is not found (incorrect weblog)`` (data: IData) = task {
let! person = data.Person.FindById crackpotPersonId (WebLogId "incorrect")
Expect.isNone person "There should not have been a person returned"
}
let ``FindById succeeds when a person is not found (bad person ID)`` (data: IData) = task {
let! person = data.Person.FindById (PersonId "bad") rootId
Expect.isNone person "There should not have been a person returned"
}
let ``FindByWebLog succeeds when people are found`` (data: IData) = task {
let! people = data.Person.FindByWebLog rootId
Expect.hasLength people 2 "There should have been 2 people returned"
Expect.equal (people |> List.head).Name "Buzzkill" "The people were not sorted correctly"
}
let ``FindByWebLog succeeds when no people are found`` (data: IData) = task {
let! people = data.Person.FindByWebLog (WebLogId "no-folks")
Expect.hasLength people 0 "There should have been no people returned"
}
let ``Update succeeds when person is updated`` (data: IData) = task {
let! person = data.Person.FindById crackpotPersonId rootId
Expect.isSome person "The original person should have been returned"
do! data.Person.Update { person.Value with Name = "Crackpot!" }
let! updated = data.Person.FindById crackpotPersonId rootId
Expect.isSome updated "The updated person should have been returned"
Expect.equal updated.Value.Name "Crackpot!" "Name was not updated"
// Put the name back
do! data.Person.Update person.Value
}
let ``Update succeeds when person is not updated`` (data: IData) = task {
let! person = data.Person.FindById crackpotPersonId rootId
Expect.isSome person "The original person should have been returned"
do! data.Person.Update { person.Value with Name = "New Name"; WebLogId = WebLogId "invalid" }
let! updated = data.Person.FindById crackpotPersonId rootId
Expect.isSome updated "The updated person should have been returned"
Expect.equal updated.Value.Name "Crackpot" "The update should not have occurred"
}
// TODO: write delete tests when they cover podcasts and episodes as well
+12 -3
View File
@@ -1,6 +1,4 @@
/// <summary> /// <summary>Integration tests for <see cref="IPostData" /> implementations</summary>
/// Integration tests for <see cref="IPostData" /> implementations
/// </summary>
module PostDataTests module PostDataTests
open System open System
@@ -39,6 +37,12 @@ let private testPost1Published = Instant.FromDateTimeOffset(DateTimeOffset.Parse
/// The category IDs for "Spitball" (parent) and "Moonshot" /// The category IDs for "Spitball" (parent) and "Moonshot"
let private testCatIds = [ CategoryId "jw6N69YtTEWVHAO33jHU-w"; CategoryId "ScVpyu1e7UiP7bDdge3ZEw" ] let private testCatIds = [ CategoryId "jw6N69YtTEWVHAO33jHU-w"; CategoryId "ScVpyu1e7UiP7bDdge3ZEw" ]
/// The person ID for "Crackpot"
let private crackpotPersonId = PersonId "Q-6ElUj4cEyg5_wQeMpH_A"
/// The person ID for "Buzzkill" (RIP, JCD)
let private buzzkillPersonId = PersonId "a8M4gN58R0evXnJEt9j95w"
/// Ensure that a list of posts has text for each post /// Ensure that a list of posts has text for each post
let private ensureHasText (posts: Post list) = let private ensureHasText (posts: Post list) =
for post in posts do Expect.isNotEmpty post.Text $"Text should not be blank (post ID {post.Id})" for post in posts do Expect.isNotEmpty post.Text $"Text should not be blank (post ID {post.Id})"
@@ -129,6 +133,11 @@ let ``FindById succeeds when a post is found`` (data: IData) = task {
Expect.equal ep.SeasonDescription (Some "The First Season") "Episode season description is incorrect" Expect.equal ep.SeasonDescription (Some "The First Season") "Episode season description is incorrect"
Expect.equal ep.EpisodeNumber (Some 1.) "Episode number is incorrect" Expect.equal ep.EpisodeNumber (Some 1.) "Episode number is incorrect"
Expect.equal ep.EpisodeDescription (Some "The first episode ever!") "Episode description is incorrect" Expect.equal ep.EpisodeDescription (Some "The first episode ever!") "Episode description is incorrect"
Expect.isSome ep.People "There should have been people on the episode"
let ppl = ep.People.Value
Expect.hasLength ppl 2 "There should have been 2 people on the episode"
Expect.contains ppl { PersonId = crackpotPersonId; Role = CoHost } "Crackpot not found on episode"
Expect.contains ppl { PersonId = buzzkillPersonId; Role = VoiceActor } "Buzzkill not found on episode"
Expect.equal Expect.equal
it.Metadata it.Metadata
[ { Name = "Density"; Value = "Non-existent" }; { Name = "Intensity"; Value = "Low" } ] [ { Name = "Density"; Value = "Non-existent" }; { Name = "Intensity"; Value = "Low" } ]
@@ -224,6 +224,40 @@ let private pageTests = testList "Page" [
] ]
] ]
/// Integration tests for the Person implementation in PostgreSQL
let private personTests = testList "Person" [
testTask "Add succeeds" {
do! PersonDataTests.``Add succeeds`` (mkData ())
}
testTask "FindByAssignment succeeds when assignments are found" {
do! PersonDataTests.``FindByAssignment succeeds when assignments are found`` (mkData ())
}
testTask "FindByAssignment succeeds when no assignments are found" {
do! PersonDataTests.``FindByAssignment succeeds when no assignments are found`` (mkData ())
}
testTask "FindById succeeds when a person is found" {
do! PersonDataTests.``FindById succeeds when a person is found`` (mkData ())
}
testTask "FindById succeeds when a person is not found (incorrect weblog)" {
do! PersonDataTests.``FindById succeeds when a person is not found (incorrect weblog)`` (mkData ())
}
testTask "FindById succeeds when a person is not found (bad person ID)" {
do! PersonDataTests.``FindById succeeds when a person is not found (bad person ID)`` (mkData ())
}
testTask "FindByWebLog succeeds when people are found" {
do! PersonDataTests.``FindByWebLog succeeds when people are found`` (mkData ())
}
testTask "FindByWebLog succeeds when no people are found" {
do! PersonDataTests.``FindByWebLog succeeds when no people are found`` (mkData ())
}
testTask "Update succeeds when person is updated" {
do! PersonDataTests.``Update succeeds when person is updated`` (mkData ())
}
testTask "Update succeeds when person is not updated" {
do! PersonDataTests.``Update succeeds when person is not updated`` (mkData ())
}
]
/// Integration tests for the Post implementation in PostgreSQL /// Integration tests for the Post implementation in PostgreSQL
let private postTests = testList "Post" [ let private postTests = testList "Post" [
testTask "Add succeeds" { testTask "Add succeeds" {
@@ -711,6 +745,7 @@ let all =
[ environmentSetUp [ environmentSetUp
categoryTests categoryTests
pageTests pageTests
personTests
postTests postTests
tagMapTests tagMapTests
themeTests themeTests
@@ -218,6 +218,40 @@ let private pageTests = testList "Page" [
] ]
] ]
/// Integration tests for the Post implementation in RethinkDB
let private personTests = testList "Person" [
testTask "Add succeeds" {
do! PersonDataTests.``Add succeeds`` data.Value
}
testTask "FindByAssignment succeeds when assignments are found" {
do! PersonDataTests.``FindByAssignment succeeds when assignments are found`` data.Value
}
testTask "FindByAssignment succeeds when no assignments are found" {
do! PersonDataTests.``FindByAssignment succeeds when no assignments are found`` data.Value
}
testTask "FindById succeeds when a person is found" {
do! PersonDataTests.``FindById succeeds when a person is found`` data.Value
}
testTask "FindById succeeds when a person is not found (incorrect weblog)" {
do! PersonDataTests.``FindById succeeds when a person is not found (incorrect weblog)`` data.Value
}
testTask "FindById succeeds when a person is not found (bad person ID)" {
do! PersonDataTests.``FindById succeeds when a person is not found (bad person ID)`` data.Value
}
testTask "FindByWebLog succeeds when people are found" {
do! PersonDataTests.``FindByWebLog succeeds when people are found`` data.Value
}
testTask "FindByWebLog succeeds when no people are found" {
do! PersonDataTests.``FindByWebLog succeeds when no people are found`` data.Value
}
testTask "Update succeeds when person is updated" {
do! PersonDataTests.``Update succeeds when person is updated`` data.Value
}
testTask "Update succeeds when person is not updated" {
do! PersonDataTests.``Update succeeds when person is not updated`` data.Value
}
]
/// Integration tests for the Post implementation in RethinkDB /// Integration tests for the Post implementation in RethinkDB
let private postTests = testList "Post" [ let private postTests = testList "Post" [
testTask "Add succeeds" { testTask "Add succeeds" {
@@ -693,6 +727,7 @@ let all =
[ environmentSetUp [ environmentSetUp
categoryTests categoryTests
pageTests pageTests
personTests
postTests postTests
tagMapTests tagMapTests
themeTests themeTests
@@ -326,6 +326,60 @@ let private pageTests = testList "Page" [
] ]
] ]
/// Integration tests for the Person implementation in SQLite
let private personTests = testList "Person" [
testTask "Add succeeds" {
let data = mkData ()
try do! PersonDataTests.``Add succeeds`` data
finally dispose data
}
testTask "FindByAssignment succeeds when assignments are found" {
let data = mkData ()
try do! PersonDataTests.``FindByAssignment succeeds when assignments are found`` data
finally dispose data
}
testTask "FindByAssignment succeeds when no assignments are found" {
let data = mkData ()
try do! PersonDataTests.``FindByAssignment succeeds when no assignments are found`` data
finally dispose data
}
testTask "FindById succeeds when a person is found" {
let data = mkData ()
try do! PersonDataTests.``FindById succeeds when a person is found`` data
finally dispose data
}
testTask "FindById succeeds when a person is not found (incorrect weblog)" {
let data = mkData ()
try do! PersonDataTests.``FindById succeeds when a person is not found (incorrect weblog)`` data
finally dispose data
}
testTask "FindById succeeds when a person is not found (bad person ID)" {
let data = mkData ()
try do! PersonDataTests.``FindById succeeds when a person is not found (bad person ID)`` data
finally dispose data
}
testTask "FindByWebLog succeeds when people are found" {
let data = mkData ()
try do! PersonDataTests.``FindByWebLog succeeds when people are found`` data
finally dispose data
}
testTask "FindByWebLog succeeds when no people are found" {
let data = mkData ()
try do! PersonDataTests.``FindByWebLog succeeds when no people are found`` data
finally dispose data
}
testTask "Update succeeds when person is updated" {
let data = mkData ()
try do! PersonDataTests.``Update succeeds when person is updated`` data
finally dispose data
}
testTask "Update succeeds when person is not updated" {
let data = mkData ()
try do! PersonDataTests.``Update succeeds when person is not updated`` data
finally dispose data
}
]
/// Integration tests for the Post implementation in SQLite /// Integration tests for the Post implementation in SQLite
let private postTests = testList "Post" [ let private postTests = testList "Post" [
testTask "Add succeeds" { testTask "Add succeeds" {
@@ -1043,6 +1097,7 @@ let all =
[ environmentSetUp [ environmentSetUp
categoryTests categoryTests
pageTests pageTests
personTests
postTests postTests
tagMapTests tagMapTests
themeTests themeTests
+6 -3
View File
@@ -1,6 +1,4 @@
/// <summary> /// <summary>Integration tests for <see cref="IWebLogData" /> implementations</summary>
/// Integration tests for <see cref="IWebLogData" /> implementations
/// </summary>
module WebLogDataTests module WebLogDataTests
open System open System
@@ -122,6 +120,11 @@ let ``FindById succeeds when a web log is found`` (data: IData) = task {
Expect.equal pod.FundingUrl (Some "https://example.com/support-us") "Podcast funding URL is incorrect" Expect.equal pod.FundingUrl (Some "https://example.com/support-us") "Podcast funding URL is incorrect"
Expect.equal pod.FundingText (Some "Support Our Work") "Podcast funding text is incorrect" Expect.equal pod.FundingText (Some "Support Our Work") "Podcast funding text is incorrect"
Expect.equal pod.Medium (Some Newsletter) "Podcast medium is incorrect" Expect.equal pod.Medium (Some Newsletter) "Podcast medium is incorrect"
Expect.isSome pod.People "There should have been people associated with the podcast"
let ppl = pod.People.Value
Expect.hasLength ppl 2 "There should have been 2 people associatged with the podcast"
Expect.contains ppl { PersonId = PersonId "Q-6ElUj4cEyg5_wQeMpH_A"; Role = Host } "Crackpot not found"
Expect.contains ppl { PersonId = PersonId "a8M4gN58R0evXnJEt9j95w"; Role = Host } "Buzzkill not found :("
} }
let ``FindById succeeds when a web log is not found`` (data: IData) = task { let ``FindById succeeds when a web log is not found`` (data: IData) = task {
@@ -33,6 +33,10 @@ let accessLevelTests = testList "AccessLevel" [
test "succeeds for \"Author\"" { test "succeeds for \"Author\"" {
Expect.equal (AccessLevel.Parse "Author") AccessLevel.ContentAuthor "Author not parsed correctly" Expect.equal (AccessLevel.Parse "Author") AccessLevel.ContentAuthor "Author not parsed correctly"
} }
test "succeeds for \"ContentAuthor\"" {
Expect.equal
(AccessLevel.Parse "ContentAuthor") AccessLevel.ContentAuthor "ContentAuthor not parsed correctly"
}
test "succeeds for \"Editor\"" { test "succeeds for \"Editor\"" {
Expect.equal (AccessLevel.Parse "Editor") Editor "Editor not parsed correctly" Expect.equal (AccessLevel.Parse "Editor") Editor "Editor not parsed correctly"
} }
@@ -49,7 +53,7 @@ let accessLevelTests = testList "AccessLevel" [
] ]
testList "ToString" [ testList "ToString" [
test "ContentAuthor succeeds" { test "ContentAuthor succeeds" {
Expect.equal (string AccessLevel.ContentAuthor) "Author" "ContentAuthor string incorrect" Expect.equal (string AccessLevel.ContentAuthor) "ContentAuthor" "ContentAuthor string incorrect"
} }
test "Editor succeeds" { test "Editor succeeds" {
Expect.equal (string Editor) "Editor" "Editor string incorrect" Expect.equal (string Editor) "Editor" "Editor string incorrect"
+1
View File
@@ -13,6 +13,7 @@
<Compile Include="Data\UtilsTests.fs" /> <Compile Include="Data\UtilsTests.fs" />
<Compile Include="Data\CategoryDataTests.fs" /> <Compile Include="Data\CategoryDataTests.fs" />
<Compile Include="Data\PageDataTests.fs" /> <Compile Include="Data\PageDataTests.fs" />
<Compile Include="Data\PersonDataTests.fs" />
<Compile Include="Data\PostDataTests.fs" /> <Compile Include="Data\PostDataTests.fs" />
<Compile Include="Data\TagMapDataTests.fs" /> <Compile Include="Data\TagMapDataTests.fs" />
<Compile Include="Data\ThemeDataTests.fs" /> <Compile Include="Data\ThemeDataTests.fs" />
+35 -2
View File
@@ -36,7 +36,17 @@
"PodcastGuid": "10fd7f79-c719-4e1d-9da7-10405dd4fd96", "PodcastGuid": "10fd7f79-c719-4e1d-9da7-10405dd4fd96",
"FundingUrl": "https://example.com/support-us", "FundingUrl": "https://example.com/support-us",
"FundingText": "Support Our Work", "FundingText": "Support Our Work",
"Medium": "newsletter" "Medium": "newsletter",
"People": [
{
"PersonId": "Q-6ElUj4cEyg5_wQeMpH_A",
"Role": "Host"
},
{
"PersonId": "a8M4gN58R0evXnJEt9j95w",
"Role": "Host"
}
]
} }
} }
] ]
@@ -200,6 +210,19 @@
] ]
} }
], ],
"People": [
{
"Id": "Q-6ElUj4cEyg5_wQeMpH_A",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Name": "Crackpot",
"Url": "bio.html"
},
{
"Id": "a8M4gN58R0evXnJEt9j95w",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Name": "Buzzkill"
}
],
"Posts": [ "Posts": [
{ {
"Id": "RCsCU2puYEmkpzotoi8p4g", "Id": "RCsCU2puYEmkpzotoi8p4g",
@@ -260,7 +283,17 @@
"SeasonNumber": 1, "SeasonNumber": 1,
"SeasonDescription": "The First Season", "SeasonDescription": "The First Season",
"EpisodeNumber": 1.0, "EpisodeNumber": 1.0,
"EpisodeDescription": "The first episode ever!" "EpisodeDescription": "The first episode ever!",
"People": [
{
"PersonId": "Q-6ElUj4cEyg5_wQeMpH_A",
"Role": "Co-Host"
},
{
"PersonId": "a8M4gN58R0evXnJEt9j95w",
"Role": "Voice Actor"
}
]
}, },
"Metadata": [ "Metadata": [
{ {
+17
View File
@@ -229,6 +229,9 @@ module Backup =
/// The pages for this web log (containing only the most recent revision) /// The pages for this web log (containing only the most recent revision)
Pages: Page list Pages: Page list
/// The person entries for this web log
People: Person list
/// The posts for this web log (containing only the most recent revision) /// The posts for this web log (containing only the most recent revision)
Posts: Post list Posts: Post list
@@ -249,6 +252,7 @@ module Backup =
let categoryCount = List.length archive.Categories let categoryCount = List.length archive.Categories
let tagMapCount = List.length archive.TagMappings let tagMapCount = List.length archive.TagMappings
let pageCount = List.length archive.Pages let pageCount = List.length archive.Pages
let personCount = List.length archive.People
let postCount = List.length archive.Posts let postCount = List.length archive.Posts
let uploadCount = List.length archive.Uploads let uploadCount = List.length archive.Uploads
@@ -263,6 +267,7 @@ module Backup =
printfn $""" - {categoryCount} categor{plural categoryCount "y" "ies"}""" printfn $""" - {categoryCount} categor{plural categoryCount "y" "ies"}"""
printfn $""" - {tagMapCount} tag mapping{plural tagMapCount "" "s"}""" printfn $""" - {tagMapCount} tag mapping{plural tagMapCount "" "s"}"""
printfn $""" - {pageCount} page{plural pageCount "" "s"}""" printfn $""" - {pageCount} page{plural pageCount "" "s"}"""
printfn $""" - {personCount} people"""
printfn $""" - {postCount} post{plural postCount "" "s"}""" printfn $""" - {postCount} post{plural postCount "" "s"}"""
printfn $""" - {uploadCount} uploaded file{plural uploadCount "" "s"}""" printfn $""" - {uploadCount} uploaded file{plural uploadCount "" "s"}"""
@@ -283,6 +288,9 @@ module Backup =
printfn "- Exporting pages..." printfn "- Exporting pages..."
let! pages = data.Page.FindFullByWebLog webLog.Id let! pages = data.Page.FindFullByWebLog webLog.Id
printfn "- Exporting people..."
let! people = data.Person.FindByWebLog webLog.Id
printfn "- Exporting posts..." printfn "- Exporting posts..."
let! posts = data.Post.FindFullByWebLog webLog.Id let! posts = data.Post.FindFullByWebLog webLog.Id
@@ -298,6 +306,7 @@ module Backup =
Categories = categories Categories = categories
TagMappings = tagMaps TagMappings = tagMaps
Pages = pages |> List.map (fun p -> { p with Revisions = List.truncate 1 p.Revisions }) Pages = pages |> List.map (fun p -> { p with Revisions = List.truncate 1 p.Revisions })
People = people
Posts = posts |> List.map (fun p -> { p with Revisions = List.truncate 1 p.Revisions }) Posts = posts |> List.map (fun p -> { p with Revisions = List.truncate 1 p.Revisions })
Uploads = uploads |> List.map EncodedUpload.fromUpload } Uploads = uploads |> List.map EncodedUpload.fromUpload }
@@ -323,11 +332,13 @@ module Backup =
let newCatIds = archive.Categories |> List.map (fun cat -> cat.Id, CategoryId.Create() ) |> dict let newCatIds = archive.Categories |> List.map (fun cat -> cat.Id, CategoryId.Create() ) |> dict
let newMapIds = archive.TagMappings |> List.map (fun tm -> tm.Id, TagMapId.Create() ) |> dict let newMapIds = archive.TagMappings |> List.map (fun tm -> tm.Id, TagMapId.Create() ) |> dict
let newPageIds = archive.Pages |> List.map (fun page -> page.Id, PageId.Create() ) |> dict let newPageIds = archive.Pages |> List.map (fun page -> page.Id, PageId.Create() ) |> dict
let newPersonIds = archive.People |> List.map (fun psn -> psn.Id, PersonId.Create() ) |> dict
let newPostIds = archive.Posts |> List.map (fun post -> post.Id, PostId.Create() ) |> dict let newPostIds = archive.Posts |> List.map (fun post -> post.Id, PostId.Create() ) |> dict
let newUserIds = archive.Users |> List.map (fun user -> user.Id, WebLogUserId.Create()) |> dict let newUserIds = archive.Users |> List.map (fun user -> user.Id, WebLogUserId.Create()) |> dict
let newUpIds = archive.Uploads |> List.map (fun up -> up.Id, UploadId.Create() ) |> dict let newUpIds = archive.Uploads |> List.map (fun up -> up.Id, UploadId.Create() ) |> dict
return return
{ archive with { archive with
// TODO: person IDs should be updated in podcasts and episodes
WebLog = { archive.WebLog with Id = newWebLogId; UrlBase = Option.get newUrlBase } WebLog = { archive.WebLog with Id = newWebLogId; UrlBase = Option.get newUrlBase }
Users = archive.Users Users = archive.Users
|> List.map (fun u -> { u with Id = newUserIds[u.Id]; WebLogId = newWebLogId }) |> List.map (fun u -> { u with Id = newUserIds[u.Id]; WebLogId = newWebLogId })
@@ -341,6 +352,9 @@ module Backup =
Id = newPageIds[page.Id] Id = newPageIds[page.Id]
WebLogId = newWebLogId WebLogId = newWebLogId
AuthorId = newUserIds[page.AuthorId] }) AuthorId = newUserIds[page.AuthorId] })
People = archive.People
|> List.map (fun person ->
{ person with Id = newPersonIds[person.Id]; WebLogId = newWebLogId })
Posts = archive.Posts Posts = archive.Posts
|> List.map (fun post -> |> List.map (fun post ->
{ post with { post with
@@ -381,6 +395,9 @@ module Backup =
if isInteractive then printfn "- Restoring pages..." if isInteractive then printfn "- Restoring pages..."
if not (List.isEmpty restore.Pages) then do! data.Page.Restore restore.Pages if not (List.isEmpty restore.Pages) then do! data.Page.Restore restore.Pages
if isInteractive then printfn "- Restoring people..."
if not (List.isEmpty restore.People) then do! data.Person.Restore restore.People
if isInteractive then printfn "- Restoring posts..." if isInteractive then printfn "- Restoring posts..."
if not (List.isEmpty restore.Posts) then do! data.Post.Restore restore.Posts if not (List.isEmpty restore.Posts) then do! data.Post.Restore restore.Posts