index.test.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
  2. import plugin, { minimumExpectedToolCount } from './index';
  3. describe('plugin health thresholds', () => {
  4. test('accounts only for intentionally disabled baseline tools', () => {
  5. expect(minimumExpectedToolCount()).toBe(5);
  6. expect(minimumExpectedToolCount(['wait_for_user'])).toBe(4);
  7. expect(minimumExpectedToolCount(['wait_for_user', 'wait_for_user'])).toBe(
  8. 4,
  9. );
  10. expect(minimumExpectedToolCount(['unknown_tool'])).toBe(5);
  11. });
  12. });
  13. describe('plugin env disable', () => {
  14. let originalEnv: typeof process.env;
  15. beforeEach(() => {
  16. originalEnv = { ...process.env };
  17. });
  18. afterEach(() => {
  19. process.env = originalEnv;
  20. });
  21. test('returns empty hooks without reading plugin context', async () => {
  22. process.env.OH_MY_OPENCODE_SLIM_DISABLE = '1';
  23. const ctx = new Proxy(
  24. {},
  25. {
  26. get(_target, property) {
  27. throw new Error(`disabled plugin read ctx.${String(property)}`);
  28. },
  29. },
  30. );
  31. const hooks = await plugin(ctx as Parameters<typeof plugin>[0]);
  32. expect(hooks).toEqual({});
  33. expect(hooks.config).toBeUndefined();
  34. expect(hooks.event).toBeUndefined();
  35. expect(hooks.tool).toBeUndefined();
  36. });
  37. });
  38. describe('plugin tool registration', () => {
  39. let originalEnv: typeof process.env;
  40. beforeEach(() => {
  41. originalEnv = { ...process.env };
  42. delete process.env.OH_MY_OPENCODE_SLIM_DISABLE;
  43. process.env.OPENCODE_CONFIG_DIR =
  44. '/private/tmp/oh-my-opencode-slim-hitl-empty-config';
  45. process.env.XDG_CONFIG_HOME =
  46. '/private/tmp/oh-my-opencode-slim-hitl-empty-xdg';
  47. process.env.XDG_DATA_HOME =
  48. '/private/tmp/oh-my-opencode-slim-hitl-empty-data';
  49. process.env.XDG_CACHE_HOME =
  50. '/private/tmp/oh-my-opencode-slim-hitl-empty-cache';
  51. process.env.OPENCODE_LOG_DIR =
  52. '/private/tmp/oh-my-opencode-slim-hitl-empty-logs';
  53. });
  54. afterEach(() => {
  55. process.env = originalEnv;
  56. });
  57. test('registers wait_for_user and recovers a stale orchestrator session mapping', async () => {
  58. const noop = async () => ({});
  59. const session = new Proxy({}, { get: () => noop }) as Record<
  60. string,
  61. unknown
  62. >;
  63. const client = new Proxy(
  64. { app: { log: noop }, session },
  65. {
  66. get(target, property) {
  67. if (property in target) {
  68. return target[property as keyof typeof target];
  69. }
  70. return new Proxy({}, { get: () => noop });
  71. },
  72. },
  73. );
  74. const hooks = await plugin({
  75. client,
  76. directory: '/private/tmp/oh-my-opencode-slim-hitl-project',
  77. worktree: '/private/tmp/oh-my-opencode-slim-hitl-project',
  78. serverUrl: new URL('http://127.0.0.1:4096'),
  79. } as never);
  80. expect(hooks.tool?.wait_for_user).toBeDefined();
  81. await expect(
  82. hooks.tool?.wait_for_user?.execute(
  83. { reason: 'Complete the external approval.' },
  84. { sessionID: 'parent-after-reload', agent: 'orchestrator' } as never,
  85. ),
  86. ).resolves.toContain('state: waiting_for_user');
  87. });
  88. });