summaryrefslogtreecommitdiff
path: root/internal/lsp/codeaction_test.go
blob: 29cb4162f801f93261a02eaa06d92db9182e5191 (plain)
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
package lsp

import (
	"context"
	"encoding/json"
	"testing"

	"codeberg.org/snonux/hexai/internal/llm"
)

type fakeLLM struct {
	resp string
	err  error
}

func (f fakeLLM) Chat(_ context.Context, _ []llm.Message, _ ...llm.RequestOption) (string, error) {
	return f.resp, f.err
}
func (f fakeLLM) Name() string         { return "fake" }
func (f fakeLLM) DefaultModel() string { return "fake-model" }

func TestBuildRewriteCodeAction_LazyAndResolves(t *testing.T) {
	s := newTestServer()
	s.llmClient = fakeLLM{resp: "REWRITTEN"}
	p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Range: Range{Start: Position{Line: 1, Character: 2}, End: Position{Line: 3, Character: 4}}}
	sel := ">rewrite>\nold code"
	ca := s.buildRewriteCodeAction(p, sel)
	if ca == nil {
		t.Fatalf("expected code action")
	}
	// Should be lazy (no edit yet)
	if ca.Edit != nil {
		t.Fatalf("expected nil Edit before resolve")
	}
	if len(ca.Data) == 0 {
		t.Fatalf("expected data payload for lazy resolve")
	}
	// Resolve now
	resolved, ok := s.resolveCodeAction(*ca)
	if !ok || resolved.Edit == nil {
		t.Fatalf("expected resolve to produce edit")
	}
	edits := resolved.Edit.Changes[p.TextDocument.URI]
	if len(edits) != 1 {
		t.Fatalf("expected 1 edit, got %d", len(edits))
	}
	if edits[0].Range != p.Range {
		t.Fatalf("edit range mismatch: got %+v want %+v", edits[0].Range, p.Range)
	}
	if edits[0].NewText == "" {
		t.Fatalf("expected non-empty replacement text")
	}
}

func TestBuildRewriteCodeAction_NoInstruction(t *testing.T) {
	s := newTestServer()
	s.llmClient = fakeLLM{resp: "IGNORED"}
	p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Range: Range{}}
	sel := "no instruction here"
	if ca := s.buildRewriteCodeAction(p, sel); ca != nil {
		t.Fatalf("expected nil action when no instruction present")
	}
}

func TestBuildDiagnosticsCodeAction_LazyAndResolves(t *testing.T) {
	s := newTestServer()
	s.llmClient = fakeLLM{resp: "FIXED"}
	p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Range: Range{Start: Position{Line: 10}, End: Position{Line: 12, Character: 5}}}
	ctx := CodeActionContext{Diagnostics: []Diagnostic{
		{Range: Range{Start: Position{Line: 11}, End: Position{Line: 11, Character: 10}}, Message: "inside"},
		{Range: Range{Start: Position{Line: 2}, End: Position{Line: 3}}, Message: "outside"},
	}}
	raw, _ := json.Marshal(ctx)
	p.Context = json.RawMessage(raw)
	sel := "some selected code"
	ca := s.buildDiagnosticsCodeAction(p, sel)
	if ca == nil {
		t.Fatalf("expected diagnostics code action")
	}
	if ca.Edit != nil {
		t.Fatalf("expected lazy action without edit")
	}
	if len(ca.Data) == 0 {
		t.Fatalf("expected data payload for lazy diagnostics action")
	}
	resolved, ok := s.resolveCodeAction(*ca)
	if !ok || resolved.Edit == nil {
		t.Fatalf("expected resolve to produce edit")
	}
}

func TestBuildDiagnosticsCodeAction_NoDiagnostics(t *testing.T) {
	s := newTestServer()
	s.llmClient = fakeLLM{resp: "FIXED"}
	p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Range: Range{}}
	// empty context
	p.Context = json.RawMessage(nil)
	if ca := s.buildDiagnosticsCodeAction(p, "sel"); ca != nil {
		t.Fatalf("expected nil action when no diagnostics")
	}
}