Files
summercms/internal/build/build_test.go
Jakub Zych 86969739ae feat(01-02): add optional plugin services and HasPlugin
- App-scoped typed Publish/Lookup with duplicate-provider errors
- Set plugin IDs before Register so HasPlugin sees the full set
- Greeter uses pact.OptionalMessage without importing optional
2026-09-16 13:24:30 +02:00

130 lines
3.6 KiB
Go

package build
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)
func TestLoadHelloManifest(t *testing.T) {
m, err := LoadManifest(filepath.Join("..", "..", "examples", "hello", "summer.yaml"))
if err != nil {
t.Fatal(err)
}
if m.Binary != "hello" {
t.Fatalf("binary = %q", m.Binary)
}
if len(m.Plugins) != 3 || m.Plugins[0].ID != "golem15.hello" || m.Plugins[1].ID != "golem15.greeter" || m.Plugins[2].ID != "golem15.optional" {
t.Fatalf("plugins = %+v", m.Plugins)
}
}
func TestParseManifestRejectsDuplicatesAndInvalidPaths(t *testing.T) {
dir := t.TempDir()
t.Run("duplicate id", func(t *testing.T) {
path := filepath.Join(dir, "dup.yaml")
write(t, path, "module: example.com/app\nbinary: app\nplugins:\n - id: a.b\n module: example.com/one\n - id: a.b\n module: example.com/two\n")
if _, err := LoadManifest(path); err == nil || !strings.Contains(err.Error(), "a.b") {
t.Fatalf("want duplicate id error, got %v", err)
}
})
t.Run("invalid module", func(t *testing.T) {
path := filepath.Join(dir, "bad.yaml")
write(t, path, "module: example.com/app\nbinary: app\nplugins:\n - id: a.b\n module: \"example.com/one; rm -rf /\"\n")
if _, err := LoadManifest(path); err == nil {
t.Fatal("want invalid module error")
}
})
t.Run("path traversal module", func(t *testing.T) {
path := filepath.Join(dir, "dotdot.yaml")
write(t, path, "module: example.com/app\nbinary: app\nplugins:\n - id: a.b\n module: example.com/../evil\n")
if _, err := LoadManifest(path); err == nil {
t.Fatal("want invalid module error")
}
})
}
func TestGenerateStableQuotedImportsInManifestOrder(t *testing.T) {
m := Manifest{
Module: "example.com/app",
Binary: "hello",
Plugins: []Plugin{
{ID: "golem15.hello", Module: "example.com/app/plugins/base"},
{ID: "golem15.greeter", Module: "example.com/app/plugins/greeter"},
},
}
first, err := generatePluginsGen(m)
if err != nil {
t.Fatal(err)
}
second, err := generatePluginsGen(m)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(first, second) {
t.Fatal("generated plugins.gen.go is not byte-stable")
}
src := string(first)
baseAt := strings.Index(src, `example.com/app/plugins/base`)
greeterAt := strings.Index(src, `example.com/app/plugins/greeter`)
if baseAt < 0 || greeterAt < 0 || baseAt > greeterAt {
t.Fatalf("imports not in manifest order:\n%s", src)
}
if !strings.Contains(src, "\t_ \"example.com/app/plugins/base\"") {
t.Fatalf("import is not Go-quoted:\n%s", src)
}
mainSrc, err := generateMain(m)
if err != nil {
t.Fatal(err)
}
again, err := generateMain(m)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(mainSrc, again) {
t.Fatal("generated main.go is not byte-stable")
}
if !bytes.Contains(mainSrc, []byte(`bonfire.NewRoot("hello"`)) {
t.Fatalf("main does not use manifest binary:\n%s", mainSrc)
}
if bytes.Contains(mainSrc, []byte("examples/hello")) {
t.Fatal("generated main hard-codes examples/hello")
}
}
func TestWriteIfChangedSkipsIdenticalBytes(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "out.go")
content := []byte("package main\n")
if err := writeIfChanged(path, content); err != nil {
t.Fatal(err)
}
info, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if err := writeIfChanged(path, content); err != nil {
t.Fatal(err)
}
again, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if !info.ModTime().Equal(again.ModTime()) {
t.Fatal("identical content rewrote the file")
}
}
func write(t *testing.T, path, body string) {
t.Helper()
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}