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
126 changes: 126 additions & 0 deletions cmd/sync/bootstrap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package sync

import (
"net/url"
"os"
"path/filepath"
"strconv"
"testing"

"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/launchdarkly/ldcli/cmd/cliflags"
"github.com/launchdarkly/ldcli/internal/config"
"github.com/launchdarkly/ldcli/internal/resources"
syncdomain "github.com/launchdarkly/ldcli/internal/sync"
syncbootstrap "github.com/launchdarkly/ldcli/internal/sync/bootstrap"
)

func TestRunPromptBootstrapsAndAddsVariations(t *testing.T) {
tests := map[string]struct {
createDirectory bool
add bool
wantInitial bool
}{
"missing workspace bootstraps without Git": {
wantInitial: true,
},
"add uses the existing workspace": {
createDirectory: true,
add: true,
wantInitial: false,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
root := t.TempDir()
if test.createDirectory {
require.NoError(t, os.Mkdir(
filepath.Join(root, syncdomain.RootDir),
0o755,
))
}
t.Chdir(root)
t.Setenv("XDG_CONFIG_HOME", t.TempDir())

var called bool
runner := func(options syncbootstrap.Options) error {
called = true
assert.Equal(t, test.wantInitial, options.Initial)
assert.NotNil(t, options.Catalog)
assert.NotNil(t, options.Input)
assert.NotNil(t, options.Output)

return nil
}

viper.Set(cliflags.AccessTokenFlag, "token")
viper.Set(cliflags.BaseURIFlag, "https://example.com")
t.Cleanup(viper.Reset)

command := newPromptCmd(noopResourceClient{}, runner)
require.NoError(t, command.Flags().Set(
addFlag,
strconv.FormatBool(test.add),
))
require.NoError(t, command.RunE(command, nil))
assert.True(t, called)
if test.wantInitial {
_, err := os.Stat(config.GetConfigFile())
assert.ErrorIs(t, err, os.ErrNotExist)
}
})
}
}

func TestRunPromptUsesExistingWorkspaceWithoutBootstrap(t *testing.T) {
root := t.TempDir()
require.NoError(t, os.Mkdir(
filepath.Join(root, syncdomain.RootDir),
0o755,
))
t.Chdir(root)
t.Setenv("XDG_CONFIG_HOME", t.TempDir())

called := false
runner := func(syncbootstrap.Options) error {
called = true

return nil
}

viper.Set(cliflags.AccessTokenFlag, "token")
viper.Set(cliflags.BaseURIFlag, "https://example.com")
t.Cleanup(viper.Reset)

command := newPromptCmd(noopResourceClient{}, runner)
require.NoError(t, command.RunE(command, nil))
assert.False(t, called)
}

type noopResourceClient struct{}

var _ resources.Client = noopResourceClient{}

func (noopResourceClient) MakeRequest(
string,
string,
string,
string,
url.Values,
[]byte,
bool,
) ([]byte, error) {
return nil, nil
}

func (noopResourceClient) MakeUnauthenticatedRequest(
string,
string,
[]byte,
) ([]byte, error) {
return nil, nil
}
72 changes: 65 additions & 7 deletions cmd/sync/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,45 @@ import (
"github.com/launchdarkly/ldcli/internal/output"
"github.com/launchdarkly/ldcli/internal/resources"
syncapi "github.com/launchdarkly/ldcli/internal/sync/api"
syncbootstrap "github.com/launchdarkly/ldcli/internal/sync/bootstrap"
synclocal "github.com/launchdarkly/ldcli/internal/sync/local"
syncsource "github.com/launchdarkly/ldcli/internal/sync/source"
)

const dryRunFlag = "dry-run"
const (
addFlag = "add"
dryRunFlag = "dry-run"
)

type bootstrapRunner func(syncbootstrap.Options) error

func NewPromptCmd(client resources.Client) *cobra.Command {
return newPromptCmd(client, syncbootstrap.Run)
}

func newPromptCmd(
client resources.Client,
bootstrap bootstrapRunner,
) *cobra.Command {
cmd := &cobra.Command{
Use: "prompt",
Short: "Synchronize local prompt variations with LaunchDarkly",
Long: "Plan synchronization changes for local prompt variations. Use --dry-run to preview changes without creating a plan.",
Long: "Bootstrap local prompt variations from LaunchDarkly, add more variations, or preview synchronization changes.",
Args: func(cmd *cobra.Command, args []string) error {
if err := cobra.NoArgs(cmd, args); err != nil {
return err
}

return validators.Validate()(cmd, args)
},
RunE: runPrompt(client),
RunE: runPrompt(client, bootstrap),
}

cmd.Flags().Bool(
addFlag,
false,
"Select additional prompt variations from LaunchDarkly",
)
cmd.Flags().Bool(
dryRunFlag,
false,
Expand All @@ -45,14 +63,54 @@ func NewPromptCmd(client resources.Client) *cobra.Command {
return cmd
}

func runPrompt(client resources.Client) func(*cobra.Command, []string) error {
func runPrompt(
client resources.Client,
bootstrap bootstrapRunner,
) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, _ []string) error {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("get working directory: %w", err)
}

workspace, err := syncsource.NewResolver(config.GetConfigFile()).Resolve(cwd)
resolver := syncsource.NewResolver(config.GetConfigFile())
root, err := resolver.ResolveRoot(cwd)
if err != nil {
return err
}

accessToken := viper.GetString(cliflags.AccessTokenFlag)
baseURI := viper.GetString(cliflags.BaseURIFlag)
store := synclocal.NewStore(root)

storeExists, err := store.Exists()
if err != nil {
return err
}
add, _ := cmd.Flags().GetBool(addFlag)
if !storeExists || add {
err := bootstrap(syncbootstrap.Options{
Catalog: syncapi.NewCatalogClient(
client,
accessToken,
baseURI,
),
Store: store,
Input: cmd.InOrStdin(),
Output: cmd.OutOrStdout(),
Initial: !storeExists,
})
if err != nil {
return output.NewCmdOutputError(
err,
cliflags.GetOutputKind(cmd),
)
}

return nil
}

workspace, err := resolver.Resolve(cwd)
if err != nil {
return err
}
Expand All @@ -64,8 +122,8 @@ func runPrompt(client resources.Client) func(*cobra.Command, []string) error {

dryRun, _ := cmd.Flags().GetBool(dryRunFlag)
plans, err := syncapi.NewClient(client).Plan(
viper.GetString(cliflags.AccessTokenFlag),
viper.GetString(cliflags.BaseURIFlag),
accessToken,
baseURI,
workspace.Source,
dryRun,
localResources,
Expand Down
Loading
Loading