Ctrl K
ComunidadGuías…

tests/device_cleanup_test.go

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

Archivo original ↗Preguntar a KiBot
package main

import (
	"net/http"
	"testing"
	"time"

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

const (
	ephemeralCleanupCron = "*/5 * * * *"
	removedCleanupCron   = "0 2 * * *"
	removedRetention     = 30 * 24 * time.Hour
	heartbeatFlushWindow = 3 * time.Second
)

// TestDeviceCleanup runs the cron jobs that delete devices for good: an ephemeral device once it has
// stayed offline past its key's timeout, and a removed device once it has been removed for 30 days.
// Each job is triggered rather than waited for, and the time it measures is moved back in the
// database. The cases share one stack, so every device carries a hostname no other case uses.
func TestDeviceCleanup(t *testing.T) {
	t.Parallel()

	compose := newConfiguredSSHEnvironment(t, t.Context(), environment.New(t, run).WithCronTrigger(), newNamespace)

	t.Run("an ephemeral device offline past its timeout is deleted and an online one is kept", func(t *testing.T) {
		key := compose.CreateProvisioningKey(t, &requests.CreateProvisioningKey{
			Name:             "ephemeral-cleanup",
			Mode:             string(models.ProvisioningKeyModeAutomatic),
			Ephemeral:        true,
			EphemeralTimeout: 1,
		}).Key

		offlineAgent := startAgent(t, t.Context(), compose, NewAgentContainerWithProvisioningKey(key), NewAgentContainerWithHostname("ephemeral-offline"))
		offline := compose.AwaitDeviceOnline(t, compose.AwaitDeviceNamed(t, "ephemeral-offline").UID)
		startAgent(t, t.Context(), compose, NewAgentContainerWithProvisioningKey(key), NewAgentContainerWithHostname("ephemeral-online"))
		online := compose.AwaitDeviceOnline(t, compose.AwaitDeviceNamed(t, "ephemeral-online").UID)
		require.True(t, offline.Ephemeral)
		require.True(t, online.Ephemeral)

		require.NoError(t, offlineAgent.Stop(t.Context(), nil))
		compose.AwaitDeviceOffline(t, offline.UID)
		time.Sleep(heartbeatFlushWindow)

		before := compose.Namespace(t, ShellHubNamespace)

		compose.AgeDevice(t, offline.UID, 2*time.Minute)

		compose.RunCron(t, ephemeralCleanupCron)

		awaitDeviceDeleted(t, compose, offline.UID)

		after := compose.Namespace(t, ShellHubNamespace)
		assert.Equal(t, before.DevicesAcceptedCount-1, after.DevicesAcceptedCount, "the namespace still counts the deleted device")

		kept := requireDevice(t, compose, online.UID)
		assert.Equal(t, models.DeviceStatusAccepted, kept.Status)
		assert.True(t, kept.Online, "the online ephemeral device went offline")
	})

	t.Run("a device removed more than 30 days ago is purged and a recently removed one is kept", func(t *testing.T) {
		expired := removeAcceptedDevice(t, compose, "removed-expired", "02:00:00:00:c1:01")
		recent := removeAcceptedDevice(t, compose, "removed-recent", "02:00:00:00:c1:02")

		before := compose.Namespace(t, ShellHubNamespace)

		compose.AgeDeviceRemoval(t, expired, removedRetention+time.Hour)
		compose.AgeDeviceRemoval(t, recent, removedRetention-24*time.Hour)

		compose.RunCron(t, removedCleanupCron)

		awaitDeviceDeleted(t, compose, expired)

		after := compose.Namespace(t, ShellHubNamespace)
		assert.Equal(t, before.DevicesRemovedCount-1, after.DevicesRemovedCount, "the namespace still counts the purged device")

		assert.Equal(t, models.DeviceStatusRemoved, requireDevice(t, compose, recent).Status, "a device removed inside the retention window was purged")
	})
}

func removeAcceptedDevice(t *testing.T, compose *environment.DockerCompose, hostname, mac string) string {
	t.Helper()

	uid := enrollDevice(t, compose, hostname, mac)
	compose.UpdateDeviceStatus(t, uid, environment.DeviceActionAccept)
	compose.DeleteDevice(t, uid)
	require.Equal(t, models.DeviceStatusRemoved, requireDevice(t, compose, uid).Status)

	return uid
}

func awaitDeviceDeleted(t *testing.T, compose *environment.DockerCompose, uid string) {
	t.Helper()

	require.EventuallyWithT(t, func(tt *assert.CollectT) {
		_, resp, err := compose.GetDevice(t.Context(), uid)
		if assert.NoError(tt, err) {
			assert.Equal(tt, http.StatusNotFound, resp.StatusCode(), "device %s is still there: %s", uid, resp.String())
		}
	}, 30*time.Second, time.Second)
}