Version 2.1 #41

Merged
danieljsummers merged 123 commits from version-2.1 into main 2024-03-27 00:13:28 +00:00
5 changed files with 291 additions and 12 deletions
Showing only changes of commit ff05bc89ed - Show all commits

View File

@ -184,6 +184,9 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
/// The batch size for restoration methods /// The batch size for restoration methods
let restoreBatchSize = 100 let restoreBatchSize = 100
/// A value to use when files need to be retrieved without their data
let emptyFile = r.Binary(Array.Empty<byte>())
/// Delete assets for the given theme ID /// Delete assets for the given theme ID
let deleteAssetsByTheme themeId = rethink { let deleteAssetsByTheme themeId = rethink {
withTable Table.ThemeAsset withTable Table.ThemeAsset
@ -876,7 +879,7 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
member _.All () = rethink<ThemeAsset list> { member _.All () = rethink<ThemeAsset list> {
withTable Table.ThemeAsset withTable Table.ThemeAsset
without [ nameof ThemeAsset.Empty.Data ] merge (r.HashMap(nameof ThemeAsset.Empty.Data, emptyFile))
result; withRetryDefault conn result; withRetryDefault conn
} }
@ -891,7 +894,7 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
member _.FindByTheme themeId = rethink<ThemeAsset list> { member _.FindByTheme themeId = rethink<ThemeAsset list> {
withTable Table.ThemeAsset withTable Table.ThemeAsset
filter (matchAssetByThemeId themeId) filter (matchAssetByThemeId themeId)
without [ nameof ThemeAsset.Empty.Data ] merge (r.HashMap(nameof ThemeAsset.Empty.Data, emptyFile))
result; withRetryDefault conn result; withRetryDefault conn
} }
@ -950,7 +953,7 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
withTable Table.Upload withTable Table.Upload
between [| webLogId :> obj; r.Minval() |] [| webLogId :> obj; r.Maxval() |] between [| webLogId :> obj; r.Minval() |] [| webLogId :> obj; r.Maxval() |]
[ Index Index.WebLogAndPath ] [ Index Index.WebLogAndPath ]
without [ nameof Upload.Empty.Data ] merge (r.HashMap(nameof Upload.Empty.Data, emptyFile))
resultCursor; withRetryCursorDefault; toList conn resultCursor; withRetryCursorDefault; toList conn
} }

View File

@ -462,6 +462,52 @@ let themeTests = testList "Theme" [
] ]
] ]
let themeAssetTests = testList "ThemeAsset" [
testList "Save" [
testTask "succeeds when adding an asset" {
do! ThemeDataTests.Asset.``Save succeeds when adding an asset`` (mkData ())
}
testTask "succeeds when updating an asset" {
do! ThemeDataTests.Asset.``Save succeeds when updating an asset`` (mkData ())
}
]
testTask "All succeeds" {
do! ThemeDataTests.Asset.``All succeeds`` (mkData ())
}
testList "FindById" [
testTask "succeeds when an asset is found" {
do! ThemeDataTests.Asset.``FindById succeeds when an asset is found`` (mkData ())
}
testTask "succeeds when an asset is not found" {
do! ThemeDataTests.Asset.``FindById succeeds when an asset is not found`` (mkData ())
}
]
testList "FindByTheme" [
testTask "succeeds when assets exist" {
do! ThemeDataTests.Asset.``FindByTheme succeeds when assets exist`` (mkData ())
}
testTask "succeeds when assets do not exist" {
do! ThemeDataTests.Asset.``FindByTheme succeeds when assets do not exist`` (mkData ())
}
]
testList "FindByThemeWithData" [
testTask "succeeds when assets exist" {
do! ThemeDataTests.Asset.``FindByThemeWithData succeeds when assets exist`` (mkData ())
}
testTask "succeeds when assets do not exist" {
do! ThemeDataTests.Asset.``FindByThemeWithData succeeds when assets do not exist`` (mkData ())
}
]
testList "DeleteByTheme" [
testTask "succeeds when assets are deleted" {
do! ThemeDataTests.Asset.``DeleteByTheme succeeds when assets are deleted`` (mkData ())
}
testTask "succeeds when no assets are deleted" {
do! ThemeDataTests.Asset.``DeleteByTheme succeeds when no assets are deleted`` (mkData ())
}
]
]
/// Drop the throwaway PostgreSQL database /// Drop the throwaway PostgreSQL database
let environmentCleanUp = test "Clean Up" { let environmentCleanUp = test "Clean Up" {
if db.IsSome then db.Value.Dispose() if db.IsSome then db.Value.Dispose()
@ -476,5 +522,6 @@ let all =
postTests postTests
tagMapTests tagMapTests
themeTests themeTests
themeAssetTests
environmentCleanUp ] environmentCleanUp ]
|> testSequenced |> testSequenced

View File

@ -462,6 +462,52 @@ let themeTests = testList "Theme" [
] ]
] ]
let themeAssetTests = testList "ThemeAsset" [
testList "Save" [
testTask "succeeds when adding an asset" {
do! ThemeDataTests.Asset.``Save succeeds when adding an asset`` data.Value
}
testTask "succeeds when updating an asset" {
do! ThemeDataTests.Asset.``Save succeeds when updating an asset`` data.Value
}
]
testTask "All succeeds" {
do! ThemeDataTests.Asset.``All succeeds`` data.Value
}
testList "FindById" [
testTask "succeeds when an asset is found" {
do! ThemeDataTests.Asset.``FindById succeeds when an asset is found`` data.Value
}
testTask "succeeds when an asset is not found" {
do! ThemeDataTests.Asset.``FindById succeeds when an asset is not found`` data.Value
}
]
testList "FindByTheme" [
testTask "succeeds when assets exist" {
do! ThemeDataTests.Asset.``FindByTheme succeeds when assets exist`` data.Value
}
testTask "succeeds when assets do not exist" {
do! ThemeDataTests.Asset.``FindByTheme succeeds when assets do not exist`` data.Value
}
]
testList "FindByThemeWithData" [
testTask "succeeds when assets exist" {
do! ThemeDataTests.Asset.``FindByThemeWithData succeeds when assets exist`` data.Value
}
testTask "succeeds when assets do not exist" {
do! ThemeDataTests.Asset.``FindByThemeWithData succeeds when assets do not exist`` data.Value
}
]
testList "DeleteByTheme" [
testTask "succeeds when assets are deleted" {
do! ThemeDataTests.Asset.``DeleteByTheme succeeds when assets are deleted`` data.Value
}
testTask "succeeds when no assets are deleted" {
do! ThemeDataTests.Asset.``DeleteByTheme succeeds when no assets are deleted`` data.Value
}
]
]
/// Drop the throwaway RethinkDB database /// Drop the throwaway RethinkDB database
let environmentCleanUp = testTask "Clean Up" { let environmentCleanUp = testTask "Clean Up" {
do! disposeData () do! disposeData ()
@ -476,5 +522,6 @@ let all =
postTests postTests
tagMapTests tagMapTests
themeTests themeTests
themeAssetTests
environmentCleanUp ] environmentCleanUp ]
|> testSequenced |> testSequenced

View File

@ -683,6 +683,74 @@ let themeTests = testList "Theme" [
] ]
] ]
let themeAssetTests = testList "ThemeAsset" [
testList "Save" [
testTask "succeeds when adding an asset" {
let data = mkData ()
try do! ThemeDataTests.Asset.``Save succeeds when adding an asset`` data
finally dispose data
}
testTask "succeeds when updating an asset" {
let data = mkData ()
try do! ThemeDataTests.Asset.``Save succeeds when updating an asset`` data
finally dispose data
}
]
testTask "All succeeds" {
let data = mkData ()
try do! ThemeDataTests.Asset.``All succeeds`` data
finally dispose data
}
testList "FindById" [
testTask "succeeds when an asset is found" {
let data = mkData ()
try do! ThemeDataTests.Asset.``FindById succeeds when an asset is found`` data
finally dispose data
}
testTask "succeeds when an asset is not found" {
let data = mkData ()
try do! ThemeDataTests.Asset.``FindById succeeds when an asset is not found`` data
finally dispose data
}
]
testList "FindByTheme" [
testTask "succeeds when assets exist" {
let data = mkData ()
try do! ThemeDataTests.Asset.``FindByTheme succeeds when assets exist`` data
finally dispose data
}
testTask "succeeds when assets do not exist" {
let data = mkData ()
try do! ThemeDataTests.Asset.``FindByTheme succeeds when assets do not exist`` data
finally dispose data
}
]
testList "FindByThemeWithData" [
testTask "succeeds when assets exist" {
let data = mkData ()
try do! ThemeDataTests.Asset.``FindByThemeWithData succeeds when assets exist`` data
finally dispose data
}
testTask "succeeds when assets do not exist" {
let data = mkData ()
try do! ThemeDataTests.Asset.``FindByThemeWithData succeeds when assets do not exist`` data
finally dispose data
}
]
testList "DeleteByTheme" [
testTask "succeeds when assets are deleted" {
let data = mkData ()
try do! ThemeDataTests.Asset.``DeleteByTheme succeeds when assets are deleted`` data
finally dispose data
}
testTask "succeeds when no assets are deleted" {
let data = mkData ()
try do! ThemeDataTests.Asset.``DeleteByTheme succeeds when no assets are deleted`` data
finally dispose data
}
]
]
/// Delete the SQLite database /// Delete the SQLite database
let environmentCleanUp = test "Clean Up" { let environmentCleanUp = test "Clean Up" {
File.Delete dbName File.Delete dbName
@ -698,5 +766,6 @@ let all =
postTests postTests
tagMapTests tagMapTests
themeTests themeTests
themeAssetTests
environmentCleanUp ] environmentCleanUp ]
|> testSequenced |> testSequenced

View File

@ -3,13 +3,24 @@
/// </summary> /// </summary>
module ThemeDataTests module ThemeDataTests
open System.IO
open Expecto open Expecto
open MyWebLog open MyWebLog
open MyWebLog.Data open MyWebLog.Data
open NodaTime
/// The ID of the default theme (restored from root-weblog.json) /// The ID of the default theme (restored from root-weblog.json)
let private defaultId = ThemeId "default" let private defaultId = ThemeId "default"
/// The ID of the test theme loaded and manipulated by these tests
let private testId = ThemeId "test-theme"
/// The dark version of the myWebLog logo
let private darkFile = File.ReadAllBytes "../admin-theme/wwwroot/logo-dark.png"
/// The light version of the myWebLog logo
let private lightFile = File.ReadAllBytes "../admin-theme/wwwroot/logo-light.png"
/// Ensure that theme templates do not have any text /// Ensure that theme templates do not have any text
let private ensureNoText theme = let private ensureNoText theme =
for template in theme.Templates do for template in theme.Templates do
@ -64,18 +75,17 @@ let ``FindByIdWithoutText succeeds when the theme does not exist`` (data: IData)
} }
let ``Save succeeds when adding a theme`` (data: IData) = task { let ``Save succeeds when adding a theme`` (data: IData) = task {
let themeId = ThemeId "test-theme"
do! data.Theme.Save do! data.Theme.Save
{ Id = themeId { Id = testId
Name = "Test Theme" Name = "Test Theme"
Version = "evergreen" Version = "evergreen"
Templates = Templates =
[ { Name = "index"; Text = "<h1>{{ values_here }}</h1>" } [ { Name = "index"; Text = "<h1>{{ values_here }}</h1>" }
{ Name = "single-post"; Text = "<p>{{ the_post }}" } ] } { Name = "single-post"; Text = "<p>{{ the_post }}" } ] }
let! saved = data.Theme.FindById themeId let! saved = data.Theme.FindById testId
Expect.isSome saved "There should have been a theme returned" Expect.isSome saved "There should have been a theme returned"
let it = saved.Value let it = saved.Value
Expect.equal it.Id themeId "ID was incorrect" Expect.equal it.Id testId "ID was incorrect"
Expect.equal it.Name "Test Theme" "Name was incorrect" Expect.equal it.Name "Test Theme" "Name was incorrect"
Expect.equal it.Version "evergreen" "Version was incorrect" Expect.equal it.Version "evergreen" "Version was incorrect"
Expect.hasLength it.Templates 2 "There should have been 2 templates" Expect.hasLength it.Templates 2 "There should have been 2 templates"
@ -86,19 +96,18 @@ let ``Save succeeds when adding a theme`` (data: IData) = task {
} }
let ``Save succeeds when updating a theme`` (data: IData) = task { let ``Save succeeds when updating a theme`` (data: IData) = task {
let themeId = ThemeId "test-theme"
do! data.Theme.Save do! data.Theme.Save
{ Id = themeId { Id = testId
Name = "Updated Theme" Name = "Updated Theme"
Version = "still evergreen" Version = "still evergreen"
Templates = Templates =
[ { Name = "index"; Text = "<h1>{{ values_there }}</h1>" } [ { Name = "index"; Text = "<h1>{{ values_there }}</h1>" }
{ Name = "layout"; Text = "<!DOCTYPE html><etc />" } { Name = "layout"; Text = "<!DOCTYPE html><etc />" }
{ Name = "single-post"; Text = "<p>{{ the_post }}" } ] } { Name = "single-post"; Text = "<p>{{ the_post }}" } ] }
let! updated = data.Theme.FindById themeId let! updated = data.Theme.FindById testId
Expect.isSome updated "The updated theme should have been returned" Expect.isSome updated "The updated theme should have been returned"
let it = updated.Value let it = updated.Value
Expect.equal it.Id themeId "ID was incorrect" Expect.equal it.Id testId "ID was incorrect"
Expect.equal it.Name "Updated Theme" "Name was incorrect" Expect.equal it.Name "Updated Theme" "Name was incorrect"
Expect.equal it.Version "still evergreen" "Version was incorrect" Expect.equal it.Version "still evergreen" "Version was incorrect"
Expect.hasLength it.Templates 3 "There should have been 3 templates" Expect.hasLength it.Templates 3 "There should have been 3 templates"
@ -111,11 +120,115 @@ let ``Save succeeds when updating a theme`` (data: IData) = task {
} }
let ``Delete succeeds when a theme is deleted`` (data: IData) = task { let ``Delete succeeds when a theme is deleted`` (data: IData) = task {
let! deleted = data.Theme.Delete (ThemeId "test-theme") // Delete should also delete assets associated with the theme
do! data.ThemeAsset.Save { Id = ThemeAssetId (testId, "logo-dark.png"); UpdatedOn = Noda.epoch; Data = darkFile }
do! data.ThemeAsset.Save { Id = ThemeAssetId (testId, "logo-light.png"); UpdatedOn = Noda.epoch; Data = lightFile }
let! deleted = data.Theme.Delete testId
Expect.isTrue deleted "The theme should have been deleted" Expect.isTrue deleted "The theme should have been deleted"
let! assets = data.ThemeAsset.FindByTheme testId
Expect.isEmpty assets "The theme's assets should have been deleted"
} }
let ``Delete succeeds when a theme is not deleted`` (data: IData) = task { let ``Delete succeeds when a theme is not deleted`` (data: IData) = task {
let! deleted = data.Theme.Delete (ThemeId "test-theme") // already deleted above let! deleted = data.Theme.Delete (ThemeId "test-theme") // already deleted above
Expect.isFalse deleted "The theme should not have been deleted" Expect.isFalse deleted "The theme should not have been deleted"
} }
/// <summary>
/// Integration tests for <see cref="IThemeAssetData" /> implementations
/// </summary>
module Asset =
/// The theme ID for which assets will be tested
let private assetThemeId = ThemeId "asset-test"
/// The asset ID for the dark logo
let private darkId = ThemeAssetId (assetThemeId, "logo-dark.png")
/// The asset ID for the light logo
let private lightId = ThemeAssetId (assetThemeId, "logo-light.png")
let ``Save succeeds when adding an asset`` (data: IData) = task {
do! data.Theme.Save { Theme.Empty with Id = assetThemeId }
do! data.ThemeAsset.Save { Id = lightId; UpdatedOn = Noda.epoch + Duration.FromDays 18; Data = lightFile }
let! asset = data.ThemeAsset.FindById lightId
Expect.isSome asset "The asset should have been found"
let it = asset.Value
Expect.equal it.Id lightId "ID was incorrect"
Expect.equal it.UpdatedOn (Noda.epoch + Duration.FromDays 18) "Updated on was incorrect"
Expect.equal it.Data lightFile "Data was incorrect"
}
let ``Save succeeds when updating an asset`` (data: IData) = task {
do! data.ThemeAsset.Save { Id = lightId; UpdatedOn = Noda.epoch + Duration.FromDays 20; Data = darkFile }
let! asset = data.ThemeAsset.FindById lightId
Expect.isSome asset "The asset should have been found"
let it = asset.Value
Expect.equal it.Id lightId "ID was incorrect"
Expect.equal it.UpdatedOn (Noda.epoch + Duration.FromDays 20) "Updated on was incorrect"
Expect.equal it.Data darkFile "Data was incorrect"
}
let ``All succeeds`` (data: IData) = task {
let! all = data.ThemeAsset.All()
Expect.hasLength all 2 "There should have been 2 assets retrieved"
for asset in all do
Expect.contains
[ ThemeAssetId (defaultId, "style.css"); lightId ] asset.Id $"Unexpected asset found ({asset.Id})"
Expect.isEmpty asset.Data $"Asset {asset.Id} should not have had data"
}
let ``FindById succeeds when an asset is found`` (data: IData) = task {
let! asset = data.ThemeAsset.FindById lightId
Expect.isSome asset "The asset should have been found"
let it = asset.Value
Expect.equal it.Id lightId "ID was incorrect"
Expect.equal it.UpdatedOn (Noda.epoch + Duration.FromDays 20) "Updated on was incorrect"
Expect.equal it.Data darkFile "Data was incorrect"
}
let ``FindById succeeds when an asset is not found`` (data: IData) = task {
let! asset = data.ThemeAsset.FindById (ThemeAssetId (assetThemeId, "404.jpg"))
Expect.isNone asset "There should not have been an asset returned"
}
let ``FindByTheme succeeds when assets exist`` (data: IData) = task {
do! data.ThemeAsset.Save { Id = darkId; UpdatedOn = Noda.epoch; Data = darkFile }
do! data.ThemeAsset.Save { Id = lightId; UpdatedOn = Noda.epoch; Data = lightFile }
let! assets = data.ThemeAsset.FindByTheme assetThemeId
Expect.hasLength assets 2 "There should have been 2 assets returned"
for asset in assets do
Expect.contains [ darkId; lightId ] asset.Id $"Unexpected asset found ({asset.Id})"
Expect.equal asset.UpdatedOn Noda.epoch $"Updated on was incorrect ({asset.Id})"
Expect.isEmpty asset.Data $"Data should not have been retrieved ({asset.Id})"
}
let ``FindByTheme succeeds when assets do not exist`` (data: IData) = task {
let! assets = data.ThemeAsset.FindByTheme (ThemeId "no-assets-here")
Expect.isEmpty assets "There should have been no assets returned"
}
let ``FindByThemeWithData succeeds when assets exist`` (data: IData) = task {
let! assets = data.ThemeAsset.FindByThemeWithData assetThemeId
Expect.hasLength assets 2 "There should have been 2 assets returned"
let darkLogo = assets |> List.find (fun it -> it.Id = darkId)
Expect.equal darkLogo.Data darkFile "The dark asset's data is incorrect"
let lightLogo = assets |> List.find (fun it -> it.Id = lightId)
Expect.equal lightLogo.Data lightFile "The light asset's data is incorrect"
}
let ``FindByThemeWithData succeeds when assets do not exist`` (data: IData) = task {
let! assets = data.ThemeAsset.FindByThemeWithData (ThemeId "still-no-assets")
Expect.isEmpty assets "There should have been no assets returned"
}
let ``DeleteByTheme succeeds when assets are deleted`` (data: IData) = task {
do! data.ThemeAsset.DeleteByTheme assetThemeId
let! assets = data.ThemeAsset.FindByTheme assetThemeId
Expect.isEmpty assets "There should be no assets remaining"
}
let ``DeleteByTheme succeeds when no assets are deleted`` (data: IData) = task {
do! data.ThemeAsset.DeleteByTheme assetThemeId // already deleted above
Expect.isTrue true "The above did not raise an exception; that's the test"
}