Ctrl K
ComunidadGuías…

tests/e2e/slack-run-model.spec.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { createHmac, randomBytes } from "node:crypto";
import { expect, test } from "@playwright/test";
import { PGlite } from "@electric-sql/pglite";
import type { SupabaseClient } from "@supabase/supabase-js";
import { createPostgrestShim } from "../../lib/db/postgrest-shim";
import { createSlackWebhookPostHandler } from "../../app/api/webhooks/slack/route";
import { createSlackModelCommandHandler } from "../../lib/slack/model-command";
import {
  getSlackModelPreference,
  upsertSlackModelPreference,
} from "../../lib/slack/model-preferences";
import { defaultStartRepoAgentRun } from "../../trigger/slack-event-lib/run-start";
import { startMogplexApiRun } from "../../lib/mogplex-api/runs";
import {
  buildRunRow,
  buildStartDeps,
} from "../unit/helpers/mogplex-api-runs-fixtures";
import { exercise } from "../support/native-run-fixture";

// Service E2E: signed Slack command -> persisted preference -> launch metadata
// -> native worker -> SDK invocation and observability. Account, queue and
// model transports are fixtures; no messages or billed work leave the test.
test("the model chosen in Slack is the model used by its repository run", async () => {
  const db = await PGlite.create();
  await db.exec(`create table slack_model_preferences(
    id uuid default gen_random_uuid(), slack_installation_id text,
    channel_id text, slack_user_id text, model_id text,
    created_at timestamptz default now(), updated_at timestamptz default now(),
    unique(slack_installation_id,channel_id,slack_user_id));`);
  const client = createPostgrestShim({
    query: async (sql, values) => ({
      rows: (await db.query(sql, values)).rows as Record<string, unknown>[],
    }),
  }) as unknown as SupabaseClient;
  const scope = {
    installationId: "installation-1",
    channelId: "D1",
    slackUserId: "U1",
  };
  const responses: string[] = [];
  const handleModel = createSlackModelCommandHandler({
    getInstallation: async () => ({
      id: scope.installationId,
      team_id: "T1",
      team_name: "Test",
      installed_by_user_id: "user-123",
      authed_user_slack_id: "U1",
      bot_user_id: "BOT",
      vault_bot_token_id: "fixture",
      scopes: [],
      created_at: new Date(0).toISOString(),
      updated_at: new Date(0).toISOString(),
    }),
    getUserMapping: async () => null,
    listUsableModels: async () => [
      "openai/gpt-6-astra",
      "meta/muse-spark-1.3-contributor",
    ],
    resolveDefaultModel: async () => "meta/muse-spark-1.3-contributor",
    getPreference: (input) => getSlackModelPreference(input, client),
    savePreference: (input) => upsertSlackModelPreference(input, client),
    postResponse: async (_url, response) => {
      responses.push(typeof response.text === "string" ? response.text : "");
    },
  });
  const secret = randomBytes(32).toString("hex");
  let delivered: Promise<void> | undefined;
  const handler = createSlackWebhookPostHandler({
    getSigningSecret: () => secret,
    scheduleAfterResponse: (work) => {
      delivered = Promise.resolve(work());
    },
    dispatch: async (event) => {
      if (event.kind !== "command") throw new Error("Expected model command");
      await handleModel(event.body);
    },
  });
  try {
    const body = new URLSearchParams({
      command: "/mogplex",
      text: "model openai/gpt-6-astra",
      team_id: "T1",
      channel_id: "D1",
      user_id: "U1",
      response_url: "https://hooks.slack.test/response",
    }).toString();
    const timestamp = String(Math.floor(Date.now() / 1000));
    const response = await handler(
      new Request("http://localhost/api/webhooks/slack", {
        method: "POST",
        body,
        headers: {
          "content-type": "application/x-www-form-urlencoded",
          "x-slack-request-timestamp": timestamp,
          "x-slack-signature": `v0=${createHmac("sha256", secret).update(`v0:${timestamp}:${body}`).digest("hex")}`,
        },
      })
    );
    expect(response.status).toBe(200);
    await delivered;
    expect(responses.join(" ")).toContain("Model set to openai/gpt-6-astra");
    let stored = buildRunRow();
    await defaultStartRepoAgentRun(
      {
        mogplexUserId: "user-123",
        repoId: "repo-1",
        prompt: "Fix the mobile header",
        idempotencyKey: "slack:fixture",
        slackContext: {
          ...scope,
          teamId: "T1",
          mode: "repo_agent",
          slackEmail: null,
          attributionMode: "mapped_profile",
        },
      },
      (input) =>
        startMogplexApiRun({
          ...input,
          deps: buildStartDeps({
            insertRun: async (insert) => {
              stored = buildRunRow({
                harness: insert.normalized.harness,
                metadata: insert.metadata,
              });
              return stored;
            },
            markRunQueued: async () => stored,
          }),
        }),
      async () => "mogplex",
      async () => {},
      async () => null,
      (input) => getSlackModelPreference(input, client)
    );
    // Changing the channel afterward cannot rewrite an accepted run.
    await upsertSlackModelPreference(
      { ...scope, modelId: "meta/muse-spark-1.3-contributor" },
      client
    );
    const execution = await exercise("success", "Fixed.", stored.metadata);
    expect(execution.caught).toBeUndefined();
    expect(execution.invokedModel).toBe("openai/gpt-6-astra");
    expect(execution.call.model).toBe("openai/gpt-6-astra");
    expect(execution.call.status).toBe("success");
  } finally {
    await db.close();
  }
});