Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Docker Image / docker (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
553 lines
20 KiB
Go
553 lines
20 KiB
Go
// cmd/gateway/plugin_cli_toolchain_test.go 包含用于约束 plugin cli toolchain 行为的测试。
|
|
|
|
package main
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPluginInitCreatesBuildableTemplate(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "sample-plugin")
|
|
handled, code := runPluginCLI([]string{
|
|
"plugin", "init", dir,
|
|
"--id", "sample-plugin",
|
|
"--module", "example.com/sample-plugin",
|
|
})
|
|
if !handled {
|
|
t.Fatal("runPluginCLI() handled = false")
|
|
}
|
|
if code != 0 {
|
|
t.Fatalf("runPluginCLI(init) code = %d, want 0", code)
|
|
}
|
|
for _, name := range []string{"manifest.yaml", "go.mod", "main.go", "main_test.go", "README.md", "testdata/config.json"} {
|
|
if _, err := os.Stat(filepath.Join(dir, filepath.FromSlash(name))); err != nil {
|
|
t.Fatalf("generated file %s stat error = %v", name, err)
|
|
}
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "manifest.json")); err == nil {
|
|
t.Fatal("plugin init generated manifest.json by default, want manifest.yaml")
|
|
}
|
|
if _, err := validatePluginDirectoryForCLI(dir, ""); err != nil {
|
|
t.Fatalf("validatePluginDirectoryForCLI() error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPluginBuildSourcePackagesTemplate(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "source-plugin")
|
|
handled, code := runPluginCLI([]string{
|
|
"plugin", "init", dir,
|
|
"--id", "source-plugin",
|
|
"--module", "example.com/source-plugin",
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(init) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
out := filepath.Join(t.TempDir(), "source-plugin.mcgp")
|
|
handled, code = runPluginCLI([]string{
|
|
"plugin", "build", dir,
|
|
"--type", "source",
|
|
"--out", out,
|
|
"--skip-tests",
|
|
"--vendor=false",
|
|
})
|
|
if !handled {
|
|
t.Fatal("runPluginCLI() handled = false")
|
|
}
|
|
if code != 0 {
|
|
t.Fatalf("runPluginCLI(build source) code = %d, want 0", code)
|
|
}
|
|
if _, err := validatePluginPathForCLI(out, "source"); err != nil {
|
|
t.Fatalf("validatePluginPathForCLI(source) error = %v", err)
|
|
}
|
|
assertZipContains(t, out, "manifest.json", "go.mod", "main.go", "main_test.go", "README.md", "testdata/config.json")
|
|
assertZipNotContains(t, out, "manifest.yaml")
|
|
}
|
|
|
|
func TestPluginBuildBothAcceptsOutDirectory(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "both-plugin")
|
|
handled, code := runPluginCLI([]string{
|
|
"plugin", "init", dir,
|
|
"--id", "both-plugin",
|
|
"--module", "example.com/both-plugin",
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(init) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
outDir := filepath.Join(t.TempDir(), "packages")
|
|
handled, code = runPluginCLI([]string{
|
|
"plugin", "build", dir,
|
|
"--type", "both",
|
|
"--out", outDir,
|
|
"--skip-tests",
|
|
"--vendor=false",
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(build both) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
binaryOut := filepath.Join(outDir, "both-plugin.mcgp")
|
|
sourceOut := filepath.Join(outDir, "both-plugin-source.mcgp")
|
|
if _, err := validatePluginPathForCLI(binaryOut, "binary"); err != nil {
|
|
t.Fatalf("validatePluginPathForCLI(binary) error = %v", err)
|
|
}
|
|
if _, err := validatePluginPathForCLI(sourceOut, "source"); err != nil {
|
|
t.Fatalf("validatePluginPathForCLI(source) error = %v", err)
|
|
}
|
|
assertZipNotContains(t, binaryOut, "manifest.yaml")
|
|
assertZipNotContains(t, sourceOut, "manifest.yaml")
|
|
}
|
|
|
|
func TestPluginTestManifestProfile(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "test-plugin")
|
|
handled, code := runPluginCLI([]string{
|
|
"plugin", "init", dir,
|
|
"--id", "test-plugin",
|
|
"--module", "example.com/test-plugin",
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(init) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "test", dir, "--profile", "manifest"})
|
|
if !handled {
|
|
t.Fatal("runPluginCLI() handled = false")
|
|
}
|
|
if code != 0 {
|
|
t.Fatalf("runPluginCLI(test manifest) code = %d, want 0", code)
|
|
}
|
|
}
|
|
|
|
func TestPluginFeaturesAndManifestCommands(t *testing.T) {
|
|
handled, code := runPluginCLI([]string{"plugin", "features"})
|
|
if !handled {
|
|
t.Fatal("runPluginCLI() handled = false")
|
|
}
|
|
if code != 0 {
|
|
t.Fatalf("runPluginCLI(features) code = %d, want 0", code)
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "manifest", "explain", "upstream.connect/v1"})
|
|
if !handled {
|
|
t.Fatal("runPluginCLI() handled = false")
|
|
}
|
|
if code != 0 {
|
|
t.Fatalf("runPluginCLI(manifest explain) code = %d, want 0", code)
|
|
}
|
|
}
|
|
|
|
func TestPluginManifestFormatWrite(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "format-plugin")
|
|
handled, code := runPluginCLI([]string{
|
|
"plugin", "init", dir,
|
|
"--id", "format-plugin",
|
|
"--module", "example.com/format-plugin",
|
|
"--manifest-format", "json",
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(init) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
manifestPath := filepath.Join(dir, "manifest.json")
|
|
if err := os.WriteFile(manifestPath, []byte(`{"schema_version":"mc-gateway.plugin/v1","id":"format-plugin","name":"Format Plugin","version":"0.1.0","artifact_type":"source","runtime":{"type":"go-plugin","entry":"plugin.so","entry_symbol":"Plugin"},"build":{"type":"go","entry":".","output":"plugin.so"},"api_version":"plugin-api/v1","sdk_module":"github.com/tursom/mc-gateway/plugin/api","sdk_module_version":"v0.1.0","extension_points":[{"type":"hook","key":"upstream.connect/v1"}],"capabilities":{"upstream_connect":{"mode":"dialer"}},"runtime_limits":{"handler_timeout_ms":3000},"config_schema":{"type":"object"}}`), 0644); err != nil {
|
|
t.Fatalf("WriteFile(manifest) error = %v", err)
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "manifest", "format", dir, "--write"})
|
|
if !handled {
|
|
t.Fatal("runPluginCLI() handled = false")
|
|
}
|
|
if code != 0 {
|
|
t.Fatalf("runPluginCLI(manifest format) code = %d, want 0", code)
|
|
}
|
|
data, err := os.ReadFile(manifestPath)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(manifest) error = %v", err)
|
|
}
|
|
if !strings.Contains(string(data), "\n \"schema_version\"") {
|
|
t.Fatalf("manifest was not formatted:\n%s", data)
|
|
}
|
|
}
|
|
|
|
func TestPluginManifestSourceFormats(t *testing.T) {
|
|
for _, format := range []string{"yaml", "toml", "jsonc", "json"} {
|
|
t.Run(format, func(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "format-"+format)
|
|
handled, code := runPluginCLI([]string{
|
|
"plugin", "init", dir,
|
|
"--id", "format-" + format,
|
|
"--module", "example.com/format-" + format,
|
|
"--manifest-format", format,
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(init %s) = (%v, %d), want handled code 0", format, handled, code)
|
|
}
|
|
source, err := readPluginManifestSource(dir, "")
|
|
if err != nil {
|
|
t.Fatalf("readPluginManifestSource(%s) error = %v", format, err)
|
|
}
|
|
if source.Manifest.ID != "format-"+format {
|
|
t.Fatalf("manifest id = %q, want format-%s", source.Manifest.ID, format)
|
|
}
|
|
if !json.Valid(source.CanonicalJSON) {
|
|
t.Fatalf("canonical JSON for %s is invalid:\n%s", format, source.CanonicalJSON)
|
|
}
|
|
packaged, err := materializedManifestJSON(source.Raw, source.Manifest, "binary", false)
|
|
if err != nil {
|
|
t.Fatalf("materializedManifestJSON(%s) error = %v", format, err)
|
|
}
|
|
var raw map[string]any
|
|
if err := json.Unmarshal(packaged, &raw); err != nil {
|
|
t.Fatalf("Unmarshal(materialized %s) error = %v", format, err)
|
|
}
|
|
if raw["artifact_type"] != "binary" || raw["go_version"] == "" || raw["go_os"] == "" || raw["go_arch"] == "" {
|
|
t.Fatalf("materialized %s manifest missing package fields: %s", format, packaged)
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "validate", dir})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(validate %s) = (%v, %d), want handled code 0", format, handled, code)
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "test", dir, "--profile", "manifest"})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(test %s) = (%v, %d), want handled code 0", format, handled, code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPluginManifestFormatWritePreservesComments(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
file string
|
|
content string
|
|
comment string
|
|
}{
|
|
{
|
|
name: "yaml",
|
|
file: "manifest.yaml",
|
|
content: "# keep yaml comment\n" + manifestYAMLTemplate(pluginInitCLIOptions{ID: "comment-yaml", Name: "Comment YAML", Extension: "upstream.connect/v1"}),
|
|
comment: "# keep yaml comment",
|
|
},
|
|
{
|
|
name: "toml",
|
|
file: "manifest.toml",
|
|
content: "# keep toml comment\n" + manifestTOMLTemplate(pluginInitCLIOptions{ID: "comment-toml", Name: "Comment TOML", Extension: "upstream.connect/v1"}),
|
|
comment: "# keep toml comment",
|
|
},
|
|
{
|
|
name: "jsonc",
|
|
file: "manifest.jsonc",
|
|
content: strings.Replace(
|
|
"// keep jsonc comment\n"+strings.TrimSuffix(manifestSourceJSONTemplate(pluginInitCLIOptions{ID: "comment-jsonc", Name: "Comment JSONC", Extension: "upstream.connect/v1"}, true), "\n"),
|
|
"\n }\n}",
|
|
"\n },\n}",
|
|
1,
|
|
),
|
|
comment: "// keep jsonc comment",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
manifestPath := filepath.Join(dir, tc.file)
|
|
if err := os.WriteFile(manifestPath, []byte(tc.content), 0644); err != nil {
|
|
t.Fatalf("WriteFile(%s) error = %v", tc.file, err)
|
|
}
|
|
before, err := readPluginManifestSource(dir, "")
|
|
if err != nil {
|
|
t.Fatalf("readPluginManifestSource(before) error = %v", err)
|
|
}
|
|
handled, code := runPluginCLI([]string{"plugin", "manifest", "format", dir, "--write"})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(manifest format %s) = (%v, %d), want handled code 0", tc.name, handled, code)
|
|
}
|
|
data, err := os.ReadFile(manifestPath)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(%s) error = %v", tc.file, err)
|
|
}
|
|
if !strings.Contains(string(data), tc.comment) {
|
|
t.Fatalf("formatted %s lost comment:\n%s", tc.file, data)
|
|
}
|
|
after, err := readPluginManifestSource(dir, "")
|
|
if err != nil {
|
|
t.Fatalf("readPluginManifestSource(after) error = %v", err)
|
|
}
|
|
if !bytes.Equal(before.CanonicalJSON, after.CanonicalJSON) {
|
|
t.Fatalf("canonical JSON changed after format\nbefore=%s\nafter=%s", before.CanonicalJSON, after.CanonicalJSON)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPluginManifestMultipleSourcesRequireExplicitManifest(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "multi-manifest")
|
|
handled, code := runPluginCLI([]string{
|
|
"plugin", "init", dir,
|
|
"--id", "multi-manifest",
|
|
"--module", "example.com/multi-manifest",
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(init) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(manifestSourceJSONTemplate(pluginInitCLIOptions{ID: "multi-manifest", Name: "Multi Manifest", Extension: "upstream.connect/v1"}, false)), 0644); err != nil {
|
|
t.Fatalf("WriteFile(manifest.json) error = %v", err)
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "validate", dir})
|
|
if !handled {
|
|
t.Fatal("runPluginCLI(validate) handled = false")
|
|
}
|
|
if code == 0 {
|
|
t.Fatal("runPluginCLI(validate) code = 0, want failure for multiple manifests")
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "validate", dir, "--manifest", "manifest.yaml"})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(validate --manifest) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "test", dir, "--profile", "manifest", "--manifest", "manifest.yaml"})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(test --manifest) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
out := filepath.Join(t.TempDir(), "multi-manifest-source.mcgp")
|
|
handled, code = runPluginCLI([]string{"plugin", "build", dir, "--type", "source", "--out", out, "--skip-tests", "--vendor=false", "--manifest", "manifest.yaml"})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(build --manifest) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
if _, err := validatePluginPathForCLI(out, "source"); err != nil {
|
|
t.Fatalf("validatePluginPathForCLI(source) error = %v", err)
|
|
}
|
|
handled, code = runPluginCLI([]string{"plugin", "manifest", "format", dir, "--manifest", "manifest.yaml", "--canonical-json", "--type", "source"})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(manifest format --manifest --canonical-json) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
}
|
|
|
|
func TestPluginGovernanceCommands(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "governance-plugin")
|
|
handled, code := runPluginCLI([]string{
|
|
"plugin", "init", dir,
|
|
"--id", "governance-plugin",
|
|
"--module", "example.com/governance-plugin",
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(init) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
artifact := filepath.Join(t.TempDir(), "governance-plugin.mcgp")
|
|
handled, code = runPluginCLI([]string{
|
|
"plugin", "build", dir,
|
|
"--type", "binary",
|
|
"--out", artifact,
|
|
"--skip-tests",
|
|
})
|
|
if !handled || code != 0 {
|
|
t.Fatalf("runPluginCLI(build binary) = (%v, %d), want handled code 0", handled, code)
|
|
}
|
|
for _, tc := range [][]string{
|
|
{"plugin", "preflight", artifact, "--config-json", `{"upstream":"127.0.0.1:25566"}`, "--profile", "dev"},
|
|
{"plugin", "self-test", artifact, "--profile", "dev"},
|
|
{"plugin", "benchmark", artifact, "--profile", "dev", "--benchmark-profile", "local-fast", "--p95-ms", "1", "--p99-ms", "2", "--error-rate", "0", "--baseline-diff", "0.1"},
|
|
{"plugin", "preflight", dir, "--manifest", "manifest.yaml", "--config-json", `{"upstream":"127.0.0.1:25566"}`, "--profile", "dev"},
|
|
} {
|
|
handled, code = runPluginCLI(tc)
|
|
if !handled {
|
|
t.Fatalf("runPluginCLI(%v) handled = false", tc)
|
|
}
|
|
if code != 0 {
|
|
t.Fatalf("runPluginCLI(%v) code = %d, want 0", tc, code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPluginRemoteCLIRequests(t *testing.T) {
|
|
type observedRequest struct {
|
|
Method string
|
|
RequestURI string
|
|
ContentType string
|
|
Body map[string]any
|
|
FileName string
|
|
}
|
|
requests := make(chan observedRequest, 16)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if got := r.Header.Get("Authorization"); got != "Bearer test-token" {
|
|
t.Errorf("Authorization = %q, want bearer token", got)
|
|
}
|
|
observed := observedRequest{
|
|
Method: r.Method,
|
|
RequestURI: r.URL.RequestURI(),
|
|
ContentType: r.Header.Get("Content-Type"),
|
|
Body: map[string]any{},
|
|
}
|
|
switch {
|
|
case strings.HasPrefix(observed.ContentType, "application/json"):
|
|
if err := json.NewDecoder(r.Body).Decode(&observed.Body); err != nil {
|
|
t.Errorf("Decode JSON body error = %v", err)
|
|
}
|
|
case strings.HasPrefix(observed.ContentType, "multipart/form-data"):
|
|
if err := r.ParseMultipartForm(64 << 20); err != nil {
|
|
t.Errorf("ParseMultipartForm error = %v", err)
|
|
} else {
|
|
file, header, err := r.FormFile("artifact")
|
|
if err != nil {
|
|
t.Errorf("FormFile(artifact) error = %v", err)
|
|
} else {
|
|
observed.FileName = header.Filename
|
|
_, _ = io.Copy(io.Discard, file)
|
|
_ = file.Close()
|
|
}
|
|
}
|
|
}
|
|
requests <- observed
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if strings.HasSuffix(r.URL.Path, "/operations") {
|
|
_, _ = io.WriteString(w, `{"operations":{"logs":[{"message":"ok"}],"traces":[],"events":[{"name":"evt"}],"event_queue":{"queued":1},"handlers":[{"plugin_id":"demo"}],"custom_metrics":[],"background_tasks":[{"id":"sync"}],"plugin_data":[{"key":"k"}],"plugin_files":[{"name":"f"}],"gc":[]}}`)
|
|
return
|
|
}
|
|
_, _ = io.WriteString(w, `{"ok":true}`)
|
|
}))
|
|
defer server.Close()
|
|
t.Setenv("MC_GATEWAY_ADMIN_URL", server.URL)
|
|
t.Setenv("MC_GATEWAY_ADMIN_TOKEN", "test-token")
|
|
|
|
runRemotePluginCLI(t, "plugin", "status", "demo")
|
|
assertRemoteRequest(t, <-requests, http.MethodGet, "/admin/api/plugins/demo")
|
|
|
|
artifactPath := filepath.Join(t.TempDir(), "demo.mcgp")
|
|
if err := os.WriteFile(artifactPath, []byte("artifact"), 0644); err != nil {
|
|
t.Fatalf("WriteFile(artifact) error = %v", err)
|
|
}
|
|
runRemotePluginCLI(t, "plugin", "upload", artifactPath)
|
|
uploadReq := <-requests
|
|
assertRemoteRequest(t, uploadReq, http.MethodPost, "/admin/api/plugin-artifacts")
|
|
if uploadReq.FileName != "demo.mcgp" {
|
|
t.Fatalf("upload file name = %q, want demo.mcgp", uploadReq.FileName)
|
|
}
|
|
|
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
|
if err := os.WriteFile(configPath, []byte(`{"upstream":"127.0.0.1:25565"}`), 0644); err != nil {
|
|
t.Fatalf("WriteFile(config) error = %v", err)
|
|
}
|
|
runRemotePluginCLI(t, "plugin", "enable", "demo", "--artifact", "art-1", "--config", configPath, "--priority", "7")
|
|
enableReq := <-requests
|
|
assertRemoteRequest(t, enableReq, http.MethodPut, "/admin/api/plugins/demo")
|
|
if enableReq.Body["artifact_id"] != "art-1" || enableReq.Body["desired_state"] != "enabled" || enableReq.Body["priority"].(float64) != 7 {
|
|
t.Fatalf("enable body = %#v", enableReq.Body)
|
|
}
|
|
|
|
runRemotePluginCLI(t, "plugin", "logs", "demo")
|
|
assertRemoteRequest(t, <-requests, http.MethodGet, "/admin/api/plugins/demo/operations")
|
|
|
|
runRemotePluginCLI(t, "plugin", "task", "run", "demo", "sync", "--confirm-token", "confirm")
|
|
taskReq := <-requests
|
|
assertRemoteRequest(t, taskReq, http.MethodPost, "/admin/api/plugins/demo/operations/tasks/sync/trigger")
|
|
if taskReq.Body["confirm_token"] != "confirm" {
|
|
t.Fatalf("task body = %#v", taskReq.Body)
|
|
}
|
|
|
|
runRemotePluginCLI(t, "plugin", "repo", "import", "demo", "--repository-type", "file", "--index", "repo.json", "--artifact", "candidate-1", "--version", "1.2.3")
|
|
repoReq := <-requests
|
|
assertRemoteRequest(t, repoReq, http.MethodPost, "/admin/api/plugin-repositories/imports")
|
|
if repoReq.Body["plugin_id"] != "demo" || repoReq.Body["repository_type"] != "file" || repoReq.Body["artifact_id"] != "candidate-1" {
|
|
t.Fatalf("repo body = %#v", repoReq.Body)
|
|
}
|
|
|
|
runRemotePluginCLI(t, "plugin", "review", "status", "demo", "--artifact", "art-1", "--profile", "prod")
|
|
assertRemoteRequest(t, <-requests, http.MethodGet, "/admin/api/plugins/demo/governance?artifact_id=art-1&profile=prod")
|
|
|
|
runRemotePluginCLI(t, "plugin", "sbom", "verify", "demo", "--artifact", "art-1", "--metadata-json", `{"sbom":{"format":"spdx"}}`)
|
|
supplyReq := <-requests
|
|
assertRemoteRequest(t, supplyReq, http.MethodPost, "/admin/api/plugin-supply-chain")
|
|
if supplyReq.Body["plugin_id"] != "demo" || supplyReq.Body["artifact_id"] != "art-1" {
|
|
t.Fatalf("supply-chain body = %#v", supplyReq.Body)
|
|
}
|
|
|
|
runRemotePluginCLI(t, "plugin", "runtime", "mode", "--mode", "go-plugin-process")
|
|
runtimeReq := <-requests
|
|
assertRemoteRequest(t, runtimeReq, http.MethodPut, "/admin/api/plugin-service")
|
|
if runtimeReq.Body["desired_mode"] != "go-plugin-process" {
|
|
t.Fatalf("runtime body = %#v", runtimeReq.Body)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeAdminAPIBase(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
raw string
|
|
want string
|
|
}{
|
|
{raw: "http://127.0.0.1:8080", want: "http://127.0.0.1:8080/admin/api"},
|
|
{raw: "http://127.0.0.1:8080/admin", want: "http://127.0.0.1:8080/admin/api"},
|
|
{raw: "http://127.0.0.1:8080/admin/api/", want: "http://127.0.0.1:8080/admin/api"},
|
|
} {
|
|
got, err := normalizeAdminAPIBase(tc.raw)
|
|
if err != nil {
|
|
t.Fatalf("normalizeAdminAPIBase(%q) error = %v", tc.raw, err)
|
|
}
|
|
if got != tc.want {
|
|
t.Fatalf("normalizeAdminAPIBase(%q) = %q, want %q", tc.raw, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func runRemotePluginCLI(t *testing.T, args ...string) {
|
|
t.Helper()
|
|
handled, code := runPluginCLI(args)
|
|
if !handled {
|
|
t.Fatalf("runPluginCLI(%v) handled = false", args)
|
|
}
|
|
if code != 0 {
|
|
t.Fatalf("runPluginCLI(%v) code = %d, want 0", args, code)
|
|
}
|
|
}
|
|
|
|
func assertRemoteRequest(t *testing.T, got struct {
|
|
Method string
|
|
RequestURI string
|
|
ContentType string
|
|
Body map[string]any
|
|
FileName string
|
|
}, wantMethod, wantURI string) {
|
|
t.Helper()
|
|
if got.Method != wantMethod || got.RequestURI != wantURI {
|
|
t.Fatalf("request = %s %s, want %s %s", got.Method, got.RequestURI, wantMethod, wantURI)
|
|
}
|
|
}
|
|
|
|
func assertZipContains(t *testing.T, zipPath string, names ...string) {
|
|
t.Helper()
|
|
reader, err := zip.OpenReader(zipPath)
|
|
if err != nil {
|
|
t.Fatalf("OpenReader(%s) error = %v", zipPath, err)
|
|
}
|
|
defer reader.Close()
|
|
seen := make(map[string]bool, len(reader.File))
|
|
for _, file := range reader.File {
|
|
seen[file.Name] = true
|
|
if strings.Contains(file.Name, `\`) {
|
|
t.Fatalf("zip entry %q uses backslash", file.Name)
|
|
}
|
|
}
|
|
for _, name := range names {
|
|
if !seen[name] {
|
|
t.Fatalf("zip %s missing entry %s; entries=%v", zipPath, name, seen)
|
|
}
|
|
}
|
|
}
|
|
|
|
func assertZipNotContains(t *testing.T, zipPath string, names ...string) {
|
|
t.Helper()
|
|
reader, err := zip.OpenReader(zipPath)
|
|
if err != nil {
|
|
t.Fatalf("OpenReader(%s) error = %v", zipPath, err)
|
|
}
|
|
defer reader.Close()
|
|
seen := make(map[string]bool, len(reader.File))
|
|
for _, file := range reader.File {
|
|
seen[file.Name] = true
|
|
}
|
|
for _, name := range names {
|
|
if seen[name] {
|
|
t.Fatalf("zip %s unexpectedly contains entry %s", zipPath, name)
|
|
}
|
|
}
|
|
}
|