Add Category tests, implement for SQLite
This commit is contained in:
		
							parent
							
								
									3835ed984e
								
							
						
					
					
						commit
						182b33ae79
					
				| @ -80,13 +80,21 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge | ||||
|         | Some cat -> | ||||
|             // Reassign any children to the category's parent category | ||||
|             let! children = conn.countByField Table.Category parentIdField EQ (string catId) | ||||
|             if children > 0 then | ||||
|                 do! conn.patchByField Table.Category parentIdField EQ (string catId) {| ParentId = cat.ParentId |} | ||||
|             if children > 0L then | ||||
|                 match cat.ParentId with | ||||
|                 | Some _ -> | ||||
|                     do! conn.patchByField Table.Category parentIdField EQ (string catId) {| ParentId = cat.ParentId |} | ||||
|                 | None -> | ||||
|                     do! conn.customNonQuery | ||||
|                             $"""UPDATE {Table.Category} | ||||
|                                    SET data = json_remove(data, '$.ParentId') | ||||
|                                  WHERE {Query.whereByField parentIdField EQ "@field"}""" | ||||
|                             [ fieldParam (string catId) ] | ||||
|             // Delete the category off all posts where it is assigned, and the category itself | ||||
|             let catIdField = nameof Post.Empty.CategoryIds | ||||
|             let! posts = | ||||
|                 conn.customList | ||||
|                     $"SELECT data ->> '{Post.Empty.Id}', data -> '{catIdField}' | ||||
|                     $"SELECT data ->> '{nameof Post.Empty.Id}', data -> '{catIdField}' | ||||
|                         FROM {Table.Post} | ||||
|                        WHERE {Document.Query.whereByWebLog} | ||||
|                          AND EXISTS | ||||
|  | ||||
| @ -4,8 +4,14 @@ open Expecto | ||||
| open MyWebLog | ||||
| open MyWebLog.Data | ||||
| 
 | ||||
| /// The ID of the root web log | ||||
| let rootId = WebLogId "uSitJEuD3UyzWC9jgOHc8g" | ||||
| 
 | ||||
| /// The ID of the Favorites category | ||||
| let favoritesId = CategoryId "S5JflPsJ9EG7gA2LD4m92A" | ||||
| 
 | ||||
| /// Tests for the Add method | ||||
| let addTests (data: IData) = task { | ||||
| let ``Add succeeds`` (data: IData) = task { | ||||
|     let category = | ||||
|         { Category.Empty with Id = CategoryId "added-cat"; WebLogId = WebLogId "test"; Name = "Added"; Slug = "added" } | ||||
|     do! data.Category.Add category  | ||||
| @ -13,3 +19,130 @@ let addTests (data: IData) = task { | ||||
|     Expect.isSome stored "The category should have been added" | ||||
| } | ||||
| 
 | ||||
| let ``CountAll succeeds when categories exist`` (data: IData) = task { | ||||
|     let! count = data.Category.CountAll rootId | ||||
|     Expect.equal count 3 "There should have been 3 categories" | ||||
| } | ||||
| 
 | ||||
| let ``CountAll succeeds when categories do not exist`` (data: IData) = task { | ||||
|     let! count = data.Category.CountAll WebLogId.Empty | ||||
|     Expect.equal count 0 "There should have been no categories" | ||||
| } | ||||
| 
 | ||||
| let ``CountTopLevel succeeds when top-level categories exist`` (data: IData) = task { | ||||
|     let! count = data.Category.CountTopLevel rootId | ||||
|     Expect.equal count 2 "There should have been 2 top-level categories" | ||||
| } | ||||
| 
 | ||||
| let ``CountTopLevel succeeds when no top-level categories exist`` (data: IData) = task { | ||||
|     let! count = data.Category.CountTopLevel WebLogId.Empty | ||||
|     Expect.equal count 0 "There should have been no top-level categories" | ||||
| } | ||||
| 
 | ||||
| let ``FindAllForView succeeds`` (data: IData) = task { | ||||
|     let! all = data.Category.FindAllForView rootId | ||||
|     Expect.equal all.Length 3 "There should have been 3 categories returned" | ||||
|     Expect.equal all[0].Name "Favorites" "The first category is incorrect" | ||||
|     Expect.equal all[0].PostCount 1 "There should be one post in this category" | ||||
|     Expect.equal all[1].Name "Spitball" "The second category is incorrect" | ||||
|     Expect.equal all[1].PostCount 2 "There should be two posts in this category" | ||||
|     Expect.equal all[2].Name "Moonshot" "The third category is incorrect" | ||||
|     Expect.equal all[2].PostCount 1 "There should be one post in this category" | ||||
| } | ||||
| 
 | ||||
| let ``FindById succeeds when a category is found`` (data: IData) = task { | ||||
|     let! cat = data.Category.FindById favoritesId rootId | ||||
|     Expect.isSome cat "There should have been a category returned" | ||||
|     Expect.equal cat.Value.Name "Favorites" "The category retrieved is incorrect" | ||||
|     Expect.equal cat.Value.Slug "favorites" "The slug is incorrect" | ||||
|     Expect.equal cat.Value.Description (Some "Favorite posts") "The description is incorrect" | ||||
|     Expect.isNone cat.Value.ParentId "There should have been no parent ID" | ||||
| } | ||||
| 
 | ||||
| let ``FindById succeeds when a category is not found`` (data: IData) = task { | ||||
|     let! cat = data.Category.FindById CategoryId.Empty rootId | ||||
|     Expect.isNone cat "There should not have been a category returned" | ||||
| } | ||||
| 
 | ||||
| let ``FindByWebLog succeeds when categories exist`` (data: IData) = task { | ||||
|     let! cats = data.Category.FindByWebLog rootId | ||||
|     Expect.equal cats.Length 3 "There should be 3 categories" | ||||
|     Expect.exists cats (fun it -> it.Name = "Favorites") "Favorites category not found" | ||||
|     Expect.exists cats (fun it -> it.Name = "Spitball") "Spitball category not found" | ||||
|     Expect.exists cats (fun it -> it.Name = "Moonshot") "Moonshot category not found" | ||||
| } | ||||
| 
 | ||||
| let ``FindByWebLog succeeds when no categories exist`` (data: IData) = task { | ||||
|     let! cats = data.Category.FindByWebLog WebLogId.Empty | ||||
|     Expect.isEmpty cats "There should have been no categories returned" | ||||
| } | ||||
| 
 | ||||
| let ``Update succeeds`` (data: IData) = task { | ||||
|     match! data.Category.FindById favoritesId rootId with | ||||
|     | Some cat -> | ||||
|         do! data.Category.Update { cat with Name = "My Favorites"; Slug = "my-favorites"; Description = None } | ||||
|         match! data.Category.FindById favoritesId rootId with | ||||
|         | Some updated -> | ||||
|             Expect.equal updated.Name "My Favorites" "Name not updated properly" | ||||
|             Expect.equal updated.Slug "my-favorites" "Slug not updated properly" | ||||
|             Expect.isNone updated.Description "Description should have been removed" | ||||
|         | None -> Expect.isTrue false "The updated favorites category could not be retrieved" | ||||
|     | None -> Expect.isTrue false "The favorites category could not be retrieved" | ||||
| } | ||||
| 
 | ||||
| let ``Delete succeeds when the category is deleted (no posts)`` (data: IData) = task { | ||||
|     let! result = data.Category.Delete (CategoryId "added-cat") (WebLogId "test") | ||||
|     Expect.equal result CategoryDeleted "The category should have been deleted" | ||||
|     let! cat = data.Category.FindById (CategoryId "added-cat") (WebLogId "test") | ||||
|     Expect.isNone cat "The deleted category should not still exist" | ||||
| } | ||||
| 
 | ||||
| let ``Delete succeeds when the category does not exist`` (data: IData) = task { | ||||
|     let! result = data.Category.Delete CategoryId.Empty (WebLogId "none") | ||||
|     Expect.equal result CategoryNotFound "The category should not have been found" | ||||
| } | ||||
| 
 | ||||
| let ``Delete succeeds when reassigning parent category to None`` (data: IData) = task { | ||||
|     let moonshotId = CategoryId "ScVpyu1e7UiP7bDdge3ZEw" | ||||
|     let spitballId = CategoryId "jw6N69YtTEWVHAO33jHU-w" | ||||
|     let! result = data.Category.Delete spitballId rootId | ||||
|     Expect.equal result ReassignedChildCategories "Child categories should have been reassigned" | ||||
|     match! data.Category.FindById moonshotId rootId with | ||||
|     | Some cat -> Expect.isNone cat.ParentId "Parent ID should have been cleared" | ||||
|     | None -> Expect.isTrue false "Unable to find former child category" | ||||
| } | ||||
| 
 | ||||
| let ``Delete succeeds when reassigning parent category to Some`` (data: IData) = task { | ||||
|     do! data.Category.Add { Category.Empty with Id = CategoryId "a"; WebLogId = WebLogId "test"; Name = "A" } | ||||
|     do! data.Category.Add | ||||
|             { Category.Empty with | ||||
|                 Id       = CategoryId "b" | ||||
|                 WebLogId = WebLogId "test" | ||||
|                 Name     = "B" | ||||
|                 ParentId = Some (CategoryId "a") } | ||||
|     do! data.Category.Add | ||||
|             { Category.Empty with | ||||
|                 Id       = CategoryId "c" | ||||
|                 WebLogId = WebLogId "test" | ||||
|                 Name     = "C" | ||||
|                 ParentId = Some (CategoryId "b") } | ||||
|     let! result = data.Category.Delete (CategoryId "b") (WebLogId "test") | ||||
|     Expect.equal result ReassignedChildCategories "Child categories should have been reassigned" | ||||
|     match! data.Category.FindById (CategoryId "c") (WebLogId "test") with | ||||
|     | Some cat -> Expect.equal cat.ParentId (Some (CategoryId "a")) "Parent category ID not reassigned properly" | ||||
|     | None -> Expect.isTrue false "Expected former child category not found" | ||||
| } | ||||
| 
 | ||||
| let ``Delete succeeds and removes category from posts`` (data: IData) = task { | ||||
|     let moonshotId = CategoryId "ScVpyu1e7UiP7bDdge3ZEw" | ||||
|     let postId     = PostId "RCsCU2puYEmkpzotoi8p4g" | ||||
|     match! data.Post.FindById postId rootId with | ||||
|     | Some post -> | ||||
|         Expect.equal post.CategoryIds [ moonshotId ] "Post category IDs are not as expected" | ||||
|         let! result = data.Category.Delete moonshotId rootId | ||||
|         Expect.equal result CategoryDeleted "The category should have been deleted (no children)" | ||||
|         match! data.Post.FindById postId rootId with | ||||
|         | Some p -> Expect.isEmpty p.CategoryIds "Category ID was not removed" | ||||
|         | None -> Expect.isTrue false "The expected updated post was not found" | ||||
|     | None -> Expect.isTrue false "The expected test post was not found" | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| module SQLiteDataTests | ||||
| 
 | ||||
| open System.IO | ||||
| open BitBadger.Documents | ||||
| open Expecto | ||||
| open Microsoft.Extensions.Logging.Abstractions | ||||
| @ -11,9 +12,12 @@ open Newtonsoft.Json | ||||
| /// JSON serializer  | ||||
| let ser = Json.configure (JsonSerializer.CreateDefault()) | ||||
| 
 | ||||
| /// The test database name | ||||
| let dbName = "test-db.db" | ||||
| 
 | ||||
| /// Create a SQLiteData instance for testing | ||||
| let mkData () = | ||||
|     Sqlite.Configuration.useConnectionString "Data Source=./test-db.db" | ||||
|     Sqlite.Configuration.useConnectionString $"Data Source=./{dbName}" | ||||
|     let conn = Sqlite.Configuration.dbConn () | ||||
|     SQLiteData(conn, NullLogger<SQLiteData>(), ser) :> IData | ||||
| 
 | ||||
| @ -21,13 +25,24 @@ let mkData () = | ||||
| let dispose (data: IData) = | ||||
|     (data :?> SQLiteData).Conn.Dispose() | ||||
| 
 | ||||
| /// Create a fresh environment from the root backup | ||||
| let freshEnvironment (data: IData option) = task { | ||||
|     let env = | ||||
|         match data with | ||||
|         | Some d -> d | ||||
|         | None -> | ||||
|             File.Delete dbName | ||||
|             mkData () | ||||
|     do! env.StartUp() | ||||
|     // This exercises Restore for all implementations; all tests are dependent on it working as expected | ||||
|     do! Maintenance.Backup.restoreBackup "root-weblog.json" None false false env | ||||
| } | ||||
| 
 | ||||
| /// Set up the environment for the SQLite tests | ||||
| let environmentSetUp = testList "Environment" [ | ||||
|     testTask "creating database" { | ||||
|         let data = mkData () | ||||
|         try | ||||
|             do! data.StartUp() | ||||
|             do! Maintenance.Backup.restoreBackup "root-weblog.json" None false data | ||||
|         try do! freshEnvironment (Some data) | ||||
|         finally dispose data | ||||
|     } | ||||
| ] | ||||
| @ -36,18 +51,101 @@ let environmentSetUp = testList "Environment" [ | ||||
| let categoryTests = testList "Category" [ | ||||
|     testTask "Add succeeds" { | ||||
|         let data = mkData () | ||||
|         try do! CategoryDataTests.addTests data | ||||
|         try do! CategoryDataTests.``Add succeeds`` data | ||||
|         finally dispose data | ||||
|     } | ||||
|     testList "CountAll" [ | ||||
|         testTask "succeeds when categories exist" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``CountAll succeeds when categories exist`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|         testTask "succeeds when categories do not exist" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``CountAll succeeds when categories do not exist`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|     ] | ||||
|     testList "CountTopLevel" [ | ||||
|         testTask "succeeds when top-level categories exist" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``CountTopLevel succeeds when top-level categories exist`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|         testTask "succeeds when no top-level categories exist" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``CountTopLevel succeeds when no top-level categories exist`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|     ] | ||||
|     testTask "FindAllForView succeeds" { | ||||
|         let data = mkData () | ||||
|         try do! CategoryDataTests.``FindAllForView succeeds`` data | ||||
|         finally dispose data | ||||
|     } | ||||
|     testList "FindById" [ | ||||
|         testTask "succeeds when a category is found" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``FindById succeeds when a category is found`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|         testTask "succeeds when a category is not found" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``FindById succeeds when a category is not found`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|     ] | ||||
|     testList "FindByWebLog" [ | ||||
|         testTask "succeeds when categories exist" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``FindByWebLog succeeds when categories exist`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|         testTask "succeeds when no categories exist" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``FindByWebLog succeeds when no categories exist`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|     ] | ||||
|     testTask "Update succeeds" { | ||||
|         let data = mkData () | ||||
|         try do! CategoryDataTests.``Update succeeds`` data | ||||
|         finally dispose data | ||||
|     } | ||||
|     testList "Delete" [ | ||||
|         testTask "succeeds when the category is deleted (no posts)" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``Delete succeeds when the category is deleted (no posts)`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|         testTask "succeeds when the category does not exist" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``Delete succeeds when the category does not exist`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|         testTask "succeeds when reassigning parent category to None" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``Delete succeeds when reassigning parent category to None`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|         testTask "succeeds when reassigning parent category to Some" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``Delete succeeds when reassigning parent category to Some`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|         testTask "succeeds and removes category from posts" { | ||||
|             let data = mkData () | ||||
|             try do! CategoryDataTests.``Delete succeeds and removes category from posts`` data | ||||
|             finally dispose data | ||||
|         } | ||||
|     ] | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| open System.IO | ||||
| 
 | ||||
| /// Delete the SQLite database | ||||
| let environmentCleanUp = test "Clean Up" { | ||||
|     File.Delete "test-db.db" | ||||
|     Expect.isFalse (File.Exists "test-db.db") "The test SQLite database should have been deleted" | ||||
|     File.Delete dbName | ||||
|     Expect.isFalse (File.Exists dbName) "The test SQLite database should have been deleted" | ||||
| }  | ||||
| 
 | ||||
| /// All SQLite data tests | ||||
|  | ||||
| @ -313,7 +313,7 @@ module Backup = | ||||
|         displayStats $"{fileName} (for <>NAME<>) contains:" webLog archive | ||||
|     } | ||||
|      | ||||
|     let private doRestore archive newUrlBase (data: IData) = task { | ||||
|     let private doRestore archive newUrlBase isInteractive (data: IData) = task { | ||||
|         let! restore = task { | ||||
|             match! data.WebLog.FindById archive.WebLog.Id with | ||||
|             | Some webLog when defaultArg newUrlBase webLog.UrlBase = webLog.UrlBase -> | ||||
| @ -357,45 +357,46 @@ module Backup = | ||||
|         } | ||||
|          | ||||
|         // Restore theme and assets (one at a time, as assets can be large) | ||||
|         printfn "" | ||||
|         printfn "- Importing theme..." | ||||
|         if isInteractive then | ||||
|             printfn "" | ||||
|             printfn "- Importing theme..." | ||||
|         do! data.Theme.Save restore.Theme | ||||
|         restore.Assets | ||||
|         |> List.iter (EncodedAsset.toAsset >> data.ThemeAsset.Save >> Async.AwaitTask >> Async.RunSynchronously) | ||||
|          | ||||
|         // Restore web log data | ||||
|          | ||||
|         printfn "- Restoring web log..." | ||||
|         if isInteractive then printfn "- Restoring web log..." | ||||
|         // v2.0 backups will not have redirect rules; fix that if restoring to v2.1 or later | ||||
|         let webLog = | ||||
|             if isNull (box restore.WebLog.RedirectRules) then { restore.WebLog with RedirectRules = [] } | ||||
|             else restore.WebLog | ||||
|         do! data.WebLog.Add webLog | ||||
|          | ||||
|         printfn "- Restoring users..." | ||||
|         if isInteractive then printfn "- Restoring users..." | ||||
|         do! data.WebLogUser.Restore restore.Users | ||||
|          | ||||
|         printfn "- Restoring categories and tag mappings..." | ||||
|         if isInteractive then printfn "- Restoring categories and tag mappings..." | ||||
|         if not (List.isEmpty restore.TagMappings) then do! data.TagMap.Restore   restore.TagMappings | ||||
|         if not (List.isEmpty restore.Categories)  then do! data.Category.Restore restore.Categories | ||||
|          | ||||
|         printfn "- Restoring pages..." | ||||
|         if isInteractive then printfn "- Restoring pages..." | ||||
|         if not (List.isEmpty restore.Pages) then do! data.Page.Restore restore.Pages | ||||
|          | ||||
|         printfn "- Restoring posts..." | ||||
|         if isInteractive then printfn "- Restoring posts..." | ||||
|         if not (List.isEmpty restore.Posts) then do! data.Post.Restore restore.Posts | ||||
|          | ||||
|         // TODO: comments not yet implemented | ||||
|          | ||||
|         printfn "- Restoring uploads..." | ||||
|         if isInteractive then printfn "- Restoring uploads..." | ||||
|         if not (List.isEmpty restore.Uploads) then | ||||
|             do! data.Upload.Restore (restore.Uploads |> List.map EncodedUpload.toUpload) | ||||
|          | ||||
|         displayStats "Restored for <>NAME<>:" restore.WebLog restore | ||||
|         if isInteractive then displayStats "Restored for <>NAME<>:" restore.WebLog restore | ||||
|     } | ||||
|      | ||||
|     /// Decide whether to restore a backup | ||||
|     let internal restoreBackup fileName newUrlBase promptForOverwrite data = task { | ||||
|     let internal restoreBackup fileName newUrlBase promptForOverwrite isInteractive data = task { | ||||
|          | ||||
|         let serializer = getSerializer false | ||||
|         use stream     = new FileStream(fileName, FileMode.Open) | ||||
| @ -413,7 +414,7 @@ module Backup = | ||||
|             doOverwrite <- not (Console.ReadKey().Key = ConsoleKey.N) | ||||
|          | ||||
|         if doOverwrite then | ||||
|             do! doRestore archive newUrlBase data | ||||
|             do! doRestore archive newUrlBase isInteractive data | ||||
|         else | ||||
|             printfn $"{archive.WebLog.Name} backup restoration canceled" | ||||
|     } | ||||
| @ -445,7 +446,7 @@ module Backup = | ||||
|         if args.Length = 2 || args.Length = 3 then | ||||
|             let data       = sp.GetRequiredService<IData>() | ||||
|             let newUrlBase = if args.Length = 3 then Some args[2] else None | ||||
|             do! restoreBackup args[1] newUrlBase (args[0] <> "do-restore") data | ||||
|             do! restoreBackup args[1] newUrlBase (args[0] <> "do-restore") true data | ||||
|         else | ||||
|             eprintfn "Usage: myWebLog restore [backup-file-name] [*url-base]" | ||||
|             eprintfn "         * optional - will restore to original URL base if omitted" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user