From f3014acc2a7a790966ff8ee4cb091bbee8a02660 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 26 Dec 2023 16:01:27 -0500 Subject: [PATCH] WIP on tests for new F# functions --- src/Postgres/Library.fs | 186 ++++++++----------- src/Tests.CSharp/PostgresDb.cs | 4 +- src/Tests/PostgresTests.fs | 316 +++++++++++++++++++-------------- 3 files changed, 263 insertions(+), 243 deletions(-) diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index 18ba007..4652ce6 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -1,7 +1,5 @@ namespace BitBadger.Documents.Postgres -open Npgsql - /// The type of index to generate for the document [] type DocumentIndex = @@ -11,11 +9,13 @@ type DocumentIndex = | Optimized +open Npgsql + /// Configuration for document handling module Configuration = /// The data source to use for query execution - let mutable private dataSourceValue : Npgsql.NpgsqlDataSource option = None + let mutable private dataSourceValue : NpgsqlDataSource option = None /// Register a data source to use for query execution (disposes the current one if it exists) let useDataSource source = @@ -38,10 +38,8 @@ module private Helpers = let internal fromDataSource () = Configuration.dataSource () |> Sql.fromDataSource - open System.Threading.Tasks - /// Execute a task and ignore the result - let internal ignoreTask<'T> (it : Task<'T>) = backgroundTask { + let internal ignoreTask<'T> (it : System.Threading.Tasks.Task<'T>) = backgroundTask { let! _ = it () } @@ -49,50 +47,36 @@ module private Helpers = open BitBadger.Documents -/// Data definition -[] -module Definition = - - /// SQL statement to create a document table - let createTable name = - $"CREATE TABLE IF NOT EXISTS %s{name} (data JSONB NOT NULL)" +/// Functions for creating parameters +[] +module Parameters = - /// SQL statement to create a key index for a document table - let createKey (name : string) = - let tableName = name.Split(".") |> Array.last - $"CREATE UNIQUE INDEX IF NOT EXISTS idx_{tableName}_key ON {name} ((data ->> '{Configuration.idField ()}'))" - - /// SQL statement to create an index on documents in the specified table - let createIndex (name : string) idxType = - let extraOps = match idxType with Full -> "" | Optimized -> " jsonb_path_ops" - let tableName = name.Split(".") |> Array.last - $"CREATE INDEX IF NOT EXISTS idx_{tableName} ON {name} USING GIN (data{extraOps})" - - /// Definitions that take SqlProps as their last parameter - module WithProps = - - /// Create a document table - let ensureTable name sqlProps = backgroundTask { - do! sqlProps |> Sql.query (createTable name) |> Sql.executeNonQueryAsync |> ignoreTask - do! sqlProps |> Sql.query (createKey name) |> Sql.executeNonQueryAsync |> ignoreTask - } + /// Create an ID parameter (name "@id", key will be treated as a string) + [] + let idParam (key: 'TKey) = + "@id", Sql.string (string key) - /// Create an index on documents in the specified table - let ensureIndex name idxType sqlProps = - sqlProps |> Sql.query (createIndex name idxType) |> Sql.executeNonQueryAsync |> ignoreTask - - /// Create a document table - let ensureTable name = - WithProps.ensureTable name (fromDataSource ()) - - let ensureIndex name idxType = - WithProps.ensureIndex name idxType (fromDataSource ()) + /// Create a parameter with a JSON value + [] + let jsonParam (name: string) (it: 'TJson) = + name, Sql.jsonb (Configuration.serializer().Serialize it) + /// Create a JSON field parameter (name "@field") + [] + let fieldParam (value: obj) = + "@field", Sql.parameter (NpgsqlParameter("@field", value)) + /// An empty parameter sequence + [] + let noParams = + Seq.empty + + /// Query construction functions [] module Query = + /// Table and index definition queries module Definition = /// SQL statement to create a document table @@ -102,10 +86,10 @@ module Query = /// SQL statement to create an index on JSON documents in the specified table [] - let ensureJsonIndex (name : string) idxType = + let ensureJsonIndex (name: string) idxType = let extraOps = match idxType with Full -> "" | Optimized -> " jsonb_path_ops" let tableName = name.Split '.' |> Array.last - $"CREATE INDEX IF NOT EXISTS idx_{tableName} ON {name} USING GIN (data{extraOps})" + $"CREATE INDEX IF NOT EXISTS idx_{tableName}_document ON {name} USING GIN (data{extraOps})" /// Create a WHERE clause fragment to implement a @> (JSON contains) condition let whereDataContains paramName = @@ -115,23 +99,6 @@ module Query = let whereJsonPathMatches paramName = $"data @? %s{paramName}::jsonpath" - /// Create a JSONB document parameter - let jsonbDocParam (it: obj) = - Sql.jsonb (Configuration.serializer().Serialize it) - - /// Create ID and data parameters for a query - let docParameters<'T> docId (doc: 'T) = - [ "@id", Sql.string docId; "@data", jsonbDocParam doc ] - - /// Query to insert a document - let insert tableName = - $"INSERT INTO %s{tableName} VALUES (@data)" - - /// Query to save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") - let save tableName = - sprintf "INSERT INTO %s VALUES (@data) ON CONFLICT ((data ->> '%s')) DO UPDATE SET data = EXCLUDED.data" - tableName (Configuration.idField ()) - /// Queries for counting documents module Count = @@ -196,31 +163,6 @@ module Query = $"""DELETE FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}""" -/// Functions for creating parameters -[] -module Parameters = - - /// Create an ID parameter (name "@id", key will be treated as a string) - [] - let idParam (key: 'TKey) = - "@id", Sql.string (string key) - - /// Create a parameter with a JSON value - [] - let jsonParam (name: string) (it: 'TJson) = - name, Sql.jsonb (Configuration.serializer().Serialize it) - - /// Create a JSON field parameter (name "@field") - [] - let fieldParam (value: obj) = - "@field", Sql.parameter (NpgsqlParameter("@field", value)) - - /// An empty parameter sequence - [] - let noParams = - Seq.empty - - /// Functions for dealing with results [] module Results = @@ -251,28 +193,28 @@ module WithProps = /// Execute a query that returns a list of results [] - let list<'T> query parameters (mapFunc: RowReader -> 'T) sqlProps = + let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) sqlProps = Sql.query query sqlProps |> Sql.parameters parameters |> Sql.executeAsync mapFunc /// Execute a query that returns a list of results - let List<'T>(query, parameters, mapFunc: System.Func, sqlProps) = backgroundTask { - let! results = list query (List.ofSeq parameters) mapFunc.Invoke sqlProps + let List<'TDoc>(query, parameters, mapFunc: System.Func, sqlProps) = backgroundTask { + let! results = list<'TDoc> query (List.ofSeq parameters) mapFunc.Invoke sqlProps return ResizeArray results } - /// Execute a query that returns one or no results (returns None if not found) + /// Execute a query that returns one or no results; returns None if not found [] - let single<'T> query parameters mapFunc sqlProps = backgroundTask { - let! results = list<'T> query parameters mapFunc sqlProps + let single<'TDoc> query parameters mapFunc sqlProps = backgroundTask { + let! results = list<'TDoc> query parameters mapFunc sqlProps return FSharp.Collections.List.tryHead results } - /// Execute a query that returns one or no results (returns null if not found) - let Single<'T when 'T: null>( - query, parameters, mapFunc: System.Func, sqlProps) = backgroundTask { - let! result = single<'T> query (FSharp.Collections.List.ofSeq parameters) mapFunc.Invoke sqlProps + /// Execute a query that returns one or no results; returns null if not found + let Single<'TDoc when 'TDoc: null>( + query, parameters, mapFunc: System.Func, sqlProps) = backgroundTask { + let! result = single<'TDoc> query (FSharp.Collections.List.ofSeq parameters) mapFunc.Invoke sqlProps return Option.toObj result } @@ -295,21 +237,25 @@ module WithProps = let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func, sqlProps) = scalar<'T> query (FSharp.Collections.List.ofSeq parameters) mapFunc.Invoke sqlProps - /// Execute a non-query statement to manipulate a document - let private executeNonQuery query (document: 'T) sqlProps = - sqlProps - |> Sql.query query - |> Sql.parameters [ "@data", Query.jsonbDocParam document ] - |> Sql.executeNonQueryAsync - |> ignoreTask + /// Table and index definition commands + module Definition = + + /// Create a document table + [] + let ensureTable name sqlProps = backgroundTask { + do! Custom.nonQuery (Query.Definition.ensureTable name) [] sqlProps + do! Custom.nonQuery (Query.Definition.ensureKey name) [] sqlProps + } - /// Execute a non-query statement to manipulate a document with an ID specified - let private executeNonQueryWithId query docId (document: 'T) sqlProps = - sqlProps - |> Sql.query query - |> Sql.parameters (Query.docParameters docId document) - |> Sql.executeNonQueryAsync - |> ignoreTask + /// Create an index on documents in the specified table + [] + let ensureJsonIndex name idxType sqlProps = + Custom.nonQuery (Query.Definition.ensureJsonIndex name idxType) [] sqlProps + + /// Create an index on field(s) within documents in the specified table + [] + let ensureFieldIndex tableName indexName fields sqlProps = + Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields) [] sqlProps /// Commands to add documents [] @@ -575,6 +521,26 @@ module Custom = WithProps.Custom.Scalar<'T>(query, parameters, mapFunc, fromDataSource ()) +/// Table and index definition commands +[] +module Definition = + + /// Create a document table + [] + let ensureTable name = + WithProps.Definition.ensureTable name (fromDataSource ()) + + /// Create an index on documents in the specified table + [] + let ensureJsonIndex name idxType = + WithProps.Definition.ensureJsonIndex name idxType (fromDataSource ()) + + /// Create an index on field(s) within documents in the specified table + [] + let ensureFieldIndex tableName indexName fields = + WithProps.Definition.ensureFieldIndex tableName indexName fields (fromDataSource ()) + + /// Document writing functions [] module Document = diff --git a/src/Tests.CSharp/PostgresDb.cs b/src/Tests.CSharp/PostgresDb.cs index ce2ec6a..c8d2411 100644 --- a/src/Tests.CSharp/PostgresDb.cs +++ b/src/Tests.CSharp/PostgresDb.cs @@ -134,8 +134,8 @@ public static class PostgresDb var sqlProps = Sql.connect(database.ConnectionString); - Sql.executeNonQuery(Sql.query(Definition.createTable(TableName), sqlProps)); - Sql.executeNonQuery(Sql.query(Definition.createKey(TableName), sqlProps)); + Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps)); + Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName), sqlProps)); Postgres.Configuration.useDataSource(MkDataSource(database.ConnectionString)); diff --git a/src/Tests/PostgresTests.fs b/src/Tests/PostgresTests.fs index afce064..5d68908 100644 --- a/src/Tests/PostgresTests.fs +++ b/src/Tests/PostgresTests.fs @@ -8,105 +8,121 @@ open BitBadger.Documents.Tests /// Tests which do not hit the database let unitTests = testList "Unit" [ - testList "Definition" [ - test "createTable succeeds" { - Expect.equal (Definition.createTable PostgresDb.TableName) - $"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)" - "CREATE TABLE statement not constructed correctly" + testList "Parameters" [ + test "idParam succeeds" { + Expect.equal (idParam 88) ("@id", Sql.string "88") "ID parameter not constructed correctly" } - test "createKey succeeds" { - Expect.equal (Definition.createKey PostgresDb.TableName) - $"CREATE UNIQUE INDEX IF NOT EXISTS idx_{PostgresDb.TableName}_key ON {PostgresDb.TableName} ((data ->> 'Id'))" - "CREATE INDEX for key statement not constructed correctly" + test "jsonParam succeeds" { + Expect.equal + (jsonParam "@test" {| Something = "good" |}) + ("@test", Sql.jsonb """{"Something":"good"}""") + "JSON parameter not constructed correctly" } - test "createIndex succeeds for full index" { - Expect.equal (Definition.createIndex "schema.tbl" Full) - "CREATE INDEX IF NOT EXISTS idx_tbl ON schema.tbl USING GIN (data)" - "CREATE INDEX statement not constructed correctly" + test "fieldParam succeeds" { + let it = fieldParam 242 + Expect.equal (fst it) "@field" "Field parameter name not correct" + match snd it with + | SqlValue.Parameter value -> + Expect.equal value.ParameterName "@field" "Parameter name not correct" + Expect.equal value.Value 242 "Parameter value not correct" + | _ -> Expect.isTrue false "The parameter was not a Parameter type" } - test "createIndex succeeds for JSONB Path Ops index" { - Expect.equal (Definition.createIndex PostgresDb.TableName Optimized) - $"CREATE INDEX IF NOT EXISTS idx_{PostgresDb.TableName} ON {PostgresDb.TableName} USING GIN (data jsonb_path_ops)" - "CREATE INDEX statement not constructed correctly" + test "noParams succeeds" { + Expect.isEmpty noParams "The no-params sequence should be empty" } ] testList "Query" [ + testList "Definition" [ + test "ensureTable succeeds" { + Expect.equal + (Query.Definition.ensureTable PostgresDb.TableName) + $"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)" + "CREATE TABLE statement not constructed correctly" + } + test "ensureJsonIndex succeeds for full index" { + Expect.equal + (Query.Definition.ensureJsonIndex "schema.tbl" Full) + "CREATE INDEX IF NOT EXISTS idx_tbl_document ON schema.tbl USING GIN (data)" + "CREATE INDEX statement not constructed correctly" + } + test "ensureJsonIndex succeeds for JSONB Path Ops index" { + Expect.equal + (Query.Definition.ensureJsonIndex PostgresDb.TableName Optimized) + (sprintf "CREATE INDEX IF NOT EXISTS idx_%s_document ON %s USING GIN (data jsonb_path_ops)" + PostgresDb.TableName PostgresDb.TableName) + "CREATE INDEX statement not constructed correctly" + } + ] test "whereDataContains succeeds" { Expect.equal (Query.whereDataContains "@test") "data @> @test" "WHERE clause not correct" } test "whereJsonPathMatches succeeds" { Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct" } - test "jsonbDocParam succeeds" { - Expect.equal (Query.jsonbDocParam {| Hello = "There" |}) (Sql.jsonb "{\"Hello\":\"There\"}") - "JSONB document not serialized correctly" - } - test "docParameters succeeds" { - let parameters = Query.docParameters "abc123" {| Testing = 456 |} - let expected = [ - "@id", Sql.string "abc123" - "@data", Sql.jsonb "{\"Testing\":456}" - ] - Expect.equal parameters expected "There should have been 2 parameters, one string and one JSONB" - } - test "insert succeeds" { - Expect.equal (Query.insert PostgresDb.TableName) $"INSERT INTO {PostgresDb.TableName} VALUES (@data)" - "INSERT statement not correct" - } - test "save succeeds" { - Expect.equal (Query.save PostgresDb.TableName) - $"INSERT INTO {PostgresDb.TableName} VALUES (@data) ON CONFLICT ((data ->> 'Id')) DO UPDATE SET data = EXCLUDED.data" - "INSERT ON CONFLICT UPDATE statement not correct" - } testList "Count" [ test "byContains succeeds" { - Expect.equal (Query.Count.byContains PostgresDb.TableName) + Expect.equal + (Query.Count.byContains PostgresDb.TableName) $"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @> @criteria" "JSON containment count query not correct" } test "byJsonPath succeeds" { - Expect.equal (Query.Count.byJsonPath PostgresDb.TableName) + Expect.equal + (Query.Count.byJsonPath PostgresDb.TableName) $"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" "JSON Path match count query not correct" } ] testList "Exists" [ test "byContains succeeds" { - Expect.equal (Query.Exists.byContains PostgresDb.TableName) + Expect.equal + (Query.Exists.byContains PostgresDb.TableName) $"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @> @criteria) AS it" "JSON containment exists query not correct" } test "byJsonPath succeeds" { - Expect.equal (Query.Exists.byJsonPath PostgresDb.TableName) + Expect.equal + (Query.Exists.byJsonPath PostgresDb.TableName) $"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath) AS it" "JSON Path match existence query not correct" } ] testList "Find" [ test "byContains succeeds" { - Expect.equal (Query.Find.byContains PostgresDb.TableName) + Expect.equal + (Query.Find.byContains PostgresDb.TableName) $"SELECT data FROM {PostgresDb.TableName} WHERE data @> @criteria" "SELECT by JSON containment query not correct" } test "byJsonPath succeeds" { - Expect.equal (Query.Find.byJsonPath PostgresDb.TableName) + Expect.equal + (Query.Find.byJsonPath PostgresDb.TableName) $"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" "SELECT by JSON Path match query not correct" } ] testList "Update" [ test "partialById succeeds" { - Expect.equal (Query.Update.partialById PostgresDb.TableName) + Expect.equal + (Query.Update.partialById PostgresDb.TableName) $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Id' = @id" "UPDATE partial by ID statement not correct" } + test "partialByField succeeds" { + Expect.equal + (Query.Update.partialByField PostgresDb.TableName "Snail" LT) + $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Snail' < @field" + "UPDATE partial by ID statement not correct" + } test "partialByContains succeeds" { - Expect.equal (Query.Update.partialByContains PostgresDb.TableName) + Expect.equal + (Query.Update.partialByContains PostgresDb.TableName) $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria" "UPDATE partial by JSON containment statement not correct" } test "partialByJsonPath succeeds" { - Expect.equal (Query.Update.partialByJsonPath PostgresDb.TableName) + Expect.equal + (Query.Update.partialByJsonPath PostgresDb.TableName) $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @? @path::jsonpath" "UPDATE partial by JSON Path statement not correct" } @@ -126,7 +142,6 @@ let unitTests = ] ] -open Npgsql.FSharp open ThrowawayDb.Postgres open Types @@ -163,17 +178,82 @@ let integrationTests = "Data source should have been the same" } ] + testList "Custom" [ + testList "list" [ + testTask "succeeds when data is found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = Custom.list (Query.selectFromTable PostgresDb.TableName) [] fromData + Expect.hasCountOf docs 5u isTrue "There should have been 5 documents returned" + } + testTask "succeeds when data is not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Custom.list $"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" + [ "@path", Sql.string "$.NumValue ? (@ > 100)" ] fromData + Expect.isEmpty docs "There should have been no documents returned" + } + ] + testList "single" [ + testTask "succeeds when a row is found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = + Custom.single $"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id" + [ "@id", Sql.string "one"] fromData + Expect.isSome doc "There should have been a document returned" + Expect.equal doc.Value.Id "one" "The incorrect document was returned" + } + testTask "succeeds when a row is not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = + Custom.single $"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id" + [ "@id", Sql.string "eighty" ] fromData + Expect.isNone doc "There should not have been a document returned" + } + ] + testList "nonQuery" [ + testTask "succeeds when operating on data" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Custom.nonQuery $"DELETE FROM {PostgresDb.TableName}" [] + + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 0 "There should be no documents remaining in the table" + } + testTask "succeeds when no data matches where clause" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Custom.nonQuery $"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" + [ "@path", Sql.string "$.NumValue ? (@ > 100)" ] + + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 5 "There should be 5 documents remaining in the table" + } + ] + testTask "scalar succeeds" { + use db = PostgresDb.BuildDb() + + let! nbr = Custom.scalar $"SELECT 5 AS test_value" [] (fun row -> row.int "test_value") + Expect.equal nbr 5 "The query should have returned the number 5" + } + ] testList "Definition" [ testTask "ensureTable succeeds" { use db = PostgresDb.BuildDb() let tableExists () = - Sql.connect db.ConnectionString - |> Sql.query "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it" - |> Sql.executeRowAsync (fun row -> row.bool "it") + Custom.scalar "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it" [] toExists let keyExists () = - Sql.connect db.ConnectionString - |> Sql.query "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it" - |> Sql.executeRowAsync (fun row -> row.bool "it") + Custom.scalar + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it" [] toExists let! exists = tableExists () let! alsoExists = keyExists () @@ -186,22 +266,38 @@ let integrationTests = Expect.isTrue exists' "The table should now exist" Expect.isTrue alsoExists' "The key index should now exist" } - testTask "ensureIndex succeeds" { + testTask "ensureJsonIndex succeeds" { use db = PostgresDb.BuildDb() let indexExists () = - Sql.connect db.ConnectionString - |> Sql.query "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured') AS it" - |> Sql.executeRowAsync (fun row -> row.bool "it") + Custom.scalar + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it" + [] + toExists let! exists = indexExists () Expect.isFalse exists "The index should not exist already" - do! Definition.ensureTable "ensured" - do! Definition.ensureIndex "ensured" Optimized + do! Definition.ensureTable "ensured" + do! Definition.ensureJsonIndex "ensured" Optimized let! exists' = indexExists () Expect.isTrue exists' "The index should now exist" // TODO: check for GIN(jsonp_path_ops), write test for "full" index that checks for their absence } + testTask "ensureFieldIndex succeeds" { + use db = PostgresDb.BuildDb() + let indexExists () = + Custom.scalar + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it" [] toExists + + let! exists = indexExists () + Expect.isFalse exists "The index should not exist already" + + do! Definition.ensureTable "ensured" + do! Definition.ensureFieldIndex "ensured" "test" [ "Id"; "Category" ] + let! exists' = indexExists () + Expect.isTrue exists' "The index should now exist" + // TODO: check for field definition + } ] testList "insert" [ testTask "succeeds" { @@ -217,8 +313,11 @@ let integrationTests = testTask "fails for duplicate key" { use db = PostgresDb.BuildDb() do! insert PostgresDb.TableName { emptyDoc with Id = "test" } - Expect.throws (fun () -> - insert PostgresDb.TableName {emptyDoc with Id = "test" } |> Async.AwaitTask |> Async.RunSynchronously) + Expect.throws + (fun () -> + insert PostgresDb.TableName {emptyDoc with Id = "test" } + |> Async.AwaitTask + |> Async.RunSynchronously) "An exception should have been raised for duplicate document ID insert" } ] @@ -257,6 +356,13 @@ let integrationTests = let! theCount = Count.all PostgresDb.TableName Expect.equal theCount 5 "There should have been 5 matching documents" } + testTask "byField succeeds" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! theCount = Count.byField PostgresDb.TableName "Value" EQ "purple" + Expect.equal theCount 2 "There should have been 2 matching documents" + } testTask "byContains succeeds" { use db = PostgresDb.BuildDb() do! loadDocs () @@ -289,6 +395,22 @@ let integrationTests = Expect.isFalse exists "There should not have been an existing document" } ] + testList "byField" [ + testTask "succeeds when documents exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byField PostgresDb.TableName "Sub" EX "" + Expect.isTrue exists "There should have been existing documents" + } + testTask "succeeds when documents do not exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byField PostgresDb.TableName "NumValue" EQ "six" + Expect.isFalse exists "There should not have been existing documents" + } + ] testList "byContains" [ testTask "succeeds when documents exist" { use db = PostgresDb.BuildDb() @@ -605,76 +727,8 @@ let integrationTests = } ] ] - testList "Custom" [ - testList "single" [ - testTask "succeeds when a row is found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = - Custom.single $"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id" - [ "@id", Sql.string "one"] fromData - Expect.isSome doc "There should have been a document returned" - Expect.equal doc.Value.Id "one" "The incorrect document was returned" - } - testTask "succeeds when a row is not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = - Custom.single $"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id" - [ "@id", Sql.string "eighty" ] fromData - Expect.isNone doc "There should not have been a document returned" - } - ] - testList "list" [ - testTask "succeeds when data is found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = Custom.list (Query.selectFromTable PostgresDb.TableName) [] fromData - Expect.hasCountOf docs 5u isTrue "There should have been 5 documents returned" - } - testTask "succeeds when data is not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = - Custom.list $"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" - [ "@path", Sql.string "$.NumValue ? (@ > 100)" ] fromData - Expect.isEmpty docs "There should have been no documents returned" - } - ] - testList "nonQuery" [ - testTask "succeeds when operating on data" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Custom.nonQuery $"DELETE FROM {PostgresDb.TableName}" [] - - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 0 "There should be no documents remaining in the table" - } - testTask "succeeds when no data matches where clause" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Custom.nonQuery $"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" - [ "@path", Sql.string "$.NumValue ? (@ > 100)" ] - - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 5 "There should be 5 documents remaining in the table" - } - ] - testTask "scalar succeeds" { - use db = PostgresDb.BuildDb() - - let! nbr = Custom.scalar $"SELECT 5 AS test_value" [] (fun row -> row.int "test_value") - Expect.equal nbr 5 "The query should have returned the number 5" - } - ] ] |> testSequenced -let all = testList "FSharp.Documents" [ unitTests; integrationTests ] +let all = testList "Postgres" [ unitTests; integrationTests ]