diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 82c8e8d7..113e99c5 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -7,10 +7,40 @@ import ( "testing" "github.com/pelletier/go-toml/v2" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// TestLoadFirstRunAfterConfigDirRecreated reproduces the scenario where a user +// deletes ~/.config/lstk/ and then runs lstk. newLogger() recreates the directory +// (for lstk.log) before config.Load() is called, so Load() must detect the +// MISSING FILE — not just the directory — to correctly set firstRun=true. +func TestLoadFirstRunAfterConfigDirRecreated(t *testing.T) { + // Cannot run in parallel: mutates process-wide HOME env and viper state. + fakeHome := t.TempDir() + t.Setenv("HOME", fakeHome) + t.Setenv("XDG_CONFIG_HOME", "") + viper.Reset() + t.Cleanup(viper.Reset) + + // ~/.config/ exists so the creation dir is ~/.config/lstk (not the OS default). + homeConfig := filepath.Join(fakeHome, ".config") + require.NoError(t, os.MkdirAll(homeConfig, 0755)) + + // Simulate newLogger() recreating ~/.config/lstk/ (with a log file) after + // the user deleted the whole directory but before config.Load() runs. + configDir := filepath.Join(homeConfig, "lstk") + require.NoError(t, os.MkdirAll(configDir, 0755)) + require.NoError(t, os.WriteFile(filepath.Join(configDir, "lstk.log"), []byte("log\n"), 0600)) + // config.toml is intentionally absent — the user deleted it along with the dir. + + firstRun, err := Load() + + require.NoError(t, err) + assert.True(t, firstRun, "Load() should return firstRun=true when the config directory exists but config.toml does not") +} + func TestSetInFileAppendsWhenKeyAbsent(t *testing.T) { path := filepath.Join(t.TempDir(), "config.toml") original := `# User comment diff --git a/test/integration/emulator_select_test.go b/test/integration/emulator_select_test.go index 2a992564..deb638c5 100644 --- a/test/integration/emulator_select_test.go +++ b/test/integration/emulator_select_test.go @@ -293,6 +293,61 @@ func TestEmulatorSelectionReappearsAfterFailedFirstRun(t *testing.T) { <-outputCh } +// Deleting the config directory after a successful run must trigger the emulator +// selector again on the next run — the selector is gated on the config file being +// absent, so the directory alone must not count as "already configured". +func TestEmulatorSelectionReappearsAfterConfigDirDeleted(t *testing.T) { + requireDocker(t) + t.Parallel() + if runtime.GOOS == "windows" { + t.Skip("PTY not supported on Windows") + } + + tmpHome := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(tmpHome, ".config"), 0755)) + e := env.Environ(testEnvWithHome(tmpHome, tmpHome)). + With(env.DisableEvents, "1"). + With(env.AuthToken, "test-token") + + // Resolve where lstk would create the config, then pre-create it so lstk + // believes this is not a first run (simulates a previous successful start). + configPath, _, err := runLstk(t, testContext(t), "", e, "config", "path") + require.NoError(t, err) + require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0755)) + require.NoError(t, os.WriteFile(configPath, []byte("[[containers]]\ntype = \"aws\"\ntag = \"latest\"\nport = \"4566\"\n"), 0644)) + require.FileExists(t, configPath) + + // Delete the entire config directory — this is what the user reported. + require.NoError(t, os.RemoveAll(filepath.Dir(configPath))) + require.NoFileExists(t, configPath) + + // The next run must show the emulator selector again, not silently default to AWS. + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + cmd := exec.CommandContext(ctx, binaryPath(), "start") + cmd.Env = e + + ptmx, err := pty.Start(cmd) + require.NoError(t, err, "failed to start lstk in PTY") + defer func() { _ = ptmx.Close() }() + + out := &syncBuffer{} + outputCh := make(chan struct{}) + go func() { + _, _ = io.Copy(out, ptmx) + close(outputCh) + }() + + require.Eventually(t, func() bool { + return bytes.Contains(out.Bytes(), []byte("Which emulator would you like to use?")) + }, 10*time.Second, 100*time.Millisecond, + "emulator selection prompt should reappear after the config directory is deleted") + + cancel() + <-outputCh +} + func TestFirstRunChecksDockerBeforeAuthAndSelection(t *testing.T) { t.Parallel() if runtime.GOOS == "windows" {