Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/JAVA/Openclaw/src/gateway/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 11 kB image not shown  

Quelle  session-kill-http.test.ts

  Sprache: JAVA
 

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

import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { GatewayAuthResult } from "./auth.js";

const TEST_GATEWAY_TOKEN = "test-gateway-token-1234567890";

let cfg: Record<string, unknown> = {};
const authMock = vi.fn(async (): Promise<GatewayAuthResult> => ({ ok: true }));
const isLocalDirectRequestMock = vi.fn(() => true);
const loadSessionEntryMock = vi.fn();
const getLatestSubagentRunByChildSessionKeyMock = vi.fn();
const resolveSubagentControllerMock = vi.fn();
const killControlledSubagentRunMock = vi.fn();
const killSubagentRunAdminMock = vi.fn();

vi.mock("../config/config.js", () => ({
  loadConfig: () => cfg,
}));

vi.mock("./auth.js", () => ({
  authorizeHttpGatewayConnect: authMock,
  isLocalDirectRequest: isLocalDirectRequestMock,
}));

vi.mock("./session-utils.js", () => ({
  loadSessionEntry: loadSessionEntryMock,
}));

vi.mock("../agents/subagent-registry.js", () => ({
  getLatestSubagentRunByChildSessionKey: getLatestSubagentRunByChildSessionKeyMock,
}));

vi.mock("../agents/subagent-control.js", () => ({
  killControlledSubagentRun: killControlledSubagentRunMock,
  killSubagentRunAdmin: killSubagentRunAdminMock,
  resolveSubagentController: resolveSubagentControllerMock,
}));

const { handleSessionKillHttpRequest } = await import("./session-kill-http.js");

let port = 0;
let server: ReturnType<typeof createServer> | undefined;

beforeAll(async () => {
  server = createServer((req, res) => {
    void handleSessionKillHttpRequest(req, res, {
      auth: { mode: "token", token: TEST_GATEWAY_TOKEN, allowTailscale: false },
    }).then((handled) => {
      if (!handled) {
        res.statusCode = 404;
        res.end("not found");
      }
    });
  });

  await new Promise<void>((resolve, reject) => {
    server?.once("error", reject);
    server?.listen(0, "127.0.0.1", () => {
      const address = server?.address() as AddressInfo | null;
      if (!address) {
        reject(new Error("server missing address"));
        return;
      }
      port = address.port;
      resolve();
    });
  });
});

afterAll(async () => {
  await new Promise<void>((resolve, reject) => {
    server?.close((err) => (err ? reject(err) : resolve()));
  });
});

beforeEach(() => {
  cfg = {};
  authMock.mockReset();
  authMock.mockResolvedValue({ ok: true, method: "token" });
  isLocalDirectRequestMock.mockReset();
  isLocalDirectRequestMock.mockReturnValue(true);
  loadSessionEntryMock.mockReset();
  getLatestSubagentRunByChildSessionKeyMock.mockReset();
  resolveSubagentControllerMock.mockReset();
  resolveSubagentControllerMock.mockReturnValue({ controllerSessionKey: "agent:main:main" });
  killControlledSubagentRunMock.mockReset();
  killSubagentRunAdminMock.mockReset();
});

async function post(
  pathname: string,
  token = TEST_GATEWAY_TOKEN,
  extraHeaders?: Record<string, string>,
) {
  const headers: Record<string, string> = {};
  if (token) {
    headers.Authorization = `Bearer ${token}`;
  }
  Object.assign(headers, extraHeaders ?? {});
  return fetch(`http://127.0.0.1:${port}${pathname}`, {
    method: "POST",
    headers,
  });
}

describe("POST /sessions/:sessionKey/kill", () => {
  it("returns 401 when auth fails", async () => {
    authMock.mockResolvedValueOnce({ ok: false, rateLimited: false });

    const response = await post("/sessions/agent%3Amain%3Asubagent%3Aworker/kill");
    expect(response.status).toBe(401);
  });

  it("returns 404 when the session key is not in the session store", async () => {
    authMock.mockResolvedValueOnce({ ok: true, method: "trusted-proxy" });
    loadSessionEntryMock.mockReturnValue({ entry: undefined });

    const response = await post(
      "/sessions/agent%3Amain%3Asubagent%3Aworker/kill",
      TEST_GATEWAY_TOKEN,
      {
        "x-openclaw-scopes": "operator.admin",
      },
    );
    expect(response.status).toBe(404);
    await expect(response.json()).resolves.toMatchObject({
      ok: false,
      error: { type: "not_found" },
    });
    expect(killSubagentRunAdminMock).not.toHaveBeenCalled();
  });

  it("kills a matching session via the admin kill helper using the canonical key", async () => {
    authMock.mockResolvedValueOnce({ ok: true, method: "trusted-proxy" });
    loadSessionEntryMock.mockReturnValue({
      entry: { sessionId: "sess-worker", updatedAt: Date.now() },
      canonicalKey: "agent:main:subagent:worker",
    });
    killSubagentRunAdminMock.mockResolvedValue({ found: true, killed: true });

    const response = await post(
      "/sessions/agent%3AMain%3ASubagent%3AWorker/kill",
      TEST_GATEWAY_TOKEN,
      {
        "x-openclaw-scopes": "operator.admin",
      },
    );
    expect(response.status).toBe(200);
    await expect(response.json()).resolves.toEqual({ ok: true, killed: true });
    expect(killSubagentRunAdminMock).toHaveBeenCalledWith({
      cfg,
      sessionKey: "agent:main:subagent:worker",
    });
  });

  it("returns killed=false when the target exists but nothing was stopped", async () => {
    authMock.mockResolvedValueOnce({ ok: true, method: "trusted-proxy" });
    loadSessionEntryMock.mockReturnValue({
      entry: { sessionId: "sess-worker", updatedAt: Date.now() },
      canonicalKey: "agent:main:subagent:worker",
    });
    killSubagentRunAdminMock.mockResolvedValue({ found: true, killed: false });

    const response = await post(
      "/sessions/agent%3Amain%3Asubagent%3Aworker/kill",
      TEST_GATEWAY_TOKEN,
      {
        "x-openclaw-scopes": "operator.admin",
      },
    );
    expect(response.status).toBe(200);
    await expect(response.json()).resolves.toEqual({ ok: true, killed: false });
  });

  it("rejects local bearer-auth kills without a trusted admin scope surface", async () => {
    const response = await post("/sessions/agent%3Amain%3Asubagent%3Aworker/kill");
    expect(response.status).toBe(403);
    await expect(response.json()).resolves.toMatchObject({
      ok: false,
      error: {
        type: "forbidden",
        message: "missing scope: operator.admin",
      },
    });
    expect(loadSessionEntryMock).not.toHaveBeenCalled();
    expect(killSubagentRunAdminMock).not.toHaveBeenCalled();
  });

  it("does not trust x-openclaw-scopes on shared-secret bearer auth", async () => {
    const response = await post(
      "/sessions/agent%3Amain%3Asubagent%3Aworker/kill",
      TEST_GATEWAY_TOKEN,
      {
        "x-openclaw-scopes": "operator.admin",
      },
    );
    expect(response.status).toBe(403);
    await expect(response.json()).resolves.toMatchObject({
      ok: false,
      error: {
        type: "forbidden",
        message: "missing scope: operator.admin",
      },
    });
    expect(loadSessionEntryMock).not.toHaveBeenCalled();
    expect(killSubagentRunAdminMock).not.toHaveBeenCalled();
  });

  it("rejects remote bearer-auth kills without requester ownership", async () => {
    isLocalDirectRequestMock.mockReturnValue(false);
    loadSessionEntryMock.mockReturnValue({
      entry: { sessionId: "sess-worker", updatedAt: Date.now() },
      canonicalKey: "agent:main:subagent:worker",
    });

    const response = await post("/sessions/agent%3Amain%3Asubagent%3Aworker/kill");
    expect(response.status).toBe(403);
    await expect(response.json()).resolves.toMatchObject({
      ok: false,
      error: { type: "forbidden" },
    });
    expect(killSubagentRunAdminMock).not.toHaveBeenCalled();
  });

  it("rejects remote kills without requester ownership or an authorized token", async () => {
    isLocalDirectRequestMock.mockReturnValue(false);
    authMock.mockResolvedValueOnce({ ok: true });
    loadSessionEntryMock.mockReturnValue({
      entry: { sessionId: "sess-worker", updatedAt: Date.now() },
      canonicalKey: "agent:main:subagent:worker",
    });

    const response = await post("/sessions/agent%3Amain%3Asubagent%3Aworker/kill", "", {
      authorization: "",
    });
    expect(response.status).toBe(403);
    expect(killSubagentRunAdminMock).not.toHaveBeenCalled();
  });

  it("uses requester ownership checks when a requester session header is provided without admin bypass", async () => {
    isLocalDirectRequestMock.mockReturnValue(false);
    authMock.mockResolvedValueOnce({ ok: true, method: "trusted-proxy" });
    loadSessionEntryMock.mockReturnValue({
      entry: { sessionId: "sess-worker", updatedAt: Date.now() },
      canonicalKey: "agent:main:subagent:worker",
    });
    getLatestSubagentRunByChildSessionKeyMock.mockReturnValue({
      runId: "run-1",
      childSessionKey: "agent:main:subagent:worker",
    });
    killControlledSubagentRunMock.mockResolvedValue({ status: "ok" });

    const response = await post("/sessions/agent%3Amain%3Asubagent%3Aworker/kill", "", {
      "x-openclaw-scopes": "operator.write",
      "x-openclaw-requester-session-key": "agent:main:main",
    });
    expect(response.status).toBe(200);
    await expect(response.json()).resolves.toEqual({ ok: true, killed: true });
    expect(resolveSubagentControllerMock).toHaveBeenCalledWith({
      cfg,
      agentSessionKey: "agent:main:main",
    });
    expect(getLatestSubagentRunByChildSessionKeyMock).toHaveBeenCalledWith(
      "agent:main:subagent:worker",
    );
    expect(killSubagentRunAdminMock).not.toHaveBeenCalled();
  });

  it("uses the newest child-session row for requester-owned kills when stale rows still exist", async () => {
    isLocalDirectRequestMock.mockReturnValue(false);
    authMock.mockResolvedValueOnce({ ok: true, method: "trusted-proxy" });
    loadSessionEntryMock.mockReturnValue({
      entry: { sessionId: "sess-worker", updatedAt: Date.now() },
      canonicalKey: "agent:main:subagent:worker",
    });
    getLatestSubagentRunByChildSessionKeyMock.mockReturnValue({
      runId: "run-current-ended",
      childSessionKey: "agent:main:subagent:worker",
      endedAt: Date.now() - 1,
    });
    killControlledSubagentRunMock.mockResolvedValue({ status: "done" });

    const response = await post("/sessions/agent%3Amain%3Asubagent%3Aworker/kill", "", {
      "x-openclaw-scopes": "operator.write",
      "x-openclaw-requester-session-key": "agent:main:main",
    });
    expect(response.status).toBe(200);
    await expect(response.json()).resolves.toEqual({ ok: true, killed: false });
    expect(killControlledSubagentRunMock).toHaveBeenCalledWith({
      cfg,
      controller: { controllerSessionKey: "agent:main:main" },
      entry: expect.objectContaining({
        runId: "run-current-ended",
        childSessionKey: "agent:main:subagent:worker",
      }),
    });
  });

  it("rejects bearer-auth requester kills without a trusted write scope surface", async () => {
    isLocalDirectRequestMock.mockReturnValue(false);
    const response = await post(
      "/sessions/agent%3Amain%3Asubagent%3Aworker/kill",
      TEST_GATEWAY_TOKEN,
      { "x-openclaw-requester-session-key": "agent:other:main" },
    );
    expect(response.status).toBe(403);
    await expect(response.json()).resolves.toMatchObject({
      ok: false,
      error: {
        type: "forbidden",
        message: "missing scope: operator.write",
      },
    });
    expect(loadSessionEntryMock).not.toHaveBeenCalled();
    expect(killSubagentRunAdminMock).not.toHaveBeenCalled();
    expect(killControlledSubagentRunMock).not.toHaveBeenCalled();
  });
});

¤ Dauer der Verarbeitung: 0.27 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.