Version 2.1 #41

Merged
danieljsummers merged 123 commits from version-2.1 into main 2024-03-27 00:13:28 +00:00
7 changed files with 295 additions and 46 deletions
Showing only changes of commit 34315a1aca - Show all commits

View File

@ -1,6 +1,5 @@
namespace MyWebLog.Data.Postgres
open BitBadger.Documents
open BitBadger.Documents.Postgres
open Microsoft.Extensions.Logging
open MyWebLog
@ -41,7 +40,7 @@ type PostgresUploadData(log: ILogger) =
(webLogIdParam webLogId :: idParam)
(fun row -> row.string "path")
if Option.isSome path then
do! Custom.nonQuery (Query.Delete.byId Table.Upload) idParam
do! Custom.nonQuery $"DELETE FROM {Table.Upload} WHERE id = @id" idParam
return Ok path.Value
else return Error $"Upload ID {uploadId} not found"
}

View File

@ -11,33 +11,33 @@ open Npgsql
open ThrowawayDb.Postgres
/// JSON serializer
let ser = Json.configure (JsonSerializer.CreateDefault())
let private ser = Json.configure (JsonSerializer.CreateDefault())
/// The throwaway database (deleted when disposed)
let mutable db: ThrowawayDatabase option = None
let mutable private db: ThrowawayDatabase option = None
/// Create a PostgresData instance for testing
let mkData () =
let private mkData () =
PostgresData(NullLogger<PostgresData>(), ser) :> IData
/// The host for the PostgreSQL test database (defaults to localhost)
let testHost =
let private testHost =
RethinkDbDataTests.env "PG_HOST" "localhost"
/// The database name for the PostgreSQL test database (defaults to postgres)
let testDb =
let private testDb =
RethinkDbDataTests.env "PG_DB" "postgres"
/// The user ID for the PostgreSQL test database (defaults to postgres)
let testUser =
let private testUser =
RethinkDbDataTests.env "PG_USER" "postgres"
/// The password for the PostgreSQL test database (defaults to postgres)
let testPw =
let private testPw =
RethinkDbDataTests.env "PG_PW" "postgres"
/// Create a fresh environment from the root backup
let freshEnvironment () = task {
let private freshEnvironment () = task {
if Option.isSome db then db.Value.Dispose()
db <- Some (ThrowawayDatabase.Create $"Host={testHost};Database={testDb};User ID={testUser};Password={testPw}")
let source = NpgsqlDataSourceBuilder db.Value.ConnectionString
@ -50,12 +50,12 @@ let freshEnvironment () = task {
}
/// Set up the environment for the PostgreSQL tests
let environmentSetUp = testTask "creating database" {
let private environmentSetUp = testTask "creating database" {
do! freshEnvironment ()
}
/// Integration tests for the Category implementation in PostgreSQL
let categoryTests = testList "Category" [
let private categoryTests = testList "Category" [
testTask "Add succeeds" {
do! CategoryDataTests.``Add succeeds`` (mkData ())
}
@ -117,7 +117,7 @@ let categoryTests = testList "Category" [
]
/// Integration tests for the Page implementation in PostgreSQL
let pageTests = testList "Page" [
let private pageTests = testList "Page" [
testTask "Add succeeds" {
do! PageDataTests.``Add succeeds`` (mkData ())
}
@ -219,7 +219,7 @@ let pageTests = testList "Page" [
]
/// Integration tests for the Post implementation in PostgreSQL
let postTests = testList "Post" [
let private postTests = testList "Post" [
testTask "Add succeeds" {
// We'll need the root website categories restored for these tests
do! freshEnvironment ()
@ -358,7 +358,7 @@ let postTests = testList "Post" [
]
]
let tagMapTests = testList "TagMap" [
let private tagMapTests = testList "TagMap" [
testList "FindById" [
testTask "succeeds when a tag mapping is found" {
do! TagMapDataTests.``FindById succeeds when a tag mapping is found`` (mkData ())
@ -416,7 +416,7 @@ let tagMapTests = testList "TagMap" [
]
]
let themeTests = testList "Theme" [
let private themeTests = testList "Theme" [
testTask "All succeeds" {
do! ThemeDataTests.``All succeeds`` (mkData ())
}
@ -462,7 +462,7 @@ let themeTests = testList "Theme" [
]
]
let themeAssetTests = testList "ThemeAsset" [
let private themeAssetTests = testList "ThemeAsset" [
testList "Save" [
testTask "succeeds when adding an asset" {
do! ThemeDataTests.Asset.``Save succeeds when adding an asset`` (mkData ())
@ -508,8 +508,49 @@ let themeAssetTests = testList "ThemeAsset" [
]
]
let private uploadTests = testList "Upload" [
testTask "Add succeeds" {
do! UploadDataTests.``Add succeeds`` (mkData ())
}
testList "FindByPath" [
testTask "succeeds when an upload is found" {
do! UploadDataTests.``FindByPath succeeds when an upload is found`` (mkData ())
}
testTask "succeeds when an upload is not found (incorrect weblog)" {
do! UploadDataTests.``FindByPath succeeds when an upload is not found (incorrect weblog)`` (mkData ())
}
testTask "succeeds when an upload is not found (bad path)" {
do! UploadDataTests.``FindByPath succeeds when an upload is not found (bad path)`` (mkData ())
}
]
testList "FindByWebLog" [
testTask "succeeds when uploads exist" {
do! UploadDataTests.``FindByWebLog succeeds when uploads exist`` (mkData ())
}
testTask "succeeds when no uploads exist" {
do! UploadDataTests.``FindByWebLog succeeds when no uploads exist`` (mkData ())
}
]
testList "FindByWebLogWithData" [
testTask "succeeds when uploads exist" {
do! UploadDataTests.``FindByWebLogWithData succeeds when uploads exist`` (mkData ())
}
testTask "succeeds when no uploads exist" {
do! UploadDataTests.``FindByWebLogWithData succeeds when no uploads exist`` (mkData ())
}
]
testList "Delete" [
testTask "succeeds when an upload is deleted" {
do! UploadDataTests.``Delete succeeds when an upload is deleted`` (mkData ())
}
testTask "succeeds when an upload is not deleted" {
do! UploadDataTests.``Delete succeeds when an upload is not deleted`` (mkData ())
}
]
]
/// Drop the throwaway PostgreSQL database
let environmentCleanUp = test "Clean Up" {
let private environmentCleanUp = test "Clean Up" {
if db.IsSome then db.Value.Dispose()
}
@ -523,5 +564,6 @@ let all =
tagMapTests
themeTests
themeAssetTests
uploadTests
environmentCleanUp ]
|> testSequenced

View File

@ -18,14 +18,14 @@ let env name value =
/// The data configuration for the test database
let dataCfg =
let private dataCfg =
DataConfig.FromUri (env "RETHINK_URI" "rethinkdb://172.17.0.2/mwl_test")
/// The active data instance to use for testing
let mutable data: IData option = None
let mutable private data: IData option = None
/// Dispose the existing data
let disposeData () = task {
let private disposeData () = task {
if data.IsSome then
let conn = (data.Value :?> RethinkDbData).Conn
do! rethink { dbDrop dataCfg.Database; write; withRetryOnce; ignoreResult conn }
@ -34,13 +34,13 @@ let disposeData () = task {
}
/// Create a new data implementation instance
let newData () =
let private newData () =
let log = NullLogger<RethinkDbData>()
let conn = dataCfg.CreateConnection log
RethinkDbData(conn, dataCfg, log)
/// Create a fresh environment from the root backup
let freshEnvironment () = task {
let private freshEnvironment () = task {
do! disposeData ()
data <- Some (newData ())
do! data.Value.StartUp()
@ -49,13 +49,13 @@ let freshEnvironment () = task {
}
/// Set up the environment for the RethinkDB tests
let environmentSetUp = testTask "creating database" {
let private environmentSetUp = testTask "creating database" {
let _ = Json.configure Converter.Serializer
do! freshEnvironment ()
}
/// Integration tests for the Category implementation in RethinkDB
let categoryTests = testList "Category" [
let private categoryTests = testList "Category" [
testTask "Add succeeds" {
do! CategoryDataTests.``Add succeeds`` data.Value
}
@ -117,7 +117,7 @@ let categoryTests = testList "Category" [
]
/// Integration tests for the Page implementation in RethinkDB
let pageTests = testList "Page" [
let private pageTests = testList "Page" [
testTask "Add succeeds" {
do! PageDataTests.``Add succeeds`` data.Value
}
@ -219,7 +219,7 @@ let pageTests = testList "Page" [
]
/// Integration tests for the Post implementation in RethinkDB
let postTests = testList "Post" [
let private postTests = testList "Post" [
testTask "Add succeeds" {
// We'll need the root website categories restored for these tests
do! freshEnvironment ()
@ -358,7 +358,7 @@ let postTests = testList "Post" [
]
]
let tagMapTests = testList "TagMap" [
let private tagMapTests = testList "TagMap" [
testList "FindById" [
testTask "succeeds when a tag mapping is found" {
do! TagMapDataTests.``FindById succeeds when a tag mapping is found`` data.Value
@ -416,7 +416,7 @@ let tagMapTests = testList "TagMap" [
]
]
let themeTests = testList "Theme" [
let private themeTests = testList "Theme" [
testTask "All succeeds" {
do! ThemeDataTests.``All succeeds`` data.Value
}
@ -462,7 +462,7 @@ let themeTests = testList "Theme" [
]
]
let themeAssetTests = testList "ThemeAsset" [
let private themeAssetTests = testList "ThemeAsset" [
testList "Save" [
testTask "succeeds when adding an asset" {
do! ThemeDataTests.Asset.``Save succeeds when adding an asset`` data.Value
@ -508,8 +508,49 @@ let themeAssetTests = testList "ThemeAsset" [
]
]
let private uploadTests = testList "Upload" [
testTask "Add succeeds" {
do! UploadDataTests.``Add succeeds`` data.Value
}
testList "FindByPath" [
testTask "succeeds when an upload is found" {
do! UploadDataTests.``FindByPath succeeds when an upload is found`` data.Value
}
testTask "succeeds when an upload is not found (incorrect weblog)" {
do! UploadDataTests.``FindByPath succeeds when an upload is not found (incorrect weblog)`` data.Value
}
testTask "succeeds when an upload is not found (bad path)" {
do! UploadDataTests.``FindByPath succeeds when an upload is not found (bad path)`` data.Value
}
]
testList "FindByWebLog" [
testTask "succeeds when uploads exist" {
do! UploadDataTests.``FindByWebLog succeeds when uploads exist`` data.Value
}
testTask "succeeds when no uploads exist" {
do! UploadDataTests.``FindByWebLog succeeds when no uploads exist`` data.Value
}
]
testList "FindByWebLogWithData" [
testTask "succeeds when uploads exist" {
do! UploadDataTests.``FindByWebLogWithData succeeds when uploads exist`` data.Value
}
testTask "succeeds when no uploads exist" {
do! UploadDataTests.``FindByWebLogWithData succeeds when no uploads exist`` data.Value
}
]
testList "Delete" [
testTask "succeeds when an upload is deleted" {
do! UploadDataTests.``Delete succeeds when an upload is deleted`` data.Value
}
testTask "succeeds when an upload is not deleted" {
do! UploadDataTests.``Delete succeeds when an upload is not deleted`` data.Value
}
]
]
/// Drop the throwaway RethinkDB database
let environmentCleanUp = testTask "Clean Up" {
let private environmentCleanUp = testTask "Clean Up" {
do! disposeData ()
}
@ -523,5 +564,6 @@ let all =
tagMapTests
themeTests
themeAssetTests
uploadTests
environmentCleanUp ]
|> testSequenced

View File

@ -10,20 +10,20 @@ open MyWebLog.Data
open Newtonsoft.Json
/// JSON serializer
let ser = Json.configure (JsonSerializer.CreateDefault())
let private ser = Json.configure (JsonSerializer.CreateDefault())
/// The test database name
let dbName =
let private dbName =
RethinkDbDataTests.env "SQLITE_DB" "test-db.db"
/// Create a SQLiteData instance for testing
let mkData () =
let private mkData () =
Configuration.useConnectionString $"Data Source=./{dbName}"
let conn = Configuration.dbConn ()
SQLiteData(conn, NullLogger<SQLiteData>(), ser) :> IData
// /// Create a SQLiteData instance for testing
// let mkTraceData () =
// let private mkTraceData () =
// Sqlite.Configuration.useConnectionString $"Data Source=./{dbName}"
// let conn = Sqlite.Configuration.dbConn ()
// let myLogger =
@ -37,11 +37,11 @@ let mkData () =
// SQLiteData(conn, myLogger, ser) :> IData
/// Dispose the connection associated with the SQLiteData instance
let dispose (data: IData) =
let private dispose (data: IData) =
(data :?> SQLiteData).Conn.Dispose()
/// Create a fresh environment from the root backup
let freshEnvironment (data: IData option) = task {
let private freshEnvironment (data: IData option) = task {
let! env = task {
match data with
| Some d ->
@ -63,7 +63,7 @@ let freshEnvironment (data: IData option) = task {
}
/// Set up the environment for the SQLite tests
let environmentSetUp = testList "Environment" [
let private environmentSetUp = testList "Environment" [
testTask "creating database" {
let data = mkData ()
try do! freshEnvironment (Some data)
@ -72,7 +72,7 @@ let environmentSetUp = testList "Environment" [
]
/// Integration tests for the Category implementation in SQLite
let categoryTests = testList "Category" [
let private categoryTests = testList "Category" [
testTask "Add succeeds" {
let data = mkData ()
try do! CategoryDataTests.``Add succeeds`` data
@ -166,7 +166,7 @@ let categoryTests = testList "Category" [
]
/// Integration tests for the Page implementation in SQLite
let pageTests = testList "Page" [
let private pageTests = testList "Page" [
testTask "Add succeeds" {
let data = mkData ()
try do! PageDataTests.``Add succeeds`` data
@ -320,7 +320,7 @@ let pageTests = testList "Page" [
]
/// Integration tests for the Post implementation in SQLite
let postTests = testList "Post" [
let private postTests = testList "Post" [
testTask "Add succeeds" {
// We'll need the root website categories restored for these tests
let! data = freshEnvironment None
@ -530,7 +530,7 @@ let postTests = testList "Post" [
]
]
let tagMapTests = testList "TagMap" [
let private tagMapTests = testList "TagMap" [
testList "FindById" [
testTask "succeeds when a tag mapping is found" {
let data = mkData ()
@ -615,7 +615,7 @@ let tagMapTests = testList "TagMap" [
]
]
let themeTests = testList "Theme" [
let private themeTests = testList "Theme" [
testTask "All succeeds" {
let data = mkData ()
try do! ThemeDataTests.``All succeeds`` data
@ -683,7 +683,7 @@ let themeTests = testList "Theme" [
]
]
let themeAssetTests = testList "ThemeAsset" [
let private themeAssetTests = testList "ThemeAsset" [
testList "Save" [
testTask "succeeds when adding an asset" {
let data = mkData ()
@ -751,8 +751,69 @@ let themeAssetTests = testList "ThemeAsset" [
]
]
let private uploadTests = testList "Upload" [
testTask "Add succeeds" {
let data = mkData ()
try do! UploadDataTests.``Add succeeds`` data
finally dispose data
}
testList "FindByPath" [
testTask "succeeds when an upload is found" {
let data = mkData ()
try do! UploadDataTests.``FindByPath succeeds when an upload is found`` data
finally dispose data
}
testTask "succeeds when an upload is not found (incorrect weblog)" {
let data = mkData ()
try do! UploadDataTests.``FindByPath succeeds when an upload is not found (incorrect weblog)`` data
finally dispose data
}
testTask "succeeds when an upload is not found (bad path)" {
let data = mkData ()
try do! UploadDataTests.``FindByPath succeeds when an upload is not found (bad path)`` data
finally dispose data
}
]
testList "FindByWebLog" [
testTask "succeeds when uploads exist" {
let data = mkData ()
try do! UploadDataTests.``FindByWebLog succeeds when uploads exist`` data
finally dispose data
}
testTask "succeeds when no uploads exist" {
let data = mkData ()
try do! UploadDataTests.``FindByWebLog succeeds when no uploads exist`` data
finally dispose data
}
]
testList "FindByWebLogWithData" [
testTask "succeeds when uploads exist" {
let data = mkData ()
try do! UploadDataTests.``FindByWebLogWithData succeeds when uploads exist`` data
finally dispose data
}
testTask "succeeds when no uploads exist" {
let data = mkData ()
try do! UploadDataTests.``FindByWebLogWithData succeeds when no uploads exist`` data
finally dispose data
}
]
testList "Delete" [
testTask "succeeds when an upload is deleted" {
let data = mkData ()
try do! UploadDataTests.``Delete succeeds when an upload is deleted`` data
finally dispose data
}
testTask "succeeds when an upload is not deleted" {
let data = mkData ()
try do! UploadDataTests.``Delete succeeds when an upload is not deleted`` data
finally dispose data
}
]
]
/// Delete the SQLite database
let environmentCleanUp = test "Clean Up" {
let private environmentCleanUp = test "Clean Up" {
File.Delete dbName
Expect.isFalse (File.Exists dbName) "The test SQLite database should have been deleted"
}
@ -767,5 +828,6 @@ let all =
tagMapTests
themeTests
themeAssetTests
uploadTests
environmentCleanUp ]
|> testSequenced

View File

@ -0,0 +1,95 @@
/// <summary>
/// Integration tests for <see cref="IUploadData" /> implementations
/// </summary>
module UploadDataTests
open System
open System.IO
open Expecto
open MyWebLog
open MyWebLog.Data
open NodaTime
/// The ID of the root web log
let private rootId = CategoryDataTests.rootId
/// The ID of the favicon upload
let private faviconId = UploadId "XweKbWQiOkqqrjEdgP9wwg"
let ``Add succeeds`` (data: IData) = task {
let file = File.ReadAllBytes "../admin-theme/wwwroot/logo-dark.png"
do! data.Upload.Add
{ Id = UploadId "new-upload"
WebLogId = rootId
UpdatedOn = Noda.epoch + Duration.FromDays 30
Path = Permalink "1970/01/logo-dark.png"
Data = file }
let! added = data.Upload.FindByPath "1970/01/logo-dark.png" rootId
Expect.isSome added "There should have been an upload returned"
let upload = added.Value
Expect.equal upload.Id (UploadId "new-upload") "ID is incorrect"
Expect.equal upload.WebLogId rootId "Web log ID is incorrect"
Expect.equal upload.UpdatedOn (Noda.epoch + Duration.FromDays 30) "Updated on is incorrect"
Expect.equal upload.Path (Permalink "1970/01/logo-dark.png") "Path is incorrect"
Expect.equal upload.Data file "Data is incorrect"
}
let ``FindByPath succeeds when an upload is found`` (data: IData) = task {
let! upload = data.Upload.FindByPath "2022/06/favicon.ico" rootId
Expect.isSome upload "There should have been an upload returned"
let it = upload.Value
Expect.equal it.Id faviconId "ID is incorrect"
Expect.equal it.WebLogId rootId "Web log ID is incorrect"
Expect.equal
it.UpdatedOn (Instant.FromDateTimeOffset(DateTimeOffset.Parse "2022-06-23T21:15:40Z")) "Updated on is incorrect"
Expect.equal it.Path (Permalink "2022/06/favicon.ico") "Path is incorrect"
Expect.isNonEmpty it.Data "Data should have been retrieved"
}
let ``FindByPath succeeds when an upload is not found (incorrect weblog)`` (data: IData) = task {
let! upload = data.Upload.FindByPath "2022/06/favicon.ico" (WebLogId "wrong")
Expect.isNone upload "There should not have been an upload returned"
}
let ``FindByPath succeeds when an upload is not found (bad path)`` (data: IData) = task {
let! upload = data.Upload.FindByPath "2022/07/favicon.ico" rootId
Expect.isNone upload "There should not have been an upload returned"
}
let ``FindByWebLog succeeds when uploads exist`` (data: IData) = task {
let! uploads = data.Upload.FindByWebLog rootId
Expect.hasLength uploads 2 "There should have been 2 uploads returned"
for upload in uploads do
Expect.contains [ faviconId; UploadId "new-upload" ] upload.Id $"Unexpected upload returned ({upload.Id})"
Expect.isEmpty upload.Data $"Upload should not have had its data ({upload.Id})"
}
let ``FindByWebLog succeeds when no uploads exist`` (data: IData) = task {
let! uploads = data.Upload.FindByWebLog (WebLogId "nothing")
Expect.isEmpty uploads "There should have been no uploads returned"
}
let ``FindByWebLogWithData succeeds when uploads exist`` (data: IData) = task {
let! uploads = data.Upload.FindByWebLogWithData rootId
Expect.hasLength uploads 2 "There should have been 2 uploads returned"
for upload in uploads do
Expect.contains [ faviconId; UploadId "new-upload" ] upload.Id $"Unexpected upload returned ({upload.Id})"
Expect.isNonEmpty upload.Data $"Upload should have had its data ({upload.Id})"
}
let ``FindByWebLogWithData succeeds when no uploads exist`` (data: IData) = task {
let! uploads = data.Upload.FindByWebLogWithData (WebLogId "data-nope")
Expect.isEmpty uploads "There should have been no uploads returned"
}
let ``Delete succeeds when an upload is deleted`` (data: IData) = task {
match! data.Upload.Delete faviconId rootId with
| Ok path -> Expect.equal path "2022/06/favicon.ico" "The path of the deleted upload was incorrect"
| Error it -> Expect.isTrue false $"Upload deletion should have succeeded (message {it})"
}
let ``Delete succeeds when an upload is not deleted`` (data: IData) = task {
match! data.Upload.Delete faviconId rootId with
| Ok it -> Expect.isTrue false $"Upload deletion should not have succeeded (path {it})"
| Error msg -> Expect.equal msg $"Upload ID {faviconId} not found" "Error message was incorrect"
}

View File

@ -15,6 +15,7 @@
<Compile Include="Data\PostDataTests.fs" />
<Compile Include="Data\TagMapDataTests.fs" />
<Compile Include="Data\ThemeDataTests.fs" />
<Compile Include="Data\UploadDataTests.fs" />
<Compile Include="Data\RethinkDbDataTests.fs" />
<Compile Include="Data\SQLiteDataTests.fs" />
<Compile Include="Data\PostgresDataTests.fs" />

File diff suppressed because one or more lines are too long