Skip to content

Commit 6ce11f1

Browse files
committed
Added support for safe partial responses to SafeCommentsCollection
1 parent 7abf4ba commit 6ce11f1

File tree

5 files changed

+219
-103
lines changed

5 files changed

+219
-103
lines changed

src/backend/move/copyFileComments.ts

+36-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { DeepPick } from "../utils/DeepPick";
12
import type {
23
SafeComment,
34
SafeCommentList,
@@ -6,6 +7,18 @@ import type {
67

78
import { paginationHelper_ } from "../utils/paginationHelper";
89

10+
interface CommentKeys {
11+
anchor: true;
12+
author: {
13+
displayName: true;
14+
me: true;
15+
};
16+
content: true;
17+
quotedFileContent: true;
18+
replies: true;
19+
resolved: true;
20+
}
21+
922
export function copyFileComments_(
1023
sourceID: string,
1124
destinationID: string,
@@ -31,15 +44,30 @@ export function copyFileComments_(
3144
function listFileComments_(
3245
fileID: string,
3346
driveService: SafeDriveService_,
34-
): Array<SafeComment> {
35-
return paginationHelper_<SafeCommentList, SafeComment>(
47+
): Array<DeepPick<SafeComment, CommentKeys>> {
48+
return paginationHelper_<
49+
SafeCommentList<CommentKeys>,
50+
DeepPick<SafeComment, CommentKeys>
51+
>(
3652
(pageToken) =>
37-
driveService.Comments.list(fileID, {
38-
fields:
39-
"nextPageToken, comments(author(me, displayName), content, resolved, quotedFileContent, anchor, replies(author(me, displayName), content, action))",
40-
maxResults: 100,
41-
pageToken,
42-
}),
53+
driveService.Comments.list(
54+
fileID,
55+
{
56+
anchor: true,
57+
author: {
58+
displayName: true,
59+
me: true,
60+
},
61+
content: true,
62+
quotedFileContent: true,
63+
replies: true,
64+
resolved: true,
65+
},
66+
{
67+
maxResults: 100,
68+
pageToken,
69+
},
70+
),
4371
(response) => response.comments,
4472
);
4573
}

src/backend/utils/SafeDriveService/SafeCommentsCollection.ts

+39-20
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
1+
import type { DeepKeyof } from "../DeepKeyof";
2+
import type { DeepPick } from "../DeepPick";
3+
4+
import { stringifyFields_ } from "./stringifyFields";
5+
16
export interface SafeComment {
7+
anchor: string;
28
author: SafeUser;
39
content: string;
410
id: string;
11+
quotedFileContent: {
12+
mimeType: string;
13+
value: string;
14+
};
515
replies: Array<SafeReply>;
16+
resolved: boolean;
617
}
718

8-
export interface SafeCommentList {
9-
comments: Array<SafeComment>;
19+
export interface SafeCommentList<F extends DeepKeyof<SafeComment>> {
20+
comments: Array<DeepPick<SafeComment, F>>;
1021
nextPageToken?: string | undefined;
1122
}
1223

@@ -20,23 +31,27 @@ interface SafeUser {
2031
me: boolean;
2132
}
2233

23-
function commentIsSafe_(
34+
function commentIsSafe_<F extends DeepKeyof<SafeComment>>(
2435
comment: GoogleAppsScript.Drive_v3.Drive.V3.Schema.Comment,
25-
): comment is SafeComment {
36+
keys: F,
37+
): comment is DeepPick<SafeComment, F> {
2638
return (
27-
comment.author !== undefined &&
28-
userIsSafe_(comment.author) &&
29-
comment.id !== undefined &&
30-
comment.content !== undefined &&
31-
comment.replies?.every((reply) => commentReplyIsSafe_(reply)) === true
39+
(keys.author !== true ||
40+
(comment.author !== undefined && userIsSafe_(comment.author))) &&
41+
(keys.id !== true || comment.id !== undefined) &&
42+
(keys.content !== true || comment.content !== undefined) &&
43+
(keys.replies !== true ||
44+
comment.replies?.every((reply) => commentReplyIsSafe_(reply)) === true)
3245
);
3346
}
3447

35-
function commentListIsSafe_(
48+
function commentListIsSafe_<F extends DeepKeyof<SafeComment>>(
3649
commentList: GoogleAppsScript.Drive_v3.Drive.V3.Schema.CommentList,
37-
): commentList is SafeCommentList {
50+
keys: F,
51+
): commentList is SafeCommentList<F> {
3852
return (
39-
commentList.comments?.every((comment) => commentIsSafe_(comment)) === true
53+
commentList.comments?.every((comment) => commentIsSafe_(comment, keys)) ===
54+
true
4055
);
4156
}
4257

@@ -57,27 +72,31 @@ function userIsSafe_(
5772
}
5873

5974
export const SafeCommentsCollection_ = {
60-
create: (
75+
create: <F extends DeepKeyof<SafeComment>>(
6176
resource: GoogleAppsScript.Drive_v3.Drive.V3.Schema.Comment,
6277
fileId: string,
63-
): SafeComment => {
78+
fields: F,
79+
): DeepPick<SafeComment, F> => {
6480
const ret = Drive.Comments.create(resource, fileId);
65-
if (!commentIsSafe_(ret)) {
81+
if (!commentIsSafe_(ret, fields)) {
6682
throw new Error("Comments.create: Comment is not safe.");
6783
}
6884
return ret;
6985
},
7086

71-
list: (
87+
list: <F extends DeepKeyof<SafeComment>>(
7288
fileId: string,
89+
fields: F,
7390
optionalArgs: {
74-
fields?: string;
7591
maxResults?: number;
7692
pageToken?: string | undefined;
7793
} = {},
78-
): SafeCommentList => {
79-
const ret = Drive.Comments.list(fileId, optionalArgs);
80-
if (!commentListIsSafe_(ret)) {
94+
): SafeCommentList<F> => {
95+
const ret = Drive.Comments.list(fileId, {
96+
...optionalArgs,
97+
fields: `nextPageToken, comments(${stringifyFields_(fields)})`,
98+
});
99+
if (!commentListIsSafe_(ret, fields)) {
81100
throw new Error("Comments.list: Comment list is not safe.");
82101
}
83102
return ret;

tests/backend/move/copyFileComments.test.ts

+43-30
Original file line numberDiff line numberDiff line change
@@ -51,27 +51,26 @@ test("copyFileComments works correctly", () => {
5151
);
5252
expect(
5353
vi.mocked(driveServiceMock.Comments.list).mock.calls[0][1],
54+
).toStrictEqual({
55+
anchor: true,
56+
author: {
57+
displayName: true,
58+
me: true,
59+
},
60+
content: true,
61+
quotedFileContent: true,
62+
replies: true,
63+
resolved: true,
64+
});
65+
expect(
66+
vi.mocked(driveServiceMock.Comments.list).mock.calls[0][2],
5467
).toBeDefined();
5568
expect(
5669
(
5770
vi.mocked(driveServiceMock.Comments.list).mock
58-
.calls[0][1] as ListCommentsOptions
71+
.calls[0][2] as ListCommentsOptions
5972
).pageToken,
6073
).toBeUndefined();
61-
expect(
62-
(
63-
vi.mocked(driveServiceMock.Comments.list).mock
64-
.calls[0][1] as ListCommentsOptions
65-
).fields,
66-
).toBeDefined();
67-
expect(
68-
(
69-
vi.mocked(driveServiceMock.Comments.list).mock
70-
.calls[0][1] as ListCommentsOptions
71-
).fields
72-
?.split(",")
73-
.map((s) => s.trim()),
74-
).toContain("nextPageToken");
7574
expect(vi.mocked(driveServiceMock.Comments.create).mock.calls).toHaveLength(
7675
2,
7776
);
@@ -84,6 +83,9 @@ test("copyFileComments works correctly", () => {
8483
expect(vi.mocked(driveServiceMock.Comments.create).mock.calls[0][1]).toBe(
8584
"DEST_FILE_ID",
8685
);
86+
expect(vi.mocked(driveServiceMock.Comments.create).mock.calls[0][2].id).toBe(
87+
true,
88+
);
8789
expect(
8890
vi.mocked(driveServiceMock.Comments.create).mock.calls[1][0].content,
8991
).toBe("COM2_CONTENT");
@@ -93,6 +95,9 @@ test("copyFileComments works correctly", () => {
9395
expect(vi.mocked(driveServiceMock.Comments.create).mock.calls[1][1]).toBe(
9496
"DEST_FILE_ID",
9597
);
98+
expect(vi.mocked(driveServiceMock.Comments.create).mock.calls[1][2].id).toBe(
99+
true,
100+
);
96101
});
97102

98103
test("copyFileComments works correctly with replies", () => {
@@ -140,27 +145,26 @@ test("copyFileComments works correctly with replies", () => {
140145
);
141146
expect(
142147
vi.mocked(driveServiceMock.Comments.list).mock.calls[0][1],
148+
).toStrictEqual({
149+
anchor: true,
150+
author: {
151+
displayName: true,
152+
me: true,
153+
},
154+
content: true,
155+
quotedFileContent: true,
156+
replies: true,
157+
resolved: true,
158+
});
159+
expect(
160+
vi.mocked(driveServiceMock.Comments.list).mock.calls[0][2],
143161
).toBeDefined();
144162
expect(
145163
(
146164
vi.mocked(driveServiceMock.Comments.list).mock
147-
.calls[0][1] as ListCommentsOptions
165+
.calls[0][2] as ListCommentsOptions
148166
).pageToken,
149167
).toBeUndefined();
150-
expect(
151-
(
152-
vi.mocked(driveServiceMock.Comments.list).mock
153-
.calls[0][1] as ListCommentsOptions
154-
).fields,
155-
).toBeDefined();
156-
expect(
157-
(
158-
vi.mocked(driveServiceMock.Comments.list).mock
159-
.calls[0][1] as ListCommentsOptions
160-
).fields
161-
?.split(",")
162-
.map((s) => s.trim()),
163-
).toContain("nextPageToken");
164168
expect(vi.mocked(driveServiceMock.Comments.create).mock.calls).toHaveLength(
165169
1,
166170
);
@@ -173,6 +177,9 @@ test("copyFileComments works correctly with replies", () => {
173177
expect(vi.mocked(driveServiceMock.Comments.create).mock.calls[0][1]).toBe(
174178
"DEST_FILE_ID",
175179
);
180+
expect(vi.mocked(driveServiceMock.Comments.create).mock.calls[0][2].id).toBe(
181+
true,
182+
);
176183
expect(vi.mocked(driveServiceMock.Replies.create).mock.calls).toHaveLength(2);
177184
expect(
178185
vi.mocked(driveServiceMock.Replies.create).mock.calls[0][0].content,
@@ -183,6 +190,9 @@ test("copyFileComments works correctly with replies", () => {
183190
expect(vi.mocked(driveServiceMock.Replies.create).mock.calls[0][2]).toBe(
184191
"DEST_COM_ID",
185192
);
193+
expect(
194+
vi.mocked(driveServiceMock.Replies.create).mock.calls[0][3].fields,
195+
).toContain("id");
186196
expect(
187197
vi.mocked(driveServiceMock.Replies.create).mock.calls[1][0].content,
188198
).toBe("REPLY2_CONTENT");
@@ -192,4 +202,7 @@ test("copyFileComments works correctly with replies", () => {
192202
expect(vi.mocked(driveServiceMock.Replies.create).mock.calls[1][2]).toBe(
193203
"DEST_COM_ID",
194204
);
205+
expect(
206+
vi.mocked(driveServiceMock.Replies.create).mock.calls[1][3].fields,
207+
).toContain("id");
195208
});

0 commit comments

Comments
 (0)