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 @@ -55,7 +55,7 @@
default:
ErrorFactory.ThrowFault(
ErrorCodes.InvalidArgument,
$"Relationship metadata type '{relationshipMetadata!.GetType().Name}' is not supported for deep insert");

Check warning on line 58 in src/Digitall.Dataverse.Testing/OrganizationRequests/DeepInsertProcessor.cs

View workflow job for this annotation

GitHub Actions / Qodana Community for .NET

Redundant nullable warning suppression expression

The nullable warning suppression expression is redundant
break;
}
}
Expand All @@ -68,21 +68,55 @@
EntityCollection children,
FakeOrganizationService state)
{
if (metadata.ReferencedEntity != parentLogicalName)
if (metadata.ReferencedEntity == parentLogicalName)
{
ErrorFactory.ThrowFault(
ErrorCodes.InvalidArgument,
$"Relationship '{metadata.SchemaName}' references entity '{metadata.ReferencedEntity}' " +
$"but the parent entity is '{parentLogicalName}'. The relationship metadata does not match the parent.");
// Parent is the "one" side → set FK on each child pointing to parent
var parentRef = new EntityReference(parentLogicalName, parentId);

foreach (var child in children.Entities)
{
child[metadata.ReferencingAttribute] = parentRef;
state.Create(child);
}
}
else if (metadata.ReferencingEntity == parentLogicalName)
{
// Parent is the "many" side (N:1) → lookup can only point to a single record
if (children.Entities.Count > 1)
{
ErrorFactory.ThrowFault(
ErrorCodes.InvalidArgument,
$"Relationship '{metadata.SchemaName}' is used from the referencing (N:1) side. " +
"A lookup attribute can only reference a single entity, but multiple related entities were provided.");
}

var parentRef = new EntityReference(parentLogicalName, parentId);
var child = children.Entities[0];

foreach (var child in children.Entities)
if (child.LogicalName != metadata.ReferencedEntity)
{
ErrorFactory.ThrowFault(
ErrorCodes.InvalidArgument,
$"Relationship '{metadata.SchemaName}' expects referenced entity '{metadata.ReferencedEntity}' " +
$"but the related entity is '{child.LogicalName}'.");
}
Comment on lines +82 to +101

// Create child first, then set FK on parent pointing to child
var childId = state.Create(child);
var childRef = new EntityReference(metadata.ReferencedEntity, childId);

var parentUpdate = new Entity(parentLogicalName, parentId)
{
[metadata.ReferencingAttribute] = childRef
};
state.Update(parentUpdate);
}
else
{
// Set the foreign key (lookup) on the child pointing to the parent
child[metadata.ReferencingAttribute] = parentRef;
state.Create(child);
ErrorFactory.ThrowFault(
ErrorCodes.InvalidArgument,
$"Relationship '{metadata.SchemaName}' is between '{metadata.ReferencedEntity}' and " +
$"'{metadata.ReferencingEntity}' but the parent entity is '{parentLogicalName}'. " +
"The relationship metadata does not match the parent.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Query;

namespace Digitall.Dataverse.Testing.Tests.OrganizationRequests;

Expand Down Expand Up @@ -114,7 +115,7 @@
account.RelatedEntities[new Relationship("unknown_relationship")] =
new EntityCollection([task]);

var act = () => _sut.Execute(new CreateRequest { Target = account });

Check notice on line 118 in tests/Digitall.Dataverse.Testing.Tests/OrganizationRequests/CreateFakeDeepInsertTests.cs

View workflow job for this annotation

GitHub Actions / Qodana Community for .NET

Convert delegate variable into local function

Use local function

await Assert.That(act).Throws<FaultException>();
}
Expand Down Expand Up @@ -197,7 +198,7 @@
account.RelatedEntities[new Relationship("mismatched_onetomany")] =
new EntityCollection([task]);

var act = () => _sut.Execute(new CreateRequest { Target = account });

Check notice on line 201 in tests/Digitall.Dataverse.Testing.Tests/OrganizationRequests/CreateFakeDeepInsertTests.cs

View workflow job for this annotation

GitHub Actions / Qodana Community for .NET

Convert delegate variable into local function

Use local function

await Assert.That(act).Throws<FaultException>();
}
Expand All @@ -212,8 +213,111 @@
account.RelatedEntities[new Relationship("systemuserroles_association")] =
new EntityCollection([role]);

var act = () => _sut.Execute(new CreateRequest { Target = account });

Check notice on line 216 in tests/Digitall.Dataverse.Testing.Tests/OrganizationRequests/CreateFakeDeepInsertTests.cs

View workflow job for this annotation

GitHub Actions / Qodana Community for .NET

Convert delegate variable into local function

Use local function

await Assert.That(act).Throws<FaultException>();
}

[Test]
public async Task Create_WithReversedOneToMany_SetsForeignKeyOnParent()
{
// Scenario: calendarrule (Referencing) has a lookup to calendar (Referenced)
// Deep insert from calendarrule side should create the calendar and set FK on calendarrule
_sut.State.Relationships["calendarrule_innercalendar"] = new OneToManyRelationshipMetadata
{
SchemaName = "calendarrule_innercalendar",
ReferencedEntity = "calendar",
ReferencedAttribute = "calendarid",
ReferencingEntity = "calendarrule",
ReferencingAttribute = "innercalendarid"
};

// Also register the parent relationship: calendar → calendarrule
_sut.State.Relationships["calendar_calendar_rules"] = new OneToManyRelationshipMetadata
{
SchemaName = "calendar_calendar_rules",
ReferencedEntity = "calendar",
ReferencedAttribute = "calendarid",
ReferencingEntity = "calendarrule",
ReferencingAttribute = "calendarid"
};

var calendar = new Entity("calendar") { Id = Guid.NewGuid(), ["name"] = "Business Hours" };
var innerCalendar = new Entity("calendar") { ["name"] = "Inner Schedule" };
var calendarRule = new Entity("calendarrule") { ["description"] = "Rule 1" };

Check notice on line 247 in tests/Digitall.Dataverse.Testing.Tests/OrganizationRequests/CreateFakeDeepInsertTests.cs

View workflow job for this annotation

GitHub Actions / Qodana Community for .NET

Use object or collection initializer when possible

Use object initializer

// Nested deep insert: calendar → calendarrule → innercalendar (reverse direction)
calendarRule.RelatedEntities[new Relationship("calendarrule_innercalendar")] =
new EntityCollection([innerCalendar]);

calendar.RelatedEntities[new Relationship("calendar_calendar_rules")] =
new EntityCollection([calendarRule]);

_sut.Execute(new CreateRequest { Target = calendar });

// Verify all entities were created
var calendars = _sut.CreateQuery("calendar").ToList();
var rules = _sut.CreateQuery("calendarrule").ToList();

await Assert.That(calendars).Count().IsEqualTo(2); // parent + inner
await Assert.That(rules).Count().IsEqualTo(1);

// Verify the FK on calendarrule points to the inner calendar
var createdRule = rules.Single();
var innerCalRef = createdRule.GetAttributeValue<EntityReference>("innercalendarid");
await Assert.That(innerCalRef).IsNotNull();
await Assert.That(innerCalRef!.LogicalName).IsEqualTo("calendar");

// The inner calendar should be the one named "Inner Schedule"
var innerCal = _sut.Retrieve("calendar", innerCalRef.Id, new ColumnSet(true));
await Assert.That(innerCal.GetAttributeValue<string>("name")).IsEqualTo("Inner Schedule");
}

[Test]
public async Task Create_WithReversedOneToMany_MultipleChildren_ThrowsFault()
{
// N:1 direction: a lookup can only point to one record
_sut.State.Relationships["calendarrule_innercalendar"] = new OneToManyRelationshipMetadata
{
SchemaName = "calendarrule_innercalendar",
ReferencedEntity = "calendar",
ReferencedAttribute = "calendarid",
ReferencingEntity = "calendarrule",
ReferencingAttribute = "innercalendarid"
};

var rule = new Entity("calendarrule") { Id = Guid.NewGuid(), ["description"] = "Rule" };
var cal1 = new Entity("calendar") { ["name"] = "Cal 1" };
var cal2 = new Entity("calendar") { ["name"] = "Cal 2" };

rule.RelatedEntities[new Relationship("calendarrule_innercalendar")] =
new EntityCollection([cal1, cal2]); // Two children on N:1 → invalid

var act = () => _sut.Execute(new CreateRequest { Target = rule });

Check notice on line 296 in tests/Digitall.Dataverse.Testing.Tests/OrganizationRequests/CreateFakeDeepInsertTests.cs

View workflow job for this annotation

GitHub Actions / Qodana Community for .NET

Convert delegate variable into local function

Use local function

await Assert.That(act).Throws<FaultException>();
}

[Test]
public async Task Create_WithReversedOneToMany_WrongChildLogicalName_ThrowsFault()
{
_sut.State.Relationships["calendarrule_innercalendar"] = new OneToManyRelationshipMetadata
{
SchemaName = "calendarrule_innercalendar",
ReferencedEntity = "calendar",
ReferencedAttribute = "calendarid",
ReferencingEntity = "calendarrule",
ReferencingAttribute = "innercalendarid"
};

var rule = new Entity("calendarrule") { Id = Guid.NewGuid(), ["description"] = "Rule" };
var wrongEntity = new Entity("account") { ["name"] = "Not a calendar" };

rule.RelatedEntities[new Relationship("calendarrule_innercalendar")] =
new EntityCollection([wrongEntity]);

var act = () => _sut.Execute(new CreateRequest { Target = rule });

Check notice on line 319 in tests/Digitall.Dataverse.Testing.Tests/OrganizationRequests/CreateFakeDeepInsertTests.cs

View workflow job for this annotation

GitHub Actions / Qodana Community for .NET

Convert delegate variable into local function

Use local function

await Assert.That(act).Throws<FaultException>();
}
}
Loading