diff --git a/src/platforms/slack/commands/auth.test.ts b/src/platforms/slack/commands/auth.test.ts index 7b8b215..0cb5194 100644 --- a/src/platforms/slack/commands/auth.test.ts +++ b/src/platforms/slack/commands/auth.test.ts @@ -2,6 +2,7 @@ import { afterAll, beforeEach, describe, expect, mock, test } from 'bun:test' import { mkdirSync, rmSync } from 'node:fs' import { homedir } from 'node:os' import { join } from 'node:path' +import { getExtractionErrorMessage } from '@/platforms/slack/commands/auth' import { CredentialManager } from '@/platforms/slack/credential-manager' import { type ExtractedWorkspace, TokenExtractor } from '@/platforms/slack/token-extractor' @@ -351,6 +352,28 @@ describe('Output Formatting', () => { }) }) +describe('getExtractionErrorMessage', () => { + test('returns cookie failure message for missing_cookie', () => { + expect(getExtractionErrorMessage(['missing_cookie'])).toContain('Cookie extraction failed') + }) + + test('returns session expired message for invalid_auth', () => { + expect(getExtractionErrorMessage(['invalid_auth'])).toContain('session has expired') + }) + + test('prioritizes missing_cookie over invalid_auth', () => { + expect(getExtractionErrorMessage(['invalid_auth', 'missing_cookie'])).toContain('Cookie extraction failed') + }) + + test('returns generic message for unknown error codes', () => { + expect(getExtractionErrorMessage(['unknown_error'])).toContain('Extracted tokens are invalid') + }) + + test('returns generic message for empty failure list', () => { + expect(getExtractionErrorMessage([])).toContain('Extracted tokens are invalid') + }) +}) + describe('Error Handling', () => { beforeEach(() => { rmSync(testSlackDir, { recursive: true, force: true }) diff --git a/src/platforms/slack/commands/auth.ts b/src/platforms/slack/commands/auth.ts index 091d04e..6ae099b 100644 --- a/src/platforms/slack/commands/auth.ts +++ b/src/platforms/slack/commands/auth.ts @@ -1,7 +1,7 @@ import { Command } from 'commander' import { handleError } from '@/shared/utils/error-handler' import { formatOutput } from '@/shared/utils/output' -import { SlackClient } from '../client' +import { SlackClient, SlackError } from '../client' import { CredentialManager } from '../credential-manager' import { TokenExtractor } from '../token-extractor' @@ -58,6 +58,7 @@ async function extractAction(options: { pretty?: boolean; debug?: boolean }): Pr const config = await credManager.load() const validWorkspaces = [] + const failureReasons: string[] = [] for (const ws of workspaces) { if (options.debug) { console.error(`[debug] Testing credentials for ${ws.workspace_id}...`) @@ -74,6 +75,10 @@ async function extractAction(options: { pretty?: boolean; debug?: boolean }): Pr console.error(`[debug] ✓ Valid: ${authInfo.team} (${authInfo.user})`) } } catch (error) { + const code = error instanceof SlackError ? error.code : undefined + if (code && !failureReasons.includes(code)) { + failureReasons.push(code) + } if (options.debug) { console.error(`[debug] ✗ Invalid: ${(error as Error).message}`) } @@ -81,11 +86,13 @@ async function extractAction(options: { pretty?: boolean; debug?: boolean }): Pr } if (validWorkspaces.length === 0) { + const errorMessage = getExtractionErrorMessage(failureReasons) console.log( formatOutput( { - error: 'Extracted tokens are invalid. Make sure you are logged into the Slack desktop app.', + error: errorMessage, extracted_count: workspaces.length, + hint: options.debug ? undefined : 'Run with --debug for more details.', }, options.pretty, ), @@ -171,6 +178,16 @@ async function statusAction(options: { pretty?: boolean }): Promise { } } +export function getExtractionErrorMessage(failureReasons: string[]): string { + if (failureReasons.includes('missing_cookie')) { + return 'Cookie extraction failed. Make sure the Slack desktop app is installed and grant Keychain access when prompted.' + } + if (failureReasons.includes('invalid_auth')) { + return 'Slack session has expired. Sign into the Slack desktop app, wait a few seconds, then re-run this command.' + } + return 'Extracted tokens are invalid. Make sure you are logged into the Slack desktop app.' +} + export const authCommand = new Command('auth') .description('Authentication commands') .addCommand(