-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(reporter): implement plain text issue formatter (#112)
closes #97
- Loading branch information
Showing
7 changed files
with
183 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* text=auto eol=lf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Globalization; | ||
using System.Linq; | ||
using SecTester.Scan.Models; | ||
|
||
namespace SecTester.Reporter; | ||
|
||
public class DefaultFormatter : Formatter | ||
{ | ||
private const string CrLf = "\r\n"; | ||
private const char NewLine = '\n'; | ||
private const char BulletPoint = '●'; | ||
private const char Tabulation = '\t'; | ||
private const string TemplateBody = @"Issue in Bright UI: {0} | ||
Name: {1} | ||
Severity: {2} | ||
Remediation: | ||
{3} | ||
Details: | ||
{4}"; | ||
private const string TemplateExtraDetails = "Extra Details:\n{5}"; | ||
private const string TemplateReferences = "References:\n{6}"; | ||
|
||
public string Format(Issue issue) | ||
{ | ||
var comments = issue.Comments ?? Array.Empty<Comment>(); | ||
var resources = issue.Resources ?? Array.Empty<string>(); | ||
|
||
var template = GenerateTemplate(comments.Any(), resources.Any()); | ||
|
||
var message = string.Format(CultureInfo.InvariantCulture, | ||
template, | ||
issue.Link, | ||
issue.Name, | ||
issue.Severity, | ||
issue.Remedy, | ||
issue.Details, | ||
FormatList(comments, FormatExtraInfo), | ||
FormatList(resources) | ||
); | ||
|
||
// ADHOC: adjust line endings respectively for OS environment | ||
return message.Trim().Replace(NewLine.ToString(), Environment.NewLine); | ||
} | ||
|
||
private static string GenerateTemplate(bool extraInfo, bool references) | ||
{ | ||
IEnumerable<string> templates = new List<string> | ||
{ | ||
TemplateBody | ||
}; | ||
|
||
if (extraInfo) | ||
{ | ||
templates = templates.Append(TemplateExtraDetails); | ||
} | ||
|
||
if (references) | ||
{ | ||
templates = templates.Append(TemplateReferences); | ||
} | ||
|
||
// ADHOC: `DefaultFormatter` works with LF, replace possible CRLF in verbatim strings | ||
templates = templates.Select(x => x.Replace(CrLf, NewLine.ToString())); | ||
|
||
return string.Join(NewLine.ToString(), templates); | ||
} | ||
|
||
private static string FormatExtraInfo(Comment comment) | ||
{ | ||
var footer = comment.Links is not null && comment.Links.Any() ? CombineList(comment.Links.Prepend("Links:")) : ""; | ||
var blocks = new List<string> { comment.Text ?? "", footer }.Select(x => Indent(x)); | ||
var document = CombineList(blocks); | ||
|
||
return CombineList(new List<string> { comment.Headline, document }); | ||
} | ||
|
||
private static string Indent(string x, int length = 1) | ||
{ | ||
// ADHOC: `DefaultFormatter` works with LF, replace possible CRLF for consistent parsing | ||
var lines = x.Replace(CrLf, NewLine.ToString()).Split(NewLine); | ||
|
||
return CombineList( | ||
lines.Select(line => $"{new string(Tabulation, length)}{line}") | ||
); | ||
} | ||
|
||
private static string FormatList<T>(IEnumerable<T> list, Func<T, string>? map = default) | ||
where T : class | ||
{ | ||
var formatItem = map ?? (x => x.ToString()); | ||
|
||
var items = list.Select( | ||
x => $"{BulletPoint} {formatItem(x)}" | ||
); | ||
|
||
return CombineList(items); | ||
} | ||
|
||
private static string CombineList(IEnumerable<string> list, char sep = NewLine) | ||
{ | ||
return string.Join(sep.ToString(), list); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
namespace SecTester.Reporter.Tests; | ||
|
||
public class DefaultFormatterTests | ||
{ | ||
private const string CrLf = "\r\n"; | ||
private const char BulletPoint = '●'; | ||
private const char Tabulation = '\t'; | ||
|
||
private static readonly Issue Issue = new("pDzxcEXQC8df1fcz1QwPf9", | ||
"Cross-site request forgery is a type of malicious website exploit.", | ||
"Database connection crashed", | ||
"The best way to protect against those kind of issues is making sure the Database resources are sufficient", | ||
new Request("https://brokencrystals.com/"), new Request("https://brokencrystals.com/"), | ||
1, Severity.Medium, Protocol.Http, DateTime.Today) | ||
{ | ||
Link = "https://app.neuralegion.com/scans/pDzxcEXQC8df1fcz1QwPf9/issues/pDzxcEXQC8df1fcz1QwPf9" | ||
}; | ||
|
||
private static readonly string FormattedText = | ||
@"Issue in Bright UI: https://app.neuralegion.com/scans/pDzxcEXQC8df1fcz1QwPf9/issues/pDzxcEXQC8df1fcz1QwPf9 | ||
Name: Database connection crashed | ||
Severity: Medium | ||
Remediation: | ||
The best way to protect against those kind of issues is making sure the Database resources are sufficient | ||
Details: | ||
Cross-site request forgery is a type of malicious website exploit."; | ||
|
||
public static readonly IEnumerable<object[]> FormatInput = new List<object[]> | ||
{ | ||
new object[] | ||
{ | ||
Issue, | ||
FormattedText | ||
}, | ||
new object[] | ||
{ | ||
Issue with | ||
{ | ||
Comments = new List<Comment> | ||
{ | ||
new("CommentHeadline", new List<string> { "https://example.com/comment/1", "https://example.com/comment/2" }, | ||
$"CommentText-1{CrLf}CommentText-2") | ||
}, | ||
Resources = new List<string> { "https://example.com/resource/1", "https://example.com/resource/2" } | ||
}, | ||
$@"{FormattedText} | ||
Extra Details: | ||
{BulletPoint} CommentHeadline | ||
{Tabulation}CommentText-1 | ||
{Tabulation}CommentText-2 | ||
{Tabulation}Links: | ||
{Tabulation}https://example.com/comment/1 | ||
{Tabulation}https://example.com/comment/2 | ||
References: | ||
{BulletPoint} https://example.com/resource/1 | ||
{BulletPoint} https://example.com/resource/2" | ||
} | ||
}; | ||
|
||
private readonly Formatter _sut = new DefaultFormatter(); | ||
|
||
[Theory] | ||
[MemberData(nameof(FormatInput))] | ||
public void Format_GivenIssue_ReturnsFormattedString(Issue issue, string expected) | ||
{ | ||
// act | ||
var result = _sut.Format(issue); | ||
|
||
// assert | ||
// ADHOC: `DefaultFormatter::Format` returns CRLF/LF line endings respectively to OS environment | ||
result.Should().Be(expected.ReplaceLineEndings(Environment.NewLine)); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
global using FluentAssertions; | ||
global using NSubstitute; | ||
global using SecTester.Scan.Models; | ||
global using Xunit; | ||
global using Microsoft.Extensions.DependencyInjection; |