fix(daily-briefing): add calendar/tasks scope re-auth guidance
This commit is contained in:
@@ -185,6 +185,19 @@ describe('calendar.today', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('API quota exceeded');
|
||||
expect(result.error).not.toContain('flynn gcal-auth');
|
||||
});
|
||||
|
||||
it('surfaces re-auth hint for insufficient scopes', async () => {
|
||||
setupValidAuth();
|
||||
mockEventsList.mockRejectedValue(new Error('Request had insufficient authentication scopes.'));
|
||||
|
||||
const [todayTool] = createGcalTools(testConfig);
|
||||
const result = await todayTool.execute({});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('insufficient authentication scopes');
|
||||
expect(result.error).toContain('flynn gcal-auth');
|
||||
});
|
||||
|
||||
it('respects calendarId parameter', async () => {
|
||||
@@ -326,6 +339,7 @@ describe('calendar.search', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('API quota exceeded');
|
||||
expect(result.error).not.toContain('flynn gcal-auth');
|
||||
});
|
||||
|
||||
it('formats events with all fields', async () => {
|
||||
|
||||
@@ -109,6 +109,20 @@ function formatEvents(events: EventSummary[]): string {
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
function parseErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
function formatToolError(message: string): string {
|
||||
const needsScopeHint = /insufficient.*scope|insufficientPermissions|Request had insufficient authentication scopes/i.test(message);
|
||||
return needsScopeHint
|
||||
? `${message}. Re-run "flynn gcal-auth" to grant Google Calendar read permissions.`
|
||||
: message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Google Calendar query tools bound to the given GcalConfig.
|
||||
* Tools create their own OAuth2 client per invocation.
|
||||
@@ -150,10 +164,11 @@ export function createGcalTools(config: NonNullable<GcalConfig>): Tool[] {
|
||||
output: formatEvents(events),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -211,10 +226,11 @@ export function createGcalTools(config: NonNullable<GcalConfig>): Tool[] {
|
||||
output: formatEvents(events),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -268,10 +284,11 @@ export function createGcalTools(config: NonNullable<GcalConfig>): Tool[] {
|
||||
output: formatEvents(events),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -171,6 +171,19 @@ describe('tasks.lists', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('API quota exceeded');
|
||||
expect(result.error).not.toContain('flynn gtasks-auth');
|
||||
});
|
||||
|
||||
it('surfaces re-auth hint for insufficient scopes', async () => {
|
||||
setupValidAuth();
|
||||
mockTasklistsList.mockRejectedValue(new Error('Request had insufficient authentication scopes.'));
|
||||
|
||||
const [listsTool] = createGtasksTools(testConfig);
|
||||
const result = await listsTool.execute({});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('insufficient authentication scopes');
|
||||
expect(result.error).toContain('flynn gtasks-auth');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -258,6 +271,7 @@ describe('tasks.list', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Not Found');
|
||||
expect(result.error).not.toContain('flynn gtasks-auth');
|
||||
});
|
||||
|
||||
it('respects maxResults parameter', async () => {
|
||||
|
||||
@@ -94,6 +94,20 @@ function formatTasks(tasks: TaskSummary[]): string {
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
function parseErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
function formatToolError(message: string): string {
|
||||
const needsScopeHint = /insufficient.*scope|insufficientPermissions|Request had insufficient authentication scopes/i.test(message);
|
||||
return needsScopeHint
|
||||
? `${message}. Re-run "flynn gtasks-auth" to grant Google Tasks read permissions.`
|
||||
: message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Google Tasks read-only tools bound to the given GtasksConfig.
|
||||
* Tools create their own OAuth2 client per invocation.
|
||||
@@ -137,10 +151,11 @@ export function createGtasksTools(config: NonNullable<GtasksConfig>): Tool[] {
|
||||
output: formatTaskLists(lists),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -204,10 +219,11 @@ export function createGtasksTools(config: NonNullable<GtasksConfig>): Tool[] {
|
||||
output: formatTasks(taskList),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user