Read options after the path or URL and refuse a second argument (closes #196)
check / check (push) Waiting to run
check / check (push) Waiting to run
Every command read options only up to its first argument, and check, fetch, freshen, list and export dropped everything after it, so `check DIR --require-signature FPR` passed an unsigned manifest. Options are now read wherever they appear, as urfave/cli v3 does by default, and `--` still ends them. Those five commands refuse a second argument, naming it. TestFlagAfterArgumentIsArgument asserted the defect (`gen DIR -v` read `-v` as a path); a test that `gen -- -x` reads `-x` as a path replaces it. Model: opus-5-5
This commit was merged in pull request #197.
This commit is contained in:
+108
-9
@@ -1664,20 +1664,119 @@ func TestGenerateValidatesInputPaths(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestFlagAfterArgumentIsArgument asserts that flags are read only before a
|
||||
// command's first argument: after it, -v is a path, not the verbose flag.
|
||||
func TestFlagAfterArgumentIsArgument(t *testing.T) {
|
||||
t.Parallel()
|
||||
// generateTestDir writes testFile1 and runs gen on testDir, which writes
|
||||
// the manifest to testDir/index.mf.
|
||||
func generateTestDir(t *testing.T, fs afero.Fs) {
|
||||
t.Helper()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
require.NoError(t, fs.MkdirAll(testDir, 0o755))
|
||||
writeTestFile(t, fs, testFile1, "content")
|
||||
|
||||
opts := testOpts([]string{testApp, cmdGenerate, testDir, "-v"}, fs)
|
||||
exitCode := runCLI(opts)
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", testDir}, fs)
|
||||
require.Equal(t, 0, runCLI(opts), testStderr(t, opts))
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, exitCode)
|
||||
assert.Contains(t, testStderr(t, opts), "path does not exist: -v")
|
||||
// TestCheckReadsOptionAfterPath runs check on an unsigned manifest with
|
||||
// --require-signature after the directory. check must refuse the manifest.
|
||||
func TestCheckReadsOptionAfterPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
generateTestDir(t, fs)
|
||||
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdCheck, "-q", testDir, "--" + flagRequireSignature, msgFpA,
|
||||
}, fs)
|
||||
assert.Equal(t, 1, runCLI(opts))
|
||||
assert.Contains(t, testStderr(t, opts), "manifest is not signed")
|
||||
}
|
||||
|
||||
// TestFreshenReadsOptionAfterPath runs freshen with --include-permissions
|
||||
// after the directory. freshen must record the file's mode.
|
||||
func TestFreshenReadsOptionAfterPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewOsFs()
|
||||
root, manifestPath := setupFreshenDir(t, fs,
|
||||
map[string]string{testFileTxt: "a file"})
|
||||
require.NoError(t, fs.Chmod(filepath.Join(root, testFileTxt), 0o640))
|
||||
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdFreshen, "-q", root, "--" + flagIncludePermissions,
|
||||
}, fs)
|
||||
require.Equal(t, 0, runCLI(opts), testStderr(t, opts))
|
||||
|
||||
files := manifestFiles(t, fs, manifestPath)
|
||||
require.Len(t, files, 1)
|
||||
assert.Equal(t, uint32(0o640), files[0].GetMode())
|
||||
}
|
||||
|
||||
// TestFetchReadsOptionAfterURL runs fetch with --dest after the URL. The
|
||||
// file and the manifest must land in that directory and nothing in the
|
||||
// current one.
|
||||
//
|
||||
//nolint:paralleltest // changes the process-global working directory
|
||||
func TestFetchReadsOptionAfterURL(t *testing.T) {
|
||||
content := []byte("fetched")
|
||||
manifest := manifestOf(t, map[string][]byte{testFileTxt: content})
|
||||
|
||||
server := httptest.NewServer(fetchTestHandler(manifest,
|
||||
map[string][]byte{testFileTxt: content}))
|
||||
defer server.Close()
|
||||
|
||||
cwd := chdirTemp(t)
|
||||
dest := t.TempDir()
|
||||
|
||||
opts := testOpts([]string{
|
||||
testApp, cmdFetch, "-q", server.URL, "--" + flagDest, dest,
|
||||
}, afero.NewOsFs())
|
||||
require.Equal(t, 0, runCLI(opts), testStderr(t, opts))
|
||||
|
||||
assert.Equal(t,
|
||||
map[string][]byte{testFileTxt: content, defaultManifestName: manifest},
|
||||
filesUnder(t, dest))
|
||||
assert.Empty(t, filesUnder(t, cwd), "fetch wrote outside --dest")
|
||||
}
|
||||
|
||||
// TestOneArgumentCommandsRefuseSecond runs each command that takes one
|
||||
// argument with two. It must fail naming the second.
|
||||
func TestOneArgumentCommandsRefuseSecond(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, command := range []string{
|
||||
cmdCheck, cmdFreshen, cmdList, cmdExport, cmdFetch,
|
||||
} {
|
||||
t.Run(command, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
generateTestDir(t, fs)
|
||||
|
||||
opts := testOpts([]string{testApp, command, testDir, "/second"}, fs)
|
||||
assert.Equal(t, 1, runCLI(opts))
|
||||
assert.Contains(t, testStderr(t, opts),
|
||||
`unexpected argument "/second"`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateReadsPathAfterDoubleDash runs gen -- -x where -x is a
|
||||
// directory. -x must be read as the path to list, not as an option.
|
||||
//
|
||||
//nolint:paralleltest // changes the process-global working directory
|
||||
func TestGenerateReadsPathAfterDoubleDash(t *testing.T) {
|
||||
chdirTemp(t)
|
||||
|
||||
fs := afero.NewOsFs()
|
||||
require.NoError(t, fs.Mkdir("-x", 0o755))
|
||||
writeTestFile(t, fs, filepath.Join("-x", testFileTxt), "content")
|
||||
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "--", "-x"}, fs)
|
||||
require.Equal(t, 0, runCLI(opts), testStderr(t, opts))
|
||||
|
||||
files := manifestFiles(t, fs, filepath.Join("-x", defaultManifestName))
|
||||
require.Len(t, files, 1)
|
||||
assert.Equal(t, testFileTxt, files[0].GetPath())
|
||||
}
|
||||
|
||||
func TestCheckDetectsManifestCorruption(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user