1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
package hexaiaction
import (
"context"
"strings"
"testing"
"codeberg.org/snonux/hexai/internal/appconfig"
"codeberg.org/snonux/hexai/internal/llm"
)
func TestParseInput_NoDiagnostics(t *testing.T) {
in := "some code here"
parts, err := ParseInput(strings.NewReader(in))
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if parts.Selection != in || len(parts.Diagnostics) != 0 {
t.Fatalf("unexpected parse: %#v", parts)
}
}
func TestParseInput_WithDiagnostics(t *testing.T) {
in := "Diagnostics:\nmissing return\nuse of undefined: foo\n\nfunc a() {}"
parts, err := ParseInput(strings.NewReader(in))
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if parts.Selection != "func a() {}" {
t.Fatalf("selection wrong: %q", parts.Selection)
}
if len(parts.Diagnostics) != 2 || parts.Diagnostics[0] != "missing return" {
t.Fatalf("diags wrong: %#v", parts.Diagnostics)
}
}
func TestExtractInstruction_Variants(t *testing.T) {
cases := []struct{ in, wantInstr string }{
{";rewrite to X;\ncode", "rewrite to X"},
{"/* fix it */\ncode", "fix it"},
{"<!-- doc me -->\ncode", "doc me"},
{"// change it\ncode", "change it"},
{"# tweak\ncode", "tweak"},
{"-- fix\ncode", "fix"},
}
for _, c := range cases {
got, cleaned := ExtractInstruction(c.in)
if got != c.wantInstr {
t.Fatalf("instr mismatch: %q != %q", got, c.wantInstr)
}
if strings.Contains(cleaned, c.wantInstr) && strings.Contains(c.in, c.wantInstr) {
t.Fatalf("expected instruction removed from selection: %q", cleaned)
}
}
}
func TestRenderAndStrip(t *testing.T) {
tpl := "Hello, {{name}}"
out := Render(tpl, map[string]string{"name": "Hex"})
if out != "Hello, Hex" {
t.Fatalf("unexpected render: %q", out)
}
fenced := "```go\npackage x\n```"
if StripFences(fenced) != "package x" {
t.Fatalf("unexpected strip")
}
}
type fakeClient struct {
last []llm.Message
out string
err error
}
func (f *fakeClient) Chat(_ context.Context, msgs []llm.Message, _ ...llm.RequestOption) (string, error) {
f.last = msgs
return f.out, f.err
}
func (f *fakeClient) DefaultModel() string { return "m" }
func TestRuners_Prompts(t *testing.T) {
cfg := appconfig.App{
PromptCodeActionRewriteSystem: "SYS-R",
PromptCodeActionRewriteUser: "R {{instruction}} :: {{selection}}",
PromptCodeActionDiagnosticsSystem: "SYS-D",
PromptCodeActionDiagnosticsUser: "D {{diagnostics}} :: {{selection}}",
PromptCodeActionDocumentSystem: "SYS-C",
PromptCodeActionDocumentUser: "C {{selection}}",
PromptCodeActionGoTestSystem: "SYS-T",
PromptCodeActionGoTestUser: "T {{function}}",
}
f := &fakeClient{out: "```\nDONE\n```"}
ctx := context.Background()
// rewrite
if out, err := runRewrite(ctx, cfg, f, "instr", "sel"); err != nil || out != "DONE" {
t.Fatalf("rewrite failed: %q %v", out, err)
}
if len(f.last) != 2 || f.last[0].Content != "SYS-R" || !strings.Contains(f.last[1].Content, "instr") {
t.Fatalf("rewrite prompts wrong: %#v", f.last)
}
// diagnostics
if out, err := runDiagnostics(ctx, cfg, f, []string{"a", "b"}, "sel"); err != nil || out != "DONE" {
t.Fatalf("diagnostics failed: %q %v", out, err)
}
if f.last[0].Content != "SYS-D" || !strings.Contains(f.last[1].Content, "a\nb") {
t.Fatalf("diagnostics prompts wrong: %#v", f.last)
}
// document
if out, err := runDocument(ctx, cfg, f, "sel"); err != nil || out != "DONE" {
t.Fatalf("document failed: %q %v", out, err)
}
if f.last[0].Content != "SYS-C" || !strings.Contains(f.last[1].Content, "sel") {
t.Fatalf("document prompts wrong: %#v", f.last)
}
// gotest
if out, err := runGoTest(ctx, cfg, f, "func A(){}"); err != nil || out != "DONE" {
t.Fatalf("gotest failed: %q %v", out, err)
}
if f.last[0].Content != "SYS-T" || !strings.Contains(f.last[1].Content, "func A(){") {
t.Fatalf("gotest prompts wrong: %#v", f.last)
}
}
|