diff --git a/src/MyWebLog.Data/Postgres/PostgresUploadData.fs b/src/MyWebLog.Data/Postgres/PostgresUploadData.fs index bfacbc9..1ceda0f 100644 --- a/src/MyWebLog.Data/Postgres/PostgresUploadData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresUploadData.fs @@ -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" } diff --git a/src/MyWebLog.Tests/Data/PostgresDataTests.fs b/src/MyWebLog.Tests/Data/PostgresDataTests.fs index 6e1c6ab..9e5c11e 100644 --- a/src/MyWebLog.Tests/Data/PostgresDataTests.fs +++ b/src/MyWebLog.Tests/Data/PostgresDataTests.fs @@ -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(), 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 diff --git a/src/MyWebLog.Tests/Data/RethinkDbDataTests.fs b/src/MyWebLog.Tests/Data/RethinkDbDataTests.fs index 867d542..2897ea2 100644 --- a/src/MyWebLog.Tests/Data/RethinkDbDataTests.fs +++ b/src/MyWebLog.Tests/Data/RethinkDbDataTests.fs @@ -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() 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 diff --git a/src/MyWebLog.Tests/Data/SQLiteDataTests.fs b/src/MyWebLog.Tests/Data/SQLiteDataTests.fs index 7e7dba7..828a07b 100644 --- a/src/MyWebLog.Tests/Data/SQLiteDataTests.fs +++ b/src/MyWebLog.Tests/Data/SQLiteDataTests.fs @@ -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(), 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 diff --git a/src/MyWebLog.Tests/Data/UploadDataTests.fs b/src/MyWebLog.Tests/Data/UploadDataTests.fs new file mode 100644 index 0000000..636a9f6 --- /dev/null +++ b/src/MyWebLog.Tests/Data/UploadDataTests.fs @@ -0,0 +1,95 @@ +/// +/// Integration tests for implementations +/// +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" +} diff --git a/src/MyWebLog.Tests/MyWebLog.Tests.fsproj b/src/MyWebLog.Tests/MyWebLog.Tests.fsproj index 7b0f796..c8a9ef1 100644 --- a/src/MyWebLog.Tests/MyWebLog.Tests.fsproj +++ b/src/MyWebLog.Tests/MyWebLog.Tests.fsproj @@ -15,6 +15,7 @@ + diff --git a/src/MyWebLog.Tests/root-weblog.json b/src/MyWebLog.Tests/root-weblog.json index 2b86eda..ccf1b6a 100644 --- a/src/MyWebLog.Tests/root-weblog.json +++ b/src/MyWebLog.Tests/root-weblog.json @@ -368,5 +368,13 @@ ] } ], - "Uploads": [] + "Uploads": [ + { + "Id": "XweKbWQiOkqqrjEdgP9wwg", + "WebLogId": "uSitJEuD3UyzWC9jgOHc8g", + "Path": "2022/06/favicon.ico", + "UpdatedOn": "2022-06-23T21:15:40Z", + "Data": "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" + } + ] } \ No newline at end of file