Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,72 @@ async IAsyncEnumerable<int> GetInts(int start, int count)

queryExecution.Result.Should().Be(45 + rnd);
}

[Fact]
public async Task ShouldSetParametersWithObjectContainingPerson()
{
var person = new Person { Name = "Test Person No Work", Age = 22 };
var anonParams = new { Shaken = true, Stirred = false, Agent = person };

var autoMock = new AutoMocker(MockBehavior.Loose);
var driverMock = autoMock.GetMock<IDriverRowSource<int>>();

object capturedParams = null;

driverMock
.Setup(x => x.SetParameters(It.IsAny<object>()))
.Callback<object>(p => capturedParams = p);

driverMock
.Setup(x => x.GetRowsAsync(It.IsAny<Action<int>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ExecutionSummary(null, null));

var subject = new ExecutableQuery<int, int>(driverMock.Object, i => i);

await subject
.WithParameters(anonParams)
.ExecuteAsync();

capturedParams.Should().BeEquivalentTo(anonParams);
}

[Fact]
public async Task ShouldSetParametersWithDictionaryContainingPerson()
{
var person = new Person { Name = "Test Person No Work", Age = 22 };
var parameters = new Dictionary<string, object> { ["agent"] = person };

var autoMock = new AutoMocker(MockBehavior.Loose);
var driverMock = autoMock.GetMock<IDriverRowSource<int>>();

Dictionary<string, object> capturedParams = null;

driverMock
.Setup(x => x.SetParameters(It.IsAny<Dictionary<string, object>>()))
.Callback<Dictionary<string, object>>(p => capturedParams = p);

driverMock
.Setup(x => x.GetRowsAsync(It.IsAny<Action<int>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ExecutionSummary(null, null));

var subject = new ExecutableQuery<int, int>(driverMock.Object, i => i);

await subject
.WithParameters(parameters)
.ExecuteAsync();

capturedParams.Should().ContainKey("agent");
capturedParams["agent"]
.Should()
.BeOfType<Person>()
.Which.Should()
.BeEquivalentTo(person);
}


public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,69 @@ public void ShouldHandleListOfArbitraryObjects()
innerList[2].As<int>().Should().Be(3);
}

// Simple two-property class
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

[Fact]
public void ToDictionary_ShouldHandleEmptyDictionary()
{
var emptyDictionary = new Dictionary<string, Person>();
var result = emptyDictionary.ToDictionary();
result.Should().BeEmpty();
}

[Fact]
public void ToDictionary_ShouldConvertDictionaryWithSimpleObjectsCorrectly()
{
var sourceDictionary = new Dictionary<string, Person>
{
{ "Key1", new Person { Name = "John", Age = 30 } },
{ "Key2", new Person { Name = "Jane", Age = 25 } }
};

var result = sourceDictionary.ToDictionary();

result.Should().HaveCount(2);
result["Key1"].Should().BeEquivalentTo(sourceDictionary["Key1"]);
result["Key2"].Should().BeEquivalentTo(sourceDictionary["Key2"]);
}

[Fact]
public void ToDictionary_ShouldReturnNullForNullDictionary()
{
Dictionary<string, Person> nullDictionary = null;
// ReSharper disable once ExpressionIsAlwaysNull
var actual = nullDictionary.ToDictionary();
actual.Should().BeNull();
}

[Fact]
public void ToDictionary_ShouldHandleNestedDictionaryCorrectly()
{
var nestedDictionary = new Dictionary<string, Dictionary<string, Person>>
{
{
"Nested", new Dictionary<string, Person>
{
{ "InnerKey", new Person { Name = "Doe", Age = 40 } }
}
}
};

var result = nestedDictionary.ToDictionary();

result.Should().ContainKey("Nested");

// Validate nested dictionary
var innerDict = result["Nested"].As<Dictionary<string, Person>>();
innerDict.Should().ContainKey("InnerKey");
innerDict["InnerKey"].Should().BeEquivalentTo(new Person { Name = "Doe", Age = 40 });
}

[Fact]
public void ShouldHandleEnumerable()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ public static IDictionary<string, object> ToDictionary(this object o)
return dictIntRo.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}

if (TryGetDictionaryOfStringKeys(o, out var dictStr))
{
return dictStr;
}

if (o is IEnumerable<KeyValuePair<string, object>> kvpSeq)
{
return kvpSeq.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Expand All @@ -134,6 +139,28 @@ public static IDictionary<string, object> ToDictionary(this object o)
return FillDictionary(o, new Dictionary<string, object>());
}

private static bool TryGetDictionaryOfStringKeys(object o, out IDictionary<string, object> dictionary)
{
dictionary = null;

var typeInfo = o.GetType().GetTypeInfo();

// get all the interfaces implemented by the type and make sure that one of them is
// IDictionary<string,?>
var interfaces = typeInfo.ImplementedInterfaces;
var canUse = interfaces.Any(
i => i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IDictionary<,>) &&
i.GenericTypeArguments[0] == typeof(string));

if (canUse)
{
dictionary = new DictionaryAccessWrapper((IDictionary)o);
}

return canUse;
}

private static IDictionary<string, object> FillDictionary(object o, IDictionary<string, object> dict)
{
foreach (var propInfo in o.GetType().GetRuntimeProperties())
Expand Down Expand Up @@ -309,4 +336,72 @@ public static void OverwriteFrom<TKey, TValue>(
}
}
}

private struct DictionaryAccessWrapper(IDictionary dictionary) : IDictionary<string, object>
{
public object this[string key]
{
get => dictionary[key];
set => throw new NotSupportedException("This dictionary is read-only.");
}

public ICollection<string> Keys => dictionary.Keys.Cast<string>().ToList();
public ICollection<object> Values => dictionary.Values.Cast<object>().ToList();

/// <inheritdoc />
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
throw new NotSupportedException("This dictionary is read-only.");
}

public int Count => dictionary.Count;
public bool IsReadOnly => true;

public void Add(string key, object value) => throw new NotSupportedException("This dictionary is read-only.");

public bool ContainsKey(string key)
{
return dictionary.Contains(key);
}

public bool Remove(string key) => throw new NotSupportedException("This dictionary is read-only.");

public bool TryGetValue(string key, out object value)
{
if (dictionary.Contains(key))
{
value = dictionary[key];
return true;
}

value = null;
return false;
}

public void Add(KeyValuePair<string, object> item) => throw new NotSupportedException("This dictionary is read-only.");

public void Clear() => throw new NotSupportedException("This dictionary is read-only.");

public bool Contains(KeyValuePair<string, object> item)
{
return TryGetValue(item.Key, out var value) && Equals(value, item.Value);
}

public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) => throw new NotSupportedException();

/// <inheritdoc />
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
foreach (DictionaryEntry entry in dictionary)
{
yield return new KeyValuePair<string, object>((string)entry.Key, entry.Value);
}
}

/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<KeyValuePair<string, object>>)this).GetEnumerator();
}
}
}
8 changes: 6 additions & 2 deletions Neo4j.Driver/Neo4j.Driver/Public/ConfigBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,9 @@ public ConfigBuilder WithTls13()
}
#endif

/// <summary>Sets a custom <see cref="ITlsNegotiator"/> to use when establishing a TLS connection.</summary>
/// <summary>Sets a custom <see cref="ITlsNegotiator"/> to use when establishing a TLS connection. Note
/// that this overrides the default TLS negotiator, which handles certificate-based trust, so if you
/// use this method you should implement certificate validation yourself.</summary>
/// <param name="tlsNegotiator">The <see cref="ITlsNegotiator"/> to use.</param>
/// <returns>A <see cref="ConfigBuilder"/> instance for further configuration options.</returns>
/// <warning>
Expand All @@ -559,7 +561,9 @@ public ConfigBuilder WithTlsNegotiator(NegotiateTlsDelegate negotiateTls)
return this;
}

/// <summary>Sets the type of custom <see cref="ITlsNegotiator"/> to use when establishing a TLS connection.</summary>
/// <summary>Sets the type of custom <see cref="ITlsNegotiator"/> to use when establishing a TLS connection. Note
/// that this overrides the default TLS negotiator, which handles certificate-based trust, so if you
/// use this method you should implement certificate validation yourself.</summary>
/// <typeparam name="T">The <see cref="ITlsNegotiator"/> to use.</typeparam>
/// <returns>A <see cref="ConfigBuilder"/> instance for further configuration options.</returns>
/// <warning>
Expand Down