























@@ -149,6 +149,23 @@ function isRecord(value: unknown): value is Record<string, unknown> {
149149return Boolean(value) && typeof value === "object" && !Array.isArray(value);
150150}
151151152+function requireRecord(value: unknown, label: string): Record<string, unknown> {
153+if (!isRecord(value)) {
154+throw new Error(`Expected ${label} to be an object`);
155+}
156+return value;
157+}
158+159+function requireFetchGuardCall(auditContext: string): Record<string, unknown> {
160+const call = (
161+fetchGuardMocks.fetchWithSsrFGuard.mock.calls as Array<[Record<string, unknown>]>
162+).find(([params]) => params.auditContext === auditContext);
163+if (!call) {
164+throw new Error(`Expected fetchWithSsrFGuard call for ${auditContext}`);
165+}
166+return call[0];
167+}
168+152169function requestUrl(input: RequestInfo | URL): URL {
153170if (typeof input === "string") {
154171return new URL(input);
@@ -829,35 +846,44 @@ describe("google-meet plugin", () => {
829846830847expect(tool.description).toContain("recover_current_tab");
831848expect(JSON.stringify(tool.parameters)).not.toContain("anyOf");
832-expect(tool.parameters).toMatchObject({
833-type: "object",
834-properties: {
835-action: {
836-type: "string",
837-enum: [
838-"join",
839-"create",
840-"status",
841-"setup_status",
842-"resolve_space",
843-"preflight",
844-"latest",
845-"calendar_events",
846-"artifacts",
847-"attendance",
848-"export",
849-"recover_current_tab",
850-"leave",
851-"end_active_conference",
852-"speak",
853-"test_speech",
854-"test_listen",
855-],
856-description: expect.stringContaining("recover_current_tab"),
857-},
858-transport: { type: "string", enum: ["chrome", "chrome-node", "twilio"] },
859-mode: { type: "string", enum: ["agent", "bidi", "transcribe"] },
860-},
849+const parameters = requireRecord(tool.parameters, "Google Meet tool parameters");
850+expect(parameters.type).toBe("object");
851+const properties = requireRecord(
852+parameters.properties,
853+"Google Meet tool parameter properties",
854+);
855+const action = requireRecord(properties.action, "Google Meet action parameter");
856+expect(action.type).toBe("string");
857+expect(action.enum).toEqual([
858+"join",
859+"create",
860+"status",
861+"setup_status",
862+"resolve_space",
863+"preflight",
864+"latest",
865+"calendar_events",
866+"artifacts",
867+"attendance",
868+"export",
869+"recover_current_tab",
870+"leave",
871+"end_active_conference",
872+"speak",
873+"test_speech",
874+"test_listen",
875+]);
876+expect(action.description).toContain("recover_current_tab");
877+expect(properties.transport).toEqual({
878+type: "string",
879+enum: ["chrome", "chrome-node", "twilio"],
880+description: "Join transport",
881+});
882+expect(properties.mode).toEqual({
883+type: "string",
884+enum: ["agent", "bidi", "transcribe"],
885+description:
886+"Join mode. agent uses realtime transcription, the configured OpenClaw agent, and regular TTS. bidi uses the realtime voice model directly. transcribe joins observe-only.",
861887});
862888});
863889@@ -887,33 +913,27 @@ describe("google-meet plugin", () => {
887913},
888914}),
889915).toBe("https://meet.google.com/abc-defg-hij");
890-await expect(
891-findGoogleMeetCalendarEvent({
892-accessToken: "token",
893-now: new Date("2026-04-25T09:50:00Z"),
894-timeMin: "2026-04-25T00:00:00Z",
895-timeMax: "2026-04-26T00:00:00Z",
896-}),
897-).resolves.toMatchObject({
898-calendarId: "primary",
899-meetingUri: "https://meet.google.com/abc-defg-hij",
900-event: { summary: "Project sync" },
901-});
902-await expect(
903-listGoogleMeetCalendarEvents({
904-accessToken: "token",
905-now: new Date("2026-04-25T09:50:00Z"),
906-timeMin: "2026-04-25T00:00:00Z",
907-timeMax: "2026-04-26T00:00:00Z",
908-}),
909-).resolves.toMatchObject({
910-events: [
911-{
912-meetingUri: "https://meet.google.com/abc-defg-hij",
913-selected: true,
914-},
915-],
916+const event = await findGoogleMeetCalendarEvent({
917+accessToken: "token",
918+now: new Date("2026-04-25T09:50:00Z"),
919+timeMin: "2026-04-25T00:00:00Z",
920+timeMax: "2026-04-26T00:00:00Z",
916921});
922+expect(event.calendarId).toBe("primary");
923+expect(event.meetingUri).toBe("https://meet.google.com/abc-defg-hij");
924+expect(event.event.summary).toBe("Project sync");
925+926+const calendarEvents = await listGoogleMeetCalendarEvents({
927+accessToken: "token",
928+now: new Date("2026-04-25T09:50:00Z"),
929+timeMin: "2026-04-25T00:00:00Z",
930+timeMax: "2026-04-26T00:00:00Z",
931+});
932+expect(calendarEvents.calendarId).toBe("primary");
933+expect(calendarEvents.events).toHaveLength(1);
934+expect(calendarEvents.events[0]?.meetingUri).toBe("https://meet.google.com/abc-defg-hij");
935+expect(calendarEvents.events[0]?.selected).toBe(true);
936+expect(calendarEvents.events[0]?.event.summary).toBe("Project sync");
917937const calendarCall = fetchMock.mock.calls.find(([input]) => {
918938const url = requestUrl(input);
919939return url.pathname === "/calendar/v3/calendars/primary/events";
@@ -924,12 +944,8 @@ describe("google-meet plugin", () => {
924944const url = requestUrl(calendarCall[0]);
925945expect(url.searchParams.get("singleEvents")).toBe("true");
926946expect(url.searchParams.get("orderBy")).toBe("startTime");
927-expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
928-expect.objectContaining({
929-policy: { allowedHostnames: ["www.googleapis.com"] },
930-auditContext: "google-meet.calendar.events.list",
931-}),
932-);
947+const guardCall = requireFetchGuardCall("google-meet.calendar.events.list");
948+expect(guardCall.policy).toEqual({ allowedHostnames: ["www.googleapis.com"] });
933949});
934950935951it("adds a reauth hint for missing Calendar scopes", async () => {
@@ -967,28 +983,26 @@ describe("google-meet plugin", () => {
967983});
968984vi.stubGlobal("fetch", fetchMock);
969985970-await expect(
971-fetchGoogleMeetSpace({
972-accessToken: "token",
973-meeting: "spaces/abc-defg-hij",
974-}),
975-).resolves.toMatchObject({ name: "spaces/abc-defg-hij" });
976-expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
977-expect.objectContaining({
978-url: "https://meet.googleapis.com/v2/spaces/abc-defg-hij",
979-init: expect.objectContaining({
980-headers: expect.objectContaining({ Authorization: "Bearer token" }),
981-}),
982-policy: { allowedHostnames: ["meet.googleapis.com"] },
983-auditContext: "google-meet.spaces.get",
984-}),
985-);
986-expect(fetchMock).toHaveBeenCalledWith(
987-"https://meet.googleapis.com/v2/spaces/abc-defg-hij",
988-expect.objectContaining({
989-headers: expect.objectContaining({ Authorization: "Bearer token" }),
990-}),
991-);
986+const space = await fetchGoogleMeetSpace({
987+accessToken: "token",
988+meeting: "spaces/abc-defg-hij",
989+});
990+expect(space.name).toBe("spaces/abc-defg-hij");
991+expect(space.meetingCode).toBe("abc-defg-hij");
992+expect(space.meetingUri).toBe("https://meet.google.com/abc-defg-hij");
993+const guardCall = requireFetchGuardCall("google-meet.spaces.get");
994+expect(guardCall.url).toBe("https://meet.googleapis.com/v2/spaces/abc-defg-hij");
995+expect(requireRecord(guardCall.init, "spaces.get init").headers).toEqual({
996+Authorization: "Bearer token",
997+Accept: "application/json",
998+});
999+expect(guardCall.policy).toEqual({ allowedHostnames: ["meet.googleapis.com"] });
1000+expect(fetchMock).toHaveBeenCalledWith("https://meet.googleapis.com/v2/spaces/abc-defg-hij", {
1001+headers: {
1002+Authorization: "Bearer token",
1003+Accept: "application/json",
1004+},
1005+});
9921006});
99310079941008it("creates Meet spaces and returns the meeting URL", async () => {
@@ -1004,22 +1018,23 @@ describe("google-meet plugin", () => {
10041018});
10051019vi.stubGlobal("fetch", fetchMock);
100610201007-await expect(createGoogleMeetSpace({ accessToken: "token" })).resolves.toMatchObject({
1008-meetingUri: "https://meet.google.com/new-abcd-xyz",
1009-space: { name: "spaces/new-space" },
1021+const result = await createGoogleMeetSpace({ accessToken: "token" });
1022+expect(result.meetingUri).toBe("https://meet.google.com/new-abcd-xyz");
1023+expect(result.space.name).toBe("spaces/new-space");
1024+expect(result.space.meetingCode).toBe("new-abcd-xyz");
1025+expect(result.space.meetingUri).toBe("https://meet.google.com/new-abcd-xyz");
1026+const guardCall = requireFetchGuardCall("google-meet.spaces.create");
1027+expect(guardCall.url).toBe("https://meet.googleapis.com/v2/spaces");
1028+expect(guardCall.init).toEqual({
1029+method: "POST",
1030+headers: {
1031+Authorization: "Bearer token",
1032+Accept: "application/json",
1033+"Content-Type": "application/json",
1034+},
1035+body: "{}",
10101036});
1011-expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
1012-expect.objectContaining({
1013-url: "https://meet.googleapis.com/v2/spaces",
1014-init: expect.objectContaining({
1015-method: "POST",
1016-headers: expect.objectContaining({ Authorization: "Bearer token" }),
1017-body: "{}",
1018-}),
1019-policy: { allowedHostnames: ["meet.googleapis.com"] },
1020-auditContext: "google-meet.spaces.create",
1021-}),
1022-);
1037+expect(guardCall.policy).toEqual({ allowedHostnames: ["meet.googleapis.com"] });
10231038});
1024103910251040it("lists Meet artifact metadata for the latest conference record by default", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。