Skip to content

Commit c4e4578

Browse files
refactor(docs): separate file management concerns from DocsService (#267)
* refactor(docs): separate file management concerns from DocsService * Update workspace-server/src/services/DriveService.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update workspace-server/src/services/DriveService.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix(drive): escape query in findFolder, add isError to handleError, use extractDocumentId in getComments --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 7c04868 commit c4e4578

7 files changed

Lines changed: 444 additions & 619 deletions

File tree

workspace-server/WORKSPACE-Context.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,13 @@ Should I create this event?
7878

7979
When creating documents in specific folders:
8080

81-
1. Create the document first
82-
2. Then move it to the folder (if specified)
81+
1. Create the document with `docs.create` (blank)
82+
2. Move it to the target folder with `drive.moveFile`
8383
3. Confirm successful completion
8484

85+
To find Google Docs, use `drive.search` with a document MIME type filter rather
86+
than searching by name alone.
87+
8588
## 📄 Docs, Sheets, and Slides
8689

8790
### Format Selection (Sheets)

workspace-server/src/__tests__/services/DocsService.comments.test.ts

Lines changed: 1 addition & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
afterEach,
1414
} from '@jest/globals';
1515
import { DocsService } from '../../services/DocsService';
16-
import { DriveService } from '../../services/DriveService';
1716
import { AuthManager } from '../../auth/AuthManager';
1817
import { google } from 'googleapis';
1918

@@ -24,9 +23,7 @@ jest.mock('../../utils/logger');
2423
describe('DocsService Comments and Suggestions', () => {
2524
let docsService: DocsService;
2625
let mockAuthManager: jest.Mocked<AuthManager>;
27-
let mockDriveService: jest.Mocked<DriveService>;
2826
let mockDocsAPI: any;
29-
let mockDriveAPI: any;
3027

3128
beforeEach(() => {
3229
jest.clearAllMocks();
@@ -35,10 +32,6 @@ describe('DocsService Comments and Suggestions', () => {
3532
getAuthenticatedClient: jest.fn(),
3633
} as any;
3734

38-
mockDriveService = {
39-
findFolder: jest.fn(),
40-
} as any;
41-
4235
mockDocsAPI = {
4336
documents: {
4437
get: jest.fn(),
@@ -47,22 +40,9 @@ describe('DocsService Comments and Suggestions', () => {
4740
},
4841
};
4942

50-
mockDriveAPI = {
51-
files: {
52-
create: jest.fn(),
53-
list: jest.fn(),
54-
get: jest.fn(),
55-
update: jest.fn(),
56-
},
57-
comments: {
58-
list: jest.fn(),
59-
},
60-
};
61-
6243
(google.docs as jest.Mock) = jest.fn().mockReturnValue(mockDocsAPI);
63-
(google.drive as jest.Mock) = jest.fn().mockReturnValue(mockDriveAPI);
6444

65-
docsService = new DocsService(mockAuthManager, mockDriveService);
45+
docsService = new DocsService(mockAuthManager);
6646

6747
const mockAuthClient = { access_token: 'test-token' };
6848
mockAuthManager.getAuthenticatedClient.mockResolvedValue(
@@ -416,164 +396,4 @@ describe('DocsService Comments and Suggestions', () => {
416396
expect(suggestions[0].text).toBe('');
417397
});
418398
});
419-
420-
describe('getComments', () => {
421-
it('should return comments as type text with JSON-stringified array', async () => {
422-
const mockComments = [
423-
{
424-
id: 'comment1',
425-
content: 'This is a comment.',
426-
author: {
427-
displayName: 'Test User',
428-
emailAddress: 'test@example.com',
429-
},
430-
createdTime: '2025-01-01T00:00:00Z',
431-
resolved: false,
432-
quotedFileContent: { value: 'quoted text' },
433-
replies: [],
434-
},
435-
];
436-
mockDriveAPI.comments.list.mockResolvedValue({
437-
data: { comments: mockComments },
438-
});
439-
440-
const result = await docsService.getComments({
441-
documentId: 'test-doc-id',
442-
});
443-
444-
expect(result.content[0].type).toBe('text');
445-
const comments = JSON.parse(result.content[0].text);
446-
expect(comments).toEqual(mockComments);
447-
});
448-
449-
it('should include replies in comment threads', async () => {
450-
const mockComments = [
451-
{
452-
id: 'comment1',
453-
content: 'Top-level comment.',
454-
author: { displayName: 'Alice', emailAddress: 'alice@example.com' },
455-
createdTime: '2025-01-01T00:00:00Z',
456-
resolved: false,
457-
quotedFileContent: { value: 'some text' },
458-
replies: [
459-
{
460-
id: 'reply1',
461-
content: 'Reply to comment.',
462-
author: {
463-
displayName: 'Bob',
464-
emailAddress: 'bob@example.com',
465-
},
466-
createdTime: '2025-01-02T00:00:00Z',
467-
},
468-
],
469-
},
470-
];
471-
mockDriveAPI.comments.list.mockResolvedValue({
472-
data: { comments: mockComments },
473-
});
474-
475-
const result = await docsService.getComments({
476-
documentId: 'test-doc-id',
477-
});
478-
479-
expect(result.content[0].type).toBe('text');
480-
const comments = JSON.parse(result.content[0].text);
481-
expect(comments).toHaveLength(1);
482-
expect(comments[0].replies).toHaveLength(1);
483-
expect(comments[0].replies[0].id).toBe('reply1');
484-
expect(comments[0].replies[0].content).toBe('Reply to comment.');
485-
});
486-
487-
it('should request replies fields in the Drive API call', async () => {
488-
mockDriveAPI.comments.list.mockResolvedValue({ data: { comments: [] } });
489-
490-
await docsService.getComments({ documentId: 'test-doc-id' });
491-
492-
expect(mockDriveAPI.comments.list).toHaveBeenCalledWith(
493-
expect.objectContaining({
494-
fields: expect.stringContaining('replies('),
495-
}),
496-
);
497-
});
498-
499-
it('should handle empty comments list', async () => {
500-
mockDriveAPI.comments.list.mockResolvedValue({
501-
data: { comments: [] },
502-
});
503-
504-
const result = await docsService.getComments({
505-
documentId: 'test-doc-id',
506-
});
507-
508-
const comments = JSON.parse(result.content[0].text);
509-
expect(comments).toEqual([]);
510-
});
511-
512-
it('should handle API errors gracefully', async () => {
513-
mockDriveAPI.comments.list.mockRejectedValue(
514-
new Error('Comments API failed'),
515-
);
516-
517-
const result = await docsService.getComments({
518-
documentId: 'test-doc-id',
519-
});
520-
521-
expect(result.isError).toBe(true);
522-
expect(result.content[0].type).toBe('text');
523-
const parsed = JSON.parse(result.content[0].text);
524-
expect(parsed).toEqual({ error: 'Comments API failed' });
525-
});
526-
527-
it('should return resolved comments with reply actions', async () => {
528-
const mockComments = [
529-
{
530-
id: 'comment1',
531-
content: 'Please fix this typo.',
532-
author: {
533-
displayName: 'Alice',
534-
emailAddress: 'alice@example.com',
535-
},
536-
createdTime: '2025-01-01T00:00:00Z',
537-
resolved: true,
538-
quotedFileContent: { value: 'teh' },
539-
replies: [
540-
{
541-
id: 'reply1',
542-
content: 'Fixed!',
543-
author: {
544-
displayName: 'Bob',
545-
emailAddress: 'bob@example.com',
546-
},
547-
createdTime: '2025-01-02T00:00:00Z',
548-
action: 'resolve',
549-
},
550-
],
551-
},
552-
];
553-
mockDriveAPI.comments.list.mockResolvedValue({
554-
data: { comments: mockComments },
555-
});
556-
557-
const result = await docsService.getComments({
558-
documentId: 'test-doc-id',
559-
});
560-
561-
const comments = JSON.parse(result.content[0].text);
562-
expect(comments).toHaveLength(1);
563-
expect(comments[0].resolved).toBe(true);
564-
expect(comments[0].replies[0].action).toBe('resolve');
565-
});
566-
567-
it('should request action field in replies', async () => {
568-
mockDriveAPI.comments.list.mockResolvedValue({ data: { comments: [] } });
569-
570-
await docsService.getComments({ documentId: 'test-doc-id' });
571-
572-
expect(mockDriveAPI.comments.list).toHaveBeenCalledWith(
573-
expect.objectContaining({
574-
fields: expect.stringContaining('action'),
575-
}),
576-
);
577-
});
578-
});
579399
});

0 commit comments

Comments
 (0)