Ctrl K
ComunidadGuías…

tests/agent_cli_test.go

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

Archivo original ↗Preguntar a KiBot
package main

import (
	"encoding/json"
	"io"
	"net"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/shellhub-io/shellhub/pkg/models"
	"github.com/shellhub-io/shellhub/tests/environment"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/testcontainers/testcontainers-go"
)

// TestAgentCLI covers the agent's commands a person runs by hand next to the daemon. For a pending
// device, login prints a code a namespace member accepts from the console, then returns when the
// server reports acceptance. An already-accepted device gets no code and is told it is accepted.
// Without a tenant, login pairs the device and persists the tenant it is accepted into. The info
// command prints the version and endpoints the server reports for the agent's configured address.
// The cases share one stack.
func TestAgentCLI(t *testing.T) {
	t.Parallel()

	compose := newSSHEnvironment(t, t.Context(), newNamespace)

	t.Run("login prints a code that accepts a pending device and returns once it is accepted", func(t *testing.T) {
		login := startAgentCommand(t, compose, []string{"./agent", "login"}, NewAgentContainerWithHostname("login-pending"))
		code := awaitAgentPairingCode(t, login)

		preview := requireLoginCodePreview(t, compose, code)
		require.Equal(t, "login-pending", preview.Name)
		require.Equal(t, models.DeviceStatusPending, preview.Status)

		compose.UpdateDeviceStatus(t, preview.UID, environment.DeviceActionAccept)

		assert.Zero(t, awaitAgentExit(t, login))
		awaitAgentLog(t, login, regexp.MustCompile(`(✓ Device accepted into namespace "`+ShellHubNamespaceName+`"\.)`), "login did not report the acceptance")
	})

	t.Run("login on an accepted device says so and asks for no code", func(t *testing.T) {
		agent, _ := startAcceptedAgent(t, t.Context(), compose)

		output := execInAgent(t, agent, "./agent", "login")

		assert.Contains(t, output, `✓ Device is already accepted into namespace "`+ShellHubNamespaceName+`".`)
		assert.NotRegexp(t, agentPairingLink, output)
	})

	t.Run("login without a tenant pairs the device and keeps the tenant it is accepted into", func(t *testing.T) {
		login := startAgentCommand(t, compose, []string{"./agent", "login"}, NewAgentContainerUnpaired(), NewAgentContainerWithHostname("login-unpaired"))

		acceptPairing(t, compose.R(t.Context()), awaitAgentPairingCode(t, login))

		assert.Zero(t, awaitAgentExit(t, login))
		awaitAgentLog(t, login, regexp.MustCompile(`(✓ Device accepted\. The agent will connect automatically\.)`), "login did not report the acceptance")
		assert.Equal(t, ShellHubNamespace, readStoppedAgentFile(t, login, "/tmp/shellhub.key.tenant"), "login did not keep the tenant the device was accepted into")
	})

	t.Run("info prints the server's version and the endpoints it reports for the agent's address", func(t *testing.T) {
		agent := startAgent(t, t.Context(), compose)

		output := execInAgent(t, agent, "./agent", "info")
		lines := strings.Split(output, "\n")

		info := new(models.Info)
		require.NoError(t, json.Unmarshal([]byte(lines[len(lines)-1]), info), output)

		_, sshPort, err := net.SplitHostPort(compose.SSHAddress())
		require.NoError(t, err)

		assert.Equal(t, models.Info{
			Version:   serverVersion(t, compose),
			Endpoints: models.Endpoints{API: "localhost:80", SSH: "localhost:" + sshPort},
		}, *info)
	})
}

func startAgentCommand(t *testing.T, compose *environment.DockerCompose, command []string, opts ...NewAgentContainerOption) testcontainers.Container {
	t.Helper()

	return startCustomAgent(t, t.Context(), compose, func(req *testcontainers.ContainerRequest) {
		req.Entrypoint = command
	}, opts...)
}

func awaitAgentExit(t *testing.T, agent testcontainers.Container) int {
	t.Helper()

	var exitCode int

	require.EventuallyWithT(t, func(tt *assert.CollectT) {
		state, err := agent.State(t.Context())
		if assert.NoError(tt, err) && assert.False(tt, state.Running, "the command is still running") {
			exitCode = state.ExitCode
		}
	}, 30*time.Second, time.Second)

	return exitCode
}

func readStoppedAgentFile(t *testing.T, agent testcontainers.Container, path string) string {
	t.Helper()

	reader, err := agent.CopyFileFromContainer(t.Context(), path)
	require.NoError(t, err)

	defer reader.Close() //nolint:errcheck // the copy has been read in full

	content, err := io.ReadAll(reader)
	require.NoError(t, err)

	return strings.TrimSpace(string(content))
}