gogcli-mcp-classroom 2.0.3 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-classroom",
5
5
  "display_name": "gogcli (Classroom)",
6
- "version": "2.0.3",
6
+ "version": "2.0.6",
7
7
  "description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-classroom",
3
- "version": "2.0.3",
3
+ "version": "2.0.6",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-classroom",
5
5
  "description": "Extended Google Classroom MCP server via gogcli — auth + full Classroom support",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.29.0",
28
- "zod": "^4.4.2"
28
+ "zod": "^4.4.3"
29
29
  },
30
30
  "license": "MIT",
31
31
  "keywords": [
package/server.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.chrischall/gogcli-mcp-classroom",
4
- "description": "Google Classroom via gogcli for Claude courses, assignments, submissions, grading",
4
+ "description": "Google Classroom via gogcli for Claude \u2014 courses, assignments, submissions, grading",
5
5
  "repository": {
6
6
  "url": "https://github.com/chrischall/gogcli-mcp",
7
7
  "source": "github",
@@ -1,5 +1,303 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { accountParam, runOrDiagnose } from '../../../gogcli-mcp/src/lib.js';
2
4
 
3
- export function registerExtraClassroomTools(_server: McpServer): void {
4
- /* no extras yet */
5
+ const courseState = z.enum(['ACTIVE', 'ARCHIVED', 'PROVISIONED', 'DECLINED', 'SUSPENDED']);
6
+ const workState = z.enum(['PUBLISHED', 'DRAFT']);
7
+ const workType = z.enum(['ASSIGNMENT', 'SHORT_ANSWER_QUESTION', 'MULTIPLE_CHOICE_QUESTION']);
8
+
9
+ // Fields shared by courses_create and courses_update. `name` is required on
10
+ // create, optional on update — keep it out of this fragment so each tool can
11
+ // declare its own rule.
12
+ const courseSharedFields = {
13
+ owner: z.string().optional().describe('Owner user ID (default "me" on create)'),
14
+ section: z.string().optional().describe('Section'),
15
+ descriptionHeading: z.string().optional().describe('Description heading'),
16
+ description: z.string().optional().describe('Description'),
17
+ room: z.string().optional().describe('Room'),
18
+ state: courseState.optional().describe('Course state'),
19
+ };
20
+
21
+ // Fields shared by coursework_create and coursework_update.
22
+ const courseworkSharedFields = {
23
+ description: z.string().optional().describe('Description'),
24
+ type: workType.optional().describe('Work type (default: ASSIGNMENT)'),
25
+ state: workState.optional().describe('State'),
26
+ maxPoints: z.number().optional().describe('Max points'),
27
+ due: z.string().optional().describe('Due datetime (combined date+time)'),
28
+ dueDate: z.string().optional().describe('Due date (YYYY-MM-DD)'),
29
+ dueTime: z.string().optional().describe('Due time (HH:MM)'),
30
+ scheduled: z.string().optional().describe('Scheduled publish time'),
31
+ topic: z.string().optional().describe('Topic ID'),
32
+ };
33
+
34
+ export function registerExtraClassroomTools(server: McpServer): void {
35
+ server.registerTool('gog_classroom_courses_create', {
36
+ description: 'Create a new Google Classroom course.',
37
+ inputSchema: {
38
+ name: z.string().describe('Course name'),
39
+ ...courseSharedFields,
40
+ account: accountParam,
41
+ },
42
+ }, async ({ name, owner, section, descriptionHeading, description, room, state, account }) => {
43
+ const args = ['classroom', 'courses', 'create', `--name=${name}`];
44
+ if (owner) args.push(`--owner=${owner}`);
45
+ if (section) args.push(`--section=${section}`);
46
+ if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
47
+ if (description) args.push(`--description=${description}`);
48
+ if (room) args.push(`--room=${room}`);
49
+ if (state) args.push(`--state=${state}`);
50
+ return runOrDiagnose(args, { account });
51
+ });
52
+
53
+ server.registerTool('gog_classroom_courses_update', {
54
+ description: 'Update an existing Google Classroom course.',
55
+ annotations: { destructiveHint: true },
56
+ inputSchema: {
57
+ courseId: z.string().describe('Course ID'),
58
+ name: z.string().optional().describe('Course name'),
59
+ ...courseSharedFields,
60
+ account: accountParam,
61
+ },
62
+ }, async ({ courseId, name, owner, section, descriptionHeading, description, room, state, account }) => {
63
+ const args = ['classroom', 'courses', 'update', courseId];
64
+ if (name) args.push(`--name=${name}`);
65
+ if (owner) args.push(`--owner=${owner}`);
66
+ if (section) args.push(`--section=${section}`);
67
+ if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
68
+ if (description) args.push(`--description=${description}`);
69
+ if (room) args.push(`--room=${room}`);
70
+ if (state) args.push(`--state=${state}`);
71
+ return runOrDiagnose(args, { account });
72
+ });
73
+
74
+ server.registerTool('gog_classroom_courses_delete', {
75
+ description: 'Delete a Google Classroom course.',
76
+ annotations: { destructiveHint: true },
77
+ inputSchema: {
78
+ courseId: z.string().describe('Course ID'),
79
+ account: accountParam,
80
+ },
81
+ }, async ({ courseId, account }) => {
82
+ return runOrDiagnose(['classroom', 'courses', 'delete', courseId], { account });
83
+ });
84
+
85
+ server.registerTool('gog_classroom_courses_archive', {
86
+ description: 'Archive a Google Classroom course.',
87
+ annotations: { destructiveHint: true },
88
+ inputSchema: {
89
+ courseId: z.string().describe('Course ID'),
90
+ account: accountParam,
91
+ },
92
+ }, async ({ courseId, account }) => {
93
+ return runOrDiagnose(['classroom', 'courses', 'archive', courseId], { account });
94
+ });
95
+
96
+ server.registerTool('gog_classroom_courses_unarchive', {
97
+ description: 'Unarchive a Google Classroom course (restore to ACTIVE).',
98
+ inputSchema: {
99
+ courseId: z.string().describe('Course ID'),
100
+ account: accountParam,
101
+ },
102
+ }, async ({ courseId, account }) => {
103
+ return runOrDiagnose(['classroom', 'courses', 'unarchive', courseId], { account });
104
+ });
105
+
106
+ server.registerTool('gog_classroom_students_add', {
107
+ description: 'Add a student to a Google Classroom course.',
108
+ inputSchema: {
109
+ courseId: z.string().describe('Course ID'),
110
+ userId: z.string().describe('Student user ID (or "me")'),
111
+ enrollmentCode: z.string().optional().describe('Enrollment code (required if adding self via code)'),
112
+ account: accountParam,
113
+ },
114
+ }, async ({ courseId, userId, enrollmentCode, account }) => {
115
+ const args = ['classroom', 'students', 'add', courseId, userId];
116
+ if (enrollmentCode) args.push(`--enrollment-code=${enrollmentCode}`);
117
+ return runOrDiagnose(args, { account });
118
+ });
119
+
120
+ server.registerTool('gog_classroom_students_remove', {
121
+ description: 'Remove a student from a Google Classroom course.',
122
+ annotations: { destructiveHint: true },
123
+ inputSchema: {
124
+ courseId: z.string().describe('Course ID'),
125
+ userId: z.string().describe('Student user ID'),
126
+ account: accountParam,
127
+ },
128
+ }, async ({ courseId, userId, account }) => {
129
+ return runOrDiagnose(['classroom', 'students', 'remove', courseId, userId], { account });
130
+ });
131
+
132
+ server.registerTool('gog_classroom_teachers_add', {
133
+ description: 'Add a teacher to a Google Classroom course.',
134
+ inputSchema: {
135
+ courseId: z.string().describe('Course ID'),
136
+ userId: z.string().describe('Teacher user ID'),
137
+ account: accountParam,
138
+ },
139
+ }, async ({ courseId, userId, account }) => {
140
+ return runOrDiagnose(['classroom', 'teachers', 'add', courseId, userId], { account });
141
+ });
142
+
143
+ server.registerTool('gog_classroom_teachers_remove', {
144
+ description: 'Remove a teacher from a Google Classroom course.',
145
+ annotations: { destructiveHint: true },
146
+ inputSchema: {
147
+ courseId: z.string().describe('Course ID'),
148
+ userId: z.string().describe('Teacher user ID'),
149
+ account: accountParam,
150
+ },
151
+ }, async ({ courseId, userId, account }) => {
152
+ return runOrDiagnose(['classroom', 'teachers', 'remove', courseId, userId], { account });
153
+ });
154
+
155
+ server.registerTool('gog_classroom_coursework_create', {
156
+ description: 'Create a new coursework item (assignment, question, etc.) in a course.',
157
+ inputSchema: {
158
+ courseId: z.string().describe('Course ID'),
159
+ title: z.string().describe('Coursework title'),
160
+ ...courseworkSharedFields,
161
+ account: accountParam,
162
+ },
163
+ }, async ({ courseId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
164
+ const args = ['classroom', 'coursework', 'create', courseId, `--title=${title}`];
165
+ if (description) args.push(`--description=${description}`);
166
+ if (type) args.push(`--type=${type}`);
167
+ if (state) args.push(`--state=${state}`);
168
+ if (maxPoints !== undefined) args.push(`--max-points=${maxPoints}`);
169
+ if (due) args.push(`--due=${due}`);
170
+ if (dueDate) args.push(`--due-date=${dueDate}`);
171
+ if (dueTime) args.push(`--due-time=${dueTime}`);
172
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
173
+ if (topic) args.push(`--topic=${topic}`);
174
+ return runOrDiagnose(args, { account });
175
+ });
176
+
177
+ server.registerTool('gog_classroom_coursework_update', {
178
+ description: 'Update an existing coursework item.',
179
+ annotations: { destructiveHint: true },
180
+ inputSchema: {
181
+ courseId: z.string().describe('Course ID'),
182
+ courseworkId: z.string().describe('Coursework ID'),
183
+ title: z.string().optional().describe('New title'),
184
+ ...courseworkSharedFields,
185
+ account: accountParam,
186
+ },
187
+ }, async ({ courseId, courseworkId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
188
+ const args = ['classroom', 'coursework', 'update', courseId, courseworkId];
189
+ if (title) args.push(`--title=${title}`);
190
+ if (description) args.push(`--description=${description}`);
191
+ if (type) args.push(`--type=${type}`);
192
+ if (state) args.push(`--state=${state}`);
193
+ if (maxPoints !== undefined) args.push(`--max-points=${maxPoints}`);
194
+ if (due) args.push(`--due=${due}`);
195
+ if (dueDate) args.push(`--due-date=${dueDate}`);
196
+ if (dueTime) args.push(`--due-time=${dueTime}`);
197
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
198
+ if (topic) args.push(`--topic=${topic}`);
199
+ return runOrDiagnose(args, { account });
200
+ });
201
+
202
+ server.registerTool('gog_classroom_coursework_delete', {
203
+ description: 'Delete a coursework item.',
204
+ annotations: { destructiveHint: true },
205
+ inputSchema: {
206
+ courseId: z.string().describe('Course ID'),
207
+ courseworkId: z.string().describe('Coursework ID'),
208
+ account: accountParam,
209
+ },
210
+ }, async ({ courseId, courseworkId, account }) => {
211
+ return runOrDiagnose(['classroom', 'coursework', 'delete', courseId, courseworkId], { account });
212
+ });
213
+
214
+ server.registerTool('gog_classroom_announcements_update', {
215
+ description: 'Update an existing announcement.',
216
+ annotations: { destructiveHint: true },
217
+ inputSchema: {
218
+ courseId: z.string().describe('Course ID'),
219
+ announcementId: z.string().describe('Announcement ID'),
220
+ text: z.string().optional().describe('New text'),
221
+ state: workState.optional().describe('State'),
222
+ scheduled: z.string().optional().describe('Scheduled publish time'),
223
+ account: accountParam,
224
+ },
225
+ }, async ({ courseId, announcementId, text, state, scheduled, account }) => {
226
+ const args = ['classroom', 'announcements', 'update', courseId, announcementId];
227
+ if (text) args.push(`--text=${text}`);
228
+ if (state) args.push(`--state=${state}`);
229
+ if (scheduled) args.push(`--scheduled=${scheduled}`);
230
+ return runOrDiagnose(args, { account });
231
+ });
232
+
233
+ server.registerTool('gog_classroom_announcements_delete', {
234
+ description: 'Delete an announcement.',
235
+ annotations: { destructiveHint: true },
236
+ inputSchema: {
237
+ courseId: z.string().describe('Course ID'),
238
+ announcementId: z.string().describe('Announcement ID'),
239
+ account: accountParam,
240
+ },
241
+ }, async ({ courseId, announcementId, account }) => {
242
+ return runOrDiagnose(['classroom', 'announcements', 'delete', courseId, announcementId], { account });
243
+ });
244
+
245
+ server.registerTool('gog_classroom_topics_create', {
246
+ description: 'Create a topic in a Google Classroom course.',
247
+ inputSchema: {
248
+ courseId: z.string().describe('Course ID'),
249
+ name: z.string().describe('Topic name'),
250
+ account: accountParam,
251
+ },
252
+ }, async ({ courseId, name, account }) => {
253
+ return runOrDiagnose(['classroom', 'topics', 'create', courseId, `--name=${name}`], { account });
254
+ });
255
+
256
+ server.registerTool('gog_classroom_topics_update', {
257
+ description: 'Rename an existing topic.',
258
+ annotations: { destructiveHint: true },
259
+ inputSchema: {
260
+ courseId: z.string().describe('Course ID'),
261
+ topicId: z.string().describe('Topic ID'),
262
+ name: z.string().describe('New topic name'),
263
+ account: accountParam,
264
+ },
265
+ }, async ({ courseId, topicId, name, account }) => {
266
+ return runOrDiagnose(['classroom', 'topics', 'update', courseId, topicId, `--name=${name}`], { account });
267
+ });
268
+
269
+ server.registerTool('gog_classroom_topics_delete', {
270
+ description: 'Delete a topic.',
271
+ annotations: { destructiveHint: true },
272
+ inputSchema: {
273
+ courseId: z.string().describe('Course ID'),
274
+ topicId: z.string().describe('Topic ID'),
275
+ account: accountParam,
276
+ },
277
+ }, async ({ courseId, topicId, account }) => {
278
+ return runOrDiagnose(['classroom', 'topics', 'delete', courseId, topicId], { account });
279
+ });
280
+
281
+ server.registerTool('gog_classroom_invitations_create', {
282
+ description: 'Create an invitation to a Google Classroom course.',
283
+ inputSchema: {
284
+ courseId: z.string().describe('Course ID'),
285
+ userId: z.string().describe('User ID to invite'),
286
+ role: z.enum(['STUDENT', 'TEACHER', 'OWNER']).describe('Role for the invited user'),
287
+ account: accountParam,
288
+ },
289
+ }, async ({ courseId, userId, role, account }) => {
290
+ return runOrDiagnose(['classroom', 'invitations', 'create', courseId, userId, `--role=${role}`], { account });
291
+ });
292
+
293
+ server.registerTool('gog_classroom_invitations_delete', {
294
+ description: 'Delete (revoke) a Google Classroom invitation.',
295
+ annotations: { destructiveHint: true },
296
+ inputSchema: {
297
+ invitationId: z.string().describe('Invitation ID'),
298
+ account: accountParam,
299
+ },
300
+ }, async ({ invitationId, account }) => {
301
+ return runOrDiagnose(['classroom', 'invitations', 'delete', invitationId], { account });
302
+ });
5
303
  }
@@ -1,12 +1,264 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
2
  import { registerExtraClassroomTools } from '../../src/tools/classroom-extra.js';
3
+ import * as lib from '../../../gogcli-mcp/src/lib.js';
4
+ import { setupExtrasHandlers, toText, type ToolHandler } from '../../../gogcli-mcp/tests/helpers/extras-harness.js';
4
5
 
5
- describe('registerExtraClassroomTools', () => {
6
- it('registers no tools (no extras yet)', () => {
7
- const server = new McpServer({ name: 'test', version: '0.0.0' });
8
- const spy = vi.spyOn(server, 'registerTool');
9
- registerExtraClassroomTools(server);
10
- expect(spy).not.toHaveBeenCalled();
6
+ vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
7
+ const actual = await importOriginal<typeof lib>();
8
+ return {
9
+ ...actual,
10
+ runOrDiagnose: vi.fn(),
11
+ };
12
+ });
13
+
14
+ let handlers: Map<string, ToolHandler>;
15
+
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
19
+ handlers = setupExtrasHandlers(registerExtraClassroomTools);
20
+ });
21
+
22
+ describe('gog_classroom_courses_create', () => {
23
+ it('calls runOrDiagnose with required name only', async () => {
24
+ await handlers.get('gog_classroom_courses_create')!({ name: 'Math 101' });
25
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
26
+ ['classroom', 'courses', 'create', '--name=Math 101'],
27
+ { account: undefined },
28
+ );
29
+ });
30
+
31
+ it('passes all optional flags', async () => {
32
+ await handlers.get('gog_classroom_courses_create')!({
33
+ name: 'Math 101',
34
+ owner: 'me',
35
+ section: 'Section A',
36
+ descriptionHeading: 'Welcome',
37
+ description: 'Algebra',
38
+ room: 'R101',
39
+ state: 'ACTIVE',
40
+ });
41
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
42
+ ['classroom', 'courses', 'create', '--name=Math 101', '--owner=me', '--section=Section A', '--description-heading=Welcome', '--description=Algebra', '--room=R101', '--state=ACTIVE'],
43
+ { account: undefined },
44
+ );
45
+ });
46
+ });
47
+
48
+ describe('gog_classroom_courses_update', () => {
49
+ it('calls runOrDiagnose with courseId only', async () => {
50
+ await handlers.get('gog_classroom_courses_update')!({ courseId: 'c1' });
51
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'update', 'c1'], { account: undefined });
52
+ });
53
+
54
+ it('passes all optional flags', async () => {
55
+ await handlers.get('gog_classroom_courses_update')!({
56
+ courseId: 'c1',
57
+ name: 'New Name',
58
+ owner: 'me',
59
+ section: 'B',
60
+ descriptionHeading: 'Heading',
61
+ description: 'Desc',
62
+ room: 'R2',
63
+ state: 'ARCHIVED',
64
+ });
65
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
66
+ ['classroom', 'courses', 'update', 'c1', '--name=New Name', '--owner=me', '--section=B', '--description-heading=Heading', '--description=Desc', '--room=R2', '--state=ARCHIVED'],
67
+ { account: undefined },
68
+ );
69
+ });
70
+ });
71
+
72
+ describe('gog_classroom_courses_delete', () => {
73
+ it('calls runOrDiagnose with courseId', async () => {
74
+ await handlers.get('gog_classroom_courses_delete')!({ courseId: 'c1' });
75
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'delete', 'c1'], { account: undefined });
76
+ });
77
+ });
78
+
79
+ describe('gog_classroom_courses_archive', () => {
80
+ it('calls runOrDiagnose with courseId', async () => {
81
+ await handlers.get('gog_classroom_courses_archive')!({ courseId: 'c1' });
82
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'archive', 'c1'], { account: undefined });
83
+ });
84
+ });
85
+
86
+ describe('gog_classroom_courses_unarchive', () => {
87
+ it('calls runOrDiagnose with courseId', async () => {
88
+ await handlers.get('gog_classroom_courses_unarchive')!({ courseId: 'c1' });
89
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'unarchive', 'c1'], { account: undefined });
90
+ });
91
+ });
92
+
93
+ describe('gog_classroom_students_add', () => {
94
+ it('calls runOrDiagnose with courseId and userId', async () => {
95
+ await handlers.get('gog_classroom_students_add')!({ courseId: 'c1', userId: 'u1' });
96
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'students', 'add', 'c1', 'u1'], { account: undefined });
97
+ });
98
+
99
+ it('passes --enrollment-code when provided', async () => {
100
+ await handlers.get('gog_classroom_students_add')!({ courseId: 'c1', userId: 'u1', enrollmentCode: 'abc123' });
101
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
102
+ ['classroom', 'students', 'add', 'c1', 'u1', '--enrollment-code=abc123'],
103
+ { account: undefined },
104
+ );
105
+ });
106
+ });
107
+
108
+ describe('gog_classroom_students_remove', () => {
109
+ it('calls runOrDiagnose with courseId and userId', async () => {
110
+ await handlers.get('gog_classroom_students_remove')!({ courseId: 'c1', userId: 'u1' });
111
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'students', 'remove', 'c1', 'u1'], { account: undefined });
112
+ });
113
+ });
114
+
115
+ describe('gog_classroom_teachers_add', () => {
116
+ it('calls runOrDiagnose with courseId and userId', async () => {
117
+ await handlers.get('gog_classroom_teachers_add')!({ courseId: 'c1', userId: 'u1' });
118
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'teachers', 'add', 'c1', 'u1'], { account: undefined });
119
+ });
120
+ });
121
+
122
+ describe('gog_classroom_teachers_remove', () => {
123
+ it('calls runOrDiagnose with courseId and userId', async () => {
124
+ await handlers.get('gog_classroom_teachers_remove')!({ courseId: 'c1', userId: 'u1' });
125
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'teachers', 'remove', 'c1', 'u1'], { account: undefined });
126
+ });
127
+ });
128
+
129
+ describe('gog_classroom_coursework_create', () => {
130
+ it('calls runOrDiagnose with required title only', async () => {
131
+ await handlers.get('gog_classroom_coursework_create')!({ courseId: 'c1', title: 'HW1' });
132
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
133
+ ['classroom', 'coursework', 'create', 'c1', '--title=HW1'],
134
+ { account: undefined },
135
+ );
136
+ });
137
+
138
+ it('passes all optional flags', async () => {
139
+ await handlers.get('gog_classroom_coursework_create')!({
140
+ courseId: 'c1',
141
+ title: 'HW1',
142
+ description: 'Chapter 1',
143
+ type: 'ASSIGNMENT',
144
+ state: 'PUBLISHED',
145
+ maxPoints: 100,
146
+ due: '2026-05-01T23:59',
147
+ dueDate: '2026-05-01',
148
+ dueTime: '23:59',
149
+ scheduled: '2026-04-30T12:00',
150
+ topic: 't1',
151
+ });
152
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
153
+ ['classroom', 'coursework', 'create', 'c1', '--title=HW1', '--description=Chapter 1', '--type=ASSIGNMENT', '--state=PUBLISHED', '--max-points=100', '--due=2026-05-01T23:59', '--due-date=2026-05-01', '--due-time=23:59', '--scheduled=2026-04-30T12:00', '--topic=t1'],
154
+ { account: undefined },
155
+ );
156
+ });
157
+ });
158
+
159
+ describe('gog_classroom_coursework_update', () => {
160
+ it('calls runOrDiagnose with ids only', async () => {
161
+ await handlers.get('gog_classroom_coursework_update')!({ courseId: 'c1', courseworkId: 'w1' });
162
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'coursework', 'update', 'c1', 'w1'], { account: undefined });
163
+ });
164
+
165
+ it('passes all optional flags', async () => {
166
+ await handlers.get('gog_classroom_coursework_update')!({
167
+ courseId: 'c1',
168
+ courseworkId: 'w1',
169
+ title: 'New Title',
170
+ description: 'Desc',
171
+ type: 'SHORT_ANSWER_QUESTION',
172
+ state: 'DRAFT',
173
+ maxPoints: 50,
174
+ due: '2026-05-01T23:59',
175
+ dueDate: '2026-05-01',
176
+ dueTime: '23:59',
177
+ scheduled: '2026-04-30T12:00',
178
+ topic: 't1',
179
+ });
180
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
181
+ ['classroom', 'coursework', 'update', 'c1', 'w1', '--title=New Title', '--description=Desc', '--type=SHORT_ANSWER_QUESTION', '--state=DRAFT', '--max-points=50', '--due=2026-05-01T23:59', '--due-date=2026-05-01', '--due-time=23:59', '--scheduled=2026-04-30T12:00', '--topic=t1'],
182
+ { account: undefined },
183
+ );
184
+ });
185
+ });
186
+
187
+ describe('gog_classroom_coursework_delete', () => {
188
+ it('calls runOrDiagnose with ids', async () => {
189
+ await handlers.get('gog_classroom_coursework_delete')!({ courseId: 'c1', courseworkId: 'w1' });
190
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'coursework', 'delete', 'c1', 'w1'], { account: undefined });
191
+ });
192
+ });
193
+
194
+ describe('gog_classroom_announcements_update', () => {
195
+ it('calls runOrDiagnose with ids only', async () => {
196
+ await handlers.get('gog_classroom_announcements_update')!({ courseId: 'c1', announcementId: 'a1' });
197
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'announcements', 'update', 'c1', 'a1'], { account: undefined });
198
+ });
199
+
200
+ it('passes all optional flags', async () => {
201
+ await handlers.get('gog_classroom_announcements_update')!({
202
+ courseId: 'c1',
203
+ announcementId: 'a1',
204
+ text: 'edited',
205
+ state: 'PUBLISHED',
206
+ scheduled: '2026-05-01T12:00',
207
+ });
208
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
209
+ ['classroom', 'announcements', 'update', 'c1', 'a1', '--text=edited', '--state=PUBLISHED', '--scheduled=2026-05-01T12:00'],
210
+ { account: undefined },
211
+ );
212
+ });
213
+ });
214
+
215
+ describe('gog_classroom_announcements_delete', () => {
216
+ it('calls runOrDiagnose with ids', async () => {
217
+ await handlers.get('gog_classroom_announcements_delete')!({ courseId: 'c1', announcementId: 'a1' });
218
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'announcements', 'delete', 'c1', 'a1'], { account: undefined });
219
+ });
220
+ });
221
+
222
+ describe('gog_classroom_topics_create', () => {
223
+ it('calls runOrDiagnose with courseId and name', async () => {
224
+ await handlers.get('gog_classroom_topics_create')!({ courseId: 'c1', name: 'Week 1' });
225
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
226
+ ['classroom', 'topics', 'create', 'c1', '--name=Week 1'],
227
+ { account: undefined },
228
+ );
229
+ });
230
+ });
231
+
232
+ describe('gog_classroom_topics_update', () => {
233
+ it('calls runOrDiagnose with ids and name', async () => {
234
+ await handlers.get('gog_classroom_topics_update')!({ courseId: 'c1', topicId: 't1', name: 'Week 2' });
235
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
236
+ ['classroom', 'topics', 'update', 'c1', 't1', '--name=Week 2'],
237
+ { account: undefined },
238
+ );
239
+ });
240
+ });
241
+
242
+ describe('gog_classroom_topics_delete', () => {
243
+ it('calls runOrDiagnose with ids', async () => {
244
+ await handlers.get('gog_classroom_topics_delete')!({ courseId: 'c1', topicId: 't1' });
245
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'topics', 'delete', 'c1', 't1'], { account: undefined });
246
+ });
247
+ });
248
+
249
+ describe('gog_classroom_invitations_create', () => {
250
+ it('calls runOrDiagnose with courseId, userId, role', async () => {
251
+ await handlers.get('gog_classroom_invitations_create')!({ courseId: 'c1', userId: 'u1', role: 'STUDENT' });
252
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
253
+ ['classroom', 'invitations', 'create', 'c1', 'u1', '--role=STUDENT'],
254
+ { account: undefined },
255
+ );
256
+ });
257
+ });
258
+
259
+ describe('gog_classroom_invitations_delete', () => {
260
+ it('calls runOrDiagnose with invitationId', async () => {
261
+ await handlers.get('gog_classroom_invitations_delete')!({ invitationId: 'i1' });
262
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'invitations', 'delete', 'i1'], { account: undefined });
11
263
  });
12
264
  });