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
27 changes: 27 additions & 0 deletions TriasDev.Templify.Tests/Helpers/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,33 @@ public DocumentBuilder AddPageBreak()
return this;
}

/// <summary>
/// Sets a document metadata property on the template.
/// </summary>
public DocumentBuilder SetAuthor(string author)
{
_document.PackageProperties.Creator = author;
return this;
}

/// <summary>
/// Sets the document title metadata property.
/// </summary>
public DocumentBuilder SetTitle(string title)
{
_document.PackageProperties.Title = title;
return this;
}

/// <summary>
/// Sets the document subject metadata property.
/// </summary>
public DocumentBuilder SetSubject(string subject)
{
_document.PackageProperties.Subject = subject;
return this;
}

/// <summary>
/// Returns the document as a MemoryStream for processing.
/// </summary>
Expand Down
35 changes: 35 additions & 0 deletions TriasDev.Templify.Tests/Helpers/DocumentVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,41 @@ public bool HasUpdateFieldsOnOpen()
return updateFields?.Val?.Value == true;
}

/// <summary>
/// Gets the document Author (Creator) property.
/// </summary>
public string? GetDocumentAuthor() => _document.PackageProperties.Creator;

/// <summary>
/// Gets the document Title property.
/// </summary>
public string? GetDocumentTitle() => _document.PackageProperties.Title;

/// <summary>
/// Gets the document Subject property.
/// </summary>
public string? GetDocumentSubject() => _document.PackageProperties.Subject;

/// <summary>
/// Gets the document Description (Comments) property.
/// </summary>
public string? GetDocumentDescription() => _document.PackageProperties.Description;

/// <summary>
/// Gets the document Keywords property.
/// </summary>
public string? GetDocumentKeywords() => _document.PackageProperties.Keywords;

/// <summary>
/// Gets the document Category property.
/// </summary>
public string? GetDocumentCategory() => _document.PackageProperties.Category;

/// <summary>
/// Gets the document LastModifiedBy property.
/// </summary>
public string? GetDocumentLastModifiedBy() => _document.PackageProperties.LastModifiedBy;

public void Dispose()
{
_document?.Dispose();
Expand Down
258 changes: 258 additions & 0 deletions TriasDev.Templify.Tests/Integration/DocumentPropertiesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// Copyright (c) 2025 TriasDev GmbH & Co. KG
// Licensed under the MIT License. See LICENSE file in the project root for full license information.

using TriasDev.Templify.Core;
using TriasDev.Templify.Tests.Helpers;

namespace TriasDev.Templify.Tests.Integration;

/// <summary>
/// Integration tests for document metadata properties (Author, Title, etc.).
/// </summary>
public sealed class DocumentPropertiesTests
{
[Fact]
public void ProcessTemplate_SetAuthorOnly_ChangesAuthorPreservesOthers()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Hello {{Name}}!");
builder.SetAuthor("Original Author");
builder.SetTitle("Original Title");

MemoryStream templateStream = builder.ToStream();

Dictionary<string, object> data = new Dictionary<string, object>
{
["Name"] = "World"
};

PlaceholderReplacementOptions options = new PlaceholderReplacementOptions
{
DocumentProperties = new DocumentProperties
{
Author = "New Author"
}
};

DocumentTemplateProcessor processor = new DocumentTemplateProcessor(options);
MemoryStream outputStream = new MemoryStream();

// Act
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);

using DocumentVerifier verifier = new DocumentVerifier(outputStream);
Assert.Equal("New Author", verifier.GetDocumentAuthor());
Assert.Equal("Original Title", verifier.GetDocumentTitle());
}

[Fact]
public void ProcessTemplate_SetMultipleProperties_AllSetCorrectly()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Content");

MemoryStream templateStream = builder.ToStream();

Dictionary<string, object> data = new Dictionary<string, object>();

PlaceholderReplacementOptions options = new PlaceholderReplacementOptions
{
DocumentProperties = new DocumentProperties
{
Author = "Test Author",
Title = "Test Title",
Subject = "Test Subject",
Description = "Test Description",
Keywords = "test, keywords",
Category = "Test Category",
LastModifiedBy = "Test User"
}
};

DocumentTemplateProcessor processor = new DocumentTemplateProcessor(options);
MemoryStream outputStream = new MemoryStream();

// Act
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);

using DocumentVerifier verifier = new DocumentVerifier(outputStream);
Assert.Equal("Test Author", verifier.GetDocumentAuthor());
Assert.Equal("Test Title", verifier.GetDocumentTitle());
Assert.Equal("Test Subject", verifier.GetDocumentSubject());
Assert.Equal("Test Description", verifier.GetDocumentDescription());
Assert.Equal("test, keywords", verifier.GetDocumentKeywords());
Assert.Equal("Test Category", verifier.GetDocumentCategory());
Assert.Equal("Test User", verifier.GetDocumentLastModifiedBy());
}

[Fact]
public void ProcessTemplate_NoDocumentProperties_PreservesOriginalAuthor()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Content");
builder.SetAuthor("Template Author");

MemoryStream templateStream = builder.ToStream();

Dictionary<string, object> data = new Dictionary<string, object>();

DocumentTemplateProcessor processor = new DocumentTemplateProcessor();
MemoryStream outputStream = new MemoryStream();

// Act
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);

using DocumentVerifier verifier = new DocumentVerifier(outputStream);
Assert.Equal("Template Author", verifier.GetDocumentAuthor());
}

[Fact]
public void ProcessTemplate_DocumentPropertiesWithAllNulls_PreservesOriginalValues()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Content");
builder.SetAuthor("Template Author");
builder.SetTitle("Template Title");
builder.SetSubject("Template Subject");

MemoryStream templateStream = builder.ToStream();

Dictionary<string, object> data = new Dictionary<string, object>();

PlaceholderReplacementOptions options = new PlaceholderReplacementOptions
{
DocumentProperties = new DocumentProperties()
};

DocumentTemplateProcessor processor = new DocumentTemplateProcessor(options);
MemoryStream outputStream = new MemoryStream();

// Act
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);

using DocumentVerifier verifier = new DocumentVerifier(outputStream);
Assert.Equal("Template Author", verifier.GetDocumentAuthor());
Assert.Equal("Template Title", verifier.GetDocumentTitle());
Assert.Equal("Template Subject", verifier.GetDocumentSubject());
}

[Fact]
public void ProcessTemplate_EmptyStringAuthor_SetsToEmpty()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Content");
builder.SetAuthor("Template Author");

MemoryStream templateStream = builder.ToStream();

Dictionary<string, object> data = new Dictionary<string, object>();

PlaceholderReplacementOptions options = new PlaceholderReplacementOptions
{
DocumentProperties = new DocumentProperties
{
Author = ""
}
};

DocumentTemplateProcessor processor = new DocumentTemplateProcessor(options);
MemoryStream outputStream = new MemoryStream();

// Act
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);

using DocumentVerifier verifier = new DocumentVerifier(outputStream);
Assert.Equal("", verifier.GetDocumentAuthor());
}

[Fact]
public void ProcessTemplate_DocumentPropertiesWithPlaceholderReplacement_BothWork()
{
// Arrange
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Hello {{Name}}!");

MemoryStream templateStream = builder.ToStream();

Dictionary<string, object> data = new Dictionary<string, object>
{
["Name"] = "World"
};

PlaceholderReplacementOptions options = new PlaceholderReplacementOptions
{
DocumentProperties = new DocumentProperties
{
Author = "Generated By Templify",
Title = "Generated Document"
}
};

DocumentTemplateProcessor processor = new DocumentTemplateProcessor(options);
MemoryStream outputStream = new MemoryStream();

// Act
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);
Assert.Equal(1, result.ReplacementCount);

using DocumentVerifier verifier = new DocumentVerifier(outputStream);
Assert.Equal("Hello World!", verifier.GetParagraphText(0));
Assert.Equal("Generated By Templify", verifier.GetDocumentAuthor());
Assert.Equal("Generated Document", verifier.GetDocumentTitle());
}

[Fact]
public void ProcessTemplate_SetAuthorOnTemplateWithNoOriginalValue_SetsAuthor()
{
// Arrange — template with no pre-existing Author/Creator
DocumentBuilder builder = new DocumentBuilder();
builder.AddParagraph("Content");

MemoryStream templateStream = builder.ToStream();

Dictionary<string, object> data = new Dictionary<string, object>();

PlaceholderReplacementOptions options = new PlaceholderReplacementOptions
{
DocumentProperties = new DocumentProperties
{
Author = "Brand New Author"
}
};

DocumentTemplateProcessor processor = new DocumentTemplateProcessor(options);
MemoryStream outputStream = new MemoryStream();

// Act
ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data);

// Assert
Assert.True(result.IsSuccess);

using DocumentVerifier verifier = new DocumentVerifier(outputStream);
Assert.Equal("Brand New Author", verifier.GetDocumentAuthor());
}
}
48 changes: 48 additions & 0 deletions TriasDev.Templify/Core/DocumentProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2025 TriasDev GmbH & Co. KG
// Licensed under the MIT License. See LICENSE file in the project root for full license information.

namespace TriasDev.Templify.Core;

/// <summary>
/// Specifies document metadata properties to set on the output document.
/// Properties left as <c>null</c> preserve the original template value.
/// </summary>
public sealed class DocumentProperties
{
/// <summary>
/// Gets or initializes the document author.
/// Maps to the OPC <c>Creator</c> property (shown as "Author" in Word).
/// </summary>
public string? Author { get; init; }

/// <summary>
/// Gets or initializes the document title.
/// </summary>
public string? Title { get; init; }

/// <summary>
/// Gets or initializes the document subject.
/// </summary>
public string? Subject { get; init; }

/// <summary>
/// Gets or initializes the document description.
/// Maps to "Comments" in the Word document properties dialog.
/// </summary>
public string? Description { get; init; }

/// <summary>
/// Gets or initializes the document keywords.
/// </summary>
public string? Keywords { get; init; }

/// <summary>
/// Gets or initializes the document category.
/// </summary>
public string? Category { get; init; }

/// <summary>
/// Gets or initializes the last modified by value.
/// </summary>
public string? LastModifiedBy { get; init; }
}
Loading
Loading