MCP, skill and tool rows in Settings say "Updated 5m ago" beside how they're reached and who gets them. updated_at now means a change to an extension's settings (an edit, or any Access save): runs, connections and MCP syncs keep their own last_checked_at / last_synced_at and no longer touch it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
89 lines
3.5 KiB
TypeScript
89 lines
3.5 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import type { SkillRecord } from "@/src/types";
|
|
import openBotSkills from "./open-bot-skills";
|
|
|
|
const shown_chars = 30000;
|
|
|
|
function skill(overrides: Partial<SkillRecord> & { slug: string }): SkillRecord {
|
|
return {
|
|
id: 1,
|
|
name: overrides.slug,
|
|
description: `When ${overrides.slug} applies.`,
|
|
body: `${overrides.slug} body`,
|
|
all_bots: false,
|
|
enabled: true,
|
|
updated_at: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("openBotSkills", () => {
|
|
test("no skills means no tool and no note", () => {
|
|
expect(openBotSkills({ skills: [], shown_chars })).toEqual({ tools: [], note: "" });
|
|
});
|
|
|
|
test("a disabled skill is left out entirely", () => {
|
|
const opened = openBotSkills({ skills: [skill({ slug: "seo", enabled: false })], shown_chars });
|
|
|
|
expect(opened.tools).toHaveLength(0);
|
|
expect(opened.note).toBe("");
|
|
});
|
|
|
|
test("one tool, named load_skill, whose description lists the slugs", () => {
|
|
const opened = openBotSkills({ skills: [skill({ slug: "seo" }), skill({ slug: "inbox", id: 2 })], shown_chars });
|
|
|
|
expect(opened.tools).toHaveLength(1);
|
|
expect(opened.tools[0]!.name).toBe("load_skill");
|
|
expect(opened.tools[0]!.description).toContain("- seo:");
|
|
expect(opened.tools[0]!.description).toContain("- inbox:");
|
|
expect(opened.note).toContain("# Skills");
|
|
});
|
|
|
|
test("a built-in that took the name wins", () => {
|
|
expect(openBotSkills({ skills: [skill({ slug: "seo" })], taken: ["load_skill"], shown_chars })).toEqual({
|
|
tools: [],
|
|
note: "",
|
|
});
|
|
});
|
|
|
|
test("the tool returns the body of the slug it's given", async () => {
|
|
const opened = openBotSkills({ skills: [skill({ slug: "seo", body: "1. Pull rankings" })], shown_chars });
|
|
const output = await opened.tools[0]!.run!({ args: { slug: "seo" }, signal: AbortSignal.timeout(1000) });
|
|
|
|
expect(output).toBe("1. Pull rankings");
|
|
});
|
|
|
|
test("an unknown slug fails with the slugs it does have", async () => {
|
|
const opened = openBotSkills({ skills: [skill({ slug: "seo" })], shown_chars });
|
|
|
|
await expect(opened.tools[0]!.run!({ args: { slug: "nope" }, signal: AbortSignal.timeout(1000) })).rejects.toThrow(
|
|
/no skill called "nope".*seo/,
|
|
);
|
|
});
|
|
|
|
test("a long body is cut at the limit and says so", async () => {
|
|
const opened = openBotSkills({
|
|
skills: [skill({ slug: "seo", body: "x".repeat(shown_chars + 500) })],
|
|
shown_chars,
|
|
});
|
|
const output = await opened.tools[0]!.run!({ args: { slug: "seo" }, signal: AbortSignal.timeout(1000) });
|
|
|
|
expect(output).toContain("Cut at");
|
|
expect(output.length).toBeLessThan(shown_chars + 200);
|
|
});
|
|
|
|
test("the cut follows the install's limit", async () => {
|
|
const opened = openBotSkills({ skills: [skill({ slug: "seo", body: "x".repeat(1500) })], shown_chars: 1000 });
|
|
const output = await opened.tools[0]!.run!({ args: { slug: "seo" }, signal: AbortSignal.timeout(1000) });
|
|
|
|
expect(output).toContain("Cut at 1000 characters");
|
|
});
|
|
|
|
test("an empty body says it is empty rather than returning nothing", async () => {
|
|
const opened = openBotSkills({ skills: [skill({ slug: "seo", body: "" })], shown_chars });
|
|
const output = await opened.tools[0]!.run!({ args: { slug: "seo" }, signal: AbortSignal.timeout(1000) });
|
|
|
|
expect(output).toContain("is empty");
|
|
});
|
|
});
|