test(16-01): add failing tests for RPT-03-f and RPT-03-g (consolidated CSV export)

- RPT-03-f: mergePermissions=false produces byte-identical output to default call
- RPT-03-g: mergePermissions=true writes consolidated header and merged rows
- Edge case: single-location entry has LocationCount=1 with no semicolons in Locations
This commit is contained in:
Dev
2026-04-09 12:32:42 +02:00
parent db42047db1
commit 4f7a6e3faa

View File

@@ -127,6 +127,122 @@ public class UserAccessCsvExportServiceTests
} }
} }
// ── RPT-03-f: mergePermissions=false produces identical output to default ──
[Fact]
public async Task WriteSingleFileAsync_mergePermissionsfalse_produces_identical_output()
{
var alice1 = MakeEntry(userLogin: "alice@contoso.com", siteTitle: "Contoso", permLevel: "Read");
var alice2 = MakeEntry(userLogin: "alice@contoso.com", siteTitle: "Dev Site", permLevel: "Read",
siteUrl: "https://contoso.sharepoint.com/sites/dev", objectUrl: "https://contoso.sharepoint.com/sites/dev/Docs");
var bob = MakeEntry(userDisplay: "Bob Smith", userLogin: "bob@contoso.com", permLevel: "Contribute");
var entries = new[] { alice1, alice2, bob };
var svc = new UserAccessCsvExportService();
var tmpDefault = Path.GetTempFileName();
var tmpExplicit = Path.GetTempFileName();
try
{
// Default call (no mergePermissions param)
await svc.WriteSingleFileAsync(entries, tmpDefault, CancellationToken.None);
// Explicit mergePermissions=false
await svc.WriteSingleFileAsync(entries, tmpExplicit, CancellationToken.None, mergePermissions: false);
var defaultContent = await File.ReadAllBytesAsync(tmpDefault);
var explicitContent = await File.ReadAllBytesAsync(tmpExplicit);
Assert.Equal(defaultContent, explicitContent);
}
finally
{
File.Delete(tmpDefault);
File.Delete(tmpExplicit);
}
}
// ── RPT-03-g: mergePermissions=true writes consolidated rows ──────────────
[Fact]
public async Task WriteSingleFileAsync_mergePermissionstrue_writes_consolidated_rows()
{
// alice has 2 entries with same key (same login, permLevel, accessType, grantedThrough)
// they should be merged into 1 row with 2 locations
var alice1 = MakeEntry(
userDisplay: "Alice Smith", userLogin: "alice@contoso.com",
siteUrl: "https://contoso.sharepoint.com", siteTitle: "Contoso",
permLevel: "Read", accessType: AccessType.Direct, grantedThrough: "Direct Permissions");
var alice2 = MakeEntry(
userDisplay: "Alice Smith", userLogin: "alice@contoso.com",
siteUrl: "https://dev.sharepoint.com", siteTitle: "Dev Site",
objectUrl: "https://dev.sharepoint.com/Docs",
permLevel: "Read", accessType: AccessType.Direct, grantedThrough: "Direct Permissions");
// bob has a different key — separate row
var bob = MakeEntry(
userDisplay: "Bob Smith", userLogin: "bob@contoso.com",
siteTitle: "Contoso", permLevel: "Contribute",
accessType: AccessType.Direct, grantedThrough: "Direct Permissions");
var entries = new[] { alice1, alice2, bob };
var svc = new UserAccessCsvExportService();
var tmpFile = Path.GetTempFileName();
try
{
await svc.WriteSingleFileAsync(entries, tmpFile, CancellationToken.None, mergePermissions: true);
var content = await File.ReadAllTextAsync(tmpFile);
// Header must contain consolidated columns
Assert.Contains("\"User\",\"User Login\",\"Permission Level\",\"Access Type\",\"Granted Through\",\"Locations\",\"Location Count\"", content);
// Alice's two entries merged — locations column contains both site titles
Assert.Contains("Contoso", content);
Assert.Contains("Dev Site", content);
// Bob appears as a separate row
Assert.Contains("bob@contoso.com", content);
// The consolidated report label should appear
Assert.Contains("User Access Audit Report (Consolidated)", content);
}
finally
{
File.Delete(tmpFile);
}
}
// ── RPT-03-g edge case: single-location consolidated entry ────────────────
[Fact]
public async Task WriteSingleFileAsync_mergePermissionstrue_singleLocation_noSemicolon()
{
var entry = MakeEntry(
userDisplay: "Alice Smith", userLogin: "alice@contoso.com",
siteTitle: "Contoso", permLevel: "Read",
accessType: AccessType.Direct, grantedThrough: "Direct Permissions");
var svc = new UserAccessCsvExportService();
var tmpFile = Path.GetTempFileName();
try
{
await svc.WriteSingleFileAsync(new[] { entry }, tmpFile, CancellationToken.None, mergePermissions: true);
var content = await File.ReadAllTextAsync(tmpFile);
// Should contain exactly "1" as LocationCount
Assert.Contains("\"1\"", content);
// Locations field for a single entry should not contain a semicolon
// Find the data row for alice and verify no semicolon in Locations
var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries);
var dataRow = lines.FirstOrDefault(l => l.Contains("alice@contoso.com") && !l.StartsWith("\"Users"));
Assert.NotNull(dataRow);
// The Locations column value is "Contoso" with no semicolons
Assert.DoesNotContain("Contoso; ", dataRow);
}
finally
{
File.Delete(tmpFile);
}
}
// ── Private helpers ─────────────────────────────────────────────────────── // ── Private helpers ───────────────────────────────────────────────────────
/// <summary> /// <summary>