Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  cli.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { Command } from "commander";
import { afterEach, describe, expect, it, vi } from "vitest";
import { registerGoogleMeetCli } from "./cli.js";
import { resolveGoogleMeetConfig } from "./config.js";
import type { GoogleMeetRuntime } from "./runtime.js";

const fetchGuardMocks = vi.hoisted(() => ({
  fetchWithSsrFGuard: vi.fn(
    async (params: {
      url: string;
      init?: RequestInit;
    }): Promise<{
      response: Response;
      release: () => Promise<void>;
    }> => ({
      response: await fetch(params.url, params.init),
      release: vi.fn(async () => {}),
    }),
  ),
}));

vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
  fetchWithSsrFGuard: fetchGuardMocks.fetchWithSsrFGuard,
}));

function captureStdout() {
  let output = "";
  const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(((chunk: unknown) => {
    output += String(chunk);
    return true;
  }) as typeof process.stdout.write);
  return {
    output: () => output,
    restore: () => writeSpy.mockRestore(),
  };
}

function jsonResponse(value: unknown): Response {
  return new Response(JSON.stringify(value), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
}

function requestUrl(input: RequestInfo | URL): URL {
  if (typeof input === "string") {
    return new URL(input);
  }
  if (input instanceof URL) {
    return input;
  }
  return new URL(input.url);
}

function stubMeetArtifactsApi() {
  vi.stubGlobal(
    "fetch",
    vi.fn(async (input: RequestInfo | URL) => {
      const url = requestUrl(input);
      if (url.pathname === "/v2/spaces/abc-defg-hij") {
        return jsonResponse({
          name: "spaces/abc-defg-hij",
          meetingCode: "abc-defg-hij",
          meetingUri: "https://meet.google.com/abc-defg-hij",
        });
      }
      if (url.pathname === "/v2/conferenceRecords") {
        return jsonResponse({
          conferenceRecords: [
            {
              name: "conferenceRecords/rec-1",
              space: "spaces/abc-defg-hij",
              startTime: "2026-04-25T10:00:00Z",
              endTime: "2026-04-25T10:30:00Z",
            },
          ],
        });
      }
      if (url.pathname === "/v2/conferenceRecords/rec-1") {
        return jsonResponse({
          name: "conferenceRecords/rec-1",
          space: "spaces/abc-defg-hij",
          startTime: "2026-04-25T10:00:00Z",
          endTime: "2026-04-25T10:30:00Z",
        });
      }
      if (url.pathname === "/v2/conferenceRecords/rec-1/participants") {
        return jsonResponse({
          participants: [
            {
              name: "conferenceRecords/rec-1/participants/p1",
              signedinUser: { displayName: "Alice" },
            },
          ],
        });
      }
      if (url.pathname === "/v2/conferenceRecords/rec-1/participants/p1/participantSessions") {
        return jsonResponse({
          participantSessions: [
            {
              name: "conferenceRecords/rec-1/participants/p1/participantSessions/s1",
              startTime: "2026-04-25T10:00:00Z",
              endTime: "2026-04-25T10:10:00Z",
            },
          ],
        });
      }
      if (url.pathname === "/v2/conferenceRecords/rec-1/recordings") {
        return jsonResponse({
          recordings: [
            {
              name: "conferenceRecords/rec-1/recordings/r1",
              state: "FILE_GENERATED",
              driveDestination: { file: "drive-file-1" },
            },
          ],
        });
      }
      if (url.pathname === "/v2/conferenceRecords/rec-1/transcripts") {
        return jsonResponse({
          transcripts: [
            {
              name: "conferenceRecords/rec-1/transcripts/t1",
              state: "FILE_GENERATED",
              docsDestination: { document: "doc-1" },
            },
          ],
        });
      }
      if (url.pathname === "/v2/conferenceRecords/rec-1/transcripts/t1/entries") {
        return jsonResponse({
          transcriptEntries: [
            {
              name: "conferenceRecords/rec-1/transcripts/t1/entries/e1",
              text: "Hello from the transcript.",
              startTime: "2026-04-25T10:01:00Z",
              participant: "conferenceRecords/rec-1/participants/p1",
            },
          ],
        });
      }
      if (url.pathname === "/v2/conferenceRecords/rec-1/smartNotes") {
        return jsonResponse({
          smartNotes: [
            {
              name: "conferenceRecords/rec-1/smartNotes/sn1",
              state: "FILE_GENERATED",
              docsDestination: { document: "notes-1" },
            },
          ],
        });
      }
      return new Response("not found", { status: 404 });
    }),
  );
}

function setupCli(params: {
  config?: Parameters<typeof resolveGoogleMeetConfig>[0];
  runtime?: Partial<GoogleMeetRuntime>;
  ensureRuntime?: () => Promise<GoogleMeetRuntime>;
}) {
  const program = new Command();
  registerGoogleMeetCli({
    program,
    config: resolveGoogleMeetConfig(params.config ?? {}),
    ensureRuntime:
      params.ensureRuntime ?? (async () => (params.runtime ?? {}) as unknown as GoogleMeetRuntime),
  });
  return program;
}

describe("google-meet CLI", () => {
  afterEach(() => {
    vi.unstubAllGlobals();
  });

  it("prints setup checks as text and JSON", async () => {
    {
      const stdout = captureStdout();
      try {
        await setupCli({
          runtime: {
            setupStatus: async () => ({
              ok: true,
              checks: [
                {
                  id: "audio-bridge",
                  ok: true,
                  message: "Chrome command-pair realtime audio bridge configured",
                },
              ],
            }),
          },
        }).parseAsync(["googlemeet", "setup"], { from: "user" });
        expect(stdout.output()).toContain("Google Meet setup: OK");
        expect(stdout.output()).toContain(
          "[ok] audio-bridge: Chrome command-pair realtime audio bridge configured",
        );
        expect(stdout.output()).not.toContain('"checks"');
      } finally {
        stdout.restore();
      }
    }

    {
      const stdout = captureStdout();
      try {
        await setupCli({
          runtime: {
            setupStatus: async () => ({
              ok: false,
              checks: [{ id: "twilio-voice-call-plugin", ok: false, message: "missing" }],
            }),
          },
        }).parseAsync(["googlemeet", "setup", "--json"], { from: "user" });
        expect(JSON.parse(stdout.output())).toMatchObject({
          ok: false,
          checks: [{ id: "twilio-voice-call-plugin", ok: false }],
        });
      } finally {
        stdout.restore();
      }
    }
  });

  it("prints artifacts and attendance output", async () => {
    stubMeetArtifactsApi();

    const artifactsStdout = captureStdout();
    try {
      await setupCli({}).parseAsync(
        [
          "googlemeet",
          "artifacts",
          "--access-token",
          "token",
          "--expires-at",
          String(Date.now() + 120_000),
          "--conference-record",
          "rec-1",
          "--json",
        ],
        { from: "user" },
      );
      expect(JSON.parse(artifactsStdout.output())).toMatchObject({
        conferenceRecords: [{ name: "conferenceRecords/rec-1" }],
        artifacts: [
          {
            recordings: [{ name: "conferenceRecords/rec-1/recordings/r1" }],
            transcripts: [{ name: "conferenceRecords/rec-1/transcripts/t1" }],
            transcriptEntries: [
              {
                transcript: "conferenceRecords/rec-1/transcripts/t1",
                entries: [{ text: "Hello from the transcript." }],
              },
            ],
            smartNotes: [{ name: "conferenceRecords/rec-1/smartNotes/sn1" }],
          },
        ],
        tokenSource: "cached-access-token",
      });
    } finally {
      artifactsStdout.restore();
    }

    const attendanceStdout = captureStdout();
    try {
      await setupCli({}).parseAsync(
        [
          "googlemeet",
          "attendance",
          "--access-token",
          "token",
          "--expires-at",
          String(Date.now() + 120_000),
          "--conference-record",
          "rec-1",
        ],
        { from: "user" },
      );
      expect(attendanceStdout.output()).toContain("attendance rows: 1");
      expect(attendanceStdout.output()).toContain("participant: Alice");
      expect(attendanceStdout.output()).toContain(
        "conferenceRecords/rec-1/participants/p1/participantSessions/s1",
      );
    } finally {
      attendanceStdout.restore();
    }
  });

  it("prints the latest conference record", async () => {
    stubMeetArtifactsApi();
    const stdout = captureStdout();

    try {
      await setupCli({}).parseAsync(
        [
          "googlemeet",
          "latest",
          "--access-token",
          "token",
          "--expires-at",
          String(Date.now() + 120_000),
          "--meeting",
          "abc-defg-hij",
        ],
        { from: "user" },
      );
      expect(stdout.output()).toContain("space: spaces/abc-defg-hij");
      expect(stdout.output()).toContain("conference record: conferenceRecords/rec-1");
    } finally {
      stdout.restore();
    }
  });

  it("prints markdown artifact and attendance output", async () => {
    stubMeetArtifactsApi();
    const tempDir = mkdtempSync(path.join(tmpdir(), "openclaw-google-meet-artifacts-"));
    const outputPath = path.join(tempDir, "artifacts.md");
    const artifactsStdout = captureStdout();

    try {
      await setupCli({}).parseAsync(
        [
          "googlemeet",
          "artifacts",
          "--access-token",
          "token",
          "--expires-at",
          String(Date.now() + 120_000),
          "--conference-record",
          "rec-1",
          "--format",
          "markdown",
          "--output",
          outputPath,
        ],
        { from: "user" },
      );
      const markdown = readFileSync(outputPath, "utf8");
      expect(artifactsStdout.output()).toContain(`wrote: ${outputPath}`);
      expect(markdown).toContain("# Google Meet Artifacts");
      expect(markdown).toContain("## conferenceRecords/rec-1");
      expect(markdown).toContain("### Transcript Entries: conferenceRecords/rec-1/transcripts/t1");
      expect(markdown).toContain("Hello from the transcript.");
    } finally {
      artifactsStdout.restore();
      rmSync(tempDir, { recursive: true, force: true });
    }

    const attendanceStdout = captureStdout();
    try {
      await setupCli({}).parseAsync(
        [
          "googlemeet",
          "attendance",
          "--access-token",
          "token",
          "--expires-at",
          String(Date.now() + 120_000),
          "--conference-record",
          "rec-1",
          "--format",
          "markdown",
        ],
        { from: "user" },
      );
      expect(attendanceStdout.output()).toContain("# Google Meet Attendance");
      expect(attendanceStdout.output()).toContain("## Alice");
      expect(attendanceStdout.output()).toContain(
        "conferenceRecords/rec-1/participants/p1/participantSessions/s1",
      );
    } finally {
      attendanceStdout.restore();
    }
  });

  it("prints human-readable session doctor output", async () => {
    const stdout = captureStdout();
    try {
      await setupCli({
        runtime: {
          status: () => ({
            found: true,
            session: {
              id: "meet_1",
              url: "https://meet.google.com/abc-defg-hij",
              state: "active",
              transport: "chrome-node",
              mode: "realtime",
              participantIdentity: "signed-in Google Chrome profile on a paired node",
              createdAt: "2026-04-25T00:00:00.000Z",
              updatedAt: "2026-04-25T00:00:01.000Z",
              realtime: { enabled: true, provider: "openai", toolPolicy: "safe-read-only" },
              chrome: {
                audioBackend: "blackhole-2ch",
                launched: true,
                nodeId: "node-1",
                audioBridge: { type: "node-command-pair", provider: "openai" },
                health: {
                  inCall: true,
                  providerConnected: true,
                  realtimeReady: true,
                  audioInputActive: true,
                  audioOutputActive: false,
                  lastInputAt: "2026-04-25T00:00:02.000Z",
                  lastInputBytes: 160,
                  lastOutputBytes: 0,
                },
              },
              notes: [],
            },
          }),
        },
      }).parseAsync(["googlemeet", "doctor", "meet_1"], { from: "user" });
      expect(stdout.output()).toContain("session: meet_1");
      expect(stdout.output()).toContain("node: node-1");
      expect(stdout.output()).toContain("provider connected: yes");
      expect(stdout.output()).toContain("audio input active: yes");
      expect(stdout.output()).toContain("audio output active: no");
    } finally {
      stdout.restore();
    }
  });

  it("verifies OAuth refresh without printing secrets", async () => {
    const fetchMock = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) =>
      jsonResponse({
        access_token: "new-access-token",
        expires_in: 3600,
        token_type: "Bearer",
      }),
    );
    vi.stubGlobal("fetch", fetchMock);
    const ensureRuntime = vi.fn(async () => {
      throw new Error("runtime should not be loaded for OAuth doctor");
    });
    const stdout = captureStdout();

    try {
      await setupCli({
        config: {
          oauth: {
            clientId: "client-id",
            clientSecret: "client-secret",
            refreshToken: "rt-secret",
          },
        },
        ensureRuntime: ensureRuntime as unknown as () => Promise<GoogleMeetRuntime>,
      }).parseAsync(["googlemeet", "doctor", "--oauth", "--json"], { from: "user" });
      const output = stdout.output();
      expect(output).not.toContain("new-access-token");
      expect(output).not.toContain("rt-secret");
      expect(output).not.toContain("client-secret");
      expect(JSON.parse(output)).toMatchObject({
        ok: true,
        configured: true,
        tokenSource: "refresh-token",
        checks: [
          { id: "oauth-config", ok: true },
          { id: "oauth-token", ok: true },
        ],
      });
      expect(ensureRuntime).not.toHaveBeenCalled();
      const body = fetchMock.mock.calls[0]?.[1]?.body as URLSearchParams;
      expect(body.get("grant_type")).toBe("refresh_token");
    } finally {
      stdout.restore();
    }
  });

  it("can prove Google Meet API create access", async () => {
    vi.stubGlobal(
      "fetch",
      vi.fn(async (input: RequestInfo | URL) => {
        const url = requestUrl(input).href;
        if (url === "https://oauth2.googleapis.com/token") {
          return jsonResponse({
            access_token: "new-access-token",
            expires_in: 3600,
            token_type: "Bearer",
          });
        }
        if (url === "https://meet.googleapis.com/v2/spaces") {
          return jsonResponse({
            name: "spaces/new-space",
            meetingUri: "https://meet.google.com/new-abcd-xyz",
          });
        }
        return new Response("not found", { status: 404 });
      }),
    );
    const stdout = captureStdout();

    try {
      await setupCli({
        config: {
          oauth: {
            clientId: "client-id",
            refreshToken: "refresh-token",
          },
        },
      }).parseAsync(["googlemeet", "doctor", "--oauth", "--create-space", "--json"], {
        from: "user",
      });
      expect(JSON.parse(stdout.output())).toMatchObject({
        ok: true,
        tokenSource: "refresh-token",
        createdSpace: "spaces/new-space",
        meetingUri: "https://meet.google.com/new-abcd-xyz",
        checks: [
          { id: "oauth-config", ok: true },
          { id: "oauth-token", ok: true },
          { id: "meet-spaces-create", ok: true },
        ],
      });
    } finally {
      stdout.restore();
    }
  });

  it("recovers and summarizes an existing Meet tab", async () => {
    const stdout = captureStdout();
    try {
      await setupCli({
        config: { defaultTransport: "chrome-node" },
        runtime: {
          recoverCurrentTab: async () => ({
            nodeId: "node-1",
            found: true,
            targetId: "tab-1",
            tab: { targetId: "tab-1", url: "https://meet.google.com/abc-defg-hij" },
            browser: {
              inCall: false,
              manualActionRequired: true,
              manualActionReason: "meet-admission-required",
              manualActionMessage: "Admit the OpenClaw browser participant in Google Meet.",
              browserUrl: "https://meet.google.com/abc-defg-hij",
            },
            message: "Admit the OpenClaw browser participant in Google Meet.",
          }),
        },
      }).parseAsync(["googlemeet", "recover-tab"], { from: "user" });
      expect(stdout.output()).toContain("Google Meet current tab: found");
      expect(stdout.output()).toContain("target: tab-1");
      expect(stdout.output()).toContain("manual reason: meet-admission-required");
    } finally {
      stdout.restore();
    }
  });
});

¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge