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 @@ -5,23 +5,22 @@ namespace DataverseProxyGenerator.Core.Generation.Mappers;

public static class ProxyClassMapper
{
/// <summary>
/// The list is limited to properties where collisions have been identified.
/// </summary>
private static readonly HashSet<string> RestrictedAttributeNames = new(StringComparer.Ordinal)
{
"Attributes", // Collision on SdkMessageProcessingStepImage
};

public static object MapToTemplateModel((TableModel Table, IReadOnlyList<string> Interfaces) input, GenerationContext context)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(input.Table);

var (table, interfaces) = input;

var processedColumns = ProcessColumnsWithClassNameConflictResolution(table.Columns, table.SchemaName);

if (table.SchemaName == "EnvironmentVariableDefinition") {
foreach (var key in table.Keys) {
Console.WriteLine(key.SchemaName);
foreach (var attr in key.KeyAttributes) {
Console.WriteLine($" {attr.SchemaName} : {attr.TypeName}");
}
}
}
var processedColumns = ProcessColumnsWithNameConflictResolution(table.Columns, table.SchemaName);

return new
{
Expand All @@ -43,8 +42,11 @@ public static object MapToTemplateModel((TableModel Table, IReadOnlyList<string>
};
}

private static IEnumerable<ColumnModel> ProcessColumnsWithClassNameConflictResolution(IEnumerable<ColumnModel> columns, string className)
private static IEnumerable<ColumnModel> ProcessColumnsWithNameConflictResolution(IEnumerable<ColumnModel> columns, string className)
{
var usedNames = new HashSet<string>(RestrictedAttributeNames, StringComparer.Ordinal);
usedNames.Add(className);

return columns.Select(c =>
{
var sanitizedColumn = c switch
Expand All @@ -60,14 +62,20 @@ private static IEnumerable<ColumnModel> ProcessColumnsWithClassNameConflictResol
},
};

// Check if sanitized schema name conflicts with class name (case-sensitive)
if (string.Equals(sanitizedColumn.SchemaName, className, StringComparison.Ordinal))
var defaultName = sanitizedColumn.SchemaName;

// Ensure the final name is unique (handle edge case where _1 suffix also conflicts)
var candidateName = defaultName;
var suffix = 0;
while (usedNames.Contains(candidateName))
{
var finalName = $"{sanitizedColumn.SchemaName}_1";
return sanitizedColumn with { SchemaName = finalName };
suffix++;
candidateName = $"{defaultName}_{suffix}";
}

return sanitizedColumn;
usedNames.Add(candidateName);

return sanitizedColumn with { SchemaName = candidateName };
});
}
}
146 changes: 146 additions & 0 deletions tests/DataverseProxyGenerator.Tests/ProxyClassMapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,152 @@ public void MapToTemplateModel_WithCaseSensitiveClassNameConflict_AppliesRenamin
Assert.Contains(resultColumns, c => c.SchemaName == "testentity");
}

[Fact]
public void MapToTemplateModel_WithEntityBaseClassPropertyConflicts_AppendsUnderscoreOne()
{
// Arrange
var table = new TableModel
{
SchemaName = "TestEntity",
LogicalName = "testentity",
DisplayName = "Test Entity",
Description = "Test entity",
EntityTypeCode = 10001,
PrimaryNameAttribute = "name",
PrimaryIdAttribute = "testentityid",
IsIntersect = false,
Columns = new ColumnModel[]
{
new StringColumnModel
{
SchemaName = "Attributes", // Conflicts with Entity.Attributes
LogicalName = "attributes",
DisplayName = "Attributes",
MaxLength = 100,
},
},
Relationships = new List<RelationshipModel>(),
};

var context = CreateTestContext();

// Act
var result = ProxyClassMapper.MapToTemplateModel((table, new List<string>()), context);
var resultColumns = GetColumnsFromResult(result);

// Assert
Assert.Single(resultColumns);
Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Renamed due to conflict
}

[Fact]
public void MapToTemplateModel_WithCaseSensitiveEntityBaseClassConflict_OnlyRenamesNonVirtualExactMatch()
{
// Arrange
var table = new TableModel
{
SchemaName = "TestEntity",
LogicalName = "testentity",
DisplayName = "Test Entity",
Description = "Test entity",
EntityTypeCode = 10001,
PrimaryNameAttribute = "name",
PrimaryIdAttribute = "testentityid",
IsIntersect = false,
Columns = new ColumnModel[]
{
new StringColumnModel
{
SchemaName = "Attributes", // Exact match with non-virtual property - should be renamed
LogicalName = "attributes",
DisplayName = "Attributes",
MaxLength = 100,
},
new StringColumnModel
{
SchemaName = "attributes", // Different case - no conflict
LogicalName = "attributes_lower",
DisplayName = "attributes lower",
MaxLength = 100,
},
new StringColumnModel
{
SchemaName = "ATTRIBUTES", // Different case - no conflict
LogicalName = "attributes_upper",
DisplayName = "ATTRIBUTES upper",
MaxLength = 100,
},
},
Relationships = new List<RelationshipModel>(),
};

var context = CreateTestContext();

// Act
var result = ProxyClassMapper.MapToTemplateModel((table, new List<string>()), context);
var resultColumns = GetColumnsFromResult(result);

// Assert
Assert.Equal(3, resultColumns.Count);
Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Exact match renamed
Assert.Contains(resultColumns, c => c.SchemaName == "attributes"); // Different case kept
Assert.Contains(resultColumns, c => c.SchemaName == "ATTRIBUTES"); // Different case kept
}

[Fact]
public void MapToTemplateModel_WithMultipleConflictTypes_AppliesAllRenames()
{
// Arrange
var table = new TableModel
{
SchemaName = "Account",
LogicalName = "account",
DisplayName = "Account",
Description = "Test account",
EntityTypeCode = 1,
PrimaryNameAttribute = "name",
PrimaryIdAttribute = "accountid",
IsIntersect = false,
Columns = new ColumnModel[]
{
new StringColumnModel
{
SchemaName = "Account", // Conflicts with class name
LogicalName = "account_field",
DisplayName = "Account Field",
MaxLength = 100,
},
new StringColumnModel
{
SchemaName = "Attributes", // Conflicts with Entity base class
LogicalName = "attributes",
DisplayName = "Attributes",
MaxLength = 100,
},
new StringColumnModel
{
SchemaName = "Name", // No conflict
LogicalName = "name",
DisplayName = "Name",
MaxLength = 100,
},
},
Relationships = new List<RelationshipModel>(),
};

var context = CreateTestContext();

// Act
var result = ProxyClassMapper.MapToTemplateModel((table, new List<string>()), context);
var resultColumns = GetColumnsFromResult(result);

// Assert
Assert.Equal(3, resultColumns.Count);
Assert.Contains(resultColumns, c => c.SchemaName == "Account_1"); // Class name conflict
Assert.Contains(resultColumns, c => c.SchemaName == "Attributes_1"); // Base class conflict (non-virtual)
Assert.Contains(resultColumns, c => c.SchemaName == "Name"); // No conflict
}

private static GenerationContext CreateTestContext()
{
return new GenerationContext
Expand Down