From 1622da7d8d233ef949d82fce81249c2521affb6d Mon Sep 17 00:00:00 2001 From: haysch <36488481+haysch@users.noreply.github.com> Date: Sat, 1 Jul 2023 14:22:43 +0200 Subject: [PATCH 1/3] Expose internals to NRedisStack test project --- src/NRedisStack/NRedisStack.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/NRedisStack/NRedisStack.csproj b/src/NRedisStack/NRedisStack.csproj index 6dd24984..13f99b67 100644 --- a/src/NRedisStack/NRedisStack.csproj +++ b/src/NRedisStack/NRedisStack.csproj @@ -20,4 +20,8 @@ + + + + From dd364e26f5256d57fa25501f8c1edb82b53765c3 Mon Sep 17 00:00:00 2001 From: haysch <36488481+haysch@users.noreply.github.com> Date: Sat, 1 Jul 2023 14:27:03 +0200 Subject: [PATCH 2/3] Extend Graph query preparation. * Escape backslashes in strings. * Add test cases for PrepareQuery. --- src/NRedisStack/Graph/RedisGraphUtilities.cs | 7 +- tests/NRedisStack.Tests/Graph/GraphTests.cs | 80 ++++++++++++++++++-- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/NRedisStack/Graph/RedisGraphUtilities.cs b/src/NRedisStack/Graph/RedisGraphUtilities.cs index a13f1049..b0eae675 100644 --- a/src/NRedisStack/Graph/RedisGraphUtilities.cs +++ b/src/NRedisStack/Graph/RedisGraphUtilities.cs @@ -114,8 +114,13 @@ internal static string QuoteString(string unquotedString) { var quotedString = new StringBuilder(unquotedString.Length + 12); + // Replace order is important, otherwise too many backslashes will be added. + var sanitizedUnquotedString = unquotedString + .Replace("\\", "\\\\") + .Replace("\"", "\\\""); + quotedString.Append('"'); - quotedString.Append(unquotedString.Replace("\"", "\\\"")); + quotedString.Append(sanitizedUnquotedString); quotedString.Append('"'); return quotedString.ToString(); diff --git a/tests/NRedisStack.Tests/Graph/GraphTests.cs b/tests/NRedisStack.Tests/Graph/GraphTests.cs index d6f6e170..32532de9 100644 --- a/tests/NRedisStack.Tests/Graph/GraphTests.cs +++ b/tests/NRedisStack.Tests/Graph/GraphTests.cs @@ -2003,12 +2003,82 @@ public void TestEqualsAndToString() [Fact] public void TestPrepareQuery() { + const string return1Query = "RETURN 1"; + const string return1QueryRecordString = "Record{values=1}"; + var graph = redisFixture.Redis.GetDatabase().GRAPH(); - var res1 = graph.Query("graph", "RETURN 1", new Dictionary { { "a", (char)'c' } }); - var res2 = graph.Query("graph", "RETURN 1", new Dictionary { { "a", null } }); - var res3 = graph.Query("graph", "RETURN 1", new Dictionary { { "a", new string[]{"foo", "bar"} } }); - var res4 = graph.Query("graph", "RETURN 1", new Dictionary { { "a", new List{"foo2", "bar2"} } }); - // TODO: complete this test + + // handle chars + var preparedQuery1 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", (char)'c' } }); + var expectedPreparedQuery1 = $"CYPHER a=\"c\" {return1Query}"; + Assert.Equal(expectedPreparedQuery1, preparedQuery1); + var res1 = graph.Query("graph", preparedQuery1); + Assert.Single(res1); + Assert.Equal(return1QueryRecordString, res1.Single().ToString()); + + // handle null + var preparedQuery2 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", null } }); + var expectedPreparedQuery2 = $"CYPHER a=null {return1Query}"; + Assert.Equal(expectedPreparedQuery2, preparedQuery2); + var res2 = graph.Query("graph", preparedQuery2); + Assert.Single(res2); + Assert.Equal(return1QueryRecordString, res2.Single().ToString()); + + // handle arrays + var preparedQuery3 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", new string[] { "foo", "bar" } } }); + var expectedPreparedQuery3 = $"CYPHER a=[\"foo\", \"bar\"] {return1Query}"; + Assert.Equal(expectedPreparedQuery3, preparedQuery3); + var res3 = graph.Query("graph", preparedQuery3); + Assert.Single(res3); + Assert.Equal(return1QueryRecordString, res3.Single().ToString()); + + // handle lists + var preparedQuery4 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", new List { "foo2", "bar2" } } }); + var expectedPreparedQuery4 = $"CYPHER a=[\"foo2\", \"bar2\"] {return1Query}"; + Assert.Equal(expectedPreparedQuery4, preparedQuery4); + var res4 = graph.Query("graph", preparedQuery4); + Assert.Single(res4); + Assert.Equal(return1QueryRecordString, res4.Single().ToString()); + + // handle bools + var preparedQuery5 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", true }, { "b", false } }); + var expectedPreparedQuery5 = $"CYPHER a=true b=false {return1Query}"; + Assert.Equal(expectedPreparedQuery5, preparedQuery5); + var res5 = graph.Query("graph", preparedQuery5); + Assert.Single(res5); + Assert.Equal(return1QueryRecordString, res4.Single().ToString()); + + // handle floats + var preparedQuery6 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", 1.4d } }); + var expectedPreparedQuery6 = $"CYPHER a=1.4 {return1Query}"; + Assert.Equal(expectedPreparedQuery6, preparedQuery6); + var res6 = graph.Query("graph", preparedQuery6); + Assert.Single(res6); + Assert.Equal(return1QueryRecordString, res4.Single().ToString()); + + // handle ints + var preparedQuery7 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", 5 } }); + var expectedPreparedQuery7 = $"CYPHER a=5 {return1Query}"; + Assert.Equal(expectedPreparedQuery7, preparedQuery7); + var res7 = graph.Query("graph", preparedQuery7); + Assert.Single(res7); + Assert.Equal(return1QueryRecordString, res4.Single().ToString()); + + // handle quotes + var preparedQuery8 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", "\"abc\"" } }); + var expectedPreparedQuery8 = $"CYPHER a=\"\\\"abc\\\"\" {return1Query}"; + Assert.Equal(expectedPreparedQuery8, preparedQuery8); + var res8 = graph.Query("graph", preparedQuery8); + Assert.Single(res8); + Assert.Equal(return1QueryRecordString, res5.Single().ToString()); + + // handle backslashes + var preparedQuery9 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", "abc\\" } }); + var expectedPreparedQuery9 = $"CYPHER a=\"abc\\\\\" {return1Query}"; + Assert.Equal(expectedPreparedQuery9, preparedQuery9); + var res9 = graph.Query("graph", preparedQuery9); + Assert.Single(res9); + Assert.Equal(return1QueryRecordString, res6.Single().ToString()); } #endregion From 9c14898b7bf4a658c26ffdbe6cffebe317e971ad Mon Sep 17 00:00:00 2001 From: shacharPash Date: Sun, 2 Jul 2023 15:55:24 +0300 Subject: [PATCH 3/3] test without InternalsVisibleTo NRedisStack.Tests --- src/NRedisStack/Graph/GraphCommandBuilder.cs | 8 +++ src/NRedisStack/NRedisStack.csproj | 4 -- tests/NRedisStack.Tests/Graph/GraphTests.cs | 54 ++++++++++---------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/NRedisStack/Graph/GraphCommandBuilder.cs b/src/NRedisStack/Graph/GraphCommandBuilder.cs index acce0ac1..126ace15 100644 --- a/src/NRedisStack/Graph/GraphCommandBuilder.cs +++ b/src/NRedisStack/Graph/GraphCommandBuilder.cs @@ -1,3 +1,4 @@ +using NRedisStack.Graph; using NRedisStack.Graph.Literals; using NRedisStack.RedisStackCommands; @@ -7,6 +8,13 @@ public static class GraphCommandBuilder { internal static readonly object CompactQueryFlag = "--COMPACT"; + /// + public static SerializedCommand Query(string graphName, string query, Dictionary parameters, long? timeout = null) + { + var preparedQuery = RedisGraphUtilities.PrepareQuery(query, parameters); + return Query(graphName, preparedQuery, timeout); + } + /// public static SerializedCommand Query(string graphName, string query, long? timeout = null) { diff --git a/src/NRedisStack/NRedisStack.csproj b/src/NRedisStack/NRedisStack.csproj index 13f99b67..6dd24984 100644 --- a/src/NRedisStack/NRedisStack.csproj +++ b/src/NRedisStack/NRedisStack.csproj @@ -20,8 +20,4 @@ - - - - diff --git a/tests/NRedisStack.Tests/Graph/GraphTests.cs b/tests/NRedisStack.Tests/Graph/GraphTests.cs index 32532de9..c339ba4e 100644 --- a/tests/NRedisStack.Tests/Graph/GraphTests.cs +++ b/tests/NRedisStack.Tests/Graph/GraphTests.cs @@ -2009,74 +2009,74 @@ public void TestPrepareQuery() var graph = redisFixture.Redis.GetDatabase().GRAPH(); // handle chars - var preparedQuery1 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", (char)'c' } }); + var buildCommand = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", (char)'c' }} ); var expectedPreparedQuery1 = $"CYPHER a=\"c\" {return1Query}"; - Assert.Equal(expectedPreparedQuery1, preparedQuery1); - var res1 = graph.Query("graph", preparedQuery1); + Assert.Equal(expectedPreparedQuery1, buildCommand.Args[1].ToString()!); + var res1 = graph.Query("graph", buildCommand.Args[1].ToString()!); Assert.Single(res1); Assert.Equal(return1QueryRecordString, res1.Single().ToString()); // handle null - var preparedQuery2 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", null } }); + var buildCommand2 = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", null } }); var expectedPreparedQuery2 = $"CYPHER a=null {return1Query}"; - Assert.Equal(expectedPreparedQuery2, preparedQuery2); - var res2 = graph.Query("graph", preparedQuery2); + Assert.Equal(expectedPreparedQuery2,buildCommand2.Args[1].ToString()!); + var res2 = graph.Query("graph",buildCommand2.Args[1].ToString()!); Assert.Single(res2); Assert.Equal(return1QueryRecordString, res2.Single().ToString()); // handle arrays - var preparedQuery3 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", new string[] { "foo", "bar" } } }); + var buildCommand3 = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", new string[] { "foo", "bar" } } }); var expectedPreparedQuery3 = $"CYPHER a=[\"foo\", \"bar\"] {return1Query}"; - Assert.Equal(expectedPreparedQuery3, preparedQuery3); - var res3 = graph.Query("graph", preparedQuery3); + Assert.Equal(expectedPreparedQuery3,buildCommand3.Args[1].ToString()!); + var res3 = graph.Query("graph",buildCommand3.Args[1].ToString()!); Assert.Single(res3); Assert.Equal(return1QueryRecordString, res3.Single().ToString()); // handle lists - var preparedQuery4 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", new List { "foo2", "bar2" } } }); + var buildCommand4 = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", new List { "foo2", "bar2" } } }); var expectedPreparedQuery4 = $"CYPHER a=[\"foo2\", \"bar2\"] {return1Query}"; - Assert.Equal(expectedPreparedQuery4, preparedQuery4); - var res4 = graph.Query("graph", preparedQuery4); + Assert.Equal(expectedPreparedQuery4,buildCommand4.Args[1].ToString()!); + var res4 = graph.Query("graph",buildCommand4.Args[1].ToString()!); Assert.Single(res4); Assert.Equal(return1QueryRecordString, res4.Single().ToString()); // handle bools - var preparedQuery5 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", true }, { "b", false } }); + var buildCommand5 = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", true }, { "b", false } }); var expectedPreparedQuery5 = $"CYPHER a=true b=false {return1Query}"; - Assert.Equal(expectedPreparedQuery5, preparedQuery5); - var res5 = graph.Query("graph", preparedQuery5); + Assert.Equal(expectedPreparedQuery5,buildCommand5.Args[1].ToString()!); + var res5 = graph.Query("graph",buildCommand5.Args[1].ToString()!); Assert.Single(res5); Assert.Equal(return1QueryRecordString, res4.Single().ToString()); // handle floats - var preparedQuery6 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", 1.4d } }); + var buildCommand6 = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", 1.4d } }); var expectedPreparedQuery6 = $"CYPHER a=1.4 {return1Query}"; - Assert.Equal(expectedPreparedQuery6, preparedQuery6); - var res6 = graph.Query("graph", preparedQuery6); + Assert.Equal(expectedPreparedQuery6,buildCommand6.Args[1].ToString()!); + var res6 = graph.Query("graph",buildCommand6.Args[1].ToString()!); Assert.Single(res6); Assert.Equal(return1QueryRecordString, res4.Single().ToString()); // handle ints - var preparedQuery7 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", 5 } }); + var buildCommand7 = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", 5 } }); var expectedPreparedQuery7 = $"CYPHER a=5 {return1Query}"; - Assert.Equal(expectedPreparedQuery7, preparedQuery7); - var res7 = graph.Query("graph", preparedQuery7); + Assert.Equal(expectedPreparedQuery7,buildCommand7.Args[1].ToString()!); + var res7 = graph.Query("graph",buildCommand7.Args[1].ToString()!); Assert.Single(res7); Assert.Equal(return1QueryRecordString, res4.Single().ToString()); // handle quotes - var preparedQuery8 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", "\"abc\"" } }); + var buildCommand8 = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", "\"abc\"" } }); var expectedPreparedQuery8 = $"CYPHER a=\"\\\"abc\\\"\" {return1Query}"; - Assert.Equal(expectedPreparedQuery8, preparedQuery8); - var res8 = graph.Query("graph", preparedQuery8); + Assert.Equal(expectedPreparedQuery8,buildCommand8.Args[1].ToString()!); + var res8 = graph.Query("graph",buildCommand8.Args[1].ToString()!); Assert.Single(res8); Assert.Equal(return1QueryRecordString, res5.Single().ToString()); // handle backslashes - var preparedQuery9 = RedisGraphUtilities.PrepareQuery(return1Query, new Dictionary { { "a", "abc\\" } }); + var buildCommand9 = GraphCommandBuilder.Query("graph", return1Query, new Dictionary { { "a", "abc\\" } }); var expectedPreparedQuery9 = $"CYPHER a=\"abc\\\\\" {return1Query}"; - Assert.Equal(expectedPreparedQuery9, preparedQuery9); - var res9 = graph.Query("graph", preparedQuery9); + Assert.Equal(expectedPreparedQuery9,buildCommand9.Args[1].ToString()!); + var res9 = graph.Query("graph",buildCommand9.Args[1].ToString()!); Assert.Single(res9); Assert.Equal(return1QueryRecordString, res6.Single().ToString()); }