discovery.test.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { describe, test, expect, beforeAll, afterAll } from "bun:test"
  2. import { Discovery } from "../../src/skill/discovery"
  3. import { Filesystem } from "../../src/util/filesystem"
  4. import { rm } from "fs/promises"
  5. import path from "path"
  6. let CLOUDFLARE_SKILLS_URL: string
  7. let server: ReturnType<typeof Bun.serve>
  8. let downloadCount = 0
  9. const fixturePath = path.join(import.meta.dir, "../fixture/skills")
  10. beforeAll(async () => {
  11. await rm(Discovery.dir(), { recursive: true, force: true })
  12. server = Bun.serve({
  13. port: 0,
  14. async fetch(req) {
  15. const url = new URL(req.url)
  16. // route /.well-known/skills/* to the fixture directory
  17. if (url.pathname.startsWith("/.well-known/skills/")) {
  18. const filePath = url.pathname.replace("/.well-known/skills/", "")
  19. const fullPath = path.join(fixturePath, filePath)
  20. if (await Filesystem.exists(fullPath)) {
  21. if (!fullPath.endsWith("index.json")) {
  22. downloadCount++
  23. }
  24. return new Response(Bun.file(fullPath))
  25. }
  26. }
  27. return new Response("Not Found", { status: 404 })
  28. },
  29. })
  30. CLOUDFLARE_SKILLS_URL = `http://localhost:${server.port}/.well-known/skills/`
  31. })
  32. afterAll(async () => {
  33. server?.stop()
  34. await rm(Discovery.dir(), { recursive: true, force: true })
  35. })
  36. describe("Discovery.pull", () => {
  37. test("downloads skills from cloudflare url", async () => {
  38. const dirs = await Discovery.pull(CLOUDFLARE_SKILLS_URL)
  39. expect(dirs.length).toBeGreaterThan(0)
  40. for (const dir of dirs) {
  41. expect(dir).toStartWith(Discovery.dir())
  42. const md = path.join(dir, "SKILL.md")
  43. expect(await Filesystem.exists(md)).toBe(true)
  44. }
  45. })
  46. test("url without trailing slash works", async () => {
  47. const dirs = await Discovery.pull(CLOUDFLARE_SKILLS_URL.replace(/\/$/, ""))
  48. expect(dirs.length).toBeGreaterThan(0)
  49. for (const dir of dirs) {
  50. const md = path.join(dir, "SKILL.md")
  51. expect(await Filesystem.exists(md)).toBe(true)
  52. }
  53. })
  54. test("returns empty array for invalid url", async () => {
  55. const dirs = await Discovery.pull(`http://localhost:${server.port}/invalid-url/`)
  56. expect(dirs).toEqual([])
  57. })
  58. test("returns empty array for non-json response", async () => {
  59. // any url not explicitly handled in server returns 404 text "Not Found"
  60. const dirs = await Discovery.pull(`http://localhost:${server.port}/some-other-path/`)
  61. expect(dirs).toEqual([])
  62. })
  63. test("downloads reference files alongside SKILL.md", async () => {
  64. const dirs = await Discovery.pull(CLOUDFLARE_SKILLS_URL)
  65. // find a skill dir that should have reference files (e.g. agents-sdk)
  66. const agentsSdk = dirs.find((d) => d.endsWith(path.sep + "agents-sdk"))
  67. expect(agentsSdk).toBeDefined()
  68. if (agentsSdk) {
  69. const refs = path.join(agentsSdk, "references")
  70. expect(await Filesystem.exists(path.join(agentsSdk, "SKILL.md"))).toBe(true)
  71. // agents-sdk has reference files per the index
  72. const refDir = await Array.fromAsync(new Bun.Glob("**/*.md").scan({ cwd: refs, onlyFiles: true }))
  73. expect(refDir.length).toBeGreaterThan(0)
  74. }
  75. })
  76. test("caches downloaded files on second pull", async () => {
  77. // clear dir and downloadCount
  78. await rm(Discovery.dir(), { recursive: true, force: true })
  79. downloadCount = 0
  80. // first pull to populate cache
  81. const first = await Discovery.pull(CLOUDFLARE_SKILLS_URL)
  82. expect(first.length).toBeGreaterThan(0)
  83. const firstCount = downloadCount
  84. expect(firstCount).toBeGreaterThan(0)
  85. // second pull should return same results from cache
  86. const second = await Discovery.pull(CLOUDFLARE_SKILLS_URL)
  87. expect(second.length).toBe(first.length)
  88. expect(second.sort()).toEqual(first.sort())
  89. // second pull should NOT increment download count
  90. expect(downloadCount).toBe(firstCount)
  91. })
  92. })