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
188 lines
3.9 KiB
Go
188 lines
3.9 KiB
Go
package recipe
|
|
|
|
import "testing"
|
|
|
|
func TestParseSimpleRecipe(t *testing.T) {
|
|
data := []byte(`
|
|
include:
|
|
- apache
|
|
- .dev
|
|
|
|
steps:
|
|
install nginx:
|
|
pkg.installed:
|
|
- name: nginx
|
|
start nginx:
|
|
service.running:
|
|
- name: nginx
|
|
- requisites:
|
|
- require: install nginx
|
|
`)
|
|
r := Parse(data)
|
|
|
|
if len(r.Errors) > 0 {
|
|
t.Fatalf("unexpected parse errors: %v", r.Errors)
|
|
}
|
|
|
|
if len(r.Includes) != 2 {
|
|
t.Fatalf("expected 2 includes, got %d", len(r.Includes))
|
|
}
|
|
if r.Includes[0].Value != "apache" {
|
|
t.Errorf("include[0] = %q, want %q", r.Includes[0].Value, "apache")
|
|
}
|
|
if r.Includes[1].Value != ".dev" {
|
|
t.Errorf("include[1] = %q, want %q", r.Includes[1].Value, ".dev")
|
|
}
|
|
|
|
if len(r.Steps) != 2 {
|
|
t.Fatalf("expected 2 steps, got %d", len(r.Steps))
|
|
}
|
|
|
|
s := r.Steps[0]
|
|
if s.ID != "install nginx" {
|
|
t.Errorf("step[0].ID = %q, want %q", s.ID, "install nginx")
|
|
}
|
|
if s.Ingredient != "pkg" {
|
|
t.Errorf("step[0].Ingredient = %q, want %q", s.Ingredient, "pkg")
|
|
}
|
|
if s.Method != "installed" {
|
|
t.Errorf("step[0].Method = %q, want %q", s.Method, "installed")
|
|
}
|
|
if len(s.Properties) != 1 || s.Properties[0].Key != "name" {
|
|
t.Errorf("step[0] expected name property, got %v", s.Properties)
|
|
}
|
|
}
|
|
|
|
func TestParseInvalidYAML(t *testing.T) {
|
|
data := []byte(`{{{invalid`)
|
|
r := Parse(data)
|
|
|
|
if len(r.Errors) == 0 {
|
|
t.Error("expected parse errors for invalid YAML")
|
|
}
|
|
}
|
|
|
|
func TestParseUnknownTopLevel(t *testing.T) {
|
|
data := []byte(`
|
|
include:
|
|
- foo
|
|
bogus_key: bar
|
|
steps: {}
|
|
`)
|
|
r := Parse(data)
|
|
|
|
found := false
|
|
for _, e := range r.Errors {
|
|
if e.Message == "unknown top-level key: bogus_key" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("expected error about unknown top-level key")
|
|
}
|
|
}
|
|
|
|
func TestParseBadMethodKey(t *testing.T) {
|
|
data := []byte(`
|
|
steps:
|
|
bad step:
|
|
nomethod:
|
|
- name: foo
|
|
`)
|
|
r := Parse(data)
|
|
|
|
found := false
|
|
for _, e := range r.Errors {
|
|
if e.Message == "step key must be in the form ingredient.method, got: nomethod" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected error about bad method key, got: %v", r.Errors)
|
|
}
|
|
}
|
|
|
|
func TestStepIDs(t *testing.T) {
|
|
data := []byte(`
|
|
steps:
|
|
step one:
|
|
file.exists:
|
|
- name: /tmp/a
|
|
step two:
|
|
file.absent:
|
|
- name: /tmp/b
|
|
`)
|
|
r := Parse(data)
|
|
ids := r.StepIDs()
|
|
|
|
if len(ids) != 2 {
|
|
t.Fatalf("expected 2 step IDs, got %d", len(ids))
|
|
}
|
|
|
|
idSet := make(map[string]bool)
|
|
for _, id := range ids {
|
|
idSet[id] = true
|
|
}
|
|
if !idSet["step one"] || !idSet["step two"] {
|
|
t.Errorf("missing expected step IDs: %v", ids)
|
|
}
|
|
}
|
|
|
|
func TestParseRequisites(t *testing.T) {
|
|
data := []byte(`
|
|
steps:
|
|
first step:
|
|
file.exists:
|
|
- name: /tmp/a
|
|
second step:
|
|
file.exists:
|
|
- name: /tmp/b
|
|
- requisites:
|
|
- require: first step
|
|
- onchanges:
|
|
- first step
|
|
`)
|
|
r := Parse(data)
|
|
|
|
if len(r.Errors) > 0 {
|
|
t.Fatalf("unexpected parse errors: %v", r.Errors)
|
|
}
|
|
|
|
if len(r.Steps) != 2 {
|
|
t.Fatalf("expected 2 steps, got %d", len(r.Steps))
|
|
}
|
|
|
|
s := r.Steps[1]
|
|
if len(s.Requisites) != 2 {
|
|
t.Fatalf("expected 2 requisites, got %d", len(s.Requisites))
|
|
}
|
|
if s.Requisites[0].Condition != "require" {
|
|
t.Errorf("requisite[0].Condition = %q, want %q", s.Requisites[0].Condition, "require")
|
|
}
|
|
if len(s.Requisites[0].StepIDs) != 1 || s.Requisites[0].StepIDs[0] != "first step" {
|
|
t.Errorf("requisite[0].StepIDs = %v, want [first step]", s.Requisites[0].StepIDs)
|
|
}
|
|
}
|
|
|
|
func TestParseEmptyRecipe(t *testing.T) {
|
|
r := Parse([]byte(""))
|
|
if r == nil {
|
|
t.Fatal("expected non-nil recipe for empty input")
|
|
}
|
|
}
|
|
|
|
func TestParseGoTemplate(t *testing.T) {
|
|
// Recipes can contain Go template syntax — the parser should not crash.
|
|
// Template directives may cause YAML errors, but the parser should handle gracefully.
|
|
data := []byte(`
|
|
steps:
|
|
install golang:
|
|
archive.extracted:
|
|
- name: /usr/local/go
|
|
`)
|
|
r := Parse(data)
|
|
if len(r.Steps) != 1 {
|
|
t.Fatalf("expected 1 step, got %d", len(r.Steps))
|
|
}
|
|
}
|