test(19.3-05): build tags, S3 tag guard, DiskExtension and public_url

- manifest_tags_test.go: build.tags load, trim, dedupe and validation
  errors naming build.tags; SaveManifest keeps tags and plugin order;
  goBuildArgs puts one -tags before -o
- s3_test.go: s3:// refused without summercms_s3 naming the tag, opens
  with it (both build modes, any scheme case)
- diskext_test.go: RESEARCH probe rows and table consistency
- url_test.go: public_url base joining, File.URL and thumbnails,
  malformed values refused naming the key
- store_test.go: Postgres regression table through Store
- gate: attach names in both tag modes, Store regression in docker
This commit is contained in:
Jakub Zych
2026-10-15 19:37:35 +02:00
parent 055f51c94f
commit 38f6d65c09
6 changed files with 573 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
package build
import (
"os"
"path/filepath"
"slices"
"strings"
"testing"
)
// Unit tests (plan 05 of phase 19.3) for summer.yaml build.tags (D-12).
const tagsManifestHead = "module: git.golem15.com/acme/acme\n" +
"binary: acme\n" +
"plugins:\n" +
" - id: acme.blog\n" +
" module: git.golem15.com/acme/acme/plugins/blog\n"
func writeManifest(t *testing.T, body string) string {
t.Helper()
path := filepath.Join(t.TempDir(), manifestName)
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
return path
}
func TestManifestBuildTags(t *testing.T) {
for _, tc := range []struct {
name string
body string
want []string
}{
{"no build block", tagsManifestHead, nil},
{"empty build block", tagsManifestHead + "build: {}\n", nil},
{"null tags", tagsManifestHead + "build:\n tags:\n", nil},
{"empty list", tagsManifestHead + "build:\n tags: []\n", nil},
{"one tag", tagsManifestHead + "build:\n tags: [summercms_s3]\n", []string{"summercms_s3"}},
{"trimmed and deduplicated in order", tagsManifestHead + "build:\n tags:\n - \" netgo \"\n - summercms_s3\n - netgo\n - \"summercms_s3\\t\"\n - osusergo\n", []string{"netgo", "summercms_s3", "osusergo"}},
{"digits and underscores", tagsManifestHead + "build:\n tags: [go1_x, \"123\"]\n", []string{"go1_x", "123"}},
} {
t.Run(tc.name, func(t *testing.T) {
m, err := LoadManifest(writeManifest(t, tc.body))
if err != nil {
t.Fatal(err)
}
if !slices.Equal(m.Tags, tc.want) {
t.Fatalf("Tags = %q, want %q", m.Tags, tc.want)
}
if len(m.Plugins) != 1 || m.Plugins[0].ID != "acme.blog" {
t.Fatalf("plugins = %+v", m.Plugins)
}
})
}
for _, tag := range []string{"summercms_s3", "a", "0", "x_1_y"} {
if err := ValidateBuildTag(tag); err != nil {
t.Errorf("ValidateBuildTag(%q) = %v", tag, err)
}
}
}
func TestManifestBuildTagsInvalid(t *testing.T) {
for _, tc := range []struct {
name string
yaml string
want string
}{
{"dash", "build:\n tags: [summercms-s3]\n", "build.tags[0]"},
{"space", "build:\n tags: [netgo, \"summercms s3\"]\n", "build.tags[1]"},
{"upper case", "build:\n tags: [SummerCMS_S3]\n", "build.tags[0]"},
{"flag injection", "build:\n tags: [\"x -ldflags=-X\"]\n", "build.tags[0]"},
{"comma", "build:\n tags: [\"a,b\"]\n", "build.tags[0]"},
{"empty string", "build:\n tags: [\" \"]\n", "build.tags[0]"},
{"non-string entry", "build:\n tags: [42]\n", "build.tags[0]"},
{"non-list tags", "build:\n tags: summercms_s3\n", "build.tags"},
{"mapping tags", "build:\n tags:\n s3: true\n", "build.tags"},
{"non-mapping build", "build: [summercms_s3]\n", "build.tags"},
{"scalar build", "build: summercms_s3\n", "build.tags"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := LoadManifest(writeManifest(t, tagsManifestHead+tc.yaml))
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("err = %v, want one naming %s", err, tc.want)
}
})
}
for _, bad := range []string{"", "A", "a-b", "a b", "a.b", "-tags", "a,b", "ä"} {
if err := ValidateBuildTag(bad); err == nil {
t.Errorf("ValidateBuildTag(%q) accepted", bad)
}
}
}
func TestSaveManifestKeepsBuildTags(t *testing.T) {
path := writeManifest(t, tagsManifestHead+
" - id: acme.shop\n"+
" module: git.golem15.com/acme/acme/plugins/shop\n"+
"build:\n tags:\n - summercms_s3\n - netgo\n")
m, err := LoadManifest(path)
if err != nil {
t.Fatal(err)
}
// What plugin:add does: append a plugin and save.
m.Plugins = append(m.Plugins, Plugin{ID: "acme.faq", Module: "git.golem15.com/acme/acme/plugins/faq"})
if err := SaveManifest(path, m); err != nil {
t.Fatal(err)
}
again, err := LoadManifest(path)
if err != nil {
t.Fatal(err)
}
var ids []string
for _, p := range again.Plugins {
ids = append(ids, p.ID)
}
if !slices.Equal(ids, []string{"acme.blog", "acme.shop", "acme.faq"}) {
t.Fatalf("plugin order = %q", ids)
}
if !slices.Equal(again.Tags, []string{"summercms_s3", "netgo"}) {
t.Fatalf("Tags = %q after the round trip", again.Tags)
}
raw, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if !strings.HasSuffix(string(raw), "build:\n tags:\n - summercms_s3\n - netgo\n") {
t.Fatalf("build block is not written after the plugins:\n%s", raw)
}
// No tags: no build block at all.
m.Tags = nil
if err := SaveManifest(path, m); err != nil {
t.Fatal(err)
}
raw, _ = os.ReadFile(path)
if strings.Contains(string(raw), "build:") {
t.Fatalf("empty tags wrote a build block:\n%s", raw)
}
m.Tags = []string{}
if err := SaveManifest(path, m); err != nil {
t.Fatal(err)
}
raw, _ = os.ReadFile(path)
if strings.Contains(string(raw), "build:") {
t.Fatalf("an empty tag list wrote a build block:\n%s", raw)
}
// SaveManifest refuses an invalid tag and leaves the file alone.
before, _ := os.ReadFile(path)
m.Tags = []string{"ok", "not-ok"}
if err := SaveManifest(path, m); err == nil || !strings.Contains(err.Error(), "build.tags[1]") {
t.Fatalf("SaveManifest with an invalid tag: %v", err)
}
after, _ := os.ReadFile(path)
if string(after) != string(before) {
t.Fatal("SaveManifest wrote a file with an invalid tag")
}
}
func TestGoBuildArgs(t *testing.T) {
if got := goBuildArgs("bin/acme", nil); !slices.Equal(got, []string{"build", "-o", "bin/acme", "."}) {
t.Fatalf("no tags = %q", got)
}
if got := goBuildArgs("bin/acme", []string{}); slices.ContainsFunc(got, func(a string) bool { return strings.HasPrefix(a, "-tags") }) {
t.Fatalf("empty tags = %q", got)
}
got := goBuildArgs("bin/acme", []string{"a", "b"})
if !slices.Equal(got, []string{"build", "-tags=a,b", "-o", "bin/acme", "."}) {
t.Fatalf("tags = %q", got)
}
n := 0
for _, a := range got {
if strings.HasPrefix(a, "-tags") {
n++
}
}
if n != 1 {
t.Fatalf("%d -tags arguments", n)
}
// The tags of a loaded manifest reach the arguments unchanged.
m, err := LoadManifest(writeManifest(t, tagsManifestHead+"build:\n tags: [summercms_s3, netgo]\n"))
if err != nil {
t.Fatal(err)
}
if got := goBuildArgs("bin/"+m.Binary, m.Tags); !slices.Equal(got, []string{"build", "-tags=summercms_s3,netgo", "-o", "bin/acme", "."}) {
t.Fatalf("manifest tags = %q", got)
}
}