Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
@@ -0,0 +1,102 @@
// Copyright (c) "Neo4j"
// Neo4j Sweden AB [https://neo4j.com]
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using FluentAssertions;
using Neo4j.Driver.Mapping.ConventionTranslation;
using Xunit;

namespace Neo4j.Driver.Tests.Mapping.ConventionTranslation;

public class CombinerTests
{
[Fact]
public void SnakeCaseCombiner_ShouldCombineTokens()
{
var combiner = new SnakeCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple", "BANANA", "Cherry" });
result.Should().Be("apple_banana_cherry");
}

[Fact]
public void SnakeCaseCombiner_ShouldCombineSingleToken()
{
var combiner = new SnakeCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple" });
result.Should().Be("apple");
}

[Fact]
public void CamelCaseCombiner_ShouldCombineTokens()
{
var combiner = new CamelCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple", "BANANA", "Cherry" });
result.Should().Be("appleBananaCherry");
}

[Fact]
public void CamelCaseCombiner_ShouldCombineSingleToken()
{
var combiner = new CamelCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple" });
result.Should().Be("apple");
}

[Fact]
public void PascalCaseCombiner_ShouldCombineTokens()
{
var combiner = new PascalCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple", "BANANA", "Cherry" });
result.Should().Be("AppleBananaCherry");
}

[Fact]
public void PascalCaseCombiner_ShouldCombineSingleToken()
{
var combiner = new PascalCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple" });
result.Should().Be("Apple");
}

[Fact]
public void ScreamingSnakeCaseCombiner_ShouldCombineTokens()
{
var combiner = new ScreamingSnakeCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple", "BANANA", "Cherry" });
result.Should().Be("APPLE_BANANA_CHERRY");
}

[Fact]
public void ScreamingSnakeCaseCombiner_ShouldCombineSingleToken()
{
var combiner = new ScreamingSnakeCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple" });
result.Should().Be("APPLE");
}

[Fact]
public void KebabCaseCombiner_ShouldCombineTokens()
{
var combiner = new KebabCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple", "BANANA", "Cherry" });
result.Should().Be("apple-banana-cherry");
}

[Fact]
public void KebabCaseCombiner_ShouldCombineSingleToken()
{
var combiner = new KebabCaseCombiner();
var result = combiner.CombineTokens(new[] { "aPple" });
result.Should().Be("apple");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright (c) "Neo4j"
// Neo4j Sweden AB [https://neo4j.com]
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using FluentAssertions;
using Neo4j.Driver.Mapping.ConventionTranslation;
using Xunit;

namespace Neo4j.Driver.Tests.Mapping.ConventionTranslation;

public class ExtractorTests
{
[Fact]
public void ShouldExtractSnakeCaseTokens()
{
var extractor = new SnakeCaseExtractor();
var tokens = extractor.ExtractTokens("apple_banana_cherry");
tokens.Should().BeEquivalentTo("apple", "banana", "cherry");
}

[Fact]
public void ShouldExtractCamelCaseTokens()
{
var extractor = new CamelCaseExtractor();
var tokens = extractor.ExtractTokens("appleBananaCherry");
tokens.Should().BeEquivalentTo("apple", "Banana", "Cherry");
}

[Fact]
public void ShouldExtractPascalCaseTokens()
{
var extractor = new PascalCaseExtractor();
var tokens = extractor.ExtractTokens("AppleBananaCherry");
tokens.Should().BeEquivalentTo("Apple", "Banana", "Cherry");
}

[Fact]
public void ShouldExtractScreamingSnakeCaseTokens()
{
var extractor = new ScreamingSnakeCaseExtractor();
var tokens = extractor.ExtractTokens("APPLE_BANANA_CHERRY");
tokens.Should().BeEquivalentTo("APPLE", "BANANA", "CHERRY");
}

[Fact]
public void ShouldExtractKebabCaseTokens()
{
var extractor = new KebabCaseExtractor();
var tokens = extractor.ExtractTokens("apple-banana-cherry");
tokens.Should().BeEquivalentTo("apple", "banana", "cherry");
}

[Fact]
public void ShouldExtractCSharpIdentifierTokens_LowerCaseStart()
{
var extractor = new CSharpIdentifierExtractor();
var tokens = extractor.ExtractTokens("appleBananaCherry");
tokens.Should().BeEquivalentTo("apple", "Banana", "Cherry");
}

[Fact]
public void ShouldExtractCSharpIdentifierTokens_UpperCaseStart()
{
var extractor = new CSharpIdentifierExtractor();
var tokens = extractor.ExtractTokens("AppleBananaCherry");
tokens.Should().BeEquivalentTo("Apple", "Banana", "Cherry");
}

[Fact]
public void ShouldExtractTokensWithCustomRegex_Dot()
{
var extractor = new RegexExtractor(@"\.", @"\.");
var tokens = extractor.ExtractTokens("apple.banana.cherry");
tokens.Should().BeEquivalentTo("apple", "banana", "cherry");
}

[Fact]
public void ShouldExtractTokensWithCustomRegex_Space()
{
var extractor = new RegexExtractor(@"\s+", @"\s+");
var tokens = extractor.ExtractTokens("apple banana cherry");
tokens.Should().BeEquivalentTo("apple", "banana", "cherry");
}

[Fact]
public void ShouldExtractTokensWithCustomRegex_Semicolon()
{
var extractor = new RegexExtractor(";", ";");
var tokens = extractor.ExtractTokens("apple;banana;cherry");
tokens.Should().BeEquivalentTo("apple", "banana", "cherry");
}

[Fact]
public void ShouldExtractTokensWithCustomRegex_Pipe()
{
var extractor = new RegexExtractor(@"\|", @"\|");
var tokens = extractor.ExtractTokens("apple|banana|cherry");
tokens.Should().BeEquivalentTo("apple", "banana", "cherry");
}

[Fact]
public void ShouldExtractTokensWithCustomRegex_MixedDelimiters()
{
var extractor = new RegexExtractor(@"[_\-\.\s]+", @"[_\-\.\s]+");
var tokens = extractor.ExtractTokens("apple_banana-cherry.mango pineapple");
tokens.Should().BeEquivalentTo("apple", "banana", "cherry", "mango", "pineapple");
}

[Fact]
public void ShouldThrowExceptionForInvalidMixedDelimiters()
{
var extractor = new RegexExtractor(@"^([a-zA-Z]+[_\-\.\s])*[a-zA-Z]+$", @"[_\-\.\s]+");
Action act = () => extractor.ExtractTokens("apple_banana-cherry.mango pineapple!");
act.Should().Throw<ArgumentException>();
}

[Fact]
public void ShouldThrowExceptionForInvalidCamelCase()
{
var extractor = new CamelCaseExtractor();
Action act = () => extractor.ExtractTokens("AppleBananaCherry");
act.Should().Throw<ArgumentException>();
}

[Fact]
public void ShouldThrowExceptionForInvalidPascalCase()
{
var extractor = new PascalCaseExtractor();
Action act = () => extractor.ExtractTokens("appleBananaCherry");
act.Should().Throw<ArgumentException>();
}

[Fact]
public void ShouldThrowExceptionForInvalidSnakeCase()
{
var extractor = new SnakeCaseExtractor();
Action act = () => extractor.ExtractTokens("apple-Banana-Cherry");
act.Should().Throw<ArgumentException>();
}

[Fact]
public void ShouldThrowExceptionForInvalidScreamingSnakeCase()
{
var extractor = new ScreamingSnakeCaseExtractor();
Action act = () => extractor.ExtractTokens("APPLE-banana-CHERRY");
act.Should().Throw<ArgumentException>();
}

[Fact]
public void ShouldThrowExceptionForInvalidKebabCase()
{
var extractor = new KebabCaseExtractor();
Action act = () => extractor.ExtractTokens("apple_Banana_Cherry");
act.Should().Throw<ArgumentException>();
}

[Fact]
public void ShouldThrowExceptionForInvalidCSharpIdentifier()
{
var extractor = new CSharpIdentifierExtractor();
Action act = () => extractor.ExtractTokens("apple_bananaCherry");
act.Should().Throw<ArgumentException>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) "Neo4j"
// Neo4j Sweden AB [https://neo4j.com]
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Collections.Generic;
using FluentAssertions;
using Neo4j.Driver.Internal.Types;
using Neo4j.Driver.Mapping;
using Neo4j.Driver.Mapping.ConventionTranslation;
using Neo4j.Driver.Tests.TestUtil;
using Xunit;

namespace Neo4j.Driver.Tests.Mapping.ConventionTranslation;

public class TranslationEndToEndTests : MappingTestWithGlobalState
{
[Fact]
public void ShouldDoSimpleTranslation()
{
var record = TestRecord.Create(["personName", "yearBorn"], ["Bob", 1977]);
RecordObjectMapping.SetRecordConventionCombiner<CamelCaseCombiner>();
var person = record.AsObjectFromBlueprint(new { PersonName = "", YearBorn = 0 });
person.PersonName.Should().Be("Bob");
person.YearBorn.Should().Be(1977);
}

[Fact]
public void ShouldMapKebabCaseFieldsToSnakeCaseProperties()
{
var record = TestRecord.Create(["person-name", "year-born"], ["Bob", 1977]);
RecordObjectMapping.SetConventionTranslation<SnakeCaseExtractor, KebabCaseCombiner>();
var person = record.AsObjectFromBlueprint(new { person_name = "", year_born = 0 });
person.person_name.Should().Be("Bob");
person.year_born.Should().Be(1977);
}

private class ExplicitNamePerson
{
[MappingSource("name-of-person")]
public string Name { get; set; }

public int YearBorn { get; set; }
}

[Fact]
public void ShouldNotTranslateWhenPropertyIsMarkedWithMappingSourceAttribute()
{
var record = TestRecord.Create(["name-of-person", "year_born"], ["Bob", 1977]);
RecordObjectMapping.SetRecordConventionCombiner<SnakeCaseCombiner>();
var person = record.AsObject<ExplicitNamePerson>();
person.Name.Should().Be("Bob");
person.YearBorn.Should().Be(1977);
}

public record Person(int NumberOfMiddleNames, string FavouriteColor);

public class FlightCrew(Person pilot, Person coPilot)
{
public Person Pilot { get; set; }
public Person CoPilot { get; set; }
}

[Fact]
public void ShouldTranslateThroughNesting()
{
var pilotNode = new Node(
0,
[],
new Dictionary<string, object> { ["number_of_middle_names"] = 1, ["favourite_color"] = "red" });

var coPilotNode = new Node(
1,
[],
new Dictionary<string, object> { ["number_of_middle_names"] = 2, ["favourite_color"] = "blue" });

var record = TestRecord.Create(("pilot", pilotNode), ("co_pilot", coPilotNode));
RecordObjectMapping.SetRecordConventionCombiner<SnakeCaseCombiner>();

var flightCrew = record.AsObject<FlightCrew>();

flightCrew.Pilot.NumberOfMiddleNames.Should().Be(1);
flightCrew.Pilot.FavouriteColor.Should().Be("red");
flightCrew.CoPilot.NumberOfMiddleNames.Should().Be(2);
flightCrew.CoPilot.FavouriteColor.Should().Be("blue");
}
}
Loading