Added an optional notice for AI-generated content (#884)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Sabrina-devopsandThorsten Sommer authored and GitHub committed 2026-10-16 20:42:04 +02:00
1 parent f5a257270e
commit 249fc778a2
26 files changed
+406 -46

No files matched your search

@@ -0,0 +1,82 @@
using AIStudio.Settings;
using AIStudio.Tools;
using AIStudio.Tools.Services;
using Microsoft.Extensions.Logging.Abstractions;
namespace AIStudio.Tests.Tools;
/// <summary>
/// Checks the notice which says that AI generated an answer the user copies or exports.
/// </summary>
/// <remarks>
/// The notice must leave the answer itself untouched, and it must read as a notice: not as part of the
/// last list item, not inside a code block the answer left open, and not with stray backslashes. Until
/// somebody switches it on, it stays away entirely.
/// </remarks>
[TestFixture]
public sealed class AIGeneratedContentDisclosureServiceTests
{
private const string NOTICE = @"**This content was generated using artificial intelligence \(AI\)\.**";
[Test]
public void TheNoticeIsOffByDefault()
{
var settingsManager = CreateSettingsManager();
var service = new AIGeneratedContentDisclosureService(settingsManager);
Assert.That(settingsManager.ConfigurationData.App.AddAIGeneratedContentDisclosure, Is.False);
Assert.That(service.AddDisclosureToMarkdown("The answer."), Is.EqualTo("The answer."), "Without the setting, the answer leaves AI Studio as it is.");
}
[Test]
public void TheNoticeEndsTheAnswerAsAParagraphOfItsOwn()
{
var answer = Lines("- first point", "- second point");
Assert.That(CreateServiceWithNotice().AddDisclosureToMarkdown(answer), Is.EqualTo(Lines(answer, string.Empty, NOTICE)));
}
[Test]
public void TrailingLineBreaksDoNotWidenTheGap()
{
var answer = Lines("The answer.", string.Empty, string.Empty);
Assert.That(CreateServiceWithNotice().AddDisclosureToMarkdown(answer), Is.EqualTo(Lines("The answer.", string.Empty, NOTICE)));
}
[TestCase("")]
[TestCase(" ")]
public void AnEmptyAnswerStaysEmpty(string answer)
{
Assert.That(CreateServiceWithNotice().AddDisclosureToMarkdown(answer), Is.EqualTo(answer), "A notice alone would say that AI wrote something which is not there.");
}
[Test]
public void AnOpenCodeBlockIsClosedBeforeTheNotice()
{
var answer = Lines("Here is the code:", string.Empty, "```csharp", "var answer = 42;");
Assert.That(CreateServiceWithNotice().AddDisclosureToMarkdown(answer), Is.EqualTo(Lines(answer, "```", string.Empty, NOTICE)));
}
[Test]
public void TheNoticeIsShownAsBoldTextBelowTheCode()
{
var answer = Lines("```csharp", "var answer = 42;");
var html = Markdig.Markdown.ToHtml(CreateServiceWithNotice().AddDisclosureToMarkdown(answer), Markdown.SAFE_MARKDOWN_PIPELINE);
Assert.That(html.TrimEnd(), Does.EndWith("</code></pre>\n<p><strong>This content was generated using artificial intelligence (AI).</strong></p>"), "The escapes keep the parentheses and the period as plain text, and the closed fence keeps the notice out of the code block.");
}
private static AIGeneratedContentDisclosureService CreateServiceWithNotice()
{
var settingsManager = CreateSettingsManager();
settingsManager.ConfigurationData.App.AddAIGeneratedContentDisclosure = true;
return new AIGeneratedContentDisclosureService(settingsManager);
}
private static SettingsManager CreateSettingsManager() => new(NullLogger<SettingsManager>.Instance, null!);
private static string Lines(params string[] lines) => string.Join(Environment.NewLine, lines);
}
+32 -1
View File
@@ -3,7 +3,8 @@ using AIStudio.Tools;
namespace AIStudio.Tests.Tools;
/// <summary>
/// Checks that an answer which opens a code fence without closing it ends where it ends.
/// Checks that an answer which opens a code fence without closing it ends where it ends, and that
/// whatever AI Studio puts below an answer stands on its own.
/// </summary>
/// <remarks>
/// A fence without its counterpart runs to the end of the document, so whatever is appended below an
@@ -62,5 +63,35 @@ public sealed class MarkdownTests
Assert.That(Markdown.CloseOpenCodeFence(string.Empty), Is.Empty);
}
[Test]
public void AnAppendedBlockFollowsABlankLine()
{
var answer = Lines("- first point", "- second point");
Assert.That(Markdown.AppendBlock(answer, "**Notice**"), Is.EqualTo(Lines(answer, string.Empty, "**Notice**")), "Without the blank line, the block would continue the last item of the list.");
}
[Test]
public void AnAppendedBlockStaysOutsideOfAnOpenFence()
{
var answer = Lines("```csharp", "var answer = 42;");
Assert.That(Markdown.AppendBlock(answer, "**Notice**"), Is.EqualTo(Lines(answer, "```", string.Empty, "**Notice**")));
}
[Test]
public void AnEmptyBlockLeavesTheTextAlone()
{
const string ANSWER = "Nothing goes below this answer.";
Assert.That(Markdown.AppendBlock(ANSWER, string.Empty), Is.EqualTo(ANSWER));
}
[Test]
public void AnEmptyTextGivesTheBlockAlone()
{
Assert.That(Markdown.AppendBlock(string.Empty, "**Notice**"), Is.EqualTo("**Notice**"), "With nothing above it, a blank line would only start the text with an empty line.");
}
private static string Lines(params string[] lines) => string.Join(Environment.NewLine, lines);
}