The comment-keeping summer.yaml edit refused a flow-style plugins list
(plugins: [{id: a, module: b}]) that plugin:add used to handle with a
full rewrite. The edit now replaces an inline list, over one or several
lines, with a block list of its items plus the new ones and keeps every
other line. A list it cannot edit in place (a tagged one, say) falls
back to the full SaveManifest rewrite with a note that the comments were
not kept. plugin:install gets the same behaviour.
163 lines
7.4 KiB
Go
163 lines
7.4 KiB
Go
package build
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestAppendManifestPlugins pins the in-place summer.yaml edit of
|
|
// plugin:add and plugin:install: comments, blank lines and key order stay,
|
|
// new items take the existing indentation, a missing plugins block is
|
|
// appended, and an edit that does not parse back leaves the file as it was.
|
|
func TestAppendManifestPlugins(t *testing.T) {
|
|
add := []Plugin{{ID: "acme.shop", Module: "example.test/sm-shop-plugin"}, {ID: "acme.pgsubs", Module: "example.test/pg/subs"}}
|
|
cases := []struct {
|
|
name, src, want string
|
|
}{
|
|
{
|
|
name: "comments, blank lines and later keys stay",
|
|
src: "# The application.\nmodule: example.test/app # app module\nbinary: app\n\nplugins:\n" +
|
|
" # Core plugins.\n - id: acme.user\n module: example.test/sm-user-plugin # pinned\n\n" +
|
|
" - id: acme.blog\n module: example.test/sm-blog-plugin\n\n# Build tags.\ntags:\n - summercms_mcp\n",
|
|
want: "# The application.\nmodule: example.test/app # app module\nbinary: app\n\nplugins:\n" +
|
|
" # Core plugins.\n - id: acme.user\n module: example.test/sm-user-plugin # pinned\n\n" +
|
|
" - id: acme.blog\n module: example.test/sm-blog-plugin\n" +
|
|
" - id: acme.shop\n module: example.test/sm-shop-plugin\n - id: acme.pgsubs\n module: example.test/pg/subs\n" +
|
|
"\n# Build tags.\ntags:\n - summercms_mcp\n",
|
|
},
|
|
{
|
|
name: "indentation is matched",
|
|
src: "module: example.test/app\nbinary: app\nplugins:\n- id: acme.user\n module: example.test/sm-user-plugin\n",
|
|
want: "module: example.test/app\nbinary: app\nplugins:\n- id: acme.user\n module: example.test/sm-user-plugin\n" +
|
|
"- id: acme.shop\n module: example.test/sm-shop-plugin\n- id: acme.pgsubs\n module: example.test/pg/subs\n",
|
|
},
|
|
{
|
|
name: "four-space indentation",
|
|
src: "module: example.test/app\nbinary: app\nplugins:\n - id: acme.user\n module: example.test/sm-user-plugin\n",
|
|
want: "module: example.test/app\nbinary: app\nplugins:\n - id: acme.user\n module: example.test/sm-user-plugin\n" +
|
|
" - id: acme.shop\n module: example.test/sm-shop-plugin\n - id: acme.pgsubs\n module: example.test/pg/subs\n",
|
|
},
|
|
{
|
|
name: "dash alone on its line",
|
|
src: "module: example.test/app\nbinary: app\nplugins:\n -\n id: acme.user\n module: example.test/sm-user-plugin\n",
|
|
want: "module: example.test/app\nbinary: app\nplugins:\n -\n id: acme.user\n module: example.test/sm-user-plugin\n" +
|
|
" - id: acme.shop\n module: example.test/sm-shop-plugin\n - id: acme.pgsubs\n module: example.test/pg/subs\n",
|
|
},
|
|
{
|
|
name: "missing plugins block is appended",
|
|
src: "# App.\nmodule: example.test/app\nbinary: app",
|
|
want: "# App.\nmodule: example.test/app\nbinary: app\nplugins:\n" +
|
|
" - id: acme.shop\n module: example.test/sm-shop-plugin\n - id: acme.pgsubs\n module: example.test/pg/subs\n",
|
|
},
|
|
{
|
|
name: "inline list becomes a block with the new items",
|
|
src: "# App.\nmodule: example.test/app\nbinary: app\nplugins: [{id: acme.user, module: example.test/u}, {id: acme.blog, module: \"example.test/b\"}] # core\n# Tags.\ntags: []\n",
|
|
want: "# App.\nmodule: example.test/app\nbinary: app\nplugins:\n" +
|
|
" - id: acme.user\n module: example.test/u\n - id: acme.blog\n module: example.test/b\n" +
|
|
" - id: acme.shop\n module: example.test/sm-shop-plugin\n - id: acme.pgsubs\n module: example.test/pg/subs\n" +
|
|
"# Tags.\ntags: []\n",
|
|
},
|
|
{
|
|
name: "inline list over several lines",
|
|
src: "module: example.test/app\nbinary: app\nplugins: [\n {id: acme.user, module: example.test/u}, # first\n {id: acme.blog,\n module: example.test/b},\n]\ntags: []\n",
|
|
want: "module: example.test/app\nbinary: app\nplugins:\n" +
|
|
" - id: acme.user\n module: example.test/u\n - id: acme.blog\n module: example.test/b\n" +
|
|
" - id: acme.shop\n module: example.test/sm-shop-plugin\n - id: acme.pgsubs\n module: example.test/pg/subs\n" +
|
|
"tags: []\n",
|
|
},
|
|
{
|
|
name: "empty inline list becomes a block",
|
|
src: "module: example.test/app\nbinary: app\nplugins: [] # none yet\ntags: []\n",
|
|
want: "module: example.test/app\nbinary: app\nplugins:\n" +
|
|
" - id: acme.shop\n module: example.test/sm-shop-plugin\n - id: acme.pgsubs\n module: example.test/pg/subs\n" +
|
|
"tags: []\n",
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), manifestName)
|
|
write(t, path, c.src)
|
|
if _, err := appendManifestPlugins(path, add); err != nil {
|
|
t.Fatalf("appendManifestPlugins: %v", err)
|
|
}
|
|
got, _ := os.ReadFile(path)
|
|
if string(got) != c.want {
|
|
t.Errorf("summer.yaml =\n%s\nwant\n%s", got, c.want)
|
|
}
|
|
m, err := LoadManifest(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
n := len(m.Plugins)
|
|
if n < 2 || m.Plugins[n-2] != add[0] || m.Plugins[n-1] != add[1] {
|
|
t.Errorf("plugins = %+v", m.Plugins)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("refusals leave the file unchanged", func(t *testing.T) {
|
|
refusals := []struct {
|
|
name, src string
|
|
plugins []Plugin
|
|
want string
|
|
}{
|
|
{"bad id", "module: example.test/app\nbinary: app\n", []Plugin{{ID: "Acme", Module: "example.test/x"}}, "plugins[0]"},
|
|
{"bad module", "module: example.test/app\nbinary: app\n", []Plugin{{ID: "acme.x", Module: "-x"}}, "module"},
|
|
{"broken file", "module: [\n", add, ""},
|
|
// A second YAML document after the plugins block: the items
|
|
// land in it, the first document does not gain them, and the
|
|
// original bytes are restored.
|
|
{"edit does not parse back", "module: example.test/app\nbinary: app\nplugins:\n- id: acme.user\n module: example.test/sm-user-plugin\n---\nother: 1\n", add, "left unchanged"},
|
|
}
|
|
for _, r := range refusals {
|
|
path := filepath.Join(t.TempDir(), manifestName)
|
|
write(t, path, r.src)
|
|
_, err := appendManifestPlugins(path, r.plugins)
|
|
if err == nil || !strings.Contains(err.Error(), r.want) {
|
|
t.Errorf("%s: err = %v, want %q", r.name, err, r.want)
|
|
}
|
|
if got, _ := os.ReadFile(path); string(got) != r.src {
|
|
t.Errorf("%s: file changed:\n%s", r.name, got)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("an inline list it cannot edit is written again", func(t *testing.T) {
|
|
// A tagged flow list: the text edit gives up and the manifest is
|
|
// written again, as plugin:add did before it kept comments.
|
|
path := filepath.Join(t.TempDir(), manifestName)
|
|
write(t, path, "# gone\nmodule: example.test/app\nbinary: app\nplugins: !!seq [{id: acme.user, module: example.test/u}]\n")
|
|
if _, err := LoadManifest(path); err != nil {
|
|
t.Fatalf("fixture does not parse: %v", err)
|
|
}
|
|
rewrote, err := appendManifestPlugins(path, add)
|
|
if err != nil || !rewrote {
|
|
t.Fatalf("rewrote %v, err %v", rewrote, err)
|
|
}
|
|
m, err := LoadManifest(path)
|
|
if err != nil || len(m.Plugins) != 3 || m.Plugins[0].ID != "acme.user" || m.Plugins[2] != add[1] {
|
|
t.Errorf("plugins = %+v, %v", m.Plugins, err)
|
|
}
|
|
})
|
|
|
|
t.Run("nothing to add", func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), manifestName)
|
|
src := "module: example.test/app\nbinary: app\n"
|
|
write(t, path, src)
|
|
if _, err := appendManifestPlugins(path, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, _ := os.ReadFile(path); string(got) != src {
|
|
t.Errorf("file changed:\n%s", got)
|
|
}
|
|
})
|
|
|
|
t.Run("missing file", func(t *testing.T) {
|
|
if _, err := appendManifestPlugins(filepath.Join(t.TempDir(), manifestName), add); err == nil {
|
|
t.Error("appendManifestPlugins on a missing file succeeded")
|
|
}
|
|
})
|
|
}
|