feat: send text to named sessions with draft protection and listener isolation

- chat send and listener-smoke accept --session; defaults remain File Transfer Assistant.
- Reject existing drafts, reacquire the send control after input changes, and require a
  fresh message fingerprint for confirmation without retrying failed sends.
- Test-NamedTextSend sends the same text twice and verifies distinct confirmed fingerprints;
  Test-NamedListeners -SendMarkers verifies per-window own events exactly once with zero
  cross-session events. Note export now requires a unique fresh owned note window and keeps
  repeated paragraphs. 97 Core tests pass; real-machine validated.
This commit is contained in:
2026-09-06 14:35:55 +08:00
parent fb8c25c83c
commit ad22a89185
7 changed files with 166 additions and 33 deletions
@@ -0,0 +1,37 @@
using WxAgent.Core;
using Xunit;
namespace WxAgent.Core.Tests;
public sealed class WechatNoteWindowTests
{
[Fact]
public void OnlyAFreshVisibleNoteInTheExpectedProcessIsSelected()
{
WechatNoteWindowCandidate[] windows = [
new(1, 10, "笔记", true, false), // Existing note must not be exported instead of the selected message.
new(2, 10, "笔记交流群", true, true),
new(3, 20, "笔记", true, false),
new(4, 10, "笔记", false, false),
new(5, 10, "微信", true, false),
new(6, 10, "笔记", true, false)];
Assert.Equal(6, WechatNoteWindow.FindOpened(windows, 10, new HashSet<long> { 1 }));
Assert.Null(WechatNoteWindow.FindOpened(windows[..5], 10, new HashSet<long> { 1 }));
}
[Fact]
public void AmbiguousNewNotesAreRejected()
{
var error = Assert.Throws<WxAgentException>(() => WechatNoteWindow.FindOpened([
new(1, 10, "笔记", true, false), new(2, 10, "笔记", true, false)], 10, new HashSet<long>()));
Assert.Equal(WxAgentErrorCode.UiStructureChanged, error.Code);
}
[Fact]
public void RepeatedNoteParagraphsMustRemainRepeated()
{
var content = new WechatNoteContent([
new("text", "重复段落"), new("text", "重复段落")]);
Assert.Equal("重复段落\n\n重复段落", content.ToMarkdown().ReplaceLineEndings("\n"));
}
}