fix(app): surface catalogue reset guidance
- commit
- 8171186d8cccc56f269f34e7e9183f0d1c36e175
- author
- brice · · 2026-07-26 13:25 UTC
- parent
- 836c233
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +28 -1
M internal/app/run.go +3 -0
| @@ -1297,6 +1297,9 @@ func runList(ctx context.Context) (retErr error) { | |||
| 1297 | 1297 | defer joinLifecycleReleaseError(&retErr, owner) | |
| 1298 | 1298 | records, loadErr := persist.LoadReadOnly(platform.StateDir()) | |
| 1299 | 1299 | if loadErr != nil { | |
| 1300 | + | if errors.Is(loadErr, persist.ErrCatalogueUnreadable) { | |
| 1301 | + | return unreadableCatalogueError(platform.StateDir()) | |
| 1302 | + | } | |
| 1300 | 1303 | return fmt.Errorf("vev: reading stored sessions: %w", loadErr) | |
| 1301 | 1304 | } | |
| 1302 | 1305 | infos := make([]ports.SessionInfo, 0, len(records)) | |
| @@ -1297,6 +1297,9 @@ func runList(ctx context.Context) (retErr error) { | |||||
| 1297 | defer joinLifecycleReleaseError(&retErr, owner) | 1297 | defer joinLifecycleReleaseError(&retErr, owner) | ||
| 1298 | records, loadErr := persist.LoadReadOnly(platform.StateDir()) | 1298 | records, loadErr := persist.LoadReadOnly(platform.StateDir()) | ||
| 1299 | if loadErr != nil { | 1299 | if loadErr != nil { | ||
| 1300 | + | if errors.Is(loadErr, persist.ErrCatalogueUnreadable) { | |||
| 1301 | + | return unreadableCatalogueError(platform.StateDir()) | |||
| 1302 | + | } | |||
| 1300 | return fmt.Errorf("vev: reading stored sessions: %w", loadErr) | 1303 | return fmt.Errorf("vev: reading stored sessions: %w", loadErr) | ||
| 1301 | } | 1304 | } | ||
| 1302 | infos := make([]ports.SessionInfo, 0, len(records)) | 1305 | infos := make([]ports.SessionInfo, 0, len(records)) | ||
M internal/app/run_test.go +24 -0
| @@ -721,6 +721,30 @@ func TestRunListReadsStoppedSessionsWithoutDaemon(t *testing.T) { | |||
| 721 | 721 | } | |
| 722 | 722 | } | |
| 723 | 723 | ||
| 724 | + | func TestRunListUnreadableCatalogueProvidesResetGuidance(t *testing.T) { | |
| 725 | + | stateRoot, runtimeRoot := t.TempDir(), t.TempDir() | |
| 726 | + | stateDir := filepath.Join(stateRoot, "vev") | |
| 727 | + | runtimeDir := filepath.Join(runtimeRoot, "vev") | |
| 728 | + | t.Setenv("XDG_STATE_HOME", stateRoot) | |
| 729 | + | t.Setenv("XDG_RUNTIME_DIR", runtimeRoot) | |
| 730 | + | require.NoError(t, os.Mkdir(stateDir, 0o700)) | |
| 731 | + | ||
| 732 | + | catalogue := persist.StorePath(stateDir) | |
| 733 | + | before := []byte("corrupt catalogue") | |
| 734 | + | require.NoError(t, os.WriteFile(catalogue, before, 0o600)) | |
| 735 | + | ||
| 736 | + | var runErr error | |
| 737 | + | stdout := captureStdout(t, func() { runErr = Run([]string{"ls"}) }) | |
| 738 | + | require.Empty(t, stdout) | |
| 739 | + | require.Error(t, runErr) | |
| 740 | + | require.ErrorContains(t, runErr, stateDir) | |
| 741 | + | require.ErrorContains(t, runErr, "rm -rf "+stateDir) | |
| 742 | + | require.NoFileExists(t, filepath.Join(runtimeDir, "daemon.sock")) | |
| 743 | + | after, err := os.ReadFile(catalogue) | |
| 744 | + | require.NoError(t, err) | |
| 745 | + | require.Equal(t, before, after) | |
| 746 | + | } | |
| 747 | + | ||
| 724 | 748 | func TestRunKillMissingStoppedSessionDoesNotCreateStore(t *testing.T) { | |
| 725 | 749 | stateRoot, runtimeRoot := t.TempDir(), t.TempDir() | |
| 726 | 750 | t.Setenv("XDG_STATE_HOME", stateRoot) | |
| @@ -721,6 +721,30 @@ func TestRunListReadsStoppedSessionsWithoutDaemon(t *testing.T) { | |||||
| 721 | } | 721 | } | ||
| 722 | } | 722 | } | ||
| 723 | 723 | ||||
| 724 | + | func TestRunListUnreadableCatalogueProvidesResetGuidance(t *testing.T) { | |||
| 725 | + | stateRoot, runtimeRoot := t.TempDir(), t.TempDir() | |||
| 726 | + | stateDir := filepath.Join(stateRoot, "vev") | |||
| 727 | + | runtimeDir := filepath.Join(runtimeRoot, "vev") | |||
| 728 | + | t.Setenv("XDG_STATE_HOME", stateRoot) | |||
| 729 | + | t.Setenv("XDG_RUNTIME_DIR", runtimeRoot) | |||
| 730 | + | require.NoError(t, os.Mkdir(stateDir, 0o700)) | |||
| 731 | + | ||||
| 732 | + | catalogue := persist.StorePath(stateDir) | |||
| 733 | + | before := []byte("corrupt catalogue") | |||
| 734 | + | require.NoError(t, os.WriteFile(catalogue, before, 0o600)) | |||
| 735 | + | ||||
| 736 | + | var runErr error | |||
| 737 | + | stdout := captureStdout(t, func() { runErr = Run([]string{"ls"}) }) | |||
| 738 | + | require.Empty(t, stdout) | |||
| 739 | + | require.Error(t, runErr) | |||
| 740 | + | require.ErrorContains(t, runErr, stateDir) | |||
| 741 | + | require.ErrorContains(t, runErr, "rm -rf "+stateDir) | |||
| 742 | + | require.NoFileExists(t, filepath.Join(runtimeDir, "daemon.sock")) | |||
| 743 | + | after, err := os.ReadFile(catalogue) | |||
| 744 | + | require.NoError(t, err) | |||
| 745 | + | require.Equal(t, before, after) | |||
| 746 | + | } | |||
| 747 | + | ||||
| 724 | func TestRunKillMissingStoppedSessionDoesNotCreateStore(t *testing.T) { | 748 | func TestRunKillMissingStoppedSessionDoesNotCreateStore(t *testing.T) { | ||
| 725 | stateRoot, runtimeRoot := t.TempDir(), t.TempDir() | 749 | stateRoot, runtimeRoot := t.TempDir(), t.TempDir() | ||
| 726 | t.Setenv("XDG_STATE_HOME", stateRoot) | 750 | t.Setenv("XDG_STATE_HOME", stateRoot) | ||
M internal/persist/persist.go +1 -1
| @@ -133,7 +133,7 @@ func LoadCatalogueReadOnly(dir string) ([]domain.CatalogueRecord, error) { | |||
| 133 | 133 | if errors.Is(err, errPersistenceUnavailable) { | |
| 134 | 134 | return []domain.CatalogueRecord{}, nil | |
| 135 | 135 | } | |
| 136 | - | return nil, err | |
| 136 | + | return nil, fmt.Errorf("%w: %s: %w", ErrCatalogueUnreadable, StorePath(dir), err) | |
| 137 | 137 | } | |
| 138 | 138 | if err := p.Close(); err != nil { | |
| 139 | 139 | return nil, err | |
| @@ -133,7 +133,7 @@ func LoadCatalogueReadOnly(dir string) ([]domain.CatalogueRecord, error) { | |||||
| 133 | if errors.Is(err, errPersistenceUnavailable) { | 133 | if errors.Is(err, errPersistenceUnavailable) { | ||
| 134 | return []domain.CatalogueRecord{}, nil | 134 | return []domain.CatalogueRecord{}, nil | ||
| 135 | } | 135 | } | ||
| 136 | - | return nil, err | 136 | + | return nil, fmt.Errorf("%w: %s: %w", ErrCatalogueUnreadable, StorePath(dir), err) |
| 137 | } | 137 | } | ||
| 138 | if err := p.Close(); err != nil { | 138 | if err := p.Close(); err != nil { | ||
| 139 | return nil, err | 139 | return nil, err | ||