701 lines
24 KiB
Go
701 lines
24 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/interfacesnapshot"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func TestCrossPlatformCoverageRunGenerateCapturesActualRootOffline(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
if exitCode := run([]string{"generate"}, &stdout, &stderr); exitCode != 0 {
|
|
t.Fatalf("run(generate) exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
snapshot, err := interfacesnapshot.Read(bytes.NewReader(stdout.Bytes()))
|
|
if err != nil {
|
|
t.Fatalf("decode generated snapshot: %v", err)
|
|
}
|
|
|
|
commands := make(map[string]interfacesnapshot.Command, len(snapshot.Commands))
|
|
for _, command := range snapshot.Commands {
|
|
commands[command.Path] = command
|
|
}
|
|
for _, path := range []string{"dws", "dws chat", "dws dev app create"} {
|
|
if _, ok := commands[path]; !ok {
|
|
t.Errorf("actual root snapshot is missing %q", path)
|
|
}
|
|
}
|
|
for _, path := range []string{"dws completion", "dws help"} {
|
|
if _, ok := commands[path]; ok {
|
|
t.Errorf("framework-noise path %q leaked into snapshot", path)
|
|
}
|
|
}
|
|
|
|
create := commands["dws dev app create"]
|
|
if !hasFlag(create.LocalFlags, "name", "string") {
|
|
t.Errorf("dev app create local flags do not contain --name string: %#v", create.LocalFlags)
|
|
}
|
|
if !hasFlag(create.InheritedFlags, "profile", "string") {
|
|
t.Errorf("dev app create inherited flags do not contain --profile string: %#v", create.InheritedFlags)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunGenerateRejectsHelpRenderingFailure(t *testing.T) {
|
|
testseam.Swap(t, &newRootCommand, func() *cobra.Command {
|
|
root := &cobra.Command{Use: "dws"}
|
|
root.SetHelpFunc(func(command *cobra.Command, _ []string) {
|
|
_, _ = io.WriteString(command.ErrOrStderr(), "injected help failure")
|
|
})
|
|
return root
|
|
})
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
if exitCode := run([]string{"generate"}, &stdout, &stderr); exitCode != 2 {
|
|
t.Fatalf("run(generate) exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
if !strings.Contains(stderr.String(), "injected help failure") {
|
|
t.Fatalf("run(generate) stderr=%q", stderr.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunCompareUsesBothSnapshotInputsAndExitCode(t *testing.T) {
|
|
current := commandSnapshot("dws")
|
|
mergeBase := commandSnapshot("dws")
|
|
stable := commandSnapshot("dws", "dws legacy")
|
|
|
|
dir := t.TempDir()
|
|
currentPath := writeSnapshot(t, dir, "current.json", current)
|
|
mergeBasePath := writeSnapshot(t, dir, "base.json", mergeBase)
|
|
stablePath := writeSnapshot(t, dir, "stable.json", stable)
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
exitCode := run([]string{
|
|
"compare",
|
|
"--current", currentPath,
|
|
"--base", mergeBasePath,
|
|
"--stable", stablePath,
|
|
}, &stdout, &stderr)
|
|
if exitCode != 1 {
|
|
t.Fatalf("run(compare) exit=%d, want 1; stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
|
|
}
|
|
if !bytes.Contains(stdout.Bytes(), []byte(`"reference": "main"`)) ||
|
|
!bytes.Contains(stdout.Bytes(), []byte(`"reference": "stable"`)) ||
|
|
!bytes.Contains(stdout.Bytes(), []byte(`"kind": "command_removed"`)) {
|
|
t.Fatalf("comparison report does not contain both references and the blocking change:\n%s", stdout.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunCompareEnforcesBaseOwnedFlagMigrationLifecycle(t *testing.T) {
|
|
dir := t.TempDir()
|
|
before := flagMigrationSnapshot(false)
|
|
after := flagMigrationSnapshot(true)
|
|
currentPath := writeSnapshot(t, dir, "current.json", after)
|
|
basePath := writeSnapshot(t, dir, "base.json", before)
|
|
stablePath := writeSnapshot(t, dir, "stable.json", before)
|
|
approvedPath := writeManifest(t, dir, "approved.json", flagMigrationManifestJSON("pending"))
|
|
candidatePath := writeManifest(t, dir, "candidate.json", flagMigrationManifestJSON("consumed"))
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
exitCode := run([]string{
|
|
"compare",
|
|
"--current", currentPath,
|
|
"--base", basePath,
|
|
"--stable", stablePath,
|
|
"--approved-flag-migrations", approvedPath,
|
|
"--candidate-flag-migrations", candidatePath,
|
|
}, &stdout, &stderr)
|
|
if exitCode != 0 {
|
|
t.Fatalf("exact base-owned migration exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
if !bytes.Contains(stdout.Bytes(), []byte(`"compatible": true`)) {
|
|
t.Fatalf("exact migration report is not compatible:\n%s", stdout.String())
|
|
}
|
|
|
|
stdout.Reset()
|
|
stderr.Reset()
|
|
emptyApproved := writeManifest(t, dir, "empty-approved.json", `{"version":1,"migrations":[]}`)
|
|
exitCode = run([]string{
|
|
"compare",
|
|
"--current", currentPath,
|
|
"--base", basePath,
|
|
"--stable", stablePath,
|
|
"--approved-flag-migrations", emptyApproved,
|
|
"--candidate-flag-migrations", candidatePath,
|
|
}, &stdout, &stderr)
|
|
if exitCode != 2 || !strings.Contains(stderr.String(), "must start pending") {
|
|
t.Fatalf("candidate self-approval exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunCompareRequiresBothFlagMigrationInputs(t *testing.T) {
|
|
dir := t.TempDir()
|
|
currentPath := writeSnapshot(t, dir, "current.json", commandSnapshot("dws"))
|
|
basePath := writeSnapshot(t, dir, "base.json", commandSnapshot("dws"))
|
|
approvedPath := writeManifest(t, dir, "approved.json", `{"version":1,"migrations":[]}`)
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
exitCode := run([]string{
|
|
"compare",
|
|
"--current", currentPath,
|
|
"--base", basePath,
|
|
"--approved-flag-migrations", approvedPath,
|
|
}, &stdout, &stderr)
|
|
if exitCode != 2 || !strings.Contains(stderr.String(), "must be provided together") {
|
|
t.Fatalf("one-sided migration input exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunCompareCommandMigrationInputs(t *testing.T) {
|
|
dir := t.TempDir()
|
|
snapshotPath := writeSnapshot(t, dir, "snapshot.json", commandSnapshot("dws"))
|
|
emptyFlag := writeManifest(t, dir, "empty-flags.json", `{"version":1,"migrations":[]}`)
|
|
emptyCommand := writeManifest(t, dir, "empty-commands.json", `{"version":1,"migrations":[]}`)
|
|
invalid := writeManifest(t, dir, "invalid-commands.json", `{`)
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
args := []string{
|
|
"compare",
|
|
"--current", snapshotPath,
|
|
"--base", snapshotPath,
|
|
"--stable", snapshotPath,
|
|
"--approved-flag-migrations", emptyFlag,
|
|
"--candidate-flag-migrations", emptyFlag,
|
|
"--approved-command-migrations", emptyCommand,
|
|
"--candidate-command-migrations", emptyCommand,
|
|
}
|
|
if exitCode := run(args, &stdout, &stderr); exitCode != 0 {
|
|
t.Fatalf("combined migration compare exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
for _, test := range []struct {
|
|
name string
|
|
approved string
|
|
candidate string
|
|
want string
|
|
}{
|
|
{"approved flag", invalid, emptyFlag, "read approved flag migrations"},
|
|
{"candidate flag", emptyFlag, invalid, "read candidate flag migrations"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
stdout.Reset()
|
|
stderr.Reset()
|
|
testArgs := []string{
|
|
"compare", "--current", snapshotPath, "--base", snapshotPath, "--stable", snapshotPath,
|
|
"--approved-flag-migrations", test.approved,
|
|
"--candidate-flag-migrations", test.candidate,
|
|
"--approved-command-migrations", emptyCommand,
|
|
"--candidate-command-migrations", emptyCommand,
|
|
}
|
|
if exitCode := run(testArgs, &stdout, &stderr); exitCode != 2 || !strings.Contains(stderr.String(), test.want) {
|
|
t.Fatalf("combined flag error exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
approved string
|
|
candidate string
|
|
want string
|
|
}{
|
|
{"approved", invalid, emptyCommand, "read approved command migrations"},
|
|
{"candidate", emptyCommand, invalid, "read candidate command migrations"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
stdout.Reset()
|
|
stderr.Reset()
|
|
testArgs := []string{
|
|
"compare", "--current", snapshotPath, "--base", snapshotPath, "--stable", snapshotPath,
|
|
"--approved-command-migrations", test.approved,
|
|
"--candidate-command-migrations", test.candidate,
|
|
}
|
|
if exitCode := run(testArgs, &stdout, &stderr); exitCode != 2 || !strings.Contains(stderr.String(), test.want) {
|
|
t.Fatalf("command manifest error exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
stderr.Reset()
|
|
if exitCode := run([]string{
|
|
"compare", "--current", snapshotPath, "--base", snapshotPath,
|
|
"--approved-command-migrations", emptyCommand,
|
|
}, &stdout, &stderr); exitCode != 2 || !strings.Contains(stderr.String(), "provided together") {
|
|
t.Fatalf("one-sided command manifest exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
|
|
if _, err := readCommandMigrationManifest(filepath.Join(dir, "missing.json")); err == nil {
|
|
t.Fatal("missing command migration manifest unexpectedly read")
|
|
}
|
|
if _, err := readCommandMigrationManifest(invalid); err == nil {
|
|
t.Fatal("invalid command migration manifest unexpectedly read")
|
|
}
|
|
|
|
pending := writeManifest(t, dir, "pending-command.json", commandMigrationManifestJSON("pending"))
|
|
consumed := writeManifest(t, dir, "consumed-command.json", commandMigrationManifestJSON("consumed"))
|
|
stderr.Reset()
|
|
if exitCode := run([]string{
|
|
"compare", "--current", snapshotPath, "--base", snapshotPath, "--stable", snapshotPath,
|
|
"--approved-command-migrations", pending,
|
|
"--candidate-command-migrations", consumed,
|
|
}, &stdout, &stderr); exitCode != 2 || !strings.Contains(stderr.String(), "validate interface migration lifecycle") {
|
|
t.Fatalf("command lifecycle error exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunCompareRequiresBothReferencesForFlagMigrations(t *testing.T) {
|
|
dir := t.TempDir()
|
|
currentPath := writeSnapshot(t, dir, "current.json", commandSnapshot("dws"))
|
|
basePath := writeSnapshot(t, dir, "base.json", commandSnapshot("dws"))
|
|
stablePath := writeSnapshot(t, dir, "stable.json", commandSnapshot("dws"))
|
|
approvedPath := writeManifest(t, dir, "approved.json", `{"version":1,"migrations":[]}`)
|
|
candidatePath := writeManifest(t, dir, "candidate.json", `{"version":1,"migrations":[]}`)
|
|
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
}{
|
|
{
|
|
name: "missing stable",
|
|
args: []string{"--base", basePath},
|
|
},
|
|
{
|
|
name: "missing base",
|
|
args: []string{"--stable", stablePath},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
args := []string{"compare", "--current", currentPath}
|
|
args = append(args, test.args...)
|
|
args = append(args,
|
|
"--approved-flag-migrations", approvedPath,
|
|
"--candidate-flag-migrations", candidatePath,
|
|
)
|
|
var stdout, stderr bytes.Buffer
|
|
exitCode := run(args, &stdout, &stderr)
|
|
if exitCode != 2 || !strings.Contains(stderr.String(), "requires both --base and --stable") {
|
|
t.Fatalf("one-reference migration compare exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunPrintsUsageForMissingAndUnknownCommands(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
wantStderr []string
|
|
}{
|
|
{
|
|
name: "missing command",
|
|
args: nil,
|
|
wantStderr: []string{"usage:", "interface-snapshot generate", "--approved-flag-migrations"},
|
|
},
|
|
{
|
|
name: "unknown command",
|
|
args: []string{"unknown"},
|
|
wantStderr: []string{`unknown command "unknown"`, "usage:", "interface-snapshot compare"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
if exitCode := run(test.args, &stdout, &stderr); exitCode != 2 {
|
|
t.Fatalf("run(%v) exit=%d, want 2", test.args, exitCode)
|
|
}
|
|
if stdout.Len() != 0 {
|
|
t.Fatalf("run(%v) unexpectedly wrote stdout: %s", test.args, stdout.String())
|
|
}
|
|
for _, want := range test.wantStderr {
|
|
if !strings.Contains(stderr.String(), want) {
|
|
t.Errorf("run(%v) stderr missing %q:\n%s", test.args, want, stderr.String())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunRejectsInvalidSubcommandArguments(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
want string
|
|
}{
|
|
{name: "generate unknown flag", args: []string{"generate", "--unknown"}, want: "flag provided but not defined"},
|
|
{name: "generate positional", args: []string{"generate", "unexpected"}, want: "generate accepts no positional arguments"},
|
|
{name: "compare unknown flag", args: []string{"compare", "--unknown"}, want: "flag provided but not defined"},
|
|
{name: "compare positional", args: []string{"compare", "unexpected"}, want: "compare accepts no positional arguments"},
|
|
{name: "compare missing current", args: []string{"compare", "--base", "base.json"}, want: "compare requires --current"},
|
|
{name: "compare missing reference", args: []string{"compare", "--current", "current.json"}, want: "compare requires --base, --stable, or both"},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
if exitCode := run(test.args, &stdout, &stderr); exitCode != 2 {
|
|
t.Fatalf("run(%v) exit=%d, want 2", test.args, exitCode)
|
|
}
|
|
if !strings.Contains(stderr.String(), test.want) {
|
|
t.Fatalf("run(%v) stderr missing %q:\n%s", test.args, test.want, stderr.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunGenerateRejectsUnsafeOutputPath(t *testing.T) {
|
|
outputDirectory := t.TempDir()
|
|
var stdout, stderr bytes.Buffer
|
|
exitCode := run([]string{"generate", "--output", outputDirectory}, &stdout, &stderr)
|
|
if exitCode != 2 || !strings.Contains(stderr.String(), "create snapshot") {
|
|
t.Fatalf("directory output exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunGenerateReportsTemporaryDirectoryFailure(t *testing.T) {
|
|
missingTempRoot := filepath.Join(t.TempDir(), "missing")
|
|
for _, name := range []string{"TMPDIR", "TMP", "TEMP"} {
|
|
t.Setenv(name, missingTempRoot)
|
|
}
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
exitCode := run([]string{"generate"}, &stdout, &stderr)
|
|
if exitCode != 2 || !strings.Contains(stderr.String(), "create isolated home") {
|
|
t.Fatalf("invalid temporary root exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunCompareReportsSnapshotReadFailures(t *testing.T) {
|
|
dir := t.TempDir()
|
|
validPath := writeSnapshot(t, dir, "valid.json", commandSnapshot("dws"))
|
|
missingPath := filepath.Join(dir, "missing.json")
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
want string
|
|
}{
|
|
{
|
|
name: "current",
|
|
args: []string{"compare", "--current", missingPath, "--base", validPath},
|
|
want: "read current snapshot",
|
|
},
|
|
{
|
|
name: "main",
|
|
args: []string{"compare", "--current", validPath, "--base", missingPath},
|
|
want: "read main/development baseline snapshot",
|
|
},
|
|
{
|
|
name: "stable",
|
|
args: []string{"compare", "--current", validPath, "--stable", missingPath},
|
|
want: "read stable snapshot",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
if exitCode := run(test.args, &stdout, &stderr); exitCode != 2 {
|
|
t.Fatalf("run(compare) exit=%d, want 2", exitCode)
|
|
}
|
|
if !strings.Contains(stderr.String(), test.want) {
|
|
t.Fatalf("stderr missing %q:\n%s", test.want, stderr.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunCompareReportsEachManifestReadFailure(t *testing.T) {
|
|
dir := t.TempDir()
|
|
snapshotPath := writeSnapshot(t, dir, "snapshot.json", commandSnapshot("dws"))
|
|
validManifest := writeManifest(t, dir, "valid-manifest.json", `{"version":1,"migrations":[]}`)
|
|
invalidManifest := writeManifest(t, dir, "invalid-manifest.json", `{`)
|
|
tests := []struct {
|
|
name string
|
|
approved string
|
|
candidate string
|
|
want string
|
|
}{
|
|
{
|
|
name: "approved manifest",
|
|
approved: invalidManifest,
|
|
candidate: validManifest,
|
|
want: "read approved flag migrations",
|
|
},
|
|
{
|
|
name: "candidate manifest",
|
|
approved: validManifest,
|
|
candidate: invalidManifest,
|
|
want: "read candidate flag migrations",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
exitCode := run([]string{
|
|
"compare",
|
|
"--current", snapshotPath,
|
|
"--base", snapshotPath,
|
|
"--stable", snapshotPath,
|
|
"--approved-flag-migrations", test.approved,
|
|
"--candidate-flag-migrations", test.candidate,
|
|
}, &stdout, &stderr)
|
|
if exitCode != 2 || !strings.Contains(stderr.String(), test.want) {
|
|
t.Fatalf("%s exit=%d stdout=%s stderr=%s", test.name, exitCode, stdout.String(), stderr.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRunCompareReportsOutputFailure(t *testing.T) {
|
|
dir := t.TempDir()
|
|
snapshotPath := writeSnapshot(t, dir, "snapshot.json", commandSnapshot("dws"))
|
|
var stderr bytes.Buffer
|
|
exitCode := run([]string{
|
|
"compare",
|
|
"--current", snapshotPath,
|
|
"--base", snapshotPath,
|
|
}, failingWriter{}, &stderr)
|
|
if exitCode != 2 || !strings.Contains(stderr.String(), "write comparison report") {
|
|
t.Fatalf("comparison output failure exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageReadHelpersRejectMissingAndInvalidInputs(t *testing.T) {
|
|
dir := t.TempDir()
|
|
missingPath := filepath.Join(dir, "missing.json")
|
|
invalidPath := filepath.Join(dir, "invalid.json")
|
|
if err := os.WriteFile(invalidPath, []byte(`{`), 0o600); err != nil {
|
|
t.Fatalf("write invalid fixture: %v", err)
|
|
}
|
|
|
|
if _, err := readSnapshot(missingPath); err == nil {
|
|
t.Fatal("readSnapshot(missing) unexpectedly succeeded")
|
|
}
|
|
if _, err := readSnapshot(invalidPath); err == nil {
|
|
t.Fatal("readSnapshot(invalid) unexpectedly succeeded")
|
|
}
|
|
if _, err := readFlagMigrationManifest(missingPath); err == nil {
|
|
t.Fatal("readFlagMigrationManifest(missing) unexpectedly succeeded")
|
|
}
|
|
if _, err := readFlagMigrationManifest(invalidPath); err == nil {
|
|
t.Fatal("readFlagMigrationManifest(invalid) unexpectedly succeeded")
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageValidateHelpRenderingReportsResolveError(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
err := validateHelpRendering(root, commandSnapshot("dws missing"))
|
|
if err == nil || !strings.Contains(err.Error(), `resolve "dws missing" before help rendering`) {
|
|
t.Fatalf("validateHelpRendering resolve error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageValidateHelpRenderingReportsTemplateError(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
root.SetHelpTemplate(`{{index .Commands 99}}`)
|
|
err := validateHelpRendering(root, commandSnapshot("dws"))
|
|
if err == nil || !strings.Contains(err.Error(), `render "dws" help`) {
|
|
t.Fatalf("validateHelpRendering template error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageValidateHelpRenderingRecoversTemplatePanic(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
root.SetHelpTemplate("{{")
|
|
err := validateHelpRendering(root, commandSnapshot("dws"))
|
|
if err == nil || !strings.Contains(err.Error(), `render "dws" help`) {
|
|
t.Fatalf("validateHelpRendering template panic = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageValidateHelpRenderingRejectsCustomHelpStderr(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
root.SetHelpFunc(func(command *cobra.Command, _ []string) {
|
|
_, _ = io.WriteString(command.ErrOrStderr(), "injected help failure")
|
|
})
|
|
err := validateHelpRendering(root, commandSnapshot("dws"))
|
|
if err == nil || !strings.Contains(err.Error(), "injected help failure") {
|
|
t.Fatalf("validateHelpRendering custom stderr = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageValidateHelpRenderingRejectsEmptyOutput(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
root.SetHelpFunc(func(*cobra.Command, []string) {})
|
|
err := validateHelpRendering(root, commandSnapshot("dws"))
|
|
if err == nil || !strings.Contains(err.Error(), "empty") {
|
|
t.Fatalf("validateHelpRendering empty output = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageValidateHelpRenderingAcceptsNormalOutput(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws", Short: "root command"}
|
|
if err := validateHelpRendering(root, commandSnapshot("dws")); err != nil {
|
|
t.Fatalf("validateHelpRendering normal output: %v", err)
|
|
}
|
|
}
|
|
|
|
type failingWriter struct{}
|
|
|
|
func (failingWriter) Write([]byte) (int, error) {
|
|
return 0, errors.New("injected write failure")
|
|
}
|
|
|
|
var _ io.Writer = failingWriter{}
|
|
|
|
func commandSnapshot(paths ...string) interfacesnapshot.Snapshot {
|
|
commands := make([]interfacesnapshot.Command, 0, len(paths))
|
|
for _, path := range paths {
|
|
commands = append(commands, interfacesnapshot.Command{
|
|
Path: path,
|
|
Aliases: []string{},
|
|
LocalFlags: []interfacesnapshot.Flag{},
|
|
InheritedFlags: []interfacesnapshot.Flag{},
|
|
})
|
|
}
|
|
return interfacesnapshot.Snapshot{
|
|
SchemaVersion: interfacesnapshot.SchemaVersion,
|
|
Rules: interfacesnapshot.Rules{
|
|
ExcludedCommandSubtrees: []string{},
|
|
ExcludedFlags: []string{},
|
|
},
|
|
Commands: commands,
|
|
}
|
|
}
|
|
|
|
func writeSnapshot(t *testing.T, dir, name string, snapshot interfacesnapshot.Snapshot) string {
|
|
t.Helper()
|
|
path := filepath.Join(dir, name)
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatalf("create %s: %v", path, err)
|
|
}
|
|
if err := interfacesnapshot.Write(file, snapshot); err != nil {
|
|
file.Close()
|
|
t.Fatalf("write %s: %v", path, err)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
t.Fatalf("close %s: %v", path, err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func writeManifest(t *testing.T, dir, name, contents string) string {
|
|
t.Helper()
|
|
path := filepath.Join(dir, name)
|
|
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
|
|
t.Fatalf("write %s: %v", path, err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func flagMigrationSnapshot(after bool) interfacesnapshot.Snapshot {
|
|
legacy := interfacesnapshot.Flag{
|
|
Name: "legacy-id",
|
|
Shorthand: "l",
|
|
Type: "string",
|
|
Default: "",
|
|
NoOpt: "auto",
|
|
Required: true,
|
|
}
|
|
flags := []interfacesnapshot.Flag{legacy}
|
|
if after {
|
|
legacy.Required = false
|
|
legacy.Hidden = true
|
|
legacy.AliasOf = "message-id"
|
|
flags = []interfacesnapshot.Flag{
|
|
legacy,
|
|
{Name: "message-id", Type: "string", Default: "", Required: true},
|
|
}
|
|
}
|
|
return interfacesnapshot.Snapshot{
|
|
SchemaVersion: interfacesnapshot.SchemaVersion,
|
|
Rules: interfacesnapshot.Rules{
|
|
ExcludedCommandSubtrees: []string{},
|
|
ExcludedFlags: []string{},
|
|
},
|
|
Commands: []interfacesnapshot.Command{
|
|
{Path: "dws", Runnable: true, Aliases: []string{}, LocalFlags: []interfacesnapshot.Flag{}, InheritedFlags: []interfacesnapshot.Flag{}},
|
|
{Path: "dws chat send", Runnable: true, Aliases: []string{}, LocalFlags: flags, InheritedFlags: []interfacesnapshot.Flag{}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func flagMigrationManifestJSON(state string) string {
|
|
return strings.Replace(`{
|
|
"version": 1,
|
|
"migrations": [{
|
|
"command": "dws chat send",
|
|
"legacy": {
|
|
"name": "legacy-id",
|
|
"before": {"present": true, "type": "string", "required": true, "shorthand": "l", "no_opt": "auto", "scope": "local"},
|
|
"after": {"present": true, "type": "string", "hidden": true, "shorthand": "l", "no_opt": "auto", "scope": "local", "alias_of": "message-id"}
|
|
},
|
|
"canonical": {
|
|
"name": "message-id",
|
|
"before": {"present": false},
|
|
"after": {"present": true, "type": "string", "required": true, "scope": "local"}
|
|
},
|
|
"state": "STATE",
|
|
"reason": "reviewed exact migration"
|
|
}]
|
|
}`, "STATE", state, 1)
|
|
}
|
|
|
|
func commandMigrationManifestJSON(state string) string {
|
|
return strings.Replace(`{
|
|
"version": 1,
|
|
"migrations": [{
|
|
"kind": "command_move",
|
|
"legacy": {
|
|
"command": "dws chat message old",
|
|
"before": {"present": true, "runnable": true},
|
|
"after": {"present": true, "runnable": true, "hidden": true}
|
|
},
|
|
"replacement": {
|
|
"command": "dws chat topic new",
|
|
"before": {"present": false},
|
|
"after": {"present": true, "runnable": true}
|
|
},
|
|
"schema": {
|
|
"product_id": "chat",
|
|
"source_tool_id": "chat.move",
|
|
"replacement_tool_id": "chat.move",
|
|
"parameters": []
|
|
},
|
|
"state": "STATE",
|
|
"reason": "reviewed command migration"
|
|
}]
|
|
}`, "STATE", state, 1)
|
|
}
|
|
|
|
func hasFlag(flags []interfacesnapshot.Flag, name, flagType string) bool {
|
|
for _, flag := range flags {
|
|
if flag.Name == name && flag.Type == flagType {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|