Forge
csharpdeeb25a2
1#nullable enable
2
3using CascadeIDE.Features.Chat;
4using CascadeIDE.Features.Intercom.Transport;
5using CascadeIDE.Models.AgentChat;
6using CascadeIDE.Models.Intercom;
7using CascadeIDE.Services.Intercom;
8using System.Text.Json;
9using Xunit;
10
11namespace CascadeIDE.Tests;
12
13public sealed class IntercomMessageRangeRelatedTests
14{
15 [Fact]
16 public void Project_MessageRangeRelated_RoundTripsAnchor()
17 {
18 var threadId = Guid.NewGuid();
19 var anchor = new AttachmentAnchor
20 {
21 Id = "a1",
22 AttachmentShape = "member",
23 File = "src/Foo.cs",
24 MemberKey = "M:Foo.Bar",
25 LineStart = 10,
26 LineEnd = 20,
27 };
28 var payload = new ChatHistoryMessageRangeRelatedPayload(
29 threadId.ToString("N"),
30 2,
31 4,
32 anchor,
33 "slash");
34
35 var events = new[]
36 {
37 new ChatHistoryEvent(
38 Guid.NewGuid(),
39 Guid.NewGuid(),
40 DateTimeOffset.UtcNow,
41 ChatHistoryEventKind.MessageRangeRelated,
42 ChatHistoryJson.Serialize(payload)),
43 };
44
45 var relates = IntercomMessageRangeRelatedProjector.Project(events);
46 Assert.Single(relates);
47 Assert.Equal(threadId, relates[0].ThreadId);
48 Assert.Equal(2, relates[0].StartOrdinal);
49 Assert.Equal(4, relates[0].EndOrdinal);
50 Assert.Single(relates[0].OrdinalSegments);
51 Assert.Equal("M:Foo.Bar", relates[0].CodeRef.MemberKey);
52 }
53
54 [Fact]
55 public void Project_DisjointOrdinalSegments_RoundTripsAllSegments()
56 {
57 var threadId = Guid.NewGuid();
58 var anchor = new AttachmentAnchor { File = "src/Foo.cs", MemberKey = "M:Foo.Bar" };
59 var payload = IntercomMessageRangeRelatedSupport.CreatePayload(
60 threadId.ToString("N"),
61 [
62 new ChatHistoryMessageOrdinalSegment(2, 3),
63 new ChatHistoryMessageOrdinalSegment(8, 10),
64 ],
65 anchor,
66 "slash");
67
68 var events = new[]
69 {
70 new ChatHistoryEvent(
71 Guid.NewGuid(),
72 Guid.NewGuid(),
73 DateTimeOffset.UtcNow,
74 ChatHistoryEventKind.MessageRangeRelated,
75 ChatHistoryJson.Serialize(payload)),
76 };
77
78 var relates = IntercomMessageRangeRelatedProjector.Project(events);
79 Assert.Single(relates);
80 Assert.Equal(2, relates[0].OrdinalSegments.Count);
81 Assert.Equal(2, relates[0].StartOrdinal);
82 Assert.Equal(10, relates[0].EndOrdinal);
83 }
84
85 [Fact]
86 public void BuildCombined_ExplicitRelate_FoundByMemberKey()
87 {
88 var messageId = Guid.NewGuid();
89 var lane = new[]
90 {
91 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(1, 0, messageId, []),
92 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(2, 1, Guid.NewGuid(), []),
93 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(3, 2, Guid.NewGuid(), []),
94 };
95
96 var anchor = new AttachmentAnchor
97 {
98 File = "src/Foo.cs",
99 MemberKey = "M:Foo.Bar",
100 LineStart = 1,
101 LineEnd = 2,
102 AttachmentShape = "member",
103 };
104
105 var relates = new[]
106 {
107 new IntercomMessageRangeRelatedProjector.ExplicitRelate(
108 Guid.NewGuid(),
109 2,
110 3,
111 [new ChatHistoryMessageOrdinalSegment(2, 3)],
112 anchor,
113 "slash"),
114 };
115
116 var entries = IntercomMessageCodeCorrespondenceProjector.BuildCombined(lane, relates);
117 var query = IntercomCodeRefQuery.FromAnchor(new AttachmentAnchor
118 {
119 File = "src/Foo.cs",
120 MemberKey = "M:Foo.Bar",
121 LineStart = 99,
122 LineEnd = 99,
123 });
124 var hits = IntercomMessageCodeCorrespondenceProjector.Find(entries, query, null);
125
126 Assert.Equal(2, hits.Count);
127 Assert.All(hits, h => Assert.Equal(IntercomMessageCodeCorrespondenceProjector.MatchKindExplicit, h.MatchKind));
128 Assert.Equal(2, hits[0].Ordinal);
129 Assert.Equal(3, hits[1].Ordinal);
130 }
131
132 [Fact]
133 public void BuildCombined_DisjointExplicitRelate_MapsOnlySegmentOrdinals()
134 {
135 var lane = new[]
136 {
137 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(1, 0, Guid.NewGuid(), []),
138 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(2, 1, Guid.NewGuid(), []),
139 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(3, 2, Guid.NewGuid(), []),
140 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(4, 3, Guid.NewGuid(), []),
141 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(5, 4, Guid.NewGuid(), []),
142 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(6, 5, Guid.NewGuid(), []),
143 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(7, 6, Guid.NewGuid(), []),
144 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(8, 7, Guid.NewGuid(), []),
145 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(9, 8, Guid.NewGuid(), []),
146 new IntercomMessageCodeCorrespondenceProjector.LaneMessage(10, 9, Guid.NewGuid(), []),
147 };
148
149 var anchor = new AttachmentAnchor
150 {
151 File = "src/Foo.cs",
152 MemberKey = "M:Foo.Bar",
153 AttachmentShape = "member",
154 };
155
156 var relates = new[]
157 {
158 new IntercomMessageRangeRelatedProjector.ExplicitRelate(
159 Guid.NewGuid(),
160 2,
161 9,
162 [
163 new ChatHistoryMessageOrdinalSegment(2, 3),
164 new ChatHistoryMessageOrdinalSegment(8, 9),
165 ],
166 anchor,
167 "slash"),
168 };
169
170 var entries = IntercomMessageCodeCorrespondenceProjector.BuildCombined(lane, relates);
171 var query = IntercomCodeRefQuery.FromAnchor(new AttachmentAnchor
172 {
173 File = "src/Foo.cs",
174 MemberKey = "M:Foo.Bar",
175 });
176 var hits = IntercomMessageCodeCorrespondenceProjector.Find(entries, query, null);
177
178 Assert.Equal(4, hits.Count);
179 Assert.Equal([2, 3, 8, 9], hits.Select(h => h.Ordinal).OrderBy(o => o));
180 Assert.DoesNotContain(hits, h => h.Ordinal is 4 or 5 or 6 or 7);
181 }
182
183 [Theory]
184 [InlineData("3:5 relate selection", 3, 5, "selection")]
185 [InlineData("2 selection", 2, 2, "selection")]
186 [InlineData("[3;5] [8;15] relate selection", 3, 5, "selection")]
187 public void RelateArgs_ParseRangeAndCodeRef(string tail, int start, int end, string codeRef)
188 {
189 Assert.True(IntercomMessageRelateArgs.TryParse(tail, out var segments, out var code, out var err), err);
190 Assert.Equal(codeRef, code);
191 Assert.Contains(segments, s => s.Start == start && s.End == end);
192 }
193
194 [Fact]
195 public void RelateArgs_Disjoint_ParseTwoSegments()
196 {
197 Assert.True(
198 IntercomMessageRelateArgs.TryParse("[3;5] [8;15] relate selection", out var segments, out _, out var err),
199 err);
200 Assert.Equal(2, segments.Count);
201 Assert.Equal(new ParametricIntRange(3, 5), segments[0]);
202 Assert.Equal(new ParametricIntRange(8, 15), segments[1]);
203 }
204
205 [Fact]
206 public void ResolveInput_MessageRelate()
207 {
208 const string line = "/intercom message 3:5 relate selection";
209 Assert.True(SlashLineResolver.TryResolveSlashLine(line, out var resolved));
210 Assert.Equal("/intercom message relate", resolved.CanonicalPath);
211 Assert.Equal("3:5 selection", resolved.ArgTail);
212 ChatSlashCatalogTestSupport.AssertResolves(line, "/intercom message relate", "3:5 selection");
213 Assert.True(IntentSlashCatalog.TryGetRoute("/intercom message relate", out var route));
214 Assert.Equal("message_relate", route.IntercomHandlerId);
215 }
216}
217
View only · write via MCP/CIDE