Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions cmd/app/commands/purge_tokenization_keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package commands

import (
"bytes"
"context"
"log/slog"
"testing"

"github.com/stretchr/testify/require"

tokenizationMocks "github.com/allisson/secrets/internal/tokenization/usecase/mocks"
)

func TestRunPurgeTokenizationKeys(t *testing.T) {
ctx := context.Background()
logger := slog.Default()
days := 30

t.Run("text-output", func(t *testing.T) {
mockUseCase := &tokenizationMocks.MockTokenizationKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(100), nil)

var out bytes.Buffer
err := RunPurgeTokenizationKeys(ctx, mockUseCase, logger, &out, days, false, "text")

require.NoError(t, err)
require.Contains(
t,
out.String(),
"Successfully deleted 100 tokenization key(s) (and associated tokens) older than 30 day(s)",
)
mockUseCase.AssertExpectations(t)
})

t.Run("text-output-dry-run", func(t *testing.T) {
mockUseCase := &tokenizationMocks.MockTokenizationKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, true).Return(int64(75), nil)

var out bytes.Buffer
err := RunPurgeTokenizationKeys(ctx, mockUseCase, logger, &out, days, true, "text")

require.NoError(t, err)
require.Contains(
t,
out.String(),
"Dry-run mode: Would delete 75 tokenization key(s) (and associated tokens) older than 30 day(s)",
)
mockUseCase.AssertExpectations(t)
})

t.Run("json-output", func(t *testing.T) {
mockUseCase := &tokenizationMocks.MockTokenizationKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, true).Return(int64(50), nil)

var out bytes.Buffer
err := RunPurgeTokenizationKeys(ctx, mockUseCase, logger, &out, days, true, "json")

require.NoError(t, err)
require.Contains(t, out.String(), `"count": 50`)
require.Contains(t, out.String(), `"days": 30`)
require.Contains(t, out.String(), `"dry_run": true`)
mockUseCase.AssertExpectations(t)
})

t.Run("json-output-no-dry-run", func(t *testing.T) {
mockUseCase := &tokenizationMocks.MockTokenizationKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(25), nil)

var out bytes.Buffer
err := RunPurgeTokenizationKeys(ctx, mockUseCase, logger, &out, days, false, "json")

require.NoError(t, err)
require.Contains(t, out.String(), `"count": 25`)
require.Contains(t, out.String(), `"days": 30`)
require.Contains(t, out.String(), `"dry_run": false`)
mockUseCase.AssertExpectations(t)
})

t.Run("invalid-days-negative", func(t *testing.T) {
mockUseCase := &tokenizationMocks.MockTokenizationKeyUseCase{}
err := RunPurgeTokenizationKeys(ctx, mockUseCase, logger, &bytes.Buffer{}, -1, false, "text")

require.Error(t, err)
require.Contains(t, err.Error(), "days must be a positive number")
})

t.Run("zero-days-allowed", func(t *testing.T) {
mockUseCase := &tokenizationMocks.MockTokenizationKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, 0, false).Return(int64(10), nil)

var out bytes.Buffer
err := RunPurgeTokenizationKeys(ctx, mockUseCase, logger, &out, 0, false, "text")

require.NoError(t, err)
require.Contains(
t,
out.String(),
"Successfully deleted 10 tokenization key(s) (and associated tokens) older than 0 day(s)",
)
mockUseCase.AssertExpectations(t)
})

t.Run("no-keys-to-delete", func(t *testing.T) {
mockUseCase := &tokenizationMocks.MockTokenizationKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(0), nil)

var out bytes.Buffer
err := RunPurgeTokenizationKeys(ctx, mockUseCase, logger, &out, days, false, "text")

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully deleted 0 tokenization key(s)")
mockUseCase.AssertExpectations(t)
})
}
102 changes: 102 additions & 0 deletions cmd/app/commands/purge_transit_keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package commands

import (
"bytes"
"context"
"log/slog"
"testing"

"github.com/stretchr/testify/require"

transitMocks "github.com/allisson/secrets/internal/transit/usecase/mocks"
)

func TestRunPurgeTransitKeys(t *testing.T) {
ctx := context.Background()
logger := slog.Default()
days := 30

t.Run("text-output", func(t *testing.T) {
mockUseCase := &transitMocks.MockTransitKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(100), nil)

var out bytes.Buffer
err := RunPurgeTransitKeys(ctx, mockUseCase, logger, &out, days, false, "text")

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully deleted 100 transit key(s) older than 30 day(s)")
mockUseCase.AssertExpectations(t)
})

t.Run("text-output-dry-run", func(t *testing.T) {
mockUseCase := &transitMocks.MockTransitKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, true).Return(int64(75), nil)

var out bytes.Buffer
err := RunPurgeTransitKeys(ctx, mockUseCase, logger, &out, days, true, "text")

require.NoError(t, err)
require.Contains(t, out.String(), "Dry-run mode: Would delete 75 transit key(s) older than 30 day(s)")
mockUseCase.AssertExpectations(t)
})

t.Run("json-output", func(t *testing.T) {
mockUseCase := &transitMocks.MockTransitKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, true).Return(int64(50), nil)

var out bytes.Buffer
err := RunPurgeTransitKeys(ctx, mockUseCase, logger, &out, days, true, "json")

require.NoError(t, err)
require.Contains(t, out.String(), `"count": 50`)
require.Contains(t, out.String(), `"days": 30`)
require.Contains(t, out.String(), `"dry_run": true`)
mockUseCase.AssertExpectations(t)
})

t.Run("json-output-no-dry-run", func(t *testing.T) {
mockUseCase := &transitMocks.MockTransitKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(25), nil)

var out bytes.Buffer
err := RunPurgeTransitKeys(ctx, mockUseCase, logger, &out, days, false, "json")

require.NoError(t, err)
require.Contains(t, out.String(), `"count": 25`)
require.Contains(t, out.String(), `"days": 30`)
require.Contains(t, out.String(), `"dry_run": false`)
mockUseCase.AssertExpectations(t)
})

t.Run("invalid-days-negative", func(t *testing.T) {
mockUseCase := &transitMocks.MockTransitKeyUseCase{}
err := RunPurgeTransitKeys(ctx, mockUseCase, logger, &bytes.Buffer{}, -1, false, "text")

require.Error(t, err)
require.Contains(t, err.Error(), "days must be a positive number")
})

t.Run("zero-days-allowed", func(t *testing.T) {
mockUseCase := &transitMocks.MockTransitKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, 0, false).Return(int64(10), nil)

var out bytes.Buffer
err := RunPurgeTransitKeys(ctx, mockUseCase, logger, &out, 0, false, "text")

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully deleted 10 transit key(s) older than 0 day(s)")
mockUseCase.AssertExpectations(t)
})

t.Run("no-keys-to-delete", func(t *testing.T) {
mockUseCase := &transitMocks.MockTransitKeyUseCase{}
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(0), nil)

var out bytes.Buffer
err := RunPurgeTransitKeys(ctx, mockUseCase, logger, &out, days, false, "text")

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully deleted 0 transit key(s)")
mockUseCase.AssertExpectations(t)
})
}
17 changes: 17 additions & 0 deletions cmd/app/commands/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package commands

import (
"context"
"testing"

"github.com/stretchr/testify/require"
)

func TestRunServer_InvalidConfig(t *testing.T) {
ctx := context.Background()

// Call RunServer which should fail (e.g. database connection refused)
// rather than blocking indefinitely in unit tests.
err := RunServer(ctx, "v1.0.0")
require.Error(t, err)
}
32 changes: 31 additions & 1 deletion cmd/app/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,17 @@ func TestGetSystemCommands(t *testing.T) {
cmds := getSystemCommands(version)
require.NotEmpty(t, cmds)

expectedCmds := []string{"server", "migrate", "clean-audit-logs", "verify-audit-logs"}
expectedCmds := []string{
"server",
"migrate",
"migrate-down",
"clean-audit-logs",
"purge-secrets",
"purge-transit-keys",
"purge-tokenization-keys",
"verify-audit-logs",
}

for _, name := range expectedCmds {
found := false
for _, cmd := range cmds {
Expand All @@ -81,4 +91,24 @@ func TestGetSystemCommands(t *testing.T) {
}
require.Truef(t, found, "command %s not found", name)
}

// Basic flag checks for specific commands
for _, cmd := range cmds {
switch cmd.Name {
case "migrate-down":
require.NotEmpty(t, cmd.Flags)
require.Equal(t, "steps", cmd.Flags[0].Names()[0])
case "purge-secrets", "purge-transit-keys", "purge-tokenization-keys", "clean-audit-logs":
require.NotEmpty(t, cmd.Flags)
// check that they have a --days flag
hasDaysFlag := false
for _, flag := range cmd.Flags {
if flag.Names()[0] == "days" {
hasDaysFlag = true
break
}
}
require.True(t, hasDaysFlag, "command %s missing --days flag", cmd.Name)
}
}
}
5 changes: 5 additions & 0 deletions cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ var (
commitSHA = "unknown" // Git commit SHA
)

// main is the primary entry point for the secrets CLI application.
// It configures the version printer, initializes the CLI command tree,
// and executes the requested command context. If the application encounters
// a fatal error before the main logging subsystem is initialized, it falls
// back to writing a JSON error payload directly to os.Stderr.
func main() {
// Custom version printer to display build metadata
cli.VersionPrinter = func(cmd *cli.Command) {
Expand Down
Loading
Loading