mirror of
https://github.com/gogrlx/grlx-lsp.git
synced 2026-04-02 03:18:47 -07:00
LSP server for grlx recipe files (.grlx) providing: - Completion for ingredients, methods, properties, requisite types, and step ID references - Diagnostics for unknown ingredients/methods, missing required properties, unknown properties, and invalid requisite types - Hover documentation for all ingredients and methods with property tables - Full schema for all 6 grlx ingredients (cmd, file, group, pkg, service, user) with accurate properties from the grlx source
190 lines
4.0 KiB
Go
190 lines
4.0 KiB
Go
package lsp
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/gogrlx/grlx-lsp/internal/recipe"
|
|
"github.com/gogrlx/grlx-lsp/internal/schema"
|
|
)
|
|
|
|
func TestDiagnoseUnknownIngredient(t *testing.T) {
|
|
h := NewHandler(schema.DefaultRegistry())
|
|
doc := &document{
|
|
content: `steps:
|
|
bad step:
|
|
bogus.method:
|
|
- name: foo`,
|
|
recipe: recipe.Parse([]byte(`steps:
|
|
bad step:
|
|
bogus.method:
|
|
- name: foo`)),
|
|
}
|
|
|
|
diags := h.diagnose(doc)
|
|
found := false
|
|
for _, d := range diags {
|
|
if d.Message == "unknown ingredient: bogus" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected unknown ingredient diagnostic, got: %v", diags)
|
|
}
|
|
}
|
|
|
|
func TestDiagnoseUnknownMethod(t *testing.T) {
|
|
h := NewHandler(schema.DefaultRegistry())
|
|
src := `steps:
|
|
bad step:
|
|
file.nonexistent:
|
|
- name: foo`
|
|
doc := &document{
|
|
content: src,
|
|
recipe: recipe.Parse([]byte(src)),
|
|
}
|
|
|
|
diags := h.diagnose(doc)
|
|
found := false
|
|
for _, d := range diags {
|
|
if d.Message == "unknown method: file.nonexistent" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected unknown method diagnostic, got: %v", diags)
|
|
}
|
|
}
|
|
|
|
func TestDiagnoseMissingRequired(t *testing.T) {
|
|
h := NewHandler(schema.DefaultRegistry())
|
|
// file.managed requires both name and source
|
|
src := `steps:
|
|
manage file:
|
|
file.managed:
|
|
- user: root`
|
|
doc := &document{
|
|
content: src,
|
|
recipe: recipe.Parse([]byte(src)),
|
|
}
|
|
|
|
diags := h.diagnose(doc)
|
|
foundName := false
|
|
foundSource := false
|
|
for _, d := range diags {
|
|
if d.Message == "missing required property: name" {
|
|
foundName = true
|
|
}
|
|
if d.Message == "missing required property: source" {
|
|
foundSource = true
|
|
}
|
|
}
|
|
if !foundName {
|
|
t.Error("expected diagnostic for missing required property: name")
|
|
}
|
|
if !foundSource {
|
|
t.Error("expected diagnostic for missing required property: source")
|
|
}
|
|
}
|
|
|
|
func TestDiagnoseUnknownProperty(t *testing.T) {
|
|
h := NewHandler(schema.DefaultRegistry())
|
|
src := `steps:
|
|
my step:
|
|
file.absent:
|
|
- name: /tmp/foo
|
|
- bogusprop: bar`
|
|
doc := &document{
|
|
content: src,
|
|
recipe: recipe.Parse([]byte(src)),
|
|
}
|
|
|
|
diags := h.diagnose(doc)
|
|
found := false
|
|
for _, d := range diags {
|
|
if d.Message == "unknown property: bogusprop for file.absent" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected unknown property diagnostic, got: %v", diags)
|
|
}
|
|
}
|
|
|
|
func TestDiagnoseValidRecipe(t *testing.T) {
|
|
h := NewHandler(schema.DefaultRegistry())
|
|
src := `steps:
|
|
install nginx:
|
|
pkg.installed:
|
|
- name: nginx`
|
|
doc := &document{
|
|
content: src,
|
|
recipe: recipe.Parse([]byte(src)),
|
|
}
|
|
|
|
diags := h.diagnose(doc)
|
|
if len(diags) != 0 {
|
|
t.Errorf("expected no diagnostics for valid recipe, got: %v", diags)
|
|
}
|
|
}
|
|
|
|
func TestDiagnoseUnknownRequisiteType(t *testing.T) {
|
|
h := NewHandler(schema.DefaultRegistry())
|
|
src := `steps:
|
|
first:
|
|
file.exists:
|
|
- name: /tmp/a
|
|
second:
|
|
file.exists:
|
|
- name: /tmp/b
|
|
- requisites:
|
|
- bogus_req: first`
|
|
doc := &document{
|
|
content: src,
|
|
recipe: recipe.Parse([]byte(src)),
|
|
}
|
|
|
|
diags := h.diagnose(doc)
|
|
found := false
|
|
for _, d := range diags {
|
|
if d.Message == "unknown requisite type: bogus_req" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected unknown requisite type diagnostic, got: %v", diags)
|
|
}
|
|
}
|
|
|
|
func TestLineAt(t *testing.T) {
|
|
content := "line0\nline1\nline2"
|
|
if got := lineAt(content, 0); got != "line0" {
|
|
t.Errorf("lineAt(0) = %q, want %q", got, "line0")
|
|
}
|
|
if got := lineAt(content, 2); got != "line2" {
|
|
t.Errorf("lineAt(2) = %q, want %q", got, "line2")
|
|
}
|
|
if got := lineAt(content, 99); got != "" {
|
|
t.Errorf("lineAt(99) = %q, want empty", got)
|
|
}
|
|
}
|
|
|
|
func TestWordAtPosition(t *testing.T) {
|
|
tests := []struct {
|
|
line string
|
|
col int
|
|
want string
|
|
}{
|
|
{" file.managed:", 8, "file.managed"},
|
|
{" - name: foo", 6, "name"},
|
|
{" - require: step one", 10, "require"},
|
|
{"", 0, ""},
|
|
{" pkg.installed:", 5, "pkg.installed"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := wordAtPosition(tt.line, tt.col)
|
|
if got != tt.want {
|
|
t.Errorf("wordAtPosition(%q, %d) = %q, want %q", tt.line, tt.col, got, tt.want)
|
|
}
|
|
}
|
|
}
|