diff --git a/src/Postmark.Tests/ClientTemplateTests.cs b/src/Postmark.Tests/ClientTemplateTests.cs index 08e5df7..145ab9f 100644 --- a/src/Postmark.Tests/ClientTemplateTests.cs +++ b/src/Postmark.Tests/ClientTemplateTests.cs @@ -3,13 +3,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; +using PostmarkDotNet.Model; namespace Postmark.Tests { public class ClientTemplateTests : ClientBaseFixture, IDisposable { + private readonly string _layoutContentPlaceholder = "{{{@content}}}"; + protected override void Setup() { _client = new PostmarkClient(WRITE_TEST_SERVER_TOKEN, BASE_URL); @@ -20,14 +22,27 @@ public async void ClientCanCreateTemplate() { var name = Guid.NewGuid().ToString(); var subject = "A subject: " + Guid.NewGuid(); - var htmlbody = "Hello, {{name}}"; + var htmlBody = "Hello, {{name}}"; var textBody = "Hello, {{name}}!"; - var newTemplate = await _client.CreateTemplateAsync(name, subject, htmlbody, textBody); + var newTemplate = await _client.CreateTemplateAsync(name, subject, htmlBody, textBody); Assert.Equal(name, newTemplate.Name); } + [Fact] + public async void ClientCanCreateLayoutTemplates() + { + var newLayoutTemplate = await GenerateLayoutTemplate(); + Assert.Equal(TemplateType.Layout, newLayoutTemplate.TemplateType); + + // Creating a standard template that uses the layout template above + var newStandardTemplate = await GenerateStandardTemplate(newLayoutTemplate.Alias); + + Assert.Equal(TemplateType.Standard, newStandardTemplate.TemplateType); + Assert.Equal(newLayoutTemplate.Alias, newStandardTemplate.LayoutTemplate); + } + [Fact] public async void ClientCanEditTemplate() { @@ -50,6 +65,22 @@ public async void ClientCanEditTemplate() Assert.Equal(existingTemplate.TextBody + existingTemplate.TextBody, updatedTemplate.TextBody); } + [Fact] + public async void ClientCanEditLayoutTemplateProperty() + { + var newLayoutTemplate = await GenerateLayoutTemplate(); + var newStandardTemplate = await GenerateStandardTemplate(newLayoutTemplate.Alias); + + // Setting the LayoutTemplate to null + var templateWithNoLayoutTemplate = await _client.EditTemplateAsync(newStandardTemplate.TemplateId, layoutTemplate: ""); + Assert.Null(templateWithNoLayoutTemplate.LayoutTemplate); + + // Setting the LayoutTemplate back to the layout template that was created + var templateWithLayoutTemplate = await _client.EditTemplateAsync(newStandardTemplate.TemplateId, + layoutTemplate: newLayoutTemplate.Alias); + Assert.Equal(newLayoutTemplate.Alias, templateWithLayoutTemplate.LayoutTemplate); + } + [Fact] public async void ClientCanDeleteTemplate() { @@ -84,6 +115,8 @@ public async void ClientCanGetTemplate() Assert.True(result.Active); Assert.True(result.AssociatedServerId > 0); Assert.Equal(newTemplate.TemplateId, result.TemplateId); + Assert.Equal(TemplateType.Standard, result.TemplateType); + Assert.Null(result.LayoutTemplate); } [Fact] @@ -103,7 +136,36 @@ public async void ClientCanGetListTemplates() Assert.False(result.Templates.FirstOrDefault(k => k.TemplateId == toDelete) != null); var offsetResults = await _client.GetTemplatesAsync(5); Assert.True(result.Templates.Skip(5).Select(k => k.TemplateId).SequenceEqual(offsetResults.Templates.Select(k => k.TemplateId))); + } + + [Fact] + public async void GetTemplatesReturnsProperResults() + { + var newLayoutTemplate = await GenerateLayoutTemplate(); + var newStandardTemplate = await GenerateStandardTemplate(newLayoutTemplate.Alias); + + var result = await _client.GetTemplatesAsync(); + Assert.Equal(2, result.TotalCount); + + var standardTemplateFromResult = result.Templates.First(t => t.TemplateId == newStandardTemplate.TemplateId); + Assert.Equal(newStandardTemplate.TemplateId, standardTemplateFromResult.TemplateId); + Assert.Equal(newStandardTemplate.Alias, standardTemplateFromResult.Alias); + Assert.Equal(newStandardTemplate.Name, standardTemplateFromResult.Name); + Assert.Equal(TemplateType.Standard, standardTemplateFromResult.TemplateType); + Assert.Equal(newLayoutTemplate.Alias, standardTemplateFromResult.LayoutTemplate); + + var layoutTemplateFromResult = result.Templates.First(t => t.TemplateId == newLayoutTemplate.TemplateId); + Assert.Equal(newLayoutTemplate.TemplateId, layoutTemplateFromResult.TemplateId); + Assert.Equal(newLayoutTemplate.Alias, layoutTemplateFromResult.Alias); + Assert.Equal(newLayoutTemplate.Name, layoutTemplateFromResult.Name); + Assert.Equal(TemplateType.Layout, layoutTemplateFromResult.TemplateType); + Assert.Null(layoutTemplateFromResult.LayoutTemplate); + + var filteredResultByType = await _client.GetTemplatesAsync(0, 100, TemplateTypeFilter.Layout); + Assert.Equal(1, filteredResultByType.TotalCount); + var filteredResultByLayoutAlias = await _client.GetTemplatesAsync(0, 100, TemplateTypeFilter.All, newLayoutTemplate.Alias); + Assert.Equal(1, filteredResultByLayoutAlias.TotalCount); } [Fact] @@ -121,6 +183,23 @@ public async void ClientCanValidateTemplate() Assert.Equal(3, products.Length); } + [Fact] + public async void ClientCanUseLayoutTemplatesWhenValidating() + { + var layoutTemplate = await GenerateLayoutTemplate(); + + var content = "Mr. Jones"; + var result = await _client.ValidateTemplateAsync("Subject", null, content, new { }, true, TemplateType.Standard, layoutTemplate.Alias); + + Assert.True(result.AllContentIsValid); + Assert.True(result.TextBody.ContentIsValid); + Assert.True(result.Subject.ContentIsValid); + + // Testing to see that indeed the validation has used the LayoutTemplate + var expectedTextBody = layoutTemplate.TextBody.Replace(_layoutContentPlaceholder, content); + Assert.Equal(expectedTextBody, result.TextBody.RenderedContent); + } + [Fact] public async void ClientCanSendWithTemplate() { @@ -137,7 +216,8 @@ public async void ClientCanSendTemplateWithStringModel() Assert.NotEqual(Guid.Empty, sendResult.MessageID); } - private Task Cleanup(){ + private Task Cleanup() + { return Task.Run(async () => { try @@ -155,6 +235,29 @@ private Task Cleanup(){ }); } + private async Task GenerateLayoutTemplate() + { + var layoutName = Guid.NewGuid().ToString(); + var layoutHtmlBody = $"header {_layoutContentPlaceholder} footer"; + var layoutTextBody = $"header {_layoutContentPlaceholder} footer"; + + var newLayoutTemplate = await _client.CreateTemplateAsync(layoutName, null, layoutHtmlBody, layoutTextBody, null, TemplateType.Layout); + return await _client.GetTemplateAsync(newLayoutTemplate.TemplateId); + } + + private async Task GenerateStandardTemplate(string layoutTemplateAlias = null) + { + var name = Guid.NewGuid().ToString(); + var subject = "A subject: " + Guid.NewGuid(); + var htmlBody = "Hello, {{name}}!"; + var textBody = "Hello, {{name}}!"; + + var newStandardTemplate = await _client.CreateTemplateAsync(name, subject, htmlBody, textBody, null, + TemplateType.Standard, layoutTemplateAlias); + + return await _client.GetTemplateAsync(newStandardTemplate.TemplateId); + } + public void Dispose() { Cleanup().Wait(); diff --git a/src/Postmark/Model/PostmarkTemplate.cs b/src/Postmark/Model/PostmarkTemplate.cs index 8532fda..d8595de 100644 --- a/src/Postmark/Model/PostmarkTemplate.cs +++ b/src/Postmark/Model/PostmarkTemplate.cs @@ -1,10 +1,13 @@ -namespace PostmarkDotNet.Model +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PostmarkDotNet.Model { public class PostmarkTemplate { public long TemplateId { get; set; } - public string Alias { get;set; } + public string Alias { get; set; } public string Subject { get; set; } @@ -18,5 +21,9 @@ public class PostmarkTemplate public bool Active { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public TemplateType TemplateType { get; set; } + + public string LayoutTemplate { get; set; } } } diff --git a/src/Postmark/Model/PostmarkTemplateListingResponse.cs b/src/Postmark/Model/PostmarkTemplateListingResponse.cs index c8add25..eae2002 100644 --- a/src/Postmark/Model/PostmarkTemplateListingResponse.cs +++ b/src/Postmark/Model/PostmarkTemplateListingResponse.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace PostmarkDotNet.Model { @@ -13,8 +15,16 @@ public class PostmarkTemplateListingResponse public class BasicTemplateInformation { public bool IsActive { get; set; } + public String Name { get; set; } + public long TemplateId { get; set; } + public string Alias { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public TemplateType TemplateType { get; set; } + + public string LayoutTemplate { get; set; } } } diff --git a/src/Postmark/Model/TemplateType.cs b/src/Postmark/Model/TemplateType.cs new file mode 100644 index 0000000..48df308 --- /dev/null +++ b/src/Postmark/Model/TemplateType.cs @@ -0,0 +1,8 @@ +namespace PostmarkDotNet.Model +{ + public enum TemplateType + { + Standard, + Layout + } +} \ No newline at end of file diff --git a/src/Postmark/Model/TemplateTypeFilter.cs b/src/Postmark/Model/TemplateTypeFilter.cs new file mode 100644 index 0000000..5c10107 --- /dev/null +++ b/src/Postmark/Model/TemplateTypeFilter.cs @@ -0,0 +1,9 @@ +namespace PostmarkDotNet.Model +{ + public enum TemplateTypeFilter + { + Standard, + Layout, + All + } +} \ No newline at end of file diff --git a/src/Postmark/PostmarkClient.cs b/src/Postmark/PostmarkClient.cs index 88e1948..611b40c 100644 --- a/src/Postmark/PostmarkClient.cs +++ b/src/Postmark/PostmarkClient.cs @@ -186,8 +186,10 @@ public async Task GetOutboundMessagesAsync(int offs parameters["fromdate"] = fromDate; parameters["status"] = status.ToString().ToLower(); - if(metadata != null){ - foreach(var a in metadata){ + if (metadata != null) + { + foreach (var a in metadata) + { parameters[$"metadata_{a.Key}"] = a.Value; } } @@ -232,7 +234,7 @@ public async Task GetOutboundMessageDumpAsync(string messageID) /// Get messages on or before YYYY-MM-DD. /// PostmarkInboundMessageList public async Task GetInboundMessagesAsync(int offset = 0, int count = 100, - string recipient = null, string fromemail = null, string subject = null, + string recipient = null, string fromemail = null, string subject = null, string mailboxhash = null, InboundMessageStatus? status = InboundMessageStatus.Processed, String toDate = null, String fromDate = null) { var parameters = new Dictionary(); @@ -245,7 +247,7 @@ public async Task GetInboundMessagesAsync(int offset parameters["todate"] = toDate; parameters["fromdate"] = fromDate; parameters["status"] = status.ToString().ToLower(); - + return await ProcessNoBodyRequestAsync("/messages/inbound", parameters); } @@ -302,7 +304,7 @@ public async Task GetServerAsync() public async Task EditServerAsync(String name = null, string color = null, bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, - bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, + bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, LinkTrackingOptions? trackLinks = null, string clickHookUrl = null, string deliveryHookUrl = null) { @@ -829,21 +831,25 @@ public async Task GetTemplateAsync(string alias) } /// - /// Get a listing of templates, optionally including deleted templates. + /// Get a listing of templates. /// - /// - /// + /// The number of templates to return. Defaults to 0. + /// The number of templates to "skip" before returning results. Defaults to 100. + /// Filter the resulting templates by their TemplateType. Defaults to: All + /// Filter results by layout template alias. /// - public async Task GetTemplatesAsync(int offset = 0, int count = 100) + public async Task GetTemplatesAsync(int offset = 0, int count = 100, TemplateTypeFilter templateType = TemplateTypeFilter.All, + string layoutTemplate = null) { var query = new Dictionary(); query["Count"] = count; query["Offset"] = offset; + query["TemplateType"] = Enum.GetName(typeof(TemplateTypeFilter), templateType); + query["LayoutTemplate"] = layoutTemplate; return await ProcessNoBodyRequestAsync("/templates/", query, HttpMethod.Get); } - /// /// Store a new template associated with this server. /// @@ -852,8 +858,11 @@ public async Task GetTemplatesAsync(int offset /// The HTMLBody to be used when sending with this template. Optional if TextBody is specified. /// The TextBody to be used when sending with this template. Optional if HtmlBody is specified. /// A friendly name to use for this template to access it, or to send with it. + /// The type of the template to create. + /// The alias of the Layout template that you want to use as layout for this Standard template. /// - public async Task CreateTemplateAsync(string name, string subject, string htmlBody = null, string textBody = null, string alias = null) + public async Task CreateTemplateAsync(string name, string subject, string htmlBody = null, string textBody = null, string alias = null, + TemplateType templateType = TemplateType.Standard, string layoutTemplate = null) { var body = new Dictionary(); body["Name"] = name; @@ -861,22 +870,27 @@ public async Task CreateTemplateAsync(string name, str body["TextBody"] = textBody; body["Subject"] = subject; body["Alias"] = alias; + body["TemplateType"] = Enum.GetName(typeof(TemplateType), templateType); + body["LayoutTemplate"] = layoutTemplate; return await ProcessRequestAsync, BasicTemplateInformation>("/templates/", HttpMethod.Post, body); } - public async Task EditTemplateAsync(string alias, string name = null, string subject = null, string htmlBody = null, string textBody = null) + public async Task EditTemplateAsync(string alias, string name = null, string subject = null, string htmlBody = null, string textBody = null, + string layoutTemplate = null) { var body = new Dictionary(); body["Name"] = name; body["HTMLBody"] = htmlBody; body["TextBody"] = textBody; body["Subject"] = subject; - + body["LayoutTemplate"] = layoutTemplate; + return await ProcessRequestAsync, BasicTemplateInformation>("/templates/" + alias, HttpMethod.Put, body); } - public async Task EditTemplateAsync(long templateId, string name = null, string subject = null, string htmlBody = null, string textBody = null, string alias = null) + public async Task EditTemplateAsync(long templateId, string name = null, string subject = null, string htmlBody = null, string textBody = null, string alias = null, + string layoutTemplate = null) { var body = new Dictionary(); body["Name"] = name; @@ -884,6 +898,7 @@ public async Task EditTemplateAsync(long templateId, s body["TextBody"] = textBody; body["Subject"] = subject; body["Alias"] = alias; + body["LayoutTemplate"] = layoutTemplate; return await ProcessRequestAsync, BasicTemplateInformation>("/templates/" + templateId, HttpMethod.Put, body); } @@ -953,11 +968,14 @@ private async Task InternalSendEmailWithTemplateAsync(objec IDictionary metadata = null, params PostmarkMessageAttachment[] attachments) { - + var email = new TemplatedPostmarkMessage(); - if(templateReference is long){ + if (templateReference is long) + { email.TemplateId = (long)templateReference; - }else{ + } + else + { email.TemplateAlias = (string)templateReference; } email.TemplateModel = templateModel; @@ -986,15 +1004,30 @@ private async Task InternalSendEmailWithTemplateAsync(objec return await SendEmailWithTemplateAsync(email); } - public async Task ValidateTemplateAsync(string subject = null, string htmlbody = null, - string textBody = null, T testRenderModel = default(T), bool inlineCssForHtmlTestRender = true) + /// + /// Validate a template. + /// + /// + /// The subject content to validate. + /// The HTML body content to validate. + /// The plain text body content to validate. + /// The template model to be used when rendering test content. + /// Controls whether style blocks will be inlined as style attributes on matching html elements in HtmlBody. + /// Validate templates based on template type. + /// An optional string to specify which layout template alias to use to validate a standard template. + /// + public async Task ValidateTemplateAsync(string subject = null, string htmlBody = null, + string textBody = null, T testRenderModel = default(T), bool inlineCssForHtmlTestRender = true, + TemplateType templateType = TemplateType.Standard, string layoutTemplate = null) { var body = new Dictionary(); body["TestRenderModel"] = testRenderModel; body["Subject"] = subject; - body["HtmlBody"] = htmlbody; + body["HtmlBody"] = htmlBody; body["TextBody"] = textBody; body["InlineCssForHtmlTestRender"] = inlineCssForHtmlTestRender; + body["TemplateType"] = Enum.GetName(typeof(TemplateType), templateType); + body["LayoutTemplate"] = layoutTemplate; return await ProcessRequestAsync, TemplateValidationResponse>("/templates/validate", HttpMethod.Post, body); }