@@ -8,6 +8,8 @@ import (
11+ "github.com/charmbracelet/crush/internal/csync"
12+ "github.com/charmbracelet/crush/internal/oauth"
11 "github.com/stretchr/testify/require"
13 "github.com/stretchr/testify/require"
@@ -510,3 +512,183 @@ func TestAutoReloadDisabledDuringReload(t *testing.T) {
510 // Verify reload completed successfully
512 // Verify reload completed successfully
511 require.False(t, store.autoReloadDisabled, "autoReloadDisabled should be false after ReloadFromDisk")
513 require.False(t, store.autoReloadDisabled, "autoReloadDisabled should be false after ReloadFromDisk")
516+func TestLoadTokenFromDisk_ReturnsNewerToken(t *testing.T) {
520+ configPath := filepath.Join(dir, "crush.json")
522+ // Create config file with a newer token on disk
527+ "access_token": "newer-token-from-disk",
528+ "refresh_token": "refresh-abc",
530+ "expires_at": 9999999999
535+ require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o600))
537+ store := &ConfigStore{
539+ globalDataPath: configPath,
542+ token, err := store.loadTokenFromDisk(ScopeGlobal, "hyper")
543+ require.NoError(t, err)
544+ require.NotNil(t, token)
545+ require.Equal(t, "newer-token-from-disk", token.AccessToken)
546+ require.Equal(t, "refresh-abc", token.RefreshToken)
547+ require.Equal(t, 3600, token.ExpiresIn)
548+ require.Equal(t, int64(9999999999), token.ExpiresAt)
551+func TestLoadTokenFromDisk_ReturnsNilWhenSameToken(t *testing.T) {
555+ configPath := filepath.Join(dir, "crush.json")
557+ // Create config file with the same token
562+ "access_token": "same-token",
563+ "refresh_token": "refresh-abc",
565+ "expires_at": 9999999999
570+ require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o600))
572+ store := &ConfigStore{
574+ globalDataPath: configPath,
577+ token, err := store.loadTokenFromDisk(ScopeGlobal, "hyper")
578+ require.NoError(t, err)
579+ require.NotNil(t, token)
580+ require.Equal(t, "same-token", token.AccessToken)
583+func TestLoadTokenFromDisk_ReturnsNilWhenFileMissing(t *testing.T) {
587+ configPath := filepath.Join(dir, "nonexistent.json")
589+ store := &ConfigStore{
591+ globalDataPath: configPath,
594+ token, err := store.loadTokenFromDisk(ScopeGlobal, "hyper")
595+ require.NoError(t, err)
596+ require.Nil(t, token)
599+func TestLoadTokenFromDisk_ReturnsNilWhenProviderMissing(t *testing.T) {
603+ configPath := filepath.Join(dir, "crush.json")
605+ // Create config file without the hyper provider
606+ configContent := `{"providers": {"openai": {"api_key": "test-key"}}}`
607+ require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o600))
609+ store := &ConfigStore{
611+ globalDataPath: configPath,
614+ token, err := store.loadTokenFromDisk(ScopeGlobal, "hyper")
615+ require.NoError(t, err)
616+ require.Nil(t, token)
619+func TestLoadTokenFromDisk_ReturnsNilWhenOAuthMissing(t *testing.T) {
623+ configPath := filepath.Join(dir, "crush.json")
625+ // Create config file with provider but no OAuth token
626+ configContent := `{"providers": {"hyper": {"api_key": "test-key"}}}`
627+ require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o600))
629+ store := &ConfigStore{
631+ globalDataPath: configPath,
634+ token, err := store.loadTokenFromDisk(ScopeGlobal, "hyper")
635+ require.NoError(t, err)
636+ require.Nil(t, token)
639+func TestRefreshOAuthToken_UsesDiskTokenWhenDifferent(t *testing.T) {
643+ configPath := filepath.Join(dir, "crush.json")
645+ // Create config file with a newer token on disk
649+ "api_key": "newer-access-token",
651+ "access_token": "newer-access-token",
652+ "refresh_token": "refresh-abc",
654+ "expires_at": 9999999999
659+ require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o600))
661+ // Set up store with an older in-memory token
662+ oldToken := &oauth.Token{
663+ AccessToken: "older-access-token",
664+ RefreshToken: "refresh-abc",
666+ ExpiresAt: time.Now().Add(-time.Hour).Unix(), // Expired
669+ providers := csync.NewMap[string, ProviderConfig]()
670+ providers.Set("hyper", ProviderConfig{
673+ APIKey: oldToken.AccessToken,
674+ OAuthToken: oldToken,
677+ store := &ConfigStore{
679+ Providers: providers,
681+ globalDataPath: configPath,
684+ // Refresh should use the disk token without making an external call
685+ err := store.RefreshOAuthToken(context.Background(), ScopeGlobal, "hyper")
686+ require.NoError(t, err)
688+ // Verify the in-memory token was updated to the disk token
689+ updatedConfig, ok := store.config.Providers.Get("hyper")
691+ require.Equal(t, "newer-access-token", updatedConfig.APIKey)
692+ require.Equal(t, "newer-access-token", updatedConfig.OAuthToken.AccessToken)
693+ require.Equal(t, "refresh-abc", updatedConfig.OAuthToken.RefreshToken)