Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/workflows/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- multicloud
paths:
- ".github/labels.yml"
- ".github/workflows/labels.yml"
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Test

on:
push:
branches: [main]
branches: [main, multicloud]
pull_request:
branches: [main]
branches: [main, multicloud]

permissions:
contents: read
Expand Down Expand Up @@ -390,6 +390,7 @@ jobs:
runs-on: windows-latest
if: >-
github.ref == 'refs/heads/main' ||
github.ref == 'refs/heads/multicloud' ||
contains(github.event.head_commit.message, '[CI: windows]')
defaults:
run:
Expand Down
23 changes: 11 additions & 12 deletions e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/urfave/cli/v3"

"github.com/mpyw/suve/internal/cli/output"
"github.com/mpyw/suve/internal/staging"
"github.com/mpyw/suve/internal/staging/store"
"github.com/mpyw/suve/internal/staging/store/agent"
"github.com/mpyw/suve/internal/staging/store/agent/daemon"
Expand Down Expand Up @@ -56,8 +57,8 @@ func TestMain(m *testing.M) {
os.Exit(1)
}

// Start daemon with error channel (localstack uses account "000000000000" and region "us-east-1")
testDaemon = daemon.NewRunner("000000000000", "us-east-1", agent.DaemonOptions()...)
// Start daemon with error channel
testDaemon = daemon.NewRunner(agent.DaemonOptions()...)
daemonErrCh := make(chan error, 1)

go func() {
Expand Down Expand Up @@ -87,8 +88,7 @@ func TestMain(m *testing.M) {

// waitForDaemon waits for the daemon to be ready by polling with ping.
func waitForDaemon(timeout time.Duration, daemonErrCh <-chan error) error {
// Use same account/region as the daemon
launcher := daemon.NewLauncher("000000000000", "us-east-1", daemon.WithAutoStartDisabled())
launcher := daemon.NewLauncher(daemon.WithAutoStartDisabled())

ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
Expand Down Expand Up @@ -142,16 +142,15 @@ func setupTempHome(t *testing.T) {
t.Setenv("HOME", t.TempDir())
}

// newStore creates a new staging store for E2E tests.
// testScope is the default scope for E2E tests.
// localstack uses account ID "000000000000" and region "us-east-1".
func newStore() store.AgentStore {
return agent.NewStore("000000000000", "us-east-1")
}
//
//nolint:gochecknoglobals // Test-only constant
var testScope = staging.AWSScope("000000000000", "us-east-1")

// newStoreForAccount creates a staging store for a specific account and region.
// Used for testing error cases when daemon is not running for that account.
func newStoreForAccount(accountID, region string) store.AgentStore {
return agent.NewStore(accountID, region)
// newStore creates a new staging store for E2E tests.
func newStore() store.AgentStore {
return agent.NewStore(testScope)
}

// runCommand executes a CLI command and returns stdout, stderr, and error.
Expand Down
24 changes: 18 additions & 6 deletions e2e/staging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ func TestDaemonLauncher_Ping(t *testing.T) {
setupTempHome(t)

// Create launcher for the running test daemon
launcher := daemon.NewLauncher("000000000000", "us-east-1", daemon.WithAutoStartDisabled())
launcher := daemon.NewLauncher(daemon.WithAutoStartDisabled())

// Test Ping
t.Run("ping-success", func(t *testing.T) {
Expand Down Expand Up @@ -1166,7 +1166,7 @@ func TestDaemonLauncher_EnsureRunning(t *testing.T) {
setupTempHome(t)

// Create launcher for the running test daemon
launcher := daemon.NewLauncher("000000000000", "us-east-1", daemon.WithAutoStartDisabled())
launcher := daemon.NewLauncher(daemon.WithAutoStartDisabled())

// Test EnsureRunning (daemon is already running from TestMain)
t.Run("ensure-running-when-running", func(t *testing.T) {
Expand Down Expand Up @@ -1231,13 +1231,25 @@ func TestDaemonLauncher_ViaStore(t *testing.T) {
})
}

// setupIsolatedSocketPath sets socket-related environment variables to a temp directory,
// causing the daemon to look for a socket in a different location where no daemon is running.
// This simulates the "daemon not running" scenario for E2E tests.
// Darwin uses TMPDIR, Linux uses XDG_RUNTIME_DIR, Windows uses LOCALAPPDATA.
func setupIsolatedSocketPath(t *testing.T) {
t.Helper()
tempDir := t.TempDir()
t.Setenv("TMPDIR", tempDir)
t.Setenv("XDG_RUNTIME_DIR", tempDir)
t.Setenv("LOCALAPPDATA", tempDir)
}

// TestDaemonLauncher_NotRunning tests launcher behavior when daemon is not running.
func TestDaemonLauncher_NotRunning(t *testing.T) {
setupEnv(t)
setupTempHome(t)
setupIsolatedSocketPath(t) // Use different socket path where no daemon exists

// Create launcher for a different account where no daemon is running
launcher := daemon.NewLauncher("999999999999", "ap-northeast-1", daemon.WithAutoStartDisabled())
launcher := daemon.NewLauncher(daemon.WithAutoStartDisabled())

// Test Ping fails when daemon not running
t.Run("ping-not-running", func(t *testing.T) {
Expand All @@ -1257,9 +1269,9 @@ func TestDaemonLauncher_NotRunning(t *testing.T) {
func TestAgentStore_NotRunning(t *testing.T) {
setupEnv(t)
setupTempHome(t)
setupIsolatedSocketPath(t) // Use different socket path where no daemon exists

// Create store for a different account where no daemon is running
store := newStoreForAccount("999999999999", "ap-northeast-1")
store := newStore()

// Test GetEntry fails when daemon not running
t.Run("get-entry-not-running", func(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions internal/api/paramapi/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,9 @@ var NewDescribeParametersPaginator = ssm.NewDescribeParametersPaginator
const (
FilterNameStringTypeName = types.ParametersFilterKeyName
)

// ParameterTier is a re-exported SSM model type.
type ParameterTier = types.ParameterTier

// ParameterInlinePolicy is a re-exported SSM model type.
type ParameterInlinePolicy = types.ParameterInlinePolicy
9 changes: 4 additions & 5 deletions internal/cli/commands/param/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import (

"github.com/urfave/cli/v3"

"github.com/mpyw/suve/internal/api/paramapi"
"github.com/mpyw/suve/internal/cli/output"
"github.com/mpyw/suve/internal/infra"
awsparam "github.com/mpyw/suve/internal/provider/aws/param"
"github.com/mpyw/suve/internal/usecase/param"
)

Expand Down Expand Up @@ -92,13 +91,13 @@ func action(ctx context.Context, cmd *cli.Command) error {
paramType = "SecureString"
}

client, err := infra.NewParamClient(ctx)
adapter, err := awsparam.NewAdapter(ctx)
if err != nil {
return fmt.Errorf("failed to initialize AWS client: %w", err)
}

r := &Runner{
UseCase: &param.CreateUseCase{Client: client},
UseCase: &param.CreateUseCase{Client: adapter},
Stdout: cmd.Root().Writer,
Stderr: cmd.Root().ErrWriter,
}
Expand All @@ -116,7 +115,7 @@ func (r *Runner) Run(ctx context.Context, opts Options) error {
result, err := r.UseCase.Execute(ctx, param.CreateInput{
Name: opts.Name,
Value: opts.Value,
Type: paramapi.ParameterType(opts.Type),
Type: opts.Type,
Description: opts.Description,
})
if err != nil {
Expand Down
57 changes: 29 additions & 28 deletions internal/cli/commands/param/create/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ package create_test
import (
"bytes"
"context"
"fmt"
"errors"
"testing"

"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/mpyw/suve/internal/api/paramapi"
appcli "github.com/mpyw/suve/internal/cli/commands"
"github.com/mpyw/suve/internal/cli/commands/param/create"
"github.com/mpyw/suve/internal/model"
"github.com/mpyw/suve/internal/usecase/param"
)

Expand Down Expand Up @@ -47,18 +46,16 @@ func TestCommand_Validation(t *testing.T) {
})
}

//nolint:lll // mock struct fields match AWS SDK interface signatures
type mockClient struct {
putParameterFunc func(ctx context.Context, params *paramapi.PutParameterInput, optFns ...func(*paramapi.Options)) (*paramapi.PutParameterOutput, error)
putParameterFunc func(ctx context.Context, p *model.Parameter, overwrite bool) (*model.ParameterWriteResult, error)
}

//nolint:lll // mock function signature must match AWS SDK interface
func (m *mockClient) PutParameter(ctx context.Context, params *paramapi.PutParameterInput, optFns ...func(*paramapi.Options)) (*paramapi.PutParameterOutput, error) {
func (m *mockClient) PutParameter(ctx context.Context, p *model.Parameter, overwrite bool) (*model.ParameterWriteResult, error) {
if m.putParameterFunc != nil {
return m.putParameterFunc(ctx, params, optFns...)
return m.putParameterFunc(ctx, p, overwrite)
}

return nil, fmt.Errorf("PutParameter not mocked")
return nil, errors.New("PutParameter not mocked")
}

func TestRun(t *testing.T) {
Expand All @@ -79,15 +76,19 @@ func TestRun(t *testing.T) {
Type: "SecureString",
},
mock: &mockClient{
//nolint:lll // inline mock function in test table
putParameterFunc: func(_ context.Context, params *paramapi.PutParameterInput, _ ...func(*paramapi.Options)) (*paramapi.PutParameterOutput, error) {
assert.Equal(t, "/app/param", lo.FromPtr(params.Name))
assert.Equal(t, "test-value", lo.FromPtr(params.Value))
assert.Equal(t, paramapi.ParameterTypeSecureString, params.Type)
assert.False(t, lo.FromPtr(params.Overwrite))

return &paramapi.PutParameterOutput{
Version: 1,
putParameterFunc: func(_ context.Context, p *model.Parameter, overwrite bool) (*model.ParameterWriteResult, error) {
assert.Equal(t, "/app/param", p.Name)
assert.Equal(t, "test-value", p.Value)

if meta := p.AWSMeta(); meta != nil {
assert.Equal(t, "SecureString", meta.Type)
}

assert.False(t, overwrite)

return &model.ParameterWriteResult{
Name: "/app/param",
Version: "1",
}, nil
},
},
Expand All @@ -107,13 +108,13 @@ func TestRun(t *testing.T) {
Description: "Test description",
},
mock: &mockClient{
//nolint:lll // inline mock function in test table
putParameterFunc: func(_ context.Context, params *paramapi.PutParameterInput, _ ...func(*paramapi.Options)) (*paramapi.PutParameterOutput, error) {
assert.Equal(t, "Test description", lo.FromPtr(params.Description))
assert.False(t, lo.FromPtr(params.Overwrite))
putParameterFunc: func(_ context.Context, p *model.Parameter, overwrite bool) (*model.ParameterWriteResult, error) {
assert.Equal(t, "Test description", p.Description)
assert.False(t, overwrite)

return &paramapi.PutParameterOutput{
Version: 1,
return &model.ParameterWriteResult{
Name: "/app/param",
Version: "1",
}, nil
},
},
Expand All @@ -123,8 +124,8 @@ func TestRun(t *testing.T) {
opts: create.Options{Name: "/app/param", Value: "test-value", Type: "String"},
wantErr: "failed to create parameter",
mock: &mockClient{
putParameterFunc: func(_ context.Context, _ *paramapi.PutParameterInput, _ ...func(*paramapi.Options)) (*paramapi.PutParameterOutput, error) {
return nil, &paramapi.ParameterAlreadyExists{Message: lo.ToPtr("already exists")}
putParameterFunc: func(_ context.Context, _ *model.Parameter, _ bool) (*model.ParameterWriteResult, error) {
return nil, errors.New("parameter already exists")
},
},
},
Expand All @@ -133,8 +134,8 @@ func TestRun(t *testing.T) {
opts: create.Options{Name: "/app/param", Value: "test-value", Type: "String"},
wantErr: "failed to create parameter",
mock: &mockClient{
putParameterFunc: func(_ context.Context, _ *paramapi.PutParameterInput, _ ...func(*paramapi.Options)) (*paramapi.PutParameterOutput, error) {
return nil, fmt.Errorf("AWS error")
putParameterFunc: func(_ context.Context, _ *model.Parameter, _ bool) (*model.ParameterWriteResult, error) {
return nil, errors.New("AWS error")
},
},
},
Expand Down
5 changes: 3 additions & 2 deletions internal/cli/commands/param/delete/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/mpyw/suve/internal/cli/confirm"
"github.com/mpyw/suve/internal/cli/output"
"github.com/mpyw/suve/internal/infra"
awsparam "github.com/mpyw/suve/internal/provider/aws/param"
"github.com/mpyw/suve/internal/usecase/param"
)

Expand Down Expand Up @@ -61,7 +62,7 @@ func action(ctx context.Context, cmd *cli.Command) error {
name := cmd.Args().First()
skipConfirm := cmd.Bool("yes")

client, err := infra.NewParamClient(ctx)
adapter, err := awsparam.NewAdapter(ctx)
if err != nil {
return fmt.Errorf("failed to initialize AWS client: %w", err)
}
Expand All @@ -72,7 +73,7 @@ func action(ctx context.Context, cmd *cli.Command) error {
identity, _ = infra.GetAWSIdentity(ctx)
}

useCase := &param.DeleteUseCase{Client: client}
useCase := &param.DeleteUseCase{Client: adapter}

// Show current value before confirming
if !skipConfirm {
Expand Down
Loading
Loading