summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-04 08:32:20 +0300
committerPaul Buetow <paul@buetow.org>2025-09-04 08:32:20 +0300
commit28945db47e13b3de8345ee1cc0c8dece1d9a4e0e (patch)
treee318283e7593df8bf29bf9f2ae7d2c0fc4f7ec22
parentbef02f9f9908c65fea5472f04e534a5fba577502 (diff)
tests(lsp,llm): add helper and factory tests to raise coverage modestly; continue toward 80% target
-rw-r--r--internal/llm/provider_test.go21
-rw-r--r--internal/llm/util_test.go9
-rw-r--r--internal/lsp/helpers_more_test.go95
3 files changed, 125 insertions, 0 deletions
diff --git a/internal/llm/provider_test.go b/internal/llm/provider_test.go
new file mode 100644
index 0000000..1412b3c
--- /dev/null
+++ b/internal/llm/provider_test.go
@@ -0,0 +1,21 @@
+package llm
+
+import (
+ "context"
+ "testing"
+)
+
+func TestNewFromConfig_DefaultsAndErrors(t *testing.T) {
+ // Unknown provider
+ if _, err := NewFromConfig(Config{Provider:"bogus"}, "", ""); err == nil { t.Fatalf("expected error for unknown provider") }
+ // OpenAI missing key
+ if _, err := NewFromConfig(Config{Provider:"openai", OpenAIModel:"g"}, "", ""); err == nil { t.Fatalf("expected key error") }
+ // Copilot missing key
+ if _, err := NewFromConfig(Config{Provider:"copilot", CopilotModel:"m"}, "", ""); err == nil { t.Fatalf("expected key error") }
+}
+
+type fakeClientMin struct{}
+func (fakeClientMin) Chat(context.Context, []Message, ...RequestOption) (string, error) { return "", nil }
+func (fakeClientMin) Name() string { return "x" }
+func (fakeClientMin) DefaultModel() string { return "m" }
+
diff --git a/internal/llm/util_test.go b/internal/llm/util_test.go
new file mode 100644
index 0000000..acffe5a
--- /dev/null
+++ b/internal/llm/util_test.go
@@ -0,0 +1,9 @@
+package llm
+
+import "testing"
+
+func TestNilStringErr(t *testing.T) {
+ s, err := nilStringErr("boom")
+ if s != "" || err == nil { t.Fatalf("expected empty string and error") }
+}
+
diff --git a/internal/lsp/helpers_more_test.go b/internal/lsp/helpers_more_test.go
new file mode 100644
index 0000000..d4a3de6
--- /dev/null
+++ b/internal/lsp/helpers_more_test.go
@@ -0,0 +1,95 @@
+package lsp
+
+import ("testing")
+
+func TestComputeWordStart(t *testing.T) {
+ s := "fooBar 123"
+ if i := computeWordStart(s, 5); i != 0 { t.Fatalf("start=%d", i) }
+ if i := computeWordStart(s, len(s)); i != 7 { t.Fatalf("end start=%d", i) }
+}
+
+func TestLeadingAndApplyIndent(t *testing.T) {
+ if got := leadingIndent("\t abc"); got == "" { t.Fatalf("expected indent") }
+ out := applyIndent(" ", "x\n y\n\n z")
+ if out == "" || out[:2] != " " { t.Fatalf("applyIndent failed: %q", out) }
+}
+
+func TestFindStrictSemicolonTag(t *testing.T) {
+ if _, _, _, ok := findStrictSemicolonTag(";do this; next"); !ok { t.Fatalf("expected strict tag") }
+ if _, _, _, ok := findStrictSemicolonTag("; spaced ;"); ok { t.Fatalf("should ignore spaced tag") }
+}
+
+// hasDoubleSemicolonTrigger tested elsewhere
+
+func TestStripDuplicatePrefixes(t *testing.T) {
+ if got := stripDuplicateAssignmentPrefix("name := ", "name := 123"); got == "name := 123" { t.Fatalf("expected trim") }
+ if got := stripDuplicateGeneralPrefix("fmt.", "fmt.Println"); got == "fmt.Println" { t.Fatalf("expected trim general") }
+}
+
+func TestExtractRangeText(t *testing.T) {
+ d := &document{text: "a\nbc\nxyz", lines: []string{"a","bc","xyz"}}
+ // single line
+ got := extractRangeText(d, Range{Start: Position{Line:1, Character:0}, End: Position{Line:1, Character:2}})
+ if got != "bc" { t.Fatalf("got %q", got) }
+ // multi-line
+ got = extractRangeText(d, Range{Start: Position{Line:0, Character:0}, End: Position{Line:2, Character:2}})
+ if got != "a\nbc\nxy" { t.Fatalf("got %q", got) }
+}
+
+func TestRangesOverlapAndOrder(t *testing.T) {
+ a := Range{Start: Position{Line:1, Character:2}, End: Position{Line:1, Character:5}}
+ b := Range{Start: Position{Line:1, Character:4}, End: Position{Line:1, Character:8}}
+ if !rangesOverlap(a, b) { t.Fatalf("expected overlap") }
+ c := Range{Start: Position{Line:2, Character:0}, End: Position{Line:2, Character:1}}
+ if rangesOverlap(a, c) { t.Fatalf("no overlap expected") }
+ if !lessPos(Position{Line:0, Character:1}, Position{Line:1, Character:0}) { t.Fatalf("lessPos failed") }
+ if !greaterPos(Position{Line:2, Character:0}, Position{Line:1, Character:9}) { t.Fatalf("greaterPos failed") }
+}
+
+func TestPromptRemovalEditsForLine(t *testing.T) {
+ edits := promptRemovalEditsForLine(";;do thing;", 3)
+ if len(edits) != 1 || edits[0].Range.Start.Line != 3 {
+ t.Fatalf("expected full-line removal for double-semicolon")
+ }
+ edits2 := promptRemovalEditsForLine(";act; and ;b;", 1)
+ if len(edits2) == 0 { t.Fatalf("expected edits to remove strict markers") }
+}
+
+func TestCollectPromptRemovalEdits_MultiLine(t *testing.T) {
+ s := newTestServer()
+ uri := "file:///t.go"
+ s.setDocument(uri, "a\n;do; x\n;;wipe;\nend")
+ edits := s.collectPromptRemovalEdits(uri)
+ if len(edits) < 2 { t.Fatalf("expected >=2 edits, got %d", len(edits)) }
+}
+
+func TestInParamListAndBuildPrompts(t *testing.T) {
+ cur := "func add(a int, b string) int"
+ if !inParamList(cur, 12) { t.Fatalf("expected in param list") }
+ p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///x.go"}, Position: Position{Line: 0, Character: 5}}
+ sys, user := buildPrompts(false, p, "above", "current", "below", "func add")
+ if sys == "" || user == "" { t.Fatalf("prompts empty") }
+}
+
+func TestLabelForCompletion(t *testing.T) {
+ if got := labelForCompletion("line one\nline two", "lin"); got != "line one" { t.Fatalf("expected label, got %q", got) }
+ if got := labelForCompletion("result", "zzz"); got != "zzz" { t.Fatalf("expected filter preferred when not prefix, got %q", got) }
+ if got := labelForCompletion("result", "re"); got != "result" { t.Fatalf("expected label when filter prefixes label, got %q", got) }
+}
+
+func TestComputeTextEditAndFilter(t *testing.T) {
+ // non-params edit
+ p := CompletionParams{Position: Position{Line: 1, Character: 4}}
+ te, filter := computeTextEditAndFilter("X", false, "ab cd", p)
+ if te == nil || filter == "" { t.Fatalf("expected edit and filter") }
+ // inside params
+ line := "func add(a int, b int)"
+ p2 := CompletionParams{Position: Position{Line: 0, Character: 12}}
+ te2, _ := computeTextEditAndFilter("string", true, line, p2)
+ if te2 == nil || te2.Range.Start.Character == 0 { t.Fatalf("expected param-range edit") }
+}
+
+func TestIsBareDoubleSemicolon(t *testing.T) {
+ if !isBareDoubleSemicolon(";; ") { t.Fatalf("expected true") }
+ if isBareDoubleSemicolon(";;x;") { t.Fatalf("expected false for content form") }
+}