Forge
csharpdeeb25a2
1using System.Text.Json;
2using System.Text.RegularExpressions;
3using CascadeIDE.Features.WebAiPortal.Application;
4using Xunit;
5
6namespace CascadeIDE.Tests;
7
8/// <summary>Контракт артефактов ADR 0108 в <c>AiPrompts/</c>: .md (json-cascade) и .chat-paste.txt (голый однострочный JSON) против whitelist моста.</summary>
9public sealed class WebAiPortalAdr0108PromptVerificationTests
10{
11 private static readonly Regex JsonCascadeFence = new(
12 """```json-cascade[ \t]*\r?\n(.*?)```""",
13 RegexOptions.Singleline | RegexOptions.CultureInvariant,
14 TimeSpan.FromSeconds(1));
15
16 private static string PromptPath =>
17 Path.Combine(AppContext.BaseDirectory, "AiPrompts", "web-ai-portal-bridge-adr0108.prompts.md");
18
19 private static string ChatPastePath =>
20 Path.Combine(AppContext.BaseDirectory, "AiPrompts", "web-ai-portal-bridge-adr0108.chat-paste.txt");
21
22 [Fact]
23 public void Prompt_file_shipped_next_to_tests()
24 {
25 Assert.True(File.Exists(PromptPath), "ожидается CopyToOutputDirectory в CascadeIDE.Tests.csproj: " + PromptPath);
26 }
27
28 [Fact]
29 public void Chat_paste_plain_file_shipped_next_to_tests()
30 {
31 Assert.True(File.Exists(ChatPastePath), "ожидается CopyToOutputDirectory в CascadeIDE.Tests.csproj: " + ChatPastePath);
32 }
33
34 /// <summary>Текст без Markdown: каждая однострочная JSON-команда должна распознаваться мостом как голый буфер и входить в whitelist.</summary>
35 [Fact]
36 public void Chat_paste_plain_examples_are_allowlisted_when_parsed_like_clipboard_payload()
37 {
38 var txt = File.ReadAllText(ChatPastePath);
39 Assert.Contains("0108-web-ai-portal-host-object-tools-bridge.md", txt, StringComparison.Ordinal);
40
41 var exampleLines = 0;
42 foreach (var segment in txt.Split('\n'))
43 {
44 var line = segment.Trim('\r').Trim();
45 if (line.Length < 18 || line[0] != '{')
46 continue;
47 if (!WebAiPortalBridgePayloadResolution.TryParseBareExecuteCommand(line, out var json))
48 continue;
49 Assert.True(WebAiPortalBridgePayloadResolution.TryGetCommandId(json!, out var id));
50 Assert.True(
51 WebAiPortalCommandBridge.Whitelist.Contains(id!),
52 $"строка-пример с command_id «{id}» вне whitelist");
53 exampleLines++;
54 }
55
56 Assert.True(exampleLines >= 5, $"ожидалось ≥5 однострочных примеров JSON, получено {exampleLines}");
57 }
58
59 [Fact]
60 public void Prompt_md_points_reader_to_chat_paste_plain_sibling_for_models_that_strip_markdown()
61 {
62 Assert.Contains(
63 "web-ai-portal-bridge-adr0108.chat-paste.txt",
64 File.ReadAllText(PromptPath),
65 StringComparison.Ordinal);
66 }
67
68 [Fact]
69 public void Prompt_references_canonical_ADR_doc_path_and_each_fence_is_allowlisted_execute_command()
70 {
71 var md = File.ReadAllText(PromptPath);
72 Assert.Contains("0108-web-ai-portal-host-object-tools-bridge.md", md, StringComparison.Ordinal);
73 Assert.Contains("ADR 0108", md, StringComparison.Ordinal);
74
75 var matches = JsonCascadeFence.Matches(md);
76 Assert.True(matches.Count >= 2, "промпт должен содержать хотя бы два fenced json-cascade");
77
78 foreach (Match m in matches)
79 {
80 Assert.True(m.Success && m.Groups.Count >= 2);
81 var inner = m.Groups[1].Value.Trim();
82 Assert.False(string.IsNullOrWhiteSpace(inner));
83
84 using var doc = JsonDocument.Parse(inner);
85 Assert.Equal(JsonValueKind.Object, doc.RootElement.ValueKind);
86 Assert.True(doc.RootElement.TryGetProperty("command_id", out var cid));
87 Assert.Equal(JsonValueKind.String, cid.ValueKind);
88 var id = cid.GetString();
89 Assert.False(string.IsNullOrWhiteSpace(id));
90 Assert.True(
91 WebAiPortalCommandBridge.Whitelist.Contains(id!),
92 $"command_id «{id}» не из whitelist моста ADR 0108");
93 Assert.False(
94 string.Equals(IdeCommands.CodebaseIndexReindex, id, StringComparison.Ordinal),
95 "промпт ADR0108 только read-PoC: reindex не в whitelist моста");
96 }
97 }
98}
99
View only · write via MCP/CIDE