| 1 | #nullable enable |
| 2 | |
| 3 | using CascadeIDE.Services.Intercom; |
| 4 | using Xunit; |
| 5 | |
| 6 | namespace CascadeIDE.Tests; |
| 7 | |
| 8 | public sealed class IntercomMemberFileInferenceTests |
| 9 | { |
| 10 | [Fact] |
| 11 | public void TryResolveRelativeFile_UniqueMemberInSidecar_InfersFile() |
| 12 | { |
| 13 | var dir = createWorkspace(); |
| 14 | seedMember(dir, "src/A.cs", "Foo.Bar", 10, 20); |
| 15 | seedMember(dir, "src/B.cs", "Other.Baz", 1, 5); |
| 16 | |
| 17 | Assert.True( |
| 18 | IntercomMemberFileInference.TryResolveRelativeFile( |
| 19 | explicitRelativeFile: null, |
| 20 | memberKey: "Foo.Bar", |
| 21 | activeFilePath: null, |
| 22 | workspaceRoot: dir, |
| 23 | solutionPath: null, |
| 24 | indexDirectoryRelative: null, |
| 25 | out var file, |
| 26 | out var error), |
| 27 | error); |
| 28 | |
| 29 | Assert.Equal("src/A.cs", file); |
| 30 | } |
| 31 | |
| 32 | [Fact] |
| 33 | public void TryResolveRelativeFile_AmbiguousMember_ReturnsError() |
| 34 | { |
| 35 | var dir = createWorkspace(); |
| 36 | seedMember(dir, "src/A.cs", "Run", 10, 20); |
| 37 | seedMember(dir, "src/B.cs", "Run", 30, 40); |
| 38 | |
| 39 | Assert.False( |
| 40 | IntercomMemberFileInference.TryResolveRelativeFile( |
| 41 | null, |
| 42 | "Run", |
| 43 | activeFilePath: null, |
| 44 | workspaceRoot: dir, |
| 45 | solutionPath: null, |
| 46 | indexDirectoryRelative: null, |
| 47 | out _, |
| 48 | out var error)); |
| 49 | |
| 50 | Assert.Contains("нескольких файлах", error); |
| 51 | } |
| 52 | |
| 53 | [Fact] |
| 54 | public void TryResolveRelativeFile_ActiveFilePreferredWhenAlsoInIndex() |
| 55 | { |
| 56 | var dir = createWorkspace(); |
| 57 | seedMember(dir, "src/A.cs", "Run", 10, 20); |
| 58 | seedMember(dir, "src/B.cs", "Run", 30, 40); |
| 59 | var active = Path.Combine(dir, "src", "B.cs"); |
| 60 | |
| 61 | Assert.True( |
| 62 | IntercomMemberFileInference.TryResolveRelativeFile( |
| 63 | null, |
| 64 | "Run", |
| 65 | active, |
| 66 | dir, |
| 67 | solutionPath: null, |
| 68 | indexDirectoryRelative: null, |
| 69 | out var file, |
| 70 | out var error), |
| 71 | error); |
| 72 | |
| 73 | Assert.Equal("src/B.cs", file); |
| 74 | } |
| 75 | |
| 76 | [Fact] |
| 77 | public void IntercomCodeRefParser_BareMember_ResolvesFileFromSidecar() |
| 78 | { |
| 79 | var dir = createWorkspace(); |
| 80 | seedMember(dir, "src/A.cs", "Foo.Bar", 10, 20); |
| 81 | |
| 82 | var editor = IntercomAttachmentResolveAtSend.EditorSnapshot.ForMcpBracketResolve(null); |
| 83 | Assert.True( |
| 84 | IntercomCodeRefParser.TryParse( |
| 85 | "M:Foo.Bar", |
| 86 | editor, |
| 87 | dir, |
| 88 | solutionPath: null, |
| 89 | out var query, |
| 90 | out var error, |
| 91 | indexDirectoryRelative: null), |
| 92 | error); |
| 93 | |
| 94 | Assert.Equal("src/A.cs", query.File.Replace('\\', '/')); |
| 95 | Assert.Equal("Foo.Bar", query.MemberKey); |
| 96 | } |
| 97 | |
| 98 | private static void seedMember(string workspace, string relativePath, string memberKey, int lineStart, int lineEnd) |
| 99 | { |
| 100 | var cache = IntercomAttachResolveCacheContext.From(workspace, null, relativePath); |
| 101 | var absolute = Path.Combine(workspace, relativePath.Replace('/', Path.DirectorySeparatorChar)); |
| 102 | Directory.CreateDirectory(Path.GetDirectoryName(absolute)!); |
| 103 | if (!File.Exists(absolute)) |
| 104 | File.WriteAllText(absolute, "// stub\n"); |
| 105 | |
| 106 | var mtime = File.GetLastWriteTimeUtc(absolute).Ticks; |
| 107 | IntercomSymbolLineIndex.ReplaceFileSymbols( |
| 108 | cache, |
| 109 | relativePath, |
| 110 | mtime, |
| 111 | [new IntercomSymbolLineEntry("docid", memberKey, lineStart, lineEnd)]); |
| 112 | } |
| 113 | |
| 114 | private static string createWorkspace() |
| 115 | { |
| 116 | var dir = Path.Combine(Path.GetTempPath(), nameof(IntercomMemberFileInferenceTests) + "_" + Guid.NewGuid().ToString("N")); |
| 117 | Directory.CreateDirectory(dir); |
| 118 | return dir; |
| 119 | } |
| 120 | } |
| 121 | |