Add manifest creation command
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/testutil"
|
||||
producerbundle "gitea.maximumdirect.net/eric/distributor/pkg/bundle"
|
||||
)
|
||||
|
||||
func decodeEnvelope(t *testing.T, stdout *bytes.Buffer) map[string]any {
|
||||
@@ -267,6 +268,206 @@ func TestExecuteInspectArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteManifestCreateExplicitFiles(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeCLIFile(t, root, "b.txt", "bravo")
|
||||
writeCLIFile(t, root, "nested/a.txt", "alpha")
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := Execute(context.Background(), []string{
|
||||
"manifest", "create", root,
|
||||
"--id", "reports.explicit",
|
||||
"--created", "2026-06-01T11:00:00Z",
|
||||
"--file", "b.txt",
|
||||
"--file", "nested/a.txt",
|
||||
}, &stdout, &stderr)
|
||||
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
for _, want := range []string{
|
||||
"created manifest.json",
|
||||
"bundle: reports.explicit",
|
||||
"files: 2",
|
||||
"digest: sha256:",
|
||||
} {
|
||||
if !strings.Contains(stdout.String(), want) {
|
||||
t.Fatalf("stdout = %q, want substring %q", stdout.String(), want)
|
||||
}
|
||||
}
|
||||
manifest, err := producerbundle.LoadManifest(root)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadManifest() error = %v", err)
|
||||
}
|
||||
if got, want := manifestPaths(manifest), []string{"b.txt", "nested/a.txt"}; !equalStrings(got, want) {
|
||||
t.Fatalf("manifest paths = %v, want %v", got, want)
|
||||
}
|
||||
if err := producerbundle.ValidateBundle(root, manifest); err != nil {
|
||||
t.Fatalf("ValidateBundle() error = %v", err)
|
||||
}
|
||||
var validateStdout, validateStderr bytes.Buffer
|
||||
validateCode := Execute(context.Background(), []string{"validate", root}, &validateStdout, &validateStderr)
|
||||
if validateCode != exitOK {
|
||||
t.Fatalf("validate exit code = %d, want %d; stderr = %q", validateCode, exitOK, validateStderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteManifestCreateScansBundle(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeCLIFile(t, root, "z.txt", "zulu")
|
||||
writeCLIFile(t, root, ".env", "dotfile")
|
||||
writeCLIFile(t, root, "nested/report.md", "# Report\n")
|
||||
writeCLIFile(t, root, storage.StateFileName, "destination state")
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := Execute(context.Background(), []string{"manifest", "create", root, "--id", "reports.scan"}, &stdout, &stderr)
|
||||
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
manifest, err := producerbundle.LoadManifest(root)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadManifest() error = %v", err)
|
||||
}
|
||||
if got, want := manifestPaths(manifest), []string{".env", "nested/report.md", "z.txt"}; !equalStrings(got, want) {
|
||||
t.Fatalf("manifest paths = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteManifestCreateJSON(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeCLIFile(t, root, "report.md", "# Report\n")
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := Execute(context.Background(), []string{"manifest", "create", root, "--id", "reports.json", "--file", "report.md", "--format", "json"}, &stdout, &stderr)
|
||||
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
envelope := decodeEnvelope(t, &stdout)
|
||||
if envelope["command"] != "manifest create" || envelope["ok"] != true {
|
||||
t.Fatalf("envelope = %#v, want manifest create ok", envelope)
|
||||
}
|
||||
result := envelopeResult(t, envelope)
|
||||
if result["id"] != "reports.json" || result["file_count"] != float64(1) {
|
||||
t.Fatalf("result = %#v, want manifest summary", result)
|
||||
}
|
||||
if stderr.Len() != 0 {
|
||||
t.Fatalf("stderr = %q, want empty", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteManifestCreateOverwrite(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeCLIFile(t, root, "report.md", "old\n")
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Execute(context.Background(), []string{"manifest", "create", root, "--id", "reports.old", "--file", "report.md"}, &stdout, &stderr)
|
||||
if code != exitOK {
|
||||
t.Fatalf("initial exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
writeCLIFile(t, root, "report.md", "new\n")
|
||||
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
code = Execute(context.Background(), []string{"manifest", "create", root, "--id", "reports.new", "--file", "report.md"}, &stdout, &stderr)
|
||||
if code != exitError {
|
||||
t.Fatalf("overwrite exit code = %d, want %d", code, exitError)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "write manifest") {
|
||||
t.Fatalf("stderr = %q, want write manifest error", stderr.String())
|
||||
}
|
||||
manifest, err := producerbundle.LoadManifest(root)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadManifest() error = %v", err)
|
||||
}
|
||||
if manifest.ID != "reports.old" {
|
||||
t.Fatalf("manifest id = %q, want reports.old", manifest.ID)
|
||||
}
|
||||
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
code = Execute(context.Background(), []string{"manifest", "create", root, "--id", "reports.new", "--file", "report.md", "--overwrite"}, &stdout, &stderr)
|
||||
if code != exitOK {
|
||||
t.Fatalf("overwrite exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
manifest, err = producerbundle.LoadManifest(root)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadManifest() error = %v", err)
|
||||
}
|
||||
if manifest.ID != "reports.new" {
|
||||
t.Fatalf("manifest id = %q, want reports.new", manifest.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteManifestCreateRejectsSymlink(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeCLIFile(t, root, "target.md", "# Report\n")
|
||||
if err := os.Symlink("target.md", filepath.Join(root, "link.md")); err != nil {
|
||||
t.Skipf("symlink unavailable: %v", err)
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := Execute(context.Background(), []string{"manifest", "create", root, "--id", "reports.link", "--file", "link.md"}, &stdout, &stderr)
|
||||
|
||||
if code != exitError {
|
||||
t.Fatalf("exit code = %d, want %d", code, exitError)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "regular file") {
|
||||
t.Fatalf("stderr = %q, want regular file error", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteManifestCreateArgs(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeCLIFile(t, root, "report.md", "# Report\n")
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantCode int
|
||||
wantStderr string
|
||||
}{
|
||||
{
|
||||
name: "missing path",
|
||||
args: []string{"manifest", "create", "--id", "reports.missing"},
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "requires exactly one bundle path",
|
||||
},
|
||||
{
|
||||
name: "missing id",
|
||||
args: []string{"manifest", "create", root},
|
||||
wantCode: exitError,
|
||||
wantStderr: "requires --id",
|
||||
},
|
||||
{
|
||||
name: "bad created",
|
||||
args: []string{"manifest", "create", root, "--id", "reports.bad", "--created", "June 1"},
|
||||
wantCode: exitError,
|
||||
wantStderr: "created must be RFC3339",
|
||||
},
|
||||
{
|
||||
name: "bad format",
|
||||
args: []string{"manifest", "create", root, "--id", "reports.bad", "--format", "xml"},
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "format must be text or json",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := Execute(context.Background(), tt.args, &stdout, &stderr)
|
||||
if code != tt.wantCode {
|
||||
t.Fatalf("exit code = %d, want %d; stderr = %q", code, tt.wantCode, stderr.String())
|
||||
}
|
||||
if stdout.Len() != 0 {
|
||||
t.Fatalf("stdout = %q, want empty", stdout.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), tt.wantStderr) {
|
||||
t.Fatalf("stderr = %q, want substring %q", stderr.String(), tt.wantStderr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteRunDryRun(t *testing.T) {
|
||||
sourceRoot := t.TempDir()
|
||||
testutil.WriteSourceBundle(t, sourceRoot, "", testutil.BundleOptions{})
|
||||
@@ -512,3 +713,34 @@ func TestUnknownCommandIsUsageError(t *testing.T) {
|
||||
t.Fatalf("stderr = %q, want unknown command error", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func manifestPaths(manifest producerbundle.Manifest) []string {
|
||||
paths := make([]string, 0, len(manifest.Files))
|
||||
for _, file := range manifest.Files {
|
||||
paths = append(paths, file.Path)
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func equalStrings(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for index := range a {
|
||||
if a[index] != b[index] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func writeCLIFile(t *testing.T, root, relative, body string) {
|
||||
t.Helper()
|
||||
path := filepath.Join(root, filepath.FromSlash(relative))
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatalf("mkdir %s: %v", filepath.Dir(path), err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
||||
t.Fatalf("write %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user