Skip to content
This repository was archived by the owner on Dec 15, 2025. It is now read-only.
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
16 changes: 13 additions & 3 deletions Fake4DataverseCore/Fake4Dataverse.Core/XrmFakedContext.Audit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public IAuditRepository AuditRepository
/// 1. Organization-level IsAuditEnabled = true
/// 2. Entity-level IsAuditEnabled = true (from EntityMetadata)
/// 3. Attribute-level IsAuditEnabled = true (from AttributeMetadata) for attribute changes
///
/// Note: If entity metadata exists but IsAuditEnabled is null, we allow auditing
/// (this matches the behavior where no explicit audit setting means auditing is allowed)
/// </summary>
private bool ShouldAuditEntity(string entityLogicalName)
{
Expand All @@ -65,8 +68,10 @@ private bool ShouldAuditEntity(string entityLogicalName)
if (EntityMetadata.ContainsKey(entityLogicalName))
{
var entityMetadata = EntityMetadata[entityLogicalName];
// IsAuditEnabled is a nullable bool, so we check for true explicitly
if (entityMetadata.IsAuditEnabled?.Value != true)
// IsAuditEnabled is a nullable bool
// If it's explicitly set to false, don't audit
// If it's true or null, allow auditing
if (entityMetadata.IsAuditEnabled?.Value == false)
{
return false;
}
Expand All @@ -79,6 +84,9 @@ private bool ShouldAuditEntity(string entityLogicalName)
/// <summary>
/// Filters attribute changes to only include audited attributes
/// Reference: https://learn.microsoft.com/en-us/power-apps/developer/data-platform/auditing/configure
///
/// Note: If attribute metadata exists but IsAuditEnabled is null, we include the attribute
/// (this matches the behavior where no explicit audit setting means auditing is allowed)
/// </summary>
private Dictionary<string, (object oldValue, object newValue)> FilterAuditedAttributes(
string entityLogicalName,
Expand Down Expand Up @@ -107,7 +115,9 @@ private bool ShouldAuditEntity(string entityLogicalName)
if (attributeMetadata != null)
{
// IsAuditEnabled is a BooleanManagedProperty
if (attributeMetadata.IsAuditEnabled?.Value == true)
// If it's explicitly set to false, don't audit
// If it's true or null, include the change
if (attributeMetadata.IsAuditEnabled?.Value != false)
{
filteredChanges[change.Key] = change.Value;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
using System;
using System.Linq;
using Microsoft.Xrm.Sdk;
using Xunit;
using Fake4Dataverse.Middleware;
using Fake4Dataverse.Service.Controllers;
using Microsoft.AspNetCore.Mvc;
using Fake4Dataverse.Abstractions.Audit;

namespace Fake4Dataverse.Service.Tests;

/// <summary>
/// Tests for the AuditController REST API
/// Reference: https://learn.microsoft.com/en-us/power-apps/developer/data-platform/auditing/overview
///
/// These tests verify that the audit REST API correctly exposes audit records
/// and allows querying audit history for entities and specific records.
/// </summary>
public class AuditControllerTests
{
private readonly AuditController _controller;
private readonly IOrganizationService _organizationService;
private readonly IAuditRepository _auditRepository;

public AuditControllerTests()
{
// Create a Fake4Dataverse context with basic configuration
var context = XrmFakedContextFactory.New();
_organizationService = context.GetOrganizationService();
_auditRepository = context.GetProperty<IAuditRepository>();

// Create the controller
_controller = new AuditController(context);
}

[Fact]
public void Should_Return_Empty_Audit_List_When_Auditing_Disabled()
{
// Arrange
_auditRepository.IsAuditEnabled = false;

// Act
var result = _controller.GetAllAudits(null, null, null, null);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var value = okResult.Value;
var valueProperty = value.GetType().GetProperty("value");
var countProperty = value.GetType().GetProperty("count");

Assert.NotNull(valueProperty);
Assert.NotNull(countProperty);

var auditRecords = valueProperty.GetValue(value) as System.Collections.IEnumerable;
Assert.NotNull(auditRecords);
Assert.Empty(auditRecords.Cast<object>());

Assert.Equal(0, countProperty.GetValue(value));
}

[Fact]
public void Should_Return_Audit_Records_When_Auditing_Enabled()
{
// Arrange - Enable auditing
_auditRepository.IsAuditEnabled = true;

// Create an entity to generate audit records
var account = new Entity("account")
{
["name"] = "Test Account"
};
var accountId = _organizationService.Create(account);

// Act
var result = _controller.GetAllAudits(null, null, null, null);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var value = okResult.Value;
var valueProperty = value.GetType().GetProperty("value");
var countProperty = value.GetType().GetProperty("count");

Assert.NotNull(valueProperty);
Assert.NotNull(countProperty);

var auditRecords = valueProperty.GetValue(value) as System.Collections.IEnumerable;
Assert.NotNull(auditRecords);
Assert.NotEmpty(auditRecords.Cast<object>());

var count = (int)countProperty.GetValue(value);
Assert.True(count > 0);
}

[Fact]
public void Should_Return_Entity_Audit_History()
{
// Arrange - Enable auditing
_auditRepository.IsAuditEnabled = true;

// Create and update an entity to generate audit records
var account = new Entity("account")
{
["name"] = "Test Account"
};
var accountId = _organizationService.Create(account);

// Update the account
var updateAccount = new Entity("account", accountId)
{
["name"] = "Updated Account"
};
_organizationService.Update(updateAccount);

// Act
var result = _controller.GetEntityAudits("account", accountId.ToString());

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var value = okResult.Value;
var valueProperty = value.GetType().GetProperty("value");
var countProperty = value.GetType().GetProperty("count");

Assert.NotNull(valueProperty);
Assert.NotNull(countProperty);

var auditRecords = valueProperty.GetValue(value) as System.Collections.IEnumerable;
Assert.NotNull(auditRecords);

var recordsList = auditRecords.Cast<object>().ToList();
Assert.True(recordsList.Count >= 2); // At least Create and Update

var count = (int)countProperty.GetValue(value);
Assert.True(count >= 2);
}

[Fact]
public void Should_Return_Audit_Details()
{
// Arrange - Enable auditing
_auditRepository.IsAuditEnabled = true;

// Create an entity to generate audit record
var account = new Entity("account")
{
["name"] = "Test Account",
["revenue"] = new Money(100000)
};
var accountId = _organizationService.Create(account);

// Get the audit records
var entityRef = new EntityReference("account", accountId);
var auditRecords = _auditRepository.GetAuditRecordsForEntity(entityRef).ToList();
Assert.NotEmpty(auditRecords);

var auditId = auditRecords.First().GetAttributeValue<Guid>("auditid");

// Act
var result = _controller.GetAuditDetails(auditId.ToString());

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
Assert.NotNull(okResult.Value);
}

[Fact]
public void Should_Return_Audit_Status()
{
// Arrange
_auditRepository.IsAuditEnabled = true;

// Act
var result = _controller.GetAuditStatus();

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var value = okResult.Value;
var isAuditEnabledProperty = value.GetType().GetProperty("isAuditEnabled");

Assert.NotNull(isAuditEnabledProperty);
var isEnabled = (bool)isAuditEnabledProperty.GetValue(value);
Assert.True(isEnabled);
}

[Fact]
public void Should_Set_Audit_Status()
{
// Arrange
_auditRepository.IsAuditEnabled = false;
var request = new AuditStatusRequest { IsAuditEnabled = true };

// Act
var result = _controller.SetAuditStatus(request);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var value = okResult.Value;
var isAuditEnabledProperty = value.GetType().GetProperty("isAuditEnabled");

Assert.NotNull(isAuditEnabledProperty);
var isEnabled = (bool)isAuditEnabledProperty.GetValue(value);
Assert.True(isEnabled);
Assert.True(_auditRepository.IsAuditEnabled);
}

[Fact]
public void Should_Return_BadRequest_For_Invalid_Guid()
{
// Act
var result = _controller.GetEntityAudits("account", "invalid-guid");

// Assert
Assert.IsType<BadRequestObjectResult>(result);
}

[Fact]
public void Should_Support_Filtering_By_Entity_Type()
{
// Arrange - Enable auditing
_auditRepository.IsAuditEnabled = true;

// Create different entity types
var account = new Entity("account") { ["name"] = "Test Account" };
var contact = new Entity("contact") { ["firstname"] = "Test" };

_organizationService.Create(account);
_organizationService.Create(contact);

// Act - Filter by account entity type
var result = _controller.GetAllAudits(null, null, null, "objecttypecode eq 'account'");

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var value = okResult.Value;
var valueProperty = value.GetType().GetProperty("value");

Assert.NotNull(valueProperty);
var auditRecords = valueProperty.GetValue(value) as System.Collections.IEnumerable;
Assert.NotNull(auditRecords);

// All records should be for account entity
var recordsList = auditRecords.Cast<object>().ToList();
Assert.NotEmpty(recordsList);
}

[Fact]
public void Should_Support_Pagination()
{
// Arrange - Enable auditing
_auditRepository.IsAuditEnabled = true;

// Create multiple entities to generate audit records
for (int i = 0; i < 5; i++)
{
var account = new Entity("account") { ["name"] = $"Test Account {i}" };
_organizationService.Create(account);
}

// Act - Request first 2 records
var result = _controller.GetAllAudits(2, 0, null, null);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var value = okResult.Value;
var valueProperty = value.GetType().GetProperty("value");
var countProperty = value.GetType().GetProperty("count");

Assert.NotNull(valueProperty);
Assert.NotNull(countProperty);

var auditRecords = valueProperty.GetValue(value) as System.Collections.IEnumerable;
Assert.NotNull(auditRecords);

var recordsList = auditRecords.Cast<object>().ToList();
Assert.Equal(2, recordsList.Count);

// Total count should be more than 2
var totalCount = (int)countProperty.GetValue(value);
Assert.True(totalCount >= 5);
}
}
Loading
Loading