auth-override.test.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { describe, expect, test } from "bun:test"
  2. import path from "path"
  3. import { pathToFileURL } from "url"
  4. import { LayerNode } from "@kirincode-ai/core/effect/layer-node"
  5. import { Effect } from "effect"
  6. import { FSUtil } from "@kirincode-ai/core/fs-util"
  7. import { provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture"
  8. import { ProviderAuth } from "@/provider/auth"
  9. import { RuntimeFlags } from "@/effect/runtime-flags"
  10. import { TestConfig } from "../fixture/config"
  11. import { testEffect } from "../lib/effect"
  12. import { CrossSpawnSpawner } from "@kirincode-ai/core/cross-spawn-spawner"
  13. import { ProviderV2 } from "@kirincode-ai/core/provider"
  14. import { Config } from "@/config/config"
  15. const it = testEffect(LayerNode.compile(LayerNode.group([CrossSpawnSpawner.node, FSUtil.node])))
  16. function providerAuthLayer(directory: string, plugins: string[]) {
  17. return LayerNode.compile(ProviderAuth.node, [
  18. [
  19. Config.node,
  20. TestConfig.layer({
  21. get: () =>
  22. Effect.succeed({
  23. plugin: plugins,
  24. plugin_origins: plugins.map((plugin) => ({
  25. spec: plugin,
  26. source: path.join(directory, "kirincode.json"),
  27. scope: "local" as const,
  28. })),
  29. }),
  30. directories: () => Effect.succeed([directory]),
  31. }),
  32. ],
  33. [RuntimeFlags.node, RuntimeFlags.layer()],
  34. ])
  35. }
  36. describe("plugin.auth-override", () => {
  37. it.instance(
  38. "user plugin overrides built-in github-copilot auth",
  39. () =>
  40. Effect.gen(function* () {
  41. const tmp = yield* TestInstance
  42. const fs = yield* FSUtil.Service
  43. const pluginDir = path.join(tmp.directory, ".kirincode", "plugin")
  44. yield* fs.writeWithDirs(
  45. path.join(pluginDir, "custom-copilot-auth.ts"),
  46. [
  47. "export default {",
  48. ' id: "demo.custom-copilot-auth",',
  49. " server: async () => ({",
  50. " auth: {",
  51. ' provider: "github-copilot",',
  52. " methods: [",
  53. ' { type: "api", label: "Test Override Auth" },',
  54. " ],",
  55. " loader: async () => ({ access: 'test-token' }),",
  56. " },",
  57. " }),",
  58. "}",
  59. "",
  60. ].join("\n"),
  61. )
  62. const plain = yield* tmpdirScoped({ git: true })
  63. const plugin = pathToFileURL(path.join(pluginDir, "custom-copilot-auth.ts")).href
  64. const methods = yield* ProviderAuth.use
  65. .methods()
  66. .pipe(Effect.provide(providerAuthLayer(tmp.directory, [plugin])))
  67. const plainMethods = yield* ProviderAuth.use
  68. .methods()
  69. .pipe(Effect.provide(providerAuthLayer(plain, [])), provideInstance(plain))
  70. const copilot = methods[ProviderV2.ID.make("github-copilot")]
  71. expect(copilot).toBeDefined()
  72. expect(copilot.length).toBe(1)
  73. expect(copilot[0].label).toBe("Test Override Auth")
  74. expect(plainMethods[ProviderV2.ID.make("github-copilot")][0].label).not.toBe("Test Override Auth")
  75. }),
  76. { git: true },
  77. 30000,
  78. )
  79. })
  80. const file = path.join(import.meta.dir, "../../src/plugin/index.ts")
  81. describe("plugin.config-hook-error-isolation", () => {
  82. test("config hooks are individually error-isolated in the layer factory", async () => {
  83. const src = await Bun.file(file).text()
  84. // Each hook's config call is wrapped in Effect.tryPromise with error logging + Effect.ignore
  85. expect(src).toContain("plugin config hook failed")
  86. const pattern =
  87. /for\s*\(const hook of hooks\)\s*\{[\s\S]*?Effect\.tryPromise[\s\S]*?\.config\?\.\([\s\S]*?plugin config hook failed[\s\S]*?Effect\.ignore/
  88. expect(pattern.test(src)).toBe(true)
  89. })
  90. })