From 16f7396664cb1b7a8ee6d3ef3b781126f40fbd9d Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 3 Feb 2024 19:45:29 -0500 Subject: [PATCH] Finish user tests --- .../Postgres/PostgresWebLogUserData.fs | 19 ++-- .../SQLite/SQLiteWebLogUserData.fs | 21 ++-- src/MyWebLog.Tests/Data/PostgresDataTests.fs | 48 +++++++++ src/MyWebLog.Tests/Data/RethinkDbDataTests.fs | 48 +++++++++ src/MyWebLog.Tests/Data/SQLiteDataTests.fs | 73 +++++++++++++- .../Data/WebLogUserDataTests.fs | 98 +++++++++++++++++++ src/MyWebLog.Tests/root-weblog.json | 2 +- 7 files changed, 292 insertions(+), 17 deletions(-) diff --git a/src/MyWebLog.Data/Postgres/PostgresWebLogUserData.fs b/src/MyWebLog.Data/Postgres/PostgresWebLogUserData.fs index d919d12..8ccf5be 100644 --- a/src/MyWebLog.Data/Postgres/PostgresWebLogUserData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresWebLogUserData.fs @@ -10,6 +10,11 @@ open Npgsql.FSharp /// PostgreSQL myWebLog user data implementation type PostgresWebLogUserData(log: ILogger) = + /// Add a user + let add (user: WebLogUser) = + log.LogTrace "WebLogUser.add" + insert Table.WebLogUser user + /// Find a user by their ID for the given web log let findById userId webLogId = log.LogTrace "WebLogUser.findById" @@ -52,7 +57,7 @@ type PostgresWebLogUserData(log: ILogger) = /// Find the names of users by their IDs for the given web log let findNames webLogId (userIds: WebLogUserId list) = backgroundTask { log.LogTrace "WebLogUser.findNames" - let idSql, idParams = inClause "AND id" "id" userIds + let idSql, idParams = inClause $"AND data ->> '{nameof WebLogUser.Empty.Id}'" "id" userIds let! users = Custom.list $"{selectWithCriteria Table.WebLogUser} {idSql}" @@ -80,13 +85,13 @@ type PostgresWebLogUserData(log: ILogger) = | false -> () } - /// Save a user - let save (user: WebLogUser) = - log.LogTrace "WebLogUser.save" - save Table.WebLogUser user + /// Update a user + let update (user: WebLogUser) = + log.LogTrace "WebLogUser.update" + Update.byId Table.WebLogUser user.Id user interface IWebLogUserData with - member _.Add user = save user + member _.Add user = add user member _.Delete userId webLogId = delete userId webLogId member _.FindByEmail email webLogId = findByEmail email webLogId member _.FindById userId webLogId = findById userId webLogId @@ -94,4 +99,4 @@ type PostgresWebLogUserData(log: ILogger) = member _.FindNames webLogId userIds = findNames webLogId userIds member _.Restore users = restore users member _.SetLastSeen userId webLogId = setLastSeen userId webLogId - member _.Update user = save user + member _.Update user = update user diff --git a/src/MyWebLog.Data/SQLite/SQLiteWebLogUserData.fs b/src/MyWebLog.Data/SQLite/SQLiteWebLogUserData.fs index f87b6c4..35a9cd5 100644 --- a/src/MyWebLog.Data/SQLite/SQLiteWebLogUserData.fs +++ b/src/MyWebLog.Data/SQLite/SQLiteWebLogUserData.fs @@ -10,6 +10,11 @@ open MyWebLog.Data /// SQLite myWebLog user data implementation type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) = + /// Add a user + let add user = + log.LogTrace "WebLogUser.add" + conn.insert Table.WebLogUser user + /// Find a user by their ID for the given web log let findById userId webLogId = log.LogTrace "WebLogUser.findById" @@ -58,15 +63,10 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) = let user = fromData rdr { Name = string user.Id; Value = user.DisplayName }) - /// Save a user - let save user = - log.LogTrace "WebLogUser.update" - conn.save Table.WebLogUser user - /// Restore users from a backup let restore users = backgroundTask { log.LogTrace "WebLogUser.restore" - for user in users do do! save user + for user in users do do! add user } /// Set a user's last seen date/time to now @@ -77,8 +77,13 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) = | None -> () } + /// Update a user + let update (user: WebLogUser) = + log.LogTrace "WebLogUser.update" + conn.updateById Table.WebLogUser user.Id user + interface IWebLogUserData with - member _.Add user = save user + member _.Add user = add user member _.Delete userId webLogId = delete userId webLogId member _.FindByEmail email webLogId = findByEmail email webLogId member _.FindById userId webLogId = findById userId webLogId @@ -86,4 +91,4 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) = member _.FindNames webLogId userIds = findNames webLogId userIds member _.Restore users = restore users member _.SetLastSeen userId webLogId = setLastSeen userId webLogId - member _.Update user = save user + member _.Update user = update user diff --git a/src/MyWebLog.Tests/Data/PostgresDataTests.fs b/src/MyWebLog.Tests/Data/PostgresDataTests.fs index 87c45fc..5fe858d 100644 --- a/src/MyWebLog.Tests/Data/PostgresDataTests.fs +++ b/src/MyWebLog.Tests/Data/PostgresDataTests.fs @@ -551,6 +551,8 @@ let private uploadTests = testList "Upload" [ let private webLogUserTests = testList "WebLogUser" [ testTask "Add succeeds" { + // This restore ensures all the posts and pages exist + do! freshEnvironment () do! WebLogUserDataTests.``Add succeeds`` (mkData ()) } testList "FindByEmail" [ @@ -575,6 +577,52 @@ let private webLogUserTests = testList "WebLogUser" [ do! WebLogUserDataTests.``FindById succeeds when a user is not found (bad ID)`` (mkData ()) } ] + testList "FindByWebLog" [ + testTask "succeeds when users exist" { + do! WebLogUserDataTests.``FindByWebLog succeeds when users exist`` (mkData ()) + } + testTask "succeeds when no users exist" { + do! WebLogUserDataTests.``FindByWebLog succeeds when no users exist`` (mkData ()) + } + ] + testList "FindNames" [ + testTask "succeeds when users exist" { + do! WebLogUserDataTests.``FindNames succeeds when users exist`` (mkData ()) + } + testTask "succeeds when users do not exist" { + do! WebLogUserDataTests.``FindNames succeeds when users do not exist`` (mkData ()) + } + ] + testList "SetLastSeen" [ + testTask "succeeds when the user exists" { + do! WebLogUserDataTests.``SetLastSeen succeeds when the user exists`` (mkData ()) + } + testTask "succeeds when the user does not exist" { + do! WebLogUserDataTests.``SetLastSeen succeeds when the user does not exist`` (mkData ()) + } + ] + testList "Update" [ + testTask "succeeds when the user exists" { + do! WebLogUserDataTests.``Update succeeds when the user exists`` (mkData ()) + } + testTask "succeeds when the user does not exist" { + do! WebLogUserDataTests.``Update succeeds when the user does not exist`` (mkData ()) + } + ] + testList "Delete" [ + testTask "fails when the user is the author of a page" { + do! WebLogUserDataTests.``Delete fails when the user is the author of a page`` (mkData ()) + } + testTask "fails when the user is the author of a post" { + do! WebLogUserDataTests.``Delete fails when the user is the author of a post`` (mkData ()) + } + testTask "succeeds when the user is not an author" { + do! WebLogUserDataTests.``Delete succeeds when the user is not an author`` (mkData ()) + } + testTask "succeeds when the user does not exist" { + do! WebLogUserDataTests.``Delete succeeds when the user does not exist`` (mkData ()) + } + ] ] /// Drop the throwaway PostgreSQL database diff --git a/src/MyWebLog.Tests/Data/RethinkDbDataTests.fs b/src/MyWebLog.Tests/Data/RethinkDbDataTests.fs index ac8195a..604648a 100644 --- a/src/MyWebLog.Tests/Data/RethinkDbDataTests.fs +++ b/src/MyWebLog.Tests/Data/RethinkDbDataTests.fs @@ -551,6 +551,8 @@ let private uploadTests = testList "Upload" [ let private webLogUserTests = testList "WebLogUser" [ testTask "Add succeeds" { + // This restore ensures all the posts and pages exist + do! freshEnvironment () do! WebLogUserDataTests.``Add succeeds`` data.Value } testList "FindByEmail" [ @@ -575,6 +577,52 @@ let private webLogUserTests = testList "WebLogUser" [ do! WebLogUserDataTests.``FindById succeeds when a user is not found (bad ID)`` data.Value } ] + testList "FindByWebLog" [ + testTask "succeeds when users exist" { + do! WebLogUserDataTests.``FindByWebLog succeeds when users exist`` data.Value + } + testTask "succeeds when no users exist" { + do! WebLogUserDataTests.``FindByWebLog succeeds when no users exist`` data.Value + } + ] + testList "FindNames" [ + testTask "succeeds when users exist" { + do! WebLogUserDataTests.``FindNames succeeds when users exist`` data.Value + } + testTask "succeeds when users do not exist" { + do! WebLogUserDataTests.``FindNames succeeds when users do not exist`` data.Value + } + ] + testList "SetLastSeen" [ + testTask "succeeds when the user exists" { + do! WebLogUserDataTests.``SetLastSeen succeeds when the user exists`` data.Value + } + testTask "succeeds when the user does not exist" { + do! WebLogUserDataTests.``SetLastSeen succeeds when the user does not exist`` data.Value + } + ] + testList "Update" [ + testTask "succeeds when the user exists" { + do! WebLogUserDataTests.``Update succeeds when the user exists`` data.Value + } + testTask "succeeds when the user does not exist" { + do! WebLogUserDataTests.``Update succeeds when the user does not exist`` data.Value + } + ] + testList "Delete" [ + testTask "fails when the user is the author of a page" { + do! WebLogUserDataTests.``Delete fails when the user is the author of a page`` data.Value + } + testTask "fails when the user is the author of a post" { + do! WebLogUserDataTests.``Delete fails when the user is the author of a post`` data.Value + } + testTask "succeeds when the user is not an author" { + do! WebLogUserDataTests.``Delete succeeds when the user is not an author`` data.Value + } + testTask "succeeds when the user does not exist" { + do! WebLogUserDataTests.``Delete succeeds when the user does not exist`` data.Value + } + ] ] /// Drop the throwaway RethinkDB database diff --git a/src/MyWebLog.Tests/Data/SQLiteDataTests.fs b/src/MyWebLog.Tests/Data/SQLiteDataTests.fs index 0f800c7..babc8fa 100644 --- a/src/MyWebLog.Tests/Data/SQLiteDataTests.fs +++ b/src/MyWebLog.Tests/Data/SQLiteDataTests.fs @@ -814,7 +814,8 @@ let private uploadTests = testList "Upload" [ let private webLogUserTests = testList "WebLogUser" [ testTask "Add succeeds" { - let data = mkData () + // This restore ensures all the posts and pages exist + let! data = freshEnvironment None try do! WebLogUserDataTests.``Add succeeds`` data finally dispose data } @@ -852,6 +853,76 @@ let private webLogUserTests = testList "WebLogUser" [ finally dispose data } ] + testList "FindByWebLog" [ + testTask "succeeds when users exist" { + let data = mkData () + try do! WebLogUserDataTests.``FindByWebLog succeeds when users exist`` data + finally dispose data + } + testTask "succeeds when no users exist" { + let data = mkData () + try do! WebLogUserDataTests.``FindByWebLog succeeds when no users exist`` data + finally dispose data + } + ] + testList "FindNames" [ + testTask "succeeds when users exist" { + let data = mkData () + try do! WebLogUserDataTests.``FindNames succeeds when users exist`` data + finally dispose data + } + testTask "succeeds when users do not exist" { + let data = mkData () + try do! WebLogUserDataTests.``FindNames succeeds when users do not exist`` data + finally dispose data + } + ] + testList "SetLastSeen" [ + testTask "succeeds when the user exists" { + let data = mkData () + try do! WebLogUserDataTests.``SetLastSeen succeeds when the user exists`` data + finally dispose data + } + testTask "succeeds when the user does not exist" { + let data = mkData () + try do! WebLogUserDataTests.``SetLastSeen succeeds when the user does not exist`` data + finally dispose data + } + ] + testList "Update" [ + testTask "succeeds when the user exists" { + let data = mkData () + try do! WebLogUserDataTests.``Update succeeds when the user exists`` data + finally dispose data + } + testTask "succeeds when the user does not exist" { + let data = mkData () + try do! WebLogUserDataTests.``Update succeeds when the user does not exist`` data + finally dispose data + } + ] + testList "Delete" [ + testTask "fails when the user is the author of a page" { + let data = mkData () + try do! WebLogUserDataTests.``Delete fails when the user is the author of a page`` data + finally dispose data + } + testTask "fails when the user is the author of a post" { + let data = mkData () + try do! WebLogUserDataTests.``Delete fails when the user is the author of a post`` data + finally dispose data + } + testTask "succeeds when the user is not an author" { + let data = mkData () + try do! WebLogUserDataTests.``Delete succeeds when the user is not an author`` data + finally dispose data + } + testTask "succeeds when the user does not exist" { + let data = mkData () + try do! WebLogUserDataTests.``Delete succeeds when the user does not exist`` data + finally dispose data + } + ] ] /// Delete the SQLite database diff --git a/src/MyWebLog.Tests/Data/WebLogUserDataTests.fs b/src/MyWebLog.Tests/Data/WebLogUserDataTests.fs index e3deb6b..ab5aa1f 100644 --- a/src/MyWebLog.Tests/Data/WebLogUserDataTests.fs +++ b/src/MyWebLog.Tests/Data/WebLogUserDataTests.fs @@ -84,3 +84,101 @@ let ``FindById succeeds when a user is not found (bad ID)`` (data: IData) = task let! user = data.WebLogUser.FindById (WebLogUserId "tom") rootId Expect.isNone user "There should not have been a user returned" } + +let ``FindByWebLog succeeds when users exist`` (data: IData) = task { + let! users = data.WebLogUser.FindByWebLog rootId + Expect.hasLength users 4 "There should have been 4 users returned" + for user in users do + Expect.contains [ adminId; editorId; authorId; newId ] user.Id $"Unexpected user returned ({user.Id})" +} + +let ``FindByWebLog succeeds when no users exist`` (data: IData) = task { + let! users = data.WebLogUser.FindByWebLog (WebLogId "no-users") + Expect.isEmpty users "There should have been no users returned" +} + +let ``FindNames succeeds when users exist`` (data: IData) = task { + let! names = data.WebLogUser.FindNames rootId [ editorId; authorId ] + let expected = + [ { Name = string editorId; Value = "Edits It-Or" }; { Name = string authorId; Value = "Mister Dude" } ] + Expect.hasLength names 2 "There should have been 2 names returned" + for name in names do Expect.contains expected name $"Unexpected name returned ({name.Name}|{name.Value})" +} + +let ``FindNames succeeds when users do not exist`` (data: IData) = task { + let! names = data.WebLogUser.FindNames rootId [ WebLogUserId "nope"; WebLogUserId "no" ] + Expect.isEmpty names "There should have been no names returned" +} + +let ``SetLastSeen succeeds when the user exists`` (data: IData) = task { + let now = Noda.now () + do! data.WebLogUser.SetLastSeen newId rootId + let! user = data.WebLogUser.FindById newId rootId + Expect.isSome user "The user should have been returned" + let it = user.Value + Expect.isSome it.LastSeenOn "Last seen on should have been set" + Expect.isGreaterThanOrEqual it.LastSeenOn.Value now "The last seen on date/time was not set correctly" +} + +let ``SetLastSeen succeeds when the user does not exist`` (data: IData) = task { + do! data.WebLogUser.SetLastSeen (WebLogUserId "matt") rootId + Expect.isTrue true "This not raising an exception is the test" +} + +let ``Update succeeds when the user exists`` (data: IData) = task { + let! currentUser = data.WebLogUser.FindById newId rootId + Expect.isSome currentUser "The current user should have been found" + do! data.WebLogUser.Update + { currentUser.Value with + Email = "newish@example.com" + FirstName = "New-ish" + LastName = "User-ish" + PreferredName = "n00b-ish" + PasswordHash = "hashed-ish-password" + Url = None + AccessLevel = Editor } + let! updated = data.WebLogUser.FindById newId rootId + Expect.isSome updated "The updated user should have been returned" + let it = updated.Value + Expect.equal it.Id newId "ID is incorrect" + Expect.equal it.WebLogId rootId "Web log ID is incorrect" + Expect.equal it.Email "newish@example.com" "E-mail address is incorrect" + Expect.equal it.FirstName "New-ish" "First name is incorrect" + Expect.equal it.LastName "User-ish" "Last name is incorrect" + Expect.equal it.PreferredName "n00b-ish" "Preferred name is incorrect" + Expect.equal it.PasswordHash "hashed-ish-password" "Password hash is incorrect" + Expect.isNone it.Url "URL is incorrect" + Expect.equal it.AccessLevel Editor "Access level is incorrect" + Expect.equal it.CreatedOn (Noda.epoch + Duration.FromDays 365) "Created on is incorrect" + Expect.isSome it.LastSeenOn "Last seen on should have had a value" +} + +let ``Update succeeds when the user does not exist`` (data: IData) = task { + do! data.WebLogUser.Update { WebLogUser.Empty with Id = WebLogUserId "nothing"; WebLogId = rootId } + let! updated = data.WebLogUser.FindById (WebLogUserId "nothing") rootId + Expect.isNone updated "The update of a missing user should not have created the user" +} + +let ``Delete fails when the user is the author of a page`` (data: IData) = task { + match! data.WebLogUser.Delete adminId rootId with + | Ok _ -> Expect.isTrue false "Deletion should have failed because the user is a page author" + | Error msg -> Expect.equal msg "User has pages or posts; cannot delete" "Error message is incorrect" +} + +let ``Delete fails when the user is the author of a post`` (data: IData) = task { + match! data.WebLogUser.Delete authorId rootId with + | Ok _ -> Expect.isTrue false "Deletion should have failed because the user is a post author" + | Error msg -> Expect.equal msg "User has pages or posts; cannot delete" "Error message is incorrect" +} + +let ``Delete succeeds when the user is not an author`` (data: IData) = task { + match! data.WebLogUser.Delete newId rootId with + | Ok _ -> Expect.isTrue true "This is the expected outcome" + | Error msg -> Expect.isTrue false $"Deletion unexpectedly failed (message {msg})" +} + +let ``Delete succeeds when the user does not exist`` (data: IData) = task { + match! data.WebLogUser.Delete newId rootId with // already deleted above + | Ok _ -> Expect.isTrue false "Deletion should have failed because the user does not exist" + | Error msg -> Expect.equal msg "User does not exist" "Error message is incorrect" +} diff --git a/src/MyWebLog.Tests/root-weblog.json b/src/MyWebLog.Tests/root-weblog.json index ccf1b6a..9c820c8 100644 --- a/src/MyWebLog.Tests/root-weblog.json +++ b/src/MyWebLog.Tests/root-weblog.json @@ -285,7 +285,7 @@ { "Id": "l4_Eh4aFO06SqqJjOymNzA", "WebLogId": "uSitJEuD3UyzWC9jgOHc8g", - "AuthorId": "5EM2rimH9kONpmd2zQkiVA", + "AuthorId": "iIRNLSeY0EanxRPyqGuwVg", "Status": "Published", "Title": "Episode 2", "Permalink": "2024/episode-2.html",