diff --git a/SharepointToolbox.Tests/Services/Export/CsvExportServiceTests.cs b/SharepointToolbox.Tests/Services/Export/CsvExportServiceTests.cs
new file mode 100644
index 0000000..3a0cbf2
--- /dev/null
+++ b/SharepointToolbox.Tests/Services/Export/CsvExportServiceTests.cs
@@ -0,0 +1,71 @@
+using SharepointToolbox.Core.Models;
+using SharepointToolbox.Services.Export;
+
+namespace SharepointToolbox.Tests.Services.Export;
+
+///
+/// Tests for PERM-05: CSV export output.
+/// These tests reference CsvExportService which will be implemented in Plan 03.
+/// Until Plan 03 runs they will fail to compile — that is expected.
+///
+public class CsvExportServiceTests
+{
+ private static PermissionEntry MakeEntry(
+ string objectType, string title, string url,
+ bool hasUnique, string users, string userLogins,
+ string permissionLevels, string grantedThrough, string principalType) =>
+ new(objectType, title, url, hasUnique, users, userLogins, permissionLevels, grantedThrough, principalType);
+
+ [Fact]
+ public void BuildCsv_WithKnownEntries_ProducesHeaderRow()
+ {
+ var entry = MakeEntry("Web", "Site A", "https://contoso.sharepoint.com/sites/A",
+ true, "alice@contoso.com", "i:0#.f|membership|alice@contoso.com",
+ "Contribute", "Direct Permissions", "User");
+
+ var svc = new CsvExportService();
+ var csv = svc.BuildCsv(new[] { entry });
+
+ Assert.Contains("Object", csv);
+ Assert.Contains("Title", csv);
+ Assert.Contains("URL", csv);
+ Assert.Contains("HasUniquePermissions", csv);
+ Assert.Contains("Users", csv);
+ Assert.Contains("UserLogins", csv);
+ Assert.Contains("Type", csv);
+ Assert.Contains("Permissions", csv);
+ Assert.Contains("GrantedThrough", csv);
+ }
+
+ [Fact]
+ public void BuildCsv_WithEmptyList_ReturnsHeaderOnly()
+ {
+ var svc = new CsvExportService();
+ var csv = svc.BuildCsv(Array.Empty());
+
+ // Should have exactly one line (header) or header + empty body
+ Assert.NotEmpty(csv);
+ Assert.Contains("Object", csv);
+ }
+
+ [Fact]
+ public void BuildCsv_WithDuplicateUserPermissionGrantedThrough_MergesLocations()
+ {
+ // PERM-05 Merge-PermissionRows: two entries with same Users+PermissionLevels+GrantedThrough
+ // but different URLs must be merged into one row with URLs pipe-joined.
+ var entryA = MakeEntry("Web", "Site A", "https://contoso.sharepoint.com/sites/A",
+ true, "alice@contoso.com", "i:0#.f|membership|alice@contoso.com",
+ "Contribute", "Direct Permissions", "User");
+ var entryB = MakeEntry("Web", "Site B", "https://contoso.sharepoint.com/sites/B",
+ true, "alice@contoso.com", "i:0#.f|membership|alice@contoso.com",
+ "Contribute", "Direct Permissions", "User");
+
+ var svc = new CsvExportService();
+ var csv = svc.BuildCsv(new[] { entryA, entryB });
+
+ // Merged row must contain both URLs separated by " | "
+ Assert.Contains("sites/A", csv);
+ Assert.Contains("sites/B", csv);
+ Assert.Contains("|", csv);
+ }
+}
diff --git a/SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs b/SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs
new file mode 100644
index 0000000..043ec3b
--- /dev/null
+++ b/SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs
@@ -0,0 +1,53 @@
+using SharepointToolbox.Core.Models;
+using SharepointToolbox.Services.Export;
+
+namespace SharepointToolbox.Tests.Services.Export;
+
+///
+/// Tests for PERM-06: HTML export output.
+/// These tests reference HtmlExportService which will be implemented in Plan 03.
+/// Until Plan 03 runs they will fail to compile — that is expected.
+///
+public class HtmlExportServiceTests
+{
+ private static PermissionEntry MakeEntry(
+ string users, string userLogins,
+ string url = "https://contoso.sharepoint.com/sites/A") =>
+ new("Web", "Site A", url, true, users, userLogins, "Read", "Direct Permissions", "User");
+
+ [Fact]
+ public void BuildHtml_WithKnownEntries_ContainsUserNames()
+ {
+ var entry = MakeEntry("Bob Smith", "bob@contoso.com");
+ var svc = new HtmlExportService();
+ var html = svc.BuildHtml(new[] { entry });
+
+ Assert.Contains("Bob Smith", html);
+ }
+
+ [Fact]
+ public void BuildHtml_WithEmptyList_ReturnsValidHtml()
+ {
+ var svc = new HtmlExportService();
+ var html = svc.BuildHtml(Array.Empty());
+
+ // Must be non-empty well-formed HTML even with no data rows
+ Assert.NotEmpty(html);
+ Assert.Contains("