diff --git a/src/Tests.CSharp/PostgresCSharpTests.cs b/src/Tests.CSharp/PostgresCSharpTests.cs index 5e38dc6..8977851 100644 --- a/src/Tests.CSharp/PostgresCSharpTests.cs +++ b/src/Tests.CSharp/PostgresCSharpTests.cs @@ -1288,6 +1288,423 @@ public static class PostgresCSharpTests ]) ]); + /// Verify a JSON array begins with "[" and ends with "]" + private static void VerifyBeginEnd(string json) + { + Expect.stringStarts(json, "[", "The array should have started with `[`"); + Expect.stringEnds(json, "]", "The array should have ended with `]`"); + } + + /// Verify the presence of a document by its ID + private static void VerifyDocById(string json, string docId) + { + Expect.stringContains(json, $"{{\"Id\": \"{docId}\",", $"Document `{docId}` not present"); + } + + /// Verify the presence of a document by its ID + private static void VerifySingleById(string json, string docId) + { + VerifyBeginEnd(json); + Expect.stringContains(json, $"{{\"Id\": \"{docId}\",", $"Document `{docId}` not present"); + } + + /// Verify the presence of any of the given document IDs in the given JSON + private static void VerifyAnyById(string json, IEnumerable docIds) + { + var theIds = docIds.ToList(); + if (theIds.Any(it => json.Contains($"{{\"Id\": \"{it}\""))) return; + var ids = string.Join(", ", theIds); + Expect.isTrue(false, $"Could not find any of IDs {ids} in {json}"); + } + + /// Verify the JSON for `all` returning data + private static void VerifyAllData(string json) + { + VerifyBeginEnd(json); + IEnumerable ids = ["one", "two", "three", "four", "five"]; + foreach (var docId in ids) VerifyDocById(json, docId); + } + + /// Verify an empty JSON array + private static void VerifyEmpty(string json) + { + Expect.equal(json, "[]", "There should be no documents returned"); + } + + /// Verify an empty JSON document + private static void VerifyNoDoc(string json) + { + Expect.equal(json, "{}", "There should be no document returned"); + } + + /// Verify the JSON for an ordered query + private static void VerifyExpectedOrder(string json, string idFirst, string idSecond, string? idThird = null, + string? idFourth = null, string? idFifth = null) + { + var firstIdx = json.IndexOf($"{{\"Id\": \"{idFirst}\",", StringComparison.Ordinal); + var secondIdx = json.IndexOf($"{{\"Id\": \"{idSecond}\",", StringComparison.Ordinal); + VerifyBeginEnd(json); + Expect.isGreaterThan(secondIdx, firstIdx, $"`{idSecond}` should have been after `{idFirst}`"); + if (idThird is null) return; + + var thirdIdx = json.IndexOf($"{{\"Id\": \"{idThird}\",", StringComparison.Ordinal); + Expect.isGreaterThan(thirdIdx, secondIdx, $"`{idThird}` should have been after `{idSecond}`"); + if (idFourth is null) return; + + var fourthIdx = json.IndexOf($"{{\"Id\": \"{idFourth}\",", StringComparison.Ordinal); + Expect.isGreaterThan(fourthIdx, thirdIdx, $"`{idFourth}` should have been after `{idThird}`"); + if (idFifth is null) return; + + var fifthIdx = json.IndexOf($"{{\"Id\": \"{idFifth}\",", StringComparison.Ordinal); + Expect.isGreaterThan(fifthIdx, fourthIdx, $"`{idFifth}` should have been after `{idFourth}`"); + } + + /// + /// Integration tests for the Json module of the PostgreSQL library + /// + private static readonly Test JsonTests = TestList("Json", + [ + TestList("All", + [ + TestCase("succeeds when there is data", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyAllData(Json.All(PostgresDb.TableName)); + }), + TestCase("succeeds when there is no data", async () => + { + await using var db = PostgresDb.BuildDb(); + VerifyEmpty(Json.All(PostgresDb.TableName)); + }) + ]), + TestList("AllOrdered", + [ + TestCase("succeeds when ordering numerically", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder(Json.AllOrdered(PostgresDb.TableName, [Field.Named("n:NumValue")]), + "one", "three", "two", "four", "five"); + }), + TestCase("succeeds when ordering numerically descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder(Json.AllOrdered(PostgresDb.TableName, [Field.Named("n:NumValue DESC")]), + "five", "four", "two", "three", "one"); + }), + TestCase("succeeds when ordering alphabetically", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder(Json.AllOrdered(PostgresDb.TableName, [Field.Named("Id DESC")]), + "two", "three", "one", "four", "five"); + }) + ]), + TestList("ById", + [ + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var json = Json.ById(PostgresDb.TableName, "two"); + Expect.stringStarts(json, """{"Id": "two",""", "An incorrect document was returned"); + Expect.stringEnds(json, "}", "JSON should have ended with this document"); + }), + TestCase("succeeds when a document is not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyNoDoc(Json.ById(PostgresDb.TableName, "three hundred eighty-seven")); + }) + ]), + TestList("ByFields", + [ + TestCase("succeeds when documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifySingleById( + Json.ByFields(PostgresDb.TableName, FieldMatch.All, + [Field.In("Value", ["purple", "blue"]), Field.Exists("Sub")]), + "four"); + }), + TestCase("succeeds when documents are found using IN with numeric field", async() => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifySingleById( + Json.ByFields(PostgresDb.TableName, FieldMatch.All, [Field.In("NumValue", [2, 4, 6, 8])]), + "three"); + }), + TestCase("succeeds when documents are not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyEmpty(Json.ByFields(PostgresDb.TableName, FieldMatch.All, + [Field.Equal("Value", "mauve"), Field.NotEqual("NumValue", 40)])); + }), + TestCase("succeeds for InArray when matching documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await Definition.EnsureTable(PostgresDb.TableName); + foreach (var doc in ArrayDocument.TestDocuments) await Document.Insert(PostgresDb.TableName, doc); + + var json = Json.ByFields(PostgresDb.TableName, FieldMatch.All, + [Field.InArray("Values", PostgresDb.TableName, ["c"])]); + VerifyBeginEnd(json); + VerifyDocById(json, "first"); + VerifyDocById(json, "second"); + }), + TestCase("succeeds for InArray when no matching documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await Definition.EnsureTable(PostgresDb.TableName); + foreach (var doc in ArrayDocument.TestDocuments) await Document.Insert(PostgresDb.TableName, doc); + VerifyEmpty(Json.ByFields(PostgresDb.TableName, FieldMatch.All, + [Field.InArray("Values", PostgresDb.TableName, ["j"])])); + }) + ]), + TestList("ByFieldsOrdered", + [ + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder( + Json.ByFieldsOrdered(PostgresDb.TableName, FieldMatch.All, [Field.Equal("Value", "purple")], + [Field.Named("Id")]), + "five", "four"); + }), + TestCase("succeeds when sorting descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder( + Json.ByFieldsOrdered(PostgresDb.TableName, FieldMatch.All, [Field.Equal("Value", "purple")], + [Field.Named("Id DESC")]), + "four", "five"); + }) + ]), + TestList("ByContains", + [ + TestCase("succeeds when documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var json = Json.ByContains(PostgresDb.TableName, new { Sub = new { Foo = "green" } }); + VerifyBeginEnd(json); + VerifyDocById(json, "two"); + VerifyDocById(json, "four"); + }), + TestCase("succeeds when documents are not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyEmpty(Json.ByContains(PostgresDb.TableName, new { Value = "mauve" })); + }) + ]), + TestList("ByContainsOrdered", + [ + // Id = two, Sub.Bar = blue; Id = four, Sub.Bar = red + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder( + Json.ByContainsOrdered(PostgresDb.TableName, new { Sub = new { Foo = "green" } }, + [Field.Named("Sub.Bar")]), + "two", "four"); + }), + TestCase("succeeds when sorting descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder( + Json.ByContainsOrdered(PostgresDb.TableName, new { Sub = new { Foo = "green" } }, + [Field.Named("Sub.Bar DESC")]), + "four", "two"); + }) + ]), + TestList("ByJsonPath", + [ + TestCase("succeeds when documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var json = Json.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 15)"); + VerifyBeginEnd(json); + VerifyDocById(json, "one"); + VerifyDocById(json, "two"); + VerifyDocById(json, "three"); + }), + TestCase("succeeds when documents are not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyEmpty(Json.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)")); + }) + ]), + TestList("ByJsonPathOrdered", + [ + // Id = one, NumValue = 0; Id = two, NumValue = 10; Id = three, NumValue = 4 + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder( + Json.ByJsonPathOrdered(PostgresDb.TableName, "$.NumValue ? (@ < 15)", + [Field.Named("n:NumValue")]), + "one", "three", "two"); + }), + TestCase("succeeds when sorting descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyExpectedOrder( + Json.ByJsonPathOrdered(PostgresDb.TableName, "$.NumValue ? (@ < 15)", + [Field.Named("n:NumValue DESC")]), + "two", "three", "one"); + }) + ]), + TestList("FirstByFields", + [ + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById( + Json.FirstByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "another")]), + "two"); + }), + TestCase("succeeds when multiple documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyAnyById( + Json.FirstByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")]), + ["five", "four"]); + }), + TestCase("succeeds when a document is not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyNoDoc(Json.FirstByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "absent")])); + }) + ]), + TestList("FirstByFieldsOrdered", + [ + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById( + Json.FirstByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")], + [Field.Named("Id")]), + "five"); + }), + TestCase("succeeds when sorting descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById( + Json.FirstByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")], + [Field.Named("Id DESC")]), + "four"); + }) + ]), + TestList("FirstByContains", + [ + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById(Json.FirstByContains(PostgresDb.TableName, new { Value = "another" }), "two"); + }), + TestCase("succeeds when multiple documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyAnyById(Json.FirstByContains(PostgresDb.TableName, new { Sub = new { Foo = "green" } }), + ["two", "four"]); + }), + TestCase("succeeds when a document is not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyNoDoc(Json.FirstByContains(PostgresDb.TableName, new { Value = "absent" })); + }) + ]), + TestList("FirstByContainsOrdered", + [ + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById( + Json.FirstByContainsOrdered(PostgresDb.TableName, new { Sub = new { Foo = "green" } }, + [Field.Named("Value")]), + "two"); + }), + TestCase("succeeds when sorting descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById( + Json.FirstByContainsOrdered(PostgresDb.TableName, new { Sub = new { Foo = "green" } }, + [Field.Named("Value DESC")]), + "four"); + }) + ]), + TestList("FirstByJsonPath", + [ + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById(Json.FirstByJsonPath(PostgresDb.TableName, """$.Value ? (@ == "FIRST!")"""), "one"); + }), + TestCase("succeeds when multiple documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyAnyById(Json.FirstByJsonPath(PostgresDb.TableName, """$.Sub.Foo ? (@ == "green")"""), + ["two", "four"]); + }), + TestCase("succeeds when a document is not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyNoDoc(Json.FirstByJsonPath(PostgresDb.TableName, """$.Id ? (@ == "nope")""")); + }) + ]), + TestList("FirstByJsonPathOrdered", + [ + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById( + Json.FirstByJsonPathOrdered(PostgresDb.TableName, """$.Sub.Foo ? (@ == "green")""", + [Field.Named("Sub.Bar")]), + "two"); + }), + TestCase("succeeds when sorting descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + VerifyDocById( + Json.FirstByJsonPathOrdered(PostgresDb.TableName, """$.Sub.Foo ? (@ == "green")""", + [Field.Named("Sub.Bar DESC")]), + "four"); + }) + ]) + ]); + /// /// Integration tests for the Update module of the PostgreSQL library /// @@ -1729,6 +2146,7 @@ public static class PostgresCSharpTests CountTests, ExistsTests, FindTests, + JsonTests, UpdateTests, PatchTests, RemoveFieldsTests, diff --git a/src/Tests/PostgresTests.fs b/src/Tests/PostgresTests.fs index 46d9a2d..a5fd012 100644 --- a/src/Tests/PostgresTests.fs +++ b/src/Tests/PostgresTests.fs @@ -1163,7 +1163,6 @@ let private verifyExpectedOrder idFirst idSecond idThird idFourth idFifth (json: | None -> () | None -> () | None -> () - Expect.stringEnds json "]" "The array should have ended with `]`" /// Integration tests for the Json module of the PostgreSQL library let jsonTests = testList "Json" [