Ctrl K
ComunidadGuías…

cli/secret_internal_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package cli

import (
	"bytes"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/spf13/pflag"
	"github.com/stretchr/testify/require"

	"github.com/coder/coder/v2/codersdk"
	"github.com/coder/serpent"
)

func TestHasSuspiciousTrailingNewline(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		input      string
		suspicious bool
	}{
		{name: "NoTrailingNewline", input: "token", suspicious: false},
		{name: "SingleTrailingLF", input: "token\n", suspicious: true},
		{name: "SingleTrailingCRLF", input: "token\r\n", suspicious: true},
		{name: "SingleTrailingCR", input: "token\r", suspicious: true},
		{name: "MultilineValue", input: "line1\nline2\n", suspicious: false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			require.Equal(t, tt.suspicious, hasSuspiciousTrailingNewline(tt.input))
		})
	}
}

func TestReadInvocationStdin(t *testing.T) {
	t.Parallel()

	t.Run("ZeroBytesRead", func(t *testing.T) {
		t.Parallel()

		inv := newSecretTestInvocation(t, strings.NewReader(""), nil)

		got, provided, err := readInvocationStdin(inv)
		require.NoError(t, err)
		require.False(t, provided)
		require.Empty(t, got)
	})

	t.Run("StringRead", func(t *testing.T) {
		t.Parallel()

		inv := newSecretTestInvocation(t, strings.NewReader("token"), nil)

		got, provided, err := readInvocationStdin(inv)
		require.NoError(t, err)
		require.True(t, provided)
		require.Equal(t, "token", got)
	})
}

func TestTrailingNewlineWarnings(t *testing.T) {
	t.Parallel()

	t.Run("WarnSuspiciousValue", func(t *testing.T) {
		t.Parallel()

		var stderr bytes.Buffer
		warnSuspiciousTrailingNewline(&stderr, "token\n")
		require.Contains(t, stderr.String(), "secret value from stdin ends with a trailing newline")
	})

	t.Run("DoesNotWarnForMultiline", func(t *testing.T) {
		t.Parallel()

		var stderr bytes.Buffer
		warnSuspiciousTrailingNewline(&stderr, "line1\nline2\n")
		require.Empty(t, stderr.String())
	})

	t.Run("SecretValueWarnsAndPreservesValue", func(t *testing.T) {
		t.Parallel()

		var stderr bytes.Buffer
		inv := newSecretTestInvocation(t, strings.NewReader("token\n"), &stderr)

		got, ok, err := secretValue(inv, "")
		require.NoError(t, err)
		require.True(t, ok)
		require.Equal(t, "token\n", got)
		require.Contains(t, stderr.String(), "secret value from stdin ends with a trailing newline")
	})

	t.Run("SecretValueDoesNotWarnForMultiline", func(t *testing.T) {
		t.Parallel()

		var stderr bytes.Buffer
		inv := newSecretTestInvocation(t, strings.NewReader("line1\nline2\n"), &stderr)

		got, ok, err := secretValue(inv, "")
		require.NoError(t, err)
		require.True(t, ok)
		require.Equal(t, "line1\nline2\n", got)
		require.Empty(t, stderr.String())
	})
}

func TestSecretsFileFormatFromPath(t *testing.T) {
	t.Parallel()

	tests := []struct {
		path string
		want codersdk.SecretsFileFormat
	}{
		{path: ".env", want: codersdk.SecretsFileFormatEnv},
		{path: "/tmp/prod.env", want: codersdk.SecretsFileFormatEnv},
		{path: "secrets.ENV", want: codersdk.SecretsFileFormatEnv},
		{path: "config.json", want: codersdk.SecretsFileFormatJSON},
		{path: "values.yaml", want: codersdk.SecretsFileFormatYAML},
		{path: "values.yml", want: codersdk.SecretsFileFormatYAML},
	}
	for _, tt := range tests {
		t.Run(tt.path, func(t *testing.T) {
			t.Parallel()

			got, err := secretsFileFormatFromPath(tt.path)
			require.NoError(t, err)
			require.Equal(t, tt.want, got)
		})
	}

	// filepath.Ext(".env.local") is ".local", so it does not map to a format.
	for _, path := range []string{"secrets.txt", "noextension", ".env.local", "-", ""} {
		t.Run("Unsupported/"+path, func(t *testing.T) {
			t.Parallel()

			_, err := secretsFileFormatFromPath(path)
			require.ErrorContains(t, err, "set --input-format to one of: env, json, yaml")
		})
	}
}

func TestReadSecretsFile(t *testing.T) {
	t.Parallel()

	t.Run("Stdin", func(t *testing.T) {
		t.Parallel()

		inv := newSecretTestInvocation(t, strings.NewReader("A=1"), nil)

		got, err := readSecretsFile(inv, "-")
		require.NoError(t, err)
		require.Equal(t, "A=1", string(got))
	})

	t.Run("File", func(t *testing.T) {
		t.Parallel()

		path := filepath.Join(t.TempDir(), "secrets.env")
		require.NoError(t, os.WriteFile(path, []byte("A=1"), 0o600))
		inv := newSecretTestInvocation(t, strings.NewReader(""), nil)

		got, err := readSecretsFile(inv, path)
		require.NoError(t, err)
		require.Equal(t, "A=1", string(got))
	})

	t.Run("MissingFile", func(t *testing.T) {
		t.Parallel()

		inv := newSecretTestInvocation(t, strings.NewReader(""), nil)

		_, err := readSecretsFile(inv, filepath.Join(t.TempDir(), "absent.env"))
		require.ErrorContains(t, err, "open secrets file")
	})

	t.Run("AtMaxSize", func(t *testing.T) {
		t.Parallel()

		content := strings.Repeat("a", codersdk.MaxSecretsFileBytes)
		inv := newSecretTestInvocation(t, strings.NewReader(content), nil)

		got, err := readSecretsFile(inv, "-")
		require.NoError(t, err)
		require.Len(t, got, codersdk.MaxSecretsFileBytes)
	})

	t.Run("OverMaxSize", func(t *testing.T) {
		t.Parallel()

		content := strings.Repeat("a", codersdk.MaxSecretsFileBytes+1)
		inv := newSecretTestInvocation(t, strings.NewReader(content), nil)

		_, err := readSecretsFile(inv, "-")
		require.ErrorContains(t, err, "exceeds the maximum allowed size")
	})
}

func TestWarnSecretsWithoutEnvNameEscapesNames(t *testing.T) {
	t.Parallel()

	var stderr bytes.Buffer
	warnSecretsWithoutEnvName(&stderr, []codersdk.UserSecret{{Name: "\x1b[31mBAD"}})

	require.Contains(t, stderr.String(), `"\x1b[31mBAD"`)
	require.NotContains(t, stderr.String(), "\x1b[31mBAD")
}

func newSecretTestInvocation(t *testing.T, stdin io.Reader, stderr io.Writer) *serpent.Invocation {
	t.Helper()

	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
	if stderr == nil {
		stderr = io.Discard
	}
	inv := (&serpent.Invocation{
		Stdin:   stdin,
		Stderr:  stderr,
		Command: &serpent.Command{},
		Args:    []string{"api-key"},
	}).WithTestParsedFlags(t, flags)
	return inv
}