mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-04 08:36:11 +02:00
feat: add image handling actions and improve IRC message formatting
This commit is contained in:
@@ -62,13 +62,11 @@ public class DataMigrationServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnsiToColorTags_RoundTrip_WithColorTagsToAnsi()
|
||||
public void AnsiToColorTags_ForegroundBackgroundAndReset_AllConverted()
|
||||
{
|
||||
// AnsiToColorTags and IrcMessageFormatter.ColorTagsToAnsi should be inverses
|
||||
var original = "{F:FF0000}red{B:00FF00}green{X}";
|
||||
var ansi = EchoHub.Server.Irc.IrcMessageFormatter.ColorTagsToAnsi(original);
|
||||
var ansi = "\x1b[38;2;255;0;0mred\x1b[48;2;0;255;0mgreen\x1b[0m";
|
||||
var backToTags = DataMigrationService.AnsiToColorTags(ansi);
|
||||
|
||||
Assert.Equal(original, backToTags);
|
||||
Assert.Equal("{F:FF0000}red{B:00FF00}green{X}", backToTags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class IrcBroadcasterTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendMessage_DecryptsAttachmentAsciiPreview()
|
||||
public async Task SendMessage_ImageAttachment_SendsLinkLineOnly()
|
||||
{
|
||||
var (_, stream) = AddConnectionWithCapture("bob", "general");
|
||||
|
||||
@@ -104,9 +104,9 @@ public class IrcBroadcasterTests
|
||||
await _broadcaster.SendMessageToChannelAsync("general", message);
|
||||
|
||||
var output = stream.GetOutputLines();
|
||||
Assert.Contains(output, l => l.Contains("[Image: photo.png]"));
|
||||
Assert.Contains(output, l => l.Contains("line1"));
|
||||
Assert.Contains(output, l => l.Contains("line2"));
|
||||
Assert.Contains(output, l => l.Contains("[Image: photo.png]") && l.Contains("/api/files/abc"));
|
||||
// ASCII preview art is never sent to IRC clients — images are links only
|
||||
Assert.DoesNotContain(output, l => l.Contains("line1"));
|
||||
Assert.DoesNotContain(output, l => l.Contains("$ENC$"));
|
||||
}
|
||||
|
||||
|
||||
@@ -118,37 +118,50 @@ public class IrcMessageFormatterTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormatMessage_ImageMessage_IncludesAsciiArt()
|
||||
public void FormatMessage_ImageMessage_NeverEmitsAsciiArt()
|
||||
{
|
||||
var msg = CreateImageMessage("line1\nline2");
|
||||
// Images are shared as plain links (the common IRC practice) — never color art,
|
||||
// regardless of what the preview contains.
|
||||
var msg = CreateImageMessage("{F:FF0000}█{X}\nline2");
|
||||
var lines = IrcMessageFormatter.FormatMessage(msg);
|
||||
|
||||
Assert.Contains(lines, l => l.Contains("line1"));
|
||||
Assert.Contains(lines, l => l.Contains("line2"));
|
||||
Assert.Single(lines);
|
||||
Assert.Contains("[Image: image.png]", lines[0]);
|
||||
Assert.DoesNotContain(lines, l => l.Contains("line2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormatMessage_ImageMessage_SkipsEmptyAsciiLines()
|
||||
public void FormatMessage_RelativeUrl_JoinedWithPublicBaseUrl()
|
||||
{
|
||||
var msg = CreateImageMessage("line1\n\nline2");
|
||||
var lines = IrcMessageFormatter.FormatMessage(msg);
|
||||
var msg = CreateImageMessage("art", "photo.jpg", "/api/files/abc");
|
||||
var lines = IrcMessageFormatter.FormatMessage(msg, "https://chat.example.com");
|
||||
|
||||
// Empty lines should be skipped
|
||||
var asciiLines = lines.Where(l => !l.Contains("[Image:") && !l.Contains("Download:")).ToList();
|
||||
Assert.Equal(2, asciiLines.Count);
|
||||
Assert.Single(lines);
|
||||
Assert.Contains("https://chat.example.com/api/files/abc", lines[0]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("$ENC$v1$abc123$def456")] // transport ciphertext a broadcast path forgot to decrypt
|
||||
[InlineData("$RC1$abc123def456")] // E2E room ciphertext the server cannot decrypt
|
||||
public void FormatMessage_ImageMessage_SkipsCiphertextPreview(string ciphertextPreview)
|
||||
[Fact]
|
||||
public void FormatMessage_AbsoluteUrl_NotRewrittenByPublicBaseUrl()
|
||||
{
|
||||
var msg = CreateImageMessage(ciphertextPreview, "photo.jpg", "https://example.com/photo.jpg");
|
||||
var lines = IrcMessageFormatter.FormatMessage(msg);
|
||||
var msg = CreateImageMessage("art", "photo.jpg", "https://cdn.example.com/photo.jpg");
|
||||
var lines = IrcMessageFormatter.FormatMessage(msg, "https://chat.example.com");
|
||||
|
||||
// Only the [Image: ...] header line — never the ciphertext blob
|
||||
Assert.Single(lines);
|
||||
Assert.Contains("[Image: photo.jpg]", lines[0]);
|
||||
Assert.Contains("https://cdn.example.com/photo.jpg", lines[0]);
|
||||
Assert.DoesNotContain("https://chat.example.com", lines[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAbsoluteUrl_NoBaseUrl_ReturnsRelativeUnchanged()
|
||||
{
|
||||
Assert.Equal("/api/files/abc", IrcMessageFormatter.ToAbsoluteUrl("/api/files/abc", null));
|
||||
Assert.Equal("/api/files/abc", IrcMessageFormatter.ToAbsoluteUrl("/api/files/abc", " "));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAbsoluteUrl_TrailingSlashBase_JoinsWithoutDoubleSlash()
|
||||
{
|
||||
Assert.Equal("https://x.example/api/files/1",
|
||||
IrcMessageFormatter.ToAbsoluteUrl("/api/files/1", "https://x.example/"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -244,62 +257,4 @@ public class IrcMessageFormatterTests
|
||||
}
|
||||
}
|
||||
|
||||
// ── ColorTagsToAnsi ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_NoTags_ReturnsUnchanged()
|
||||
{
|
||||
Assert.Equal("Hello world", IrcMessageFormatter.ColorTagsToAnsi("Hello world"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_ForegroundTag_ConvertsToAnsi()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{F:FF0000}Red text");
|
||||
Assert.Equal("\x1b[38;2;255;0;0mRed text", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_BackgroundTag_ConvertsToAnsi()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{B:00FF00}Green bg");
|
||||
Assert.Equal("\x1b[48;2;0;255;0mGreen bg", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_ResetTag_ConvertsToReset()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{F:FF0000}Red{X} Normal");
|
||||
Assert.Equal("\x1b[38;2;255;0;0mRed\x1b[0m Normal", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_MultipleTags_ConvertsAll()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{F:FF0000}Red {F:0000FF}Blue{X}");
|
||||
Assert.Contains("\x1b[38;2;255;0;0m", result);
|
||||
Assert.Contains("\x1b[38;2;0;0;255m", result);
|
||||
Assert.Contains("\x1b[0m", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_LowercaseHex_ConvertsCorrectly()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{F:ff8800}text");
|
||||
Assert.Equal("\x1b[38;2;255;136;0mtext", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_NoBraces_SkipsProcessing()
|
||||
{
|
||||
var text = "plain text without braces";
|
||||
Assert.Equal(text, IrcMessageFormatter.ColorTagsToAnsi(text));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_ExistingAnsiCodes_PreservesUnchanged()
|
||||
{
|
||||
var text = "\x1b[31mAlready colored\x1b[0m";
|
||||
Assert.Equal(text, IrcMessageFormatter.ColorTagsToAnsi(text));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +59,24 @@ public class IrcMessageFormatterTests
|
||||
attachments: [new AttachmentDto(AttachmentKind.Image, "/api/files/abc", "photo.png", 0, "{F:FF0000}█{X}")]);
|
||||
var lines = IrcMessageFormatter.FormatMessage(msg);
|
||||
|
||||
Assert.True(lines.Count >= 2);
|
||||
// Images are a single link line — the ASCII preview is never sent to IRC clients
|
||||
Assert.Single(lines);
|
||||
Assert.Contains("[Image: photo.png]", lines[0]);
|
||||
Assert.Contains("/api/files/abc", lines[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormatMessage_WithPublicBaseUrl_EmitsAbsoluteAttachmentLinks()
|
||||
{
|
||||
var msg = CreateMessage(
|
||||
content: "",
|
||||
attachments: [new AttachmentDto(AttachmentKind.Image, "/api/files/abc", "photo.png", 0, null)]);
|
||||
var lines = IrcMessageFormatter.FormatMessage(msg, "https://chat.example.com/");
|
||||
|
||||
Assert.Single(lines);
|
||||
Assert.Contains("https://chat.example.com/api/files/abc", lines[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormatMessage_FileAttachment_IncludesFileTag()
|
||||
{
|
||||
@@ -121,48 +134,6 @@ public class IrcMessageFormatterTests
|
||||
Assert.Contains(lines, l => l.Contains("[File: c.pdf]"));
|
||||
}
|
||||
|
||||
// ── ColorTagsToAnsi ───────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_ForegroundTag_ConvertsToAnsiEscape()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{F:FF0000}text");
|
||||
Assert.Contains("\x1b[38;2;255;0;0m", result);
|
||||
Assert.Contains("text", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_BackgroundTag_ConvertsToAnsiEscape()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{B:00FF00}text");
|
||||
Assert.Contains("\x1b[48;2;0;255;0m", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_ResetTag_ConvertsToAnsiReset()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{X}");
|
||||
Assert.Equal("\x1b[0m", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_NoTags_ReturnsUnchanged()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("plain text");
|
||||
Assert.Equal("plain text", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorTagsToAnsi_MultipleTags_ConvertsAll()
|
||||
{
|
||||
var result = IrcMessageFormatter.ColorTagsToAnsi("{F:FF0000}red{F:0000FF}blue{X}");
|
||||
Assert.Contains("\x1b[38;2;255;0;0m", result);
|
||||
Assert.Contains("\x1b[38;2;0;0;255m", result);
|
||||
Assert.Contains("\x1b[0m", result);
|
||||
Assert.Contains("red", result);
|
||||
Assert.Contains("blue", result);
|
||||
}
|
||||
|
||||
// ── SplitMessage ──────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user